From 59c4edb69ba926d3790198cd2b62dc601944a632 Mon Sep 17 00:00:00 2001 From: Carl Hetherington Date: Sat, 18 Dec 2021 23:34:26 +0100 Subject: [PATCH] Add image_as_jpeg() --- src/lib/image_jpeg.cc | 135 ++++++++++++++++++++++++++++++++++++++++++ src/lib/image_jpeg.h | 22 +++++++ src/lib/wscript | 3 +- src/tools/wscript | 2 +- test/image_test.cc | 14 +++++ test/test.cc | 15 +++++ test/wscript | 2 +- wscript | 12 ++++ 8 files changed, 202 insertions(+), 3 deletions(-) create mode 100644 src/lib/image_jpeg.cc create mode 100644 src/lib/image_jpeg.h diff --git a/src/lib/image_jpeg.cc b/src/lib/image_jpeg.cc new file mode 100644 index 000000000..28bbb7574 --- /dev/null +++ b/src/lib/image_jpeg.cc @@ -0,0 +1,135 @@ +/* + Copyright (C) 2021 Carl Hetherington + + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + DCP-o-matic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DCP-o-matic. If not, see . + +*/ + + +#include "dcpomatic_assert.h" +#include "exceptions.h" +#include "image.h" +#include + +#include "i18n.h" + + +using std::shared_ptr; + + +struct destination_mgr +{ + jpeg_destination_mgr pub; + std::vector* data; +}; + + +static void +init_destination (j_compress_ptr) +{ + +} + + +/* Empty the output buffer (i.e. make more space for data); we'll make + * the output buffer bigger instead. + */ +static boolean +empty_output_buffer (j_compress_ptr state) +{ + auto dest = reinterpret_cast (state->dest); + + auto const old_size = dest->data->size(); + dest->data->resize (old_size * 2); + + dest->pub.next_output_byte = dest->data->data() + old_size; + dest->pub.free_in_buffer = old_size; + + return TRUE; +} + + +static void +term_destination (j_compress_ptr state) +{ + auto dest = reinterpret_cast (state->dest); + + dest->data->resize (dest->data->size() - dest->pub.free_in_buffer); +} + + +void +error_exit (j_common_ptr) +{ + throw EncodeError (N_("JPEG encoding error")); +} + + +dcp::ArrayData +image_as_jpeg (shared_ptr image, int quality) +{ + if (image->pixel_format() != AV_PIX_FMT_RGB24) { + return image_as_jpeg( + Image::ensure_alignment(image, Image::Alignment::PADDED)->convert_pixel_format(dcp::YUVToRGB::REC709, AV_PIX_FMT_RGB24, Image::Alignment::PADDED, false), + quality + ); + } + + struct jpeg_compress_struct compress; + struct jpeg_error_mgr error; + + compress.err = jpeg_std_error (&error); + error.error_exit = error_exit; + jpeg_create_compress (&compress); + + auto mgr = new destination_mgr; + compress.dest = reinterpret_cast(mgr); + + mgr->pub.init_destination = init_destination; + mgr->pub.empty_output_buffer = empty_output_buffer; + mgr->pub.term_destination = term_destination; + + std::vector data(4096); + mgr->data = &data; + mgr->pub.next_output_byte = data.data(); + mgr->pub.free_in_buffer = data.size(); + + compress.image_width = image->size().width; + compress.image_height = image->size().height; + compress.input_components = 3; + compress.in_color_space = JCS_RGB; + + jpeg_set_defaults (&compress); + jpeg_set_quality (&compress, quality, TRUE); + + jpeg_start_compress (&compress, TRUE); + + JSAMPROW row[1]; + auto const source_data = image->data()[0]; + auto const source_stride = image->stride()[0]; + for (auto y = 0U; y < compress.image_height; ++y) { + row[0] = source_data + y * source_stride; + jpeg_write_scanlines (&compress, row, 1); + } + + jpeg_finish_compress (&compress); + delete mgr; + jpeg_destroy_compress (&compress); + + return dcp::ArrayData (data.data(), data.size()); +} + + diff --git a/src/lib/image_jpeg.h b/src/lib/image_jpeg.h new file mode 100644 index 000000000..5c017fc2c --- /dev/null +++ b/src/lib/image_jpeg.h @@ -0,0 +1,22 @@ +/* + Copyright (C) 2021 Carl Hetherington + + This file is part of DCP-o-matic. + + DCP-o-matic is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + DCP-o-matic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DCP-o-matic. If not, see . + +*/ + + +dcp::ArrayData image_as_jpeg (std::shared_ptr image, int quality); diff --git a/src/lib/wscript b/src/lib/wscript index 49b0e4bac..d1eef8e33 100644 --- a/src/lib/wscript +++ b/src/lib/wscript @@ -122,6 +122,7 @@ sources = """ image_decoder.cc image_examiner.cc image_filename_sorter.cc + image_jpeg.cc image_png.cc image_proxy.cc j2k_image_proxy.cc @@ -207,7 +208,7 @@ def build(bld): AVCODEC AVUTIL AVFORMAT AVFILTER SWSCALE BOOST_FILESYSTEM BOOST_THREAD BOOST_DATETIME BOOST_SIGNALS2 BOOST_REGEX SAMPLERATE POSTPROC TIFF SSH DCP CXML GLIB LZMA XML++ - CURL ZIP BZ2 FONTCONFIG PANGOMM CAIROMM XMLSEC SUB ICU NETTLE PNG LEQM_NRT + CURL ZIP BZ2 FONTCONFIG PANGOMM CAIROMM XMLSEC SUB ICU NETTLE PNG JPEG LEQM_NRT """ if bld.env.TARGET_OSX: diff --git a/src/tools/wscript b/src/tools/wscript index fe5e037a5..bb3798aa7 100644 --- a/src/tools/wscript +++ b/src/tools/wscript @@ -30,7 +30,7 @@ def configure(conf): def build(bld): uselib = 'BOOST_THREAD BOOST_DATETIME DCP XMLSEC CXML XMLPP AVFORMAT AVFILTER AVCODEC ' uselib += 'AVUTIL SWSCALE SWRESAMPLE POSTPROC CURL BOOST_FILESYSTEM SSH ZIP CAIROMM FONTCONFIG PANGOMM SUB ' - uselib += 'SNDFILE SAMPLERATE BOOST_REGEX ICU NETTLE RTAUDIO PNG LEQM_NRT ' + uselib += 'SNDFILE SAMPLERATE BOOST_REGEX ICU NETTLE RTAUDIO PNG JPEG LEQM_NRT ' if bld.env.ENABLE_DISK: if bld.env.TARGET_LINUX: diff --git a/test/image_test.cc b/test/image_test.cc index 18d96d4fc..8d934bdd6 100644 --- a/test/image_test.cc +++ b/test/image_test.cc @@ -30,6 +30,7 @@ #include "lib/image.h" #include "lib/image_content.h" #include "lib/image_decoder.h" +#include "lib/image_jpeg.h" #include "lib/image_png.h" #include "lib/ffmpeg_image_proxy.h" #include "test.h" @@ -384,6 +385,19 @@ BOOST_AUTO_TEST_CASE (as_png_test) } +BOOST_AUTO_TEST_CASE (as_jpeg_test) +{ + auto proxy = make_shared("test/data/3d_test/000001.png"); + auto image_rgb = proxy->image(Image::Alignment::PADDED).image; + auto image_bgr = image_rgb->convert_pixel_format(dcp::YUVToRGB::REC709, AV_PIX_FMT_BGRA, Image::Alignment::PADDED, false); + image_as_jpeg(image_rgb, 60).write("build/test/as_jpeg_rgb.jpeg"); + image_as_jpeg(image_bgr, 60).write("build/test/as_jpeg_bgr.jpeg"); + + check_image ("test/data/as_jpeg_rgb.jpeg", "build/test/as_jpeg_rgb.jpeg"); + check_image ("test/data/as_jpeg_bgr.jpeg", "build/test/as_jpeg_bgr.jpeg"); +} + + /* Very dumb test to fade black to make sure it stays black */ static void fade_test_format_black (AVPixelFormat f, string name) diff --git a/test/test.cc b/test/test.cc index 6b2e584a2..7136b038d 100644 --- a/test/test.cc +++ b/test/test.cc @@ -369,6 +369,8 @@ rms_error (boost::filesystem::path ref, boost::filesystem::path check) FFmpegImageProxy check_proxy (check); auto check_image = check_proxy.image(Image::Alignment::COMPACT).image; + BOOST_REQUIRE_EQUAL (ref_image->planes(), check_image->planes()); + BOOST_REQUIRE_EQUAL (ref_image->pixel_format(), check_image->pixel_format()); auto const format = ref_image->pixel_format(); @@ -417,6 +419,19 @@ rms_error (boost::filesystem::path ref, boost::filesystem::path check) } break; } + case AV_PIX_FMT_YUVJ420P: + { + for (int c = 0; c < ref_image->planes(); ++c) { + for (int y = 0; y < height / ref_image->vertical_factor(c); ++y) { + auto p = ref_image->data()[c] + y * ref_image->stride()[c]; + auto q = check_image->data()[c] + y * check_image->stride()[c]; + for (int x = 0; x < ref_image->line_size()[c]; ++x) { + sum_square += pow((*p++ - *q++), 2); + } + } + } + break; + } default: BOOST_REQUIRE_MESSAGE (false, "unrecognised pixel format " << format); } diff --git a/test/wscript b/test/wscript index 9b1c0173e..1178264f0 100644 --- a/test/wscript +++ b/test/wscript @@ -36,7 +36,7 @@ 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 PNG ' + obj.uselib += 'AVFORMAT AVFILTER AVCODEC AVUTIL SWSCALE SWRESAMPLE POSTPROC CXML SUB GLIB CURL SSH XMLSEC BOOST_REGEX ICU NETTLE PNG JPEG ' obj.uselib += 'LEQM_NRT ZIP ' if bld.env.TARGET_WINDOWS_64 or bld.env.TARGET_WINDOWS_32: obj.uselib += 'WINSOCK2 DBGHELP SHLWAPI MSWSOCK BOOST_LOCALE ' diff --git a/wscript b/wscript index 6b5f582d9..c0d337d80 100644 --- a/wscript +++ b/wscript @@ -348,6 +348,18 @@ def configure(conf): # libpng conf.check_cfg(package='libpng', args='--cflags --libs', uselib_store='PNG', mandatory=True) + # libjpeg + conf.check_cxx(fragment=""" + #include + #include + #include + int main() { struct jpeg_compress_struct compress; jpeg_create_compress (&compress); return 0; } + """, + msg='Checking for libjpeg', + libpath='/usr/local/lib', + lib=['jpeg'], + uselib_store='JPEG') + # lwext4 if conf.options.enable_disk: conf.check_cxx(fragment=""" -- 2.30.2