From 0caa6358a49c66d52d2af090bd2a130203673807 Mon Sep 17 00:00:00 2001 From: Carl Hetherington Date: Thu, 1 Oct 2020 10:44:11 +0200 Subject: [PATCH] Remove Image/GraphicsMagick dependency. --- test/data | 2 +- test/image_test.cc | 16 ++-- test/player_test.cc | 10 +-- test/test.cc | 191 ++++++++++++++++++++++++++++++++++++++++---- test/test.h | 7 +- test/wscript | 5 +- 6 files changed, 191 insertions(+), 40 deletions(-) diff --git a/test/data b/test/data index 129e08be2..49de24b4d 160000 --- a/test/data +++ b/test/data @@ -1 +1 @@ -Subproject commit 129e08be241f35a15ef98f177d44ae146dc3fc3a +Subproject commit 49de24b4d45b1f3ee9bddf152dcf1dc03402c124 diff --git a/test/image_test.cc b/test/image_test.cc index 5fccf6b9b..820a44e88 100644 --- a/test/image_test.cc +++ b/test/image_test.cc @@ -172,7 +172,7 @@ alpha_blend_test_one (AVPixelFormat format, string suffix) shared_ptr save = background->convert_pixel_format (dcp::YUV_TO_RGB_REC709, AV_PIX_FMT_RGB24, false, false); - write_image (save, "build/test/image_test_" + suffix + ".png", "RGB"); + write_image (save, "build/test/image_test_" + suffix + ".png"); check_image ("build/test/image_test_" + suffix + ".png", TestPaths::private_data / ("image_test_" + suffix + ".png")); } @@ -263,7 +263,7 @@ BOOST_AUTO_TEST_CASE (crop_scale_window_test) shared_ptr raw = proxy->image().image; shared_ptr out = raw->crop_scale_window(Crop(), dcp::Size(1998, 836), dcp::Size(1998, 1080), dcp::YUV_TO_RGB_REC709, VIDEO_RANGE_FULL, AV_PIX_FMT_YUV420P, true, false); shared_ptr save = out->scale(dcp::Size(1998, 1080), dcp::YUV_TO_RGB_REC709, AV_PIX_FMT_RGB24, false, false); - write_image(save, "build/test/crop_scale_window_test.png", "RGB"); + write_image(save, "build/test/crop_scale_window_test.png"); check_image("test/data/crop_scale_window_test.png", "build/test/crop_scale_window_test.png"); } @@ -280,7 +280,7 @@ BOOST_AUTO_TEST_CASE (crop_scale_window_test3) shared_ptr proxy(new FFmpegImageProxy("test/data/player_seek_test_0.png")); shared_ptr xyz = proxy->image().image->convert_pixel_format(dcp::YUV_TO_RGB_REC709, AV_PIX_FMT_RGB24, true, false); shared_ptr cropped = xyz->crop_scale_window(Crop(512, 0, 0, 0), dcp::Size(1486, 1080), dcp::Size(1998, 1080), dcp::YUV_TO_RGB_REC709, VIDEO_RANGE_FULL, AV_PIX_FMT_RGB24, false, false); - write_image(cropped, "build/test/crop_scale_window_test3.png", "RGB", MagickCore::CharPixel); + write_image(cropped, "build/test/crop_scale_window_test3.png"); check_image("test/data/crop_scale_window_test3.png", "build/test/crop_scale_window_test3.png"); } @@ -289,8 +289,8 @@ BOOST_AUTO_TEST_CASE (crop_scale_window_test4) shared_ptr proxy(new FFmpegImageProxy("test/data/player_seek_test_0.png")); shared_ptr xyz = proxy->image().image->convert_pixel_format(dcp::YUV_TO_RGB_REC709, AV_PIX_FMT_RGB24, true, false); shared_ptr cropped = xyz->crop_scale_window(Crop(512, 0, 0, 0), dcp::Size(1486, 1080), dcp::Size(1998, 1080), dcp::YUV_TO_RGB_REC709, VIDEO_RANGE_FULL, AV_PIX_FMT_XYZ12LE, false, false); - write_image(cropped, "build/test/crop_scale_window_test4.png", "RGB", MagickCore::ShortPixel); - check_image("test/data/crop_scale_window_test4.png", "build/test/crop_scale_window_test4.png"); + write_image(cropped, "build/test/crop_scale_window_test4.png"); + check_image("test/data/crop_scale_window_test4.png", "build/test/crop_scale_window_test4.png", 35000); } BOOST_AUTO_TEST_CASE (crop_scale_window_test5) @@ -298,7 +298,7 @@ BOOST_AUTO_TEST_CASE (crop_scale_window_test5) shared_ptr proxy(new FFmpegImageProxy("test/data/player_seek_test_0.png")); shared_ptr xyz = proxy->image().image->convert_pixel_format(dcp::YUV_TO_RGB_REC709, AV_PIX_FMT_XYZ12LE, true, false); shared_ptr cropped = xyz->crop_scale_window(Crop(512, 0, 0, 0), dcp::Size(1486, 1080), dcp::Size(1998, 1080), dcp::YUV_TO_RGB_REC709, VIDEO_RANGE_FULL, AV_PIX_FMT_RGB24, false, false); - write_image(cropped, "build/test/crop_scale_window_test5.png", "RGB", MagickCore::CharPixel); + write_image(cropped, "build/test/crop_scale_window_test5.png"); check_image("test/data/crop_scale_window_test5.png", "build/test/crop_scale_window_test5.png"); } @@ -307,8 +307,8 @@ BOOST_AUTO_TEST_CASE (crop_scale_window_test6) shared_ptr proxy(new FFmpegImageProxy("test/data/player_seek_test_0.png")); shared_ptr xyz = proxy->image().image->convert_pixel_format(dcp::YUV_TO_RGB_REC709, AV_PIX_FMT_XYZ12LE, true, false); shared_ptr cropped = xyz->crop_scale_window(Crop(512, 0, 0, 0), dcp::Size(1486, 1080), dcp::Size(1998, 1080), dcp::YUV_TO_RGB_REC709, VIDEO_RANGE_FULL, AV_PIX_FMT_XYZ12LE, false, false); - write_image(cropped, "build/test/crop_scale_window_test6.png", "RGB", MagickCore::ShortPixel); - check_image("test/data/crop_scale_window_test6.png", "build/test/crop_scale_window_test6.png"); + write_image(cropped, "build/test/crop_scale_window_test6.png"); + check_image("test/data/crop_scale_window_test6.png", "build/test/crop_scale_window_test6.png", 35000); } BOOST_AUTO_TEST_CASE (as_png_test) diff --git a/test/player_test.cc b/test/player_test.cc index a68d76083..e52a73e5e 100644 --- a/test/player_test.cc +++ b/test/player_test.cc @@ -223,12 +223,12 @@ BOOST_AUTO_TEST_CASE (player_seek_test) butler->seek (t, true); pair, DCPTime> video = butler->get_video(true, 0); BOOST_CHECK_EQUAL(video.second.get(), t.get()); - write_image(video.first->image(bind(PlayerVideo::force, _1, AV_PIX_FMT_RGB24), false, true), String::compose("build/test/player_seek_test_%1.png", i), "RGB"); - /* This 0.055 is empirically chosen (hopefully) to accept changes in rendering between the reference and a test machine + write_image(video.first->image(bind(PlayerVideo::force, _1, AV_PIX_FMT_RGB24), false, true), String::compose("build/test/player_seek_test_%1.png", i)); + /* This 14.08 is empirically chosen (hopefully) to accept changes in rendering between the reference and a test machine (17.10 and 16.04 seem to anti-alias a little differently) but to reject gross errors e.g. missing fonts or missing text altogether. */ - check_image(String::compose("test/data/player_seek_test_%1.png", i), String::compose("build/test/player_seek_test_%1.png", i), 0.055); + check_image(String::compose("test/data/player_seek_test_%1.png", i), String::compose("build/test/player_seek_test_%1.png", i), 14.08); } } @@ -256,8 +256,8 @@ BOOST_AUTO_TEST_CASE (player_seek_test2) butler->seek (t, true); pair, DCPTime> video = butler->get_video(true, 0); BOOST_CHECK_EQUAL(video.second.get(), t.get()); - write_image(video.first->image(bind(PlayerVideo::force, _1, AV_PIX_FMT_RGB24), false, true), String::compose("build/test/player_seek_test2_%1.png", i), "RGB"); - check_image(String::compose("test/data/player_seek_test2_%1.png", i), String::compose("build/test/player_seek_test2_%1.png", i), 0.055); + write_image(video.first->image(bind(PlayerVideo::force, _1, AV_PIX_FMT_RGB24), false, true), String::compose("build/test/player_seek_test2_%1.png", i)); + check_image(String::compose("test/data/player_seek_test2_%1.png", i), String::compose("build/test/player_seek_test2_%1.png", i), 14.08); } } diff --git a/test/test.cc b/test/test.cc index 8f8db3714..5aee44230 100644 --- a/test/test.cc +++ b/test/test.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2012-2019 Carl Hetherington + Copyright (C) 2012-2020 Carl Hetherington This file is part of DCP-o-matic. @@ -30,6 +30,7 @@ #include "lib/job.h" #include "lib/cross.h" #include "lib/encode_server_finder.h" +#include "lib/ffmpeg_image_proxy.h" #include "lib/image.h" #include "lib/ratio.h" #include "lib/dcp_content_type.h" @@ -46,9 +47,9 @@ #include #include #include +#include #include #include -#include extern "C" { #include } @@ -317,22 +318,88 @@ mxf_atmos_files_same (boost::filesystem::path ref, boost::filesystem::path check } +static +double +rms_error (boost::filesystem::path ref, boost::filesystem::path check) +{ + FFmpegImageProxy ref_proxy (ref); + shared_ptr ref_image = ref_proxy.image().image; + FFmpegImageProxy check_proxy (check); + shared_ptr check_image = check_proxy.image().image; + + BOOST_REQUIRE_EQUAL (ref_image->pixel_format(), check_image->pixel_format()); + AVPixelFormat const format = ref_image->pixel_format(); + + BOOST_REQUIRE (ref_image->size() == check_image->size()); + int const width = ref_image->size().width; + int const height = ref_image->size().height; + + double sum_square = 0; + switch (format) { + case AV_PIX_FMT_RGBA: + { + for (int y = 0; y < height; ++y) { + uint8_t* p = ref_image->data()[0] + y * ref_image->stride()[0]; + uint8_t* q = check_image->data()[0] + y * check_image->stride()[0]; + for (int x = 0; x < width; ++x) { + for (int c = 0; c < 4; ++c) { + sum_square += pow((*p++ - *q++), 2); + } + } + } + break; + } + case AV_PIX_FMT_RGB24: + { + for (int y = 0; y < height; ++y) { + uint8_t* p = ref_image->data()[0] + y * ref_image->stride()[0]; + uint8_t* q = check_image->data()[0] + y * check_image->stride()[0]; + for (int x = 0; x < width; ++x) { + for (int c = 0; c < 3; ++c) { + sum_square += pow((*p++ - *q++), 2); + } + } + } + break; + } + case AV_PIX_FMT_RGB48BE: + { + for (int y = 0; y < height; ++y) { + uint16_t* p = reinterpret_cast(ref_image->data()[0] + y * ref_image->stride()[0]); + uint16_t* q = reinterpret_cast(check_image->data()[0] + y * check_image->stride()[0]); + for (int x = 0; x < width; ++x) { + for (int c = 0; c < 3; ++c) { + sum_square += pow((*p++ - *q++), 2); + } + } + } + break; + } + default: + BOOST_REQUIRE_MESSAGE (false, "unrecognised pixel format " << format); + } + + return sqrt(sum_square / (height * width)); +} + + +BOOST_AUTO_TEST_CASE (rms_error_test) +{ + BOOST_CHECK_CLOSE (rms_error("test/data/check_image0.png", "test/data/check_image0.png"), 0, 0.001); + BOOST_CHECK_CLOSE (rms_error("test/data/check_image0.png", "test/data/check_image1.png"), 2.2778, 0.001); + BOOST_CHECK_CLOSE (rms_error("test/data/check_image0.png", "test/data/check_image2.png"), 59.8896, 0.001); + BOOST_CHECK_CLOSE (rms_error("test/data/check_image0.png", "test/data/check_image3.png"), 0.89164, 0.001); +} + + void check_image (boost::filesystem::path ref, boost::filesystem::path check, double threshold) { - using namespace MagickCore; - - Magick::Image ref_image; - ref_image.read (ref.string ()); - Magick::Image check_image; - check_image.read (check.string ()); - /* XXX: this is a hack; we really want the ImageMagick call but GraphicsMagick doesn't have it; - this may cause random test failures on platforms that use GraphicsMagick. - */ - double const dist = ref_image.compare(check_image, Magick::RootMeanSquaredErrorMetric); - BOOST_CHECK_MESSAGE (dist < threshold, ref << " differs from " << check << " " << dist); + double const e = rms_error (ref, check); + BOOST_CHECK_MESSAGE (e < threshold, ref << " differs from " << check << " " << e); } + void check_file (boost::filesystem::path ref, boost::filesystem::path check) { @@ -511,15 +578,105 @@ wait_for_jobs () return false; } + +class Memory +{ +public: + Memory () + : data(0) + , size(0) + {} + + ~Memory () + { + free (data); + } + + uint8_t* data; + size_t size; +}; + + +static void +png_write_data (png_structp png_ptr, png_bytep data, png_size_t length) +{ + Memory* mem = reinterpret_cast(png_get_io_ptr(png_ptr)); + size_t size = mem->size + length; + + if (mem->data) { + mem->data = reinterpret_cast(realloc(mem->data, size)); + } else { + mem->data = reinterpret_cast(malloc(size)); + } + + BOOST_REQUIRE (mem->data); + + memcpy (mem->data + mem->size, data, length); + mem->size += length; +} + + +static void +png_flush (png_structp) +{ + +} + + +static void +png_error_fn (png_structp png_ptr, char const * message) +{ + reinterpret_cast(png_get_error_ptr(png_ptr))->png_error (message); +} + + void -write_image (shared_ptr image, boost::filesystem::path file, string format, MagickCore::StorageType pixel_type) +write_image (shared_ptr image, boost::filesystem::path file) { - using namespace MagickCore; + int png_color_type = 0; + int bits_per_pixel = 0; + switch (image->pixel_format()) { + case AV_PIX_FMT_RGB24: + png_color_type = PNG_COLOR_TYPE_RGB; + bits_per_pixel = 8; + break; + case AV_PIX_FMT_XYZ12LE: + png_color_type = PNG_COLOR_TYPE_RGB; + bits_per_pixel = 16; + break; + default: + BOOST_REQUIRE_MESSAGE (false, "unexpected pixel format " << image->pixel_format()); + } + + /* error handling? */ + png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, reinterpret_cast(const_cast(image.get())), png_error_fn, 0); + BOOST_REQUIRE (png_ptr); + + Memory state; - Magick::Image m (image->size().width, image->size().height, format.c_str(), pixel_type, (void *) image->data()[0]); - m.write (file.string ()); + png_set_write_fn (png_ptr, &state, png_write_data, png_flush); + + png_infop info_ptr = png_create_info_struct(png_ptr); + BOOST_REQUIRE (info_ptr); + + png_set_IHDR (png_ptr, info_ptr, image->size().width, image->size().height, bits_per_pixel, png_color_type, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); + + png_byte ** row_pointers = reinterpret_cast(png_malloc(png_ptr, image->size().height * sizeof(png_byte *))); + for (int i = 0; i < image->size().height; ++i) { + row_pointers[i] = (png_byte *) (image->data()[0] + i * image->stride()[0]); + } + + png_write_info (png_ptr, info_ptr); + png_write_image (png_ptr, row_pointers); + png_write_end (png_ptr, info_ptr); + + png_destroy_write_struct (&png_ptr, &info_ptr); + png_free (png_ptr, row_pointers); + + dcp::Data(state.data, state.size).write(file); } + void check_ffmpeg (boost::filesystem::path ref, boost::filesystem::path check, int audio_tolerance) { diff --git a/test/test.h b/test/test.h index b620a68a0..aa58aac09 100644 --- a/test/test.h +++ b/test/test.h @@ -19,9 +19,6 @@ */ #include "lib/warnings.h" -DCPOMATIC_DISABLE_WARNINGS -#include -DCPOMATIC_ENABLE_WARNINGS #include #include @@ -50,9 +47,9 @@ extern bool mxf_atmos_files_same (boost::filesystem::path ref, boost::filesystem extern void check_xml (boost::filesystem::path, boost::filesystem::path, std::list); extern void check_file (boost::filesystem::path, boost::filesystem::path); extern void check_ffmpeg (boost::filesystem::path, boost::filesystem::path, int audio_tolerance); -extern void check_image (boost::filesystem::path, boost::filesystem::path, double threshold = 0.01); +extern void check_image (boost::filesystem::path, boost::filesystem::path, double threshold = 4); extern boost::filesystem::path test_film_dir (std::string); -extern void write_image (boost::shared_ptr image, boost::filesystem::path file, std::string format, MagickCore::StorageType pixel_type = MagickCore::CharPixel); +extern void write_image (boost::shared_ptr image, boost::filesystem::path file); boost::filesystem::path dcp_file (boost::shared_ptr film, std::string prefix); void check_one_frame (boost::filesystem::path dcp, int64_t index, boost::filesystem::path ref); extern boost::filesystem::path subtitle_file (boost::shared_ptr film); diff --git a/test/wscript b/test/wscript index 88df9177f..e161c82d5 100644 --- a/test/wscript +++ b/test/wscript @@ -30,14 +30,11 @@ def configure(conf): int main() {} """, msg = 'Checking for boost unit testing library', lib = 'boost_unit_test_framework%s' % boost_test_suffix, uselib_store = 'BOOST_TEST') - if conf.check_cfg(package='ImageMagick++', args='--cflags --libs', uselib_store='MAGICK', mandatory=False) is None: - conf.check_cfg(package='Magick++', args='--cflags --libs', uselib_store='MAGICK', mandatory=True) - def build(bld): obj = bld(features='cxx cxxprogram') obj.name = 'unit-tests' obj.uselib = 'BOOST_TEST BOOST_THREAD BOOST_FILESYSTEM BOOST_DATETIME SNDFILE SAMPLERATE DCP FONTCONFIG CAIROMM PANGOMM XMLPP ' - obj.uselib += 'AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE SWRESAMPLE POSTPROC CXML SUB GLIB CURL SSH XMLSEC BOOST_REGEX ICU NETTLE MAGICK PNG ' + obj.uselib += 'AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE SWRESAMPLE POSTPROC CXML SUB GLIB CURL SSH XMLSEC BOOST_REGEX ICU NETTLE PNG ' obj.uselib += 'LEQM_NRT ZIP ' if bld.env.TARGET_WINDOWS: obj.uselib += 'WINSOCK2 DBGHELP SHLWAPI MSWSOCK BOOST_LOCALE ' -- 2.30.2