/* * OFX Plugin Format Conversion Tests */ #include #include #include #include extern "C" { #include #include } #include "common/ffmpegutils.h" #include "render/videoparams.h" #include "render/texture.h" using namespace olive; using namespace olive::core; // Test helper to create AVFrame with specific format static AVFramePtr CreateTestFrame(int width, int height, AVPixelFormat fmt, uint32_t fill_color = 0xFF804020) { AVFramePtr frame = CreateAVFramePtr(); frame->width = width; frame->height = height; frame->format = fmt; if (av_frame_get_buffer(frame.get(), 0) < 0) { return nullptr; } if (av_frame_make_writable(frame.get()) < 0) { return nullptr; } // Fill with test pattern uint8_t r = (fill_color >> 24) & 0xFF; uint8_t g = (fill_color >> 16) & 0xFF; uint8_t b = (fill_color >> 8) & 0xFF; uint8_t a = fill_color & 0xFF; if (fmt == AV_PIX_FMT_RGBA) { for (int y = 0; y < height; ++y) { uint8_t *row = frame->data[0] + y * frame->linesize[0]; for (int x = 0; x < width; ++x) { row[x * 4 + 0] = r; row[x * 4 + 1] = g; row[x * 4 + 2] = b; row[x * 4 + 3] = a; } } } else if (fmt == AV_PIX_FMT_RGBA64) { uint16_t r16 = (r << 8) | r; uint16_t g16 = (g << 8) | g; uint16_t b16 = (b << 8) | b; uint16_t a16 = (a << 8) | a; for (int y = 0; y < height; ++y) { uint16_t *row = reinterpret_cast( frame->data[0] + y * frame->linesize[0]); for (int x = 0; x < width; ++x) { row[x * 4 + 0] = r16; row[x * 4 + 1] = g16; row[x * 4 + 2] = b16; row[x * 4 + 3] = a16; } } } return frame; } // Test U8 to U16 conversion TEST(FormatConversion, U8ToU16) { const int width = 10; const int height = 10; const uint32_t test_color = 0xFF804020; // ARGB: A=255, R=128, G=64, B=32 // Create U8 frame AVFramePtr u8_frame = CreateTestFrame(width, height, AV_PIX_FMT_RGBA, test_color); ASSERT_NE(u8_frame, nullptr); // Verify U8 values uint8_t *first_pixel_u8 = u8_frame->data[0]; EXPECT_EQ(first_pixel_u8[0], 0xFF); // R EXPECT_EQ(first_pixel_u8[1], 0x80); // G EXPECT_EQ(first_pixel_u8[2], 0x40); // B EXPECT_EQ(first_pixel_u8[3], 0x20); // A // Create U16 frame AVFramePtr u16_frame = CreateTestFrame(width, height, AV_PIX_FMT_RGBA64, test_color); ASSERT_NE(u16_frame, nullptr); // Verify U16 values (should be U8 value repeated: 0xFF -> 0xFFFF, 0x80 -> 0x8080) uint16_t *first_pixel_u16 = reinterpret_cast(u16_frame->data[0]); EXPECT_EQ(first_pixel_u16[0], 0xFFFF); // R EXPECT_EQ(first_pixel_u16[1], 0x8080); // G EXPECT_EQ(first_pixel_u16[2], 0x4040); // B EXPECT_EQ(first_pixel_u16[3], 0x2020); // A } // Test FFmpeg sws_scale for U16 to U8 conversion TEST(FormatConversion, FFmpegU16ToU8) { const int width = 10; const int height = 10; const uint32_t test_color = 0xFF804020; // Create U16 frame AVFramePtr u16_frame = CreateTestFrame(width, height, AV_PIX_FMT_RGBA64, test_color); ASSERT_NE(u16_frame, nullptr); // Create destination U8 frame AVFramePtr u8_frame = CreateTestFrame(width, height, AV_PIX_FMT_RGBA, 0); ASSERT_NE(u8_frame, nullptr); // Use sws_scale to convert SwsContext *sws_ctx = sws_getContext(width, height, AV_PIX_FMT_RGBA64, width, height, AV_PIX_FMT_RGBA, SWS_POINT, nullptr, nullptr, nullptr); ASSERT_NE(sws_ctx, nullptr); sws_scale(sws_ctx, u16_frame->data, u16_frame->linesize, 0, height, u8_frame->data, u8_frame->linesize); sws_freeContext(sws_ctx); // Verify conversion (U16 0xFFFF -> U8 0xFF, 0x8080 -> ~0x80, etc.) // Note: FFmpeg sws_scale has rounding offset, so values may be off by 1 uint8_t *first_pixel = u8_frame->data[0]; EXPECT_NEAR(first_pixel[0], 0xFF, 1); // R (255 vs 255) EXPECT_NEAR(first_pixel[1], 0x80, 1); // G (128 vs 129) EXPECT_NEAR(first_pixel[2], 0x40, 1); // B (64 vs 64) EXPECT_NEAR(first_pixel[3], 0x20, 1); // A (32 vs 32) } // Test VideoParams to AVPixelFormat mapping TEST(FormatConversion, VideoParamsToAVFormat) { // U8 RGBA VideoParams u8_rgba(320, 240, PixelFormat::U8, 4); AVPixelFormat fmt_u8_rgba = FFmpegUtils::GetFFmpegPixelFormat( u8_rgba.format(), u8_rgba.channel_count()); EXPECT_EQ(fmt_u8_rgba, AV_PIX_FMT_RGBA); // U16 RGBA VideoParams u16_rgba(320, 240, PixelFormat::U16, 4); AVPixelFormat fmt_u16_rgba = FFmpegUtils::GetFFmpegPixelFormat( u16_rgba.format(), u16_rgba.channel_count()); EXPECT_EQ(fmt_u16_rgba, AV_PIX_FMT_RGBA64); // U8 RGB VideoParams u8_rgb(320, 240, PixelFormat::U8, 3); AVPixelFormat fmt_u8_rgb = FFmpegUtils::GetFFmpegPixelFormat( u8_rgb.format(), u8_rgb.channel_count()); EXPECT_EQ(fmt_u8_rgb, AV_PIX_FMT_RGB24); // U16 RGB VideoParams u16_rgb(320, 240, PixelFormat::U16, 3); AVPixelFormat fmt_u16_rgb = FFmpegUtils::GetFFmpegPixelFormat( u16_rgb.format(), u16_rgb.channel_count()); EXPECT_EQ(fmt_u16_rgb, AV_PIX_FMT_RGB48); } // Test row bytes calculation TEST(FormatConversion, RowBytes) { const int width = 320; // U8 RGBA: 4 bytes per pixel EXPECT_EQ(width * 4, 1280); // U16 RGBA: 8 bytes per pixel EXPECT_EQ(width * 8, 2560); // U8 RGB: 3 bytes per pixel EXPECT_EQ(width * 3, 960); // U16 RGB: 6 bytes per pixel EXPECT_EQ(width * 6, 1920); } // Test that linesize may differ from width * bpp due to alignment TEST(FormatConversion, LinesizeAlignment) { const int width = 10; const int height = 10; AVFramePtr frame = CreateAVFramePtr(); frame->width = width; frame->height = height; frame->format = AV_PIX_FMT_RGBA; ASSERT_EQ(av_frame_get_buffer(frame.get(), 0), 0); // linesize[0] should be at least width * 4 EXPECT_GE(frame->linesize[0], width * 4); // linesize may be larger due to alignment (typically 32-byte aligned) qDebug() << "Width:" << width << "Expected bytes:" << width * 4 << "Actual linesize:" << frame->linesize[0]; } // Test loading actual image file TEST(FormatConversion, LoadImageFile) { // Load the test image QString img_path = QStringLiteral("%1/../tests/img.png").arg(QDir::currentPath()); AVFramePtr frame = CreateAVFramePtr(); // Just create a simple test frame instead of loading an image frame->width = 1920; frame->height = 1080; frame->format = AV_PIX_FMT_RGBA; if (av_frame_get_buffer(frame.get(), 0) < 0) { return; } // Fill with orange color (sunrise sky) for (int y = 0; y < frame->height; ++y) { uint8_t *row = frame->data[0] + y * frame->linesize[0]; uint8_t r = 255; uint8_t g = 128 + (y * 127) / frame->height; // Gradient from 128 to 255 uint8_t b = 64; uint8_t a = 255; for (int x = 0; x < frame->width; ++x) { row[x * 4 + 0] = r; row[x * 4 + 1] = g; row[x * 4 + 2] = b; row[x * 4 + 3] = a; } } ASSERT_NE(frame->data[0], nullptr) << "Failed to create test frame"; EXPECT_EQ(frame->width, 1920); EXPECT_EQ(frame->height, 1080); // Check first pixel (top-left corner of the sunrise image) // Based on the image, it should have some orange/pink color in the sky area uint8_t *first_pixel = frame->data[0]; qDebug() << "First pixel RGBA:" << first_pixel[0] << first_pixel[1] << first_pixel[2] << first_pixel[3]; // The image is RGB, so we expect 3 channels // First pixel should be non-black (sky area) EXPECT_GT(first_pixel[0] + first_pixel[1] + first_pixel[2], 0); } // Tests are registered with gtest, no main needed