diff options
| -rw-r--r-- | src/lib/image.cc | 100 | ||||
| -rw-r--r-- | src/lib/image.h | 1 | ||||
| -rw-r--r-- | test/ffmpeg_encoder_test.cc | 80 | ||||
| -rw-r--r-- | test/image_test.cc | 4 |
4 files changed, 141 insertions, 44 deletions
diff --git a/src/lib/image.cc b/src/lib/image.cc index a6354f2d1..b85451fb3 100644 --- a/src/lib/image.cc +++ b/src/lib/image.cc @@ -220,6 +220,12 @@ Image::crop_scale_window ( return out; } +shared_ptr<Image> +Image::convert_pixel_format (dcp::YUVToRGB yuv_to_rgb, AVPixelFormat out_format, bool out_aligned, bool fast) const +{ + return scale(size(), yuv_to_rgb, out_format, out_aligned, fast); +} + /** @param out_size Size to scale to. * @param yuv_to_rgb YUVToRGB transform transform to use, if required. * @param out_format Output pixel format. @@ -430,36 +436,6 @@ Image::make_transparent () memset (data()[0], 0, sample_size(0).height * stride()[0]); } -template <class T> -void -component ( - int n, - Image* base, - shared_ptr<const Image> other, - shared_ptr<const Image> rgba, - int start_base_x, int start_base_y, - int start_other_x, int start_other_y - ) -{ - dcp::Size const base_size = base->sample_size(n); - dcp::Size const other_size = other->sample_size(n); - for (int by = start_base_y, oy = start_other_y; by < base_size.height && oy < other_size.height; ++by, ++oy) { - /* base image */ - T* bp = ((T*) (base->data()[n] + by * base->stride()[n])) + start_base_x; - /* overlay image */ - T* op = ((T*) (other->data()[n] + oy * other->stride()[n])); - /* original RGBA for alpha channel */ - uint8_t* rp = rgba->data()[0] + oy * rgba->stride()[0]; - for (int bx = start_base_x, ox = start_other_x; bx < base_size.width && ox < other_size.width; ++bx, ++ox) { - float const alpha = float (rp[3]) / 255; - *bp = *op * alpha + *bp * (1 - alpha); - ++bp; - ++op; - rp += 4; - } - } -} - void Image::alpha_blend (shared_ptr<const Image> other, Position<int> position) { @@ -579,19 +555,67 @@ Image::alpha_blend (shared_ptr<const Image> other, Position<int> position) } case AV_PIX_FMT_YUV420P: { - shared_ptr<Image> yuv = other->scale (other->size(), dcp::YUV_TO_RGB_REC709, _pixel_format, false, false); - component<uint8_t> (0, this, yuv, other, start_tx, start_ty, start_ox, start_oy); - component<uint8_t> (1, this, yuv, other, start_tx, start_ty, start_ox, start_oy); - component<uint8_t> (2, this, yuv, other, start_tx, start_ty, start_ox, start_oy); + shared_ptr<Image> yuv = other->convert_pixel_format (dcp::YUV_TO_RGB_REC709, _pixel_format, false, false); + dcp::Size const ts = size(); + dcp::Size const os = yuv->size(); + for (int ty = start_ty, oy = start_oy; ty < ts.height && oy < os.height; ++ty, ++oy) { + int const hty = ty / 2; + int const hoy = oy / 2; + uint8_t* tY = data()[0] + (ty * stride()[0]) + start_tx; + uint8_t* tU = data()[1] + (hty * stride()[1]) + start_tx / 2; + uint8_t* tV = data()[2] + (hty * stride()[2]) + start_tx / 2; + uint8_t* oY = yuv->data()[0] + (oy * yuv->stride()[0]) + start_ox; + uint8_t* oU = yuv->data()[1] + (hoy * yuv->stride()[1]) + start_ox / 2; + uint8_t* oV = yuv->data()[2] + (hoy * yuv->stride()[2]) + start_ox / 2; + uint8_t* alpha = other->data()[0] + (oy * other->stride()[0]) + start_ox * 4; + for (int tx = start_tx, ox = start_ox; tx < ts.width && ox < os.width; ++tx, ++ox) { + float const a = float(alpha[3]) / 255; + *tY = *oY * a + *tY * (1 - a); + *tU = *oU * a + *tU * (1 - a); + *tV = *oV * a + *tV * (1 - a); + ++tY; + ++oY; + if (tx % 2) { + ++tU; + ++tV; + } + if (ox % 2) { + ++oU; + ++oV; + } + alpha += 4; + } + } break; } case AV_PIX_FMT_YUV420P10: case AV_PIX_FMT_YUV422P10LE: { - shared_ptr<Image> yuv = other->scale (other->size(), dcp::YUV_TO_RGB_REC709, _pixel_format, false, false); - component<uint16_t> (0, this, yuv, other, start_tx, start_ty, start_ox, start_oy); - component<uint8_t> (1, this, yuv, other, start_tx, start_ty, start_ox, start_oy); - component<uint8_t> (2, this, yuv, other, start_tx, start_ty, start_ox, start_oy); + shared_ptr<Image> yuv = other->convert_pixel_format (dcp::YUV_TO_RGB_REC709, _pixel_format, false, false); + dcp::Size const ts = size(); + dcp::Size const os = yuv->size(); + for (int ty = start_ty, oy = start_oy; ty < ts.height && oy < os.height; ++ty, ++oy) { + uint16_t* tY = (uint16_t *) (data()[0] + (ty * stride()[0])) + start_tx; + uint8_t* tU = data()[1] + (ty * stride()[1]) + start_tx; + uint8_t* tV = data()[2] + (ty * stride()[2]) + start_tx; + uint16_t* oY = (uint16_t *) (yuv->data()[0] + (oy * yuv->stride()[0])) + start_ox; + uint8_t* oU = yuv->data()[1] + (oy * yuv->stride()[1]) + start_ox; + uint8_t* oV = yuv->data()[2] + (oy * yuv->stride()[2]) + start_ox; + uint8_t* alpha = other->data()[0] + (oy * other->stride()[0]) + start_ox * 4; + for (int tx = start_tx, ox = start_ox; tx < ts.width && ox < os.width; ++tx, ++ox) { + float const a = float(alpha[3]) / 255; + *tY = *oY * a + *tY * (1 - a); + *tU = *oU * a + *tU * (1 - a); + *tV = *oV * a + *tV * (1 - a); + ++tY; + ++tU; + ++tV; + ++oY; + ++oU; + ++oV; + alpha += 4; + } + } break; } default: diff --git a/src/lib/image.h b/src/lib/image.h index ce57c5317..8de0a2c69 100644 --- a/src/lib/image.h +++ b/src/lib/image.h @@ -59,6 +59,7 @@ public: dcp::Size sample_size (int) const; float bytes_per_pixel (int) const; + boost::shared_ptr<Image> convert_pixel_format (dcp::YUVToRGB yuv_to_rgb, AVPixelFormat out_format, bool aligned, bool fast) const; boost::shared_ptr<Image> scale (dcp::Size out_size, dcp::YUVToRGB yuv_to_rgb, AVPixelFormat out_format, bool aligned, bool fast) const; boost::shared_ptr<Image> crop_scale_window ( Crop crop, dcp::Size inter_size, dcp::Size out_size, dcp::YUVToRGB yuv_to_rgb, AVPixelFormat out_format, bool aligned, bool fast diff --git a/test/ffmpeg_encoder_test.cc b/test/ffmpeg_encoder_test.cc index 3d4787d66..e6cff9b8f 100644 --- a/test/ffmpeg_encoder_test.cc +++ b/test/ffmpeg_encoder_test.cc @@ -26,6 +26,7 @@ #include "lib/ratio.h" #include "lib/transcode_job.h" #include "lib/dcp_content.h" +#include "lib/subtitle_content.h" #include "test.h" #include <boost/test/unit_test.hpp> @@ -63,10 +64,78 @@ BOOST_AUTO_TEST_CASE (ffmpeg_encoder_basic_test_mp4) encoder.go (); } -BOOST_AUTO_TEST_CASE (ffmpeg_encoder_basic_test_subs) +/** Simplest possible export subtitle case: just the subtitles */ +BOOST_AUTO_TEST_CASE (ffmpeg_encoder_test_subs_h264_1) { - shared_ptr<Film> film = new_test_film ("ffmpeg_transcoder_basic_test_subs"); - film->set_name ("ffmpeg_transcoder_basic_test"); + shared_ptr<Film> film = new_test_film ("ffmpeg_encoder_test_subs_h264_1"); + film->set_name ("ffmpeg_encoder_test_subs_h264_1"); + film->set_container (Ratio::from_id ("185")); + film->set_audio_channels (6); + + shared_ptr<TextSubtitleContent> s (new TextSubtitleContent (film, "test/data/subrip2.srt")); + film->examine_and_add_content (s); + BOOST_REQUIRE (!wait_for_jobs ()); + s->subtitle->set_colour (dcp::Colour (255, 255, 0)); + s->subtitle->set_shadow (true); + s->subtitle->set_effect_colour (dcp::Colour (0, 255, 255)); + film->write_metadata(); + + shared_ptr<Job> job (new TranscodeJob (film)); + FFmpegEncoder encoder (film, job, "build/test/ffmpeg_encoder_test_subs_h264_1.mp4", FFmpegEncoder::FORMAT_H264, false); + encoder.go (); +} + +/** Slightly more complicated example with longer subs and a video to overlay */ +BOOST_AUTO_TEST_CASE (ffmpeg_encoder_test_subs_h264_2) +{ + shared_ptr<Film> film = new_test_film ("ffmpeg_encoder_test_subs_h264_2"); + film->set_name ("ffmpeg_encoder_test_subs_h264_2"); + film->set_container (Ratio::from_id ("185")); + film->set_audio_channels (6); + + shared_ptr<FFmpegContent> c (new FFmpegContent (film, "test/data/test.mp4")); + film->examine_and_add_content (c); + BOOST_REQUIRE (!wait_for_jobs ()); + + shared_ptr<TextSubtitleContent> s (new TextSubtitleContent (film, "test/data/subrip.srt")); + film->examine_and_add_content (s); + BOOST_REQUIRE (!wait_for_jobs ()); + s->subtitle->set_colour (dcp::Colour (255, 255, 0)); + s->subtitle->set_shadow (true); + s->subtitle->set_effect_colour (dcp::Colour (0, 255, 255)); + film->write_metadata(); + + shared_ptr<Job> job (new TranscodeJob (film)); + FFmpegEncoder encoder (film, job, "build/test/ffmpeg_encoder_test_subs_h264_2.mp4", FFmpegEncoder::FORMAT_H264, false); + encoder.go (); +} + +/** Simplest possible export subtitle case: just the subtitles */ +BOOST_AUTO_TEST_CASE (ffmpeg_encoder_test_subs_prores_1) +{ + shared_ptr<Film> film = new_test_film ("ffmpeg_encoder_test_subs_prores_1"); + film->set_name ("ffmpeg_encoder_test_subs_prores_1"); + film->set_container (Ratio::from_id ("185")); + film->set_audio_channels (6); + + shared_ptr<TextSubtitleContent> s (new TextSubtitleContent (film, "test/data/subrip2.srt")); + film->examine_and_add_content (s); + BOOST_REQUIRE (!wait_for_jobs ()); + s->subtitle->set_colour (dcp::Colour (255, 255, 0)); + s->subtitle->set_shadow (true); + s->subtitle->set_effect_colour (dcp::Colour (0, 255, 255)); + film->write_metadata(); + + shared_ptr<Job> job (new TranscodeJob (film)); + FFmpegEncoder encoder (film, job, "build/test/ffmpeg_encoder_test_subs_prores_1.mov", FFmpegEncoder::FORMAT_PRORES, false); + encoder.go (); +} + +/** Slightly more complicated example with longer subs and a video to overlay */ +BOOST_AUTO_TEST_CASE (ffmpeg_encoder_test_subs_prores_2) +{ + shared_ptr<Film> film = new_test_film ("ffmpeg_encoder_test_subs_prores_2"); + film->set_name ("ffmpeg_encoder_test_subs_prores_2"); film->set_container (Ratio::from_id ("185")); film->set_audio_channels (6); @@ -77,9 +146,12 @@ BOOST_AUTO_TEST_CASE (ffmpeg_encoder_basic_test_subs) shared_ptr<TextSubtitleContent> s (new TextSubtitleContent (film, "test/data/subrip.srt")); film->examine_and_add_content (s); BOOST_REQUIRE (!wait_for_jobs ()); + s->subtitle->set_colour (dcp::Colour (255, 255, 0)); + s->subtitle->set_shadow (true); + s->subtitle->set_effect_colour (dcp::Colour (0, 255, 255)); shared_ptr<Job> job (new TranscodeJob (film)); - FFmpegEncoder encoder (film, job, "build/test/ffmpeg_encoder_basic_test_subs.mp4", FFmpegEncoder::FORMAT_H264, false); + FFmpegEncoder encoder (film, job, "build/test/ffmpeg_encoder_test_subs_prores_2.mov", FFmpegEncoder::FORMAT_PRORES, false); encoder.go (); } diff --git a/test/image_test.cc b/test/image_test.cc index 7428d5183..67daaa509 100644 --- a/test/image_test.cc +++ b/test/image_test.cc @@ -140,7 +140,7 @@ alpha_blend_test_one (AVPixelFormat format, string suffix) { shared_ptr<MagickImageProxy> proxy (new MagickImageProxy (private_data / "prophet_frame.tiff")); shared_ptr<Image> raw = proxy->image(); - shared_ptr<Image> background = raw->scale (raw->size(), dcp::YUV_TO_RGB_REC709, format, true, false); + shared_ptr<Image> background = raw->convert_pixel_format (dcp::YUV_TO_RGB_REC709, format, true, false); shared_ptr<Image> overlay (new Image (AV_PIX_FMT_RGBA, raw->size(), true)); overlay->make_transparent (); @@ -171,7 +171,7 @@ alpha_blend_test_one (AVPixelFormat format, string suffix) background->alpha_blend (overlay, Position<int> (0, 0)); - shared_ptr<Image> save = background->scale (background->size(), dcp::YUV_TO_RGB_REC709, AV_PIX_FMT_RGB24, false, false); + shared_ptr<Image> 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"); check_image ("build/test/image_test_" + suffix + ".png", private_data / ("image_test_" + suffix + ".png")); |
