/** @param pixel_format Pixel format functor that will be used when calling ::image on PlayerVideos coming out of this
* butler. This will be used (where possible) to prepare the PlayerVideos so that calling image() on them is quick.
+ * @param display_container Same as above for the `display_container` value.
+ * @param film_container Same as above for the `film_container` value.
* @param alignment Same as above for the `alignment' value.
* @param fast Same as above for the `fast' flag.
*/
AudioMapping audio_mapping,
int audio_channels,
function<AVPixelFormat (AVPixelFormat)> pixel_format,
+ dcp::Size display_container,
+ dcp::Size film_container,
VideoRange video_range,
Image::Alignment alignment,
bool fast,
, _audio_channels (audio_channels)
, _disable_audio (audio == Audio::DISABLED)
, _pixel_format (pixel_format)
+ , _display_container(display_container)
+ , _film_container(film_container)
, _video_range (video_range)
, _alignment (alignment)
, _fast (fast)
/* If the weak_ptr cannot be locked the video obviously no longer requires any work */
if (video) {
LOG_TIMING("start-prepare in %1", thread_id());
- video->prepare (_pixel_format, _video_range, _alignment, _fast, _prepare_only_proxy);
+ video->prepare(_pixel_format, _display_container, _film_container, _video_range, _alignment, _fast, _prepare_only_proxy);
LOG_TIMING("finish-prepare in %1", thread_id());
}
}
if (type == ChangeType::DONE) {
auto film = _film.lock();
if (film) {
- _video.reset_metadata (film, _player->video_container_size());
+ _video.reset_metadata(film);
}
}
return;
AudioMapping map,
int audio_channels,
std::function<AVPixelFormat (AVPixelFormat)> pixel_format,
+ dcp::Size display_container,
+ dcp::Size film_container,
VideoRange video_range,
Image::Alignment alignment,
bool fast,
bool _disable_audio;
std::function<AVPixelFormat (AVPixelFormat)> _pixel_format;
+ dcp::Size _display_container;
+ dcp::Size _film_container;
VideoRange _video_range;
Image::Alignment _alignment;
bool _fast;
* @param bw J2K bandwidth to use (see Config::j2k_bandwidth ())
*/
DCPVideo::DCPVideo (
- shared_ptr<const PlayerVideo> frame, int index, int dcp_fps, int bw, Resolution r
+ shared_ptr<const PlayerVideo> frame, dcp::Size container, int index, int dcp_fps, int bw, Resolution r
)
: _frame (frame)
+ , _container(container)
, _index (index)
, _frames_per_second (dcp_fps)
, _j2k_bandwidth (bw)
}
shared_ptr<dcp::OpenJPEGImage>
-DCPVideo::convert_to_xyz (shared_ptr<const PlayerVideo> frame, dcp::NoteHandler note)
+DCPVideo::convert_to_xyz (shared_ptr<const PlayerVideo> frame, dcp::Size container, dcp::NoteHandler note)
{
shared_ptr<dcp::OpenJPEGImage> xyz;
- auto image = frame->image (bind(&PlayerVideo::keep_xyz_or_rgb, _1), VideoRange::FULL, false);
+ auto image = frame->image(
+ bind(&PlayerVideo::keep_xyz_or_rgb, _1),
+ container,
+ container,
+ VideoRange::FULL,
+ false
+ );
+
if (frame->colour_conversion()) {
xyz = dcp::rgb_to_xyz (
image->data()[0],
int const minimum_size = 16384;
LOG_DEBUG_ENCODE("Using minimum frame size %1", minimum_size);
- auto xyz = convert_to_xyz (_frame, boost::bind(&Log::dcp_log, dcpomatic_log.get(), _1, _2));
+ auto xyz = convert_to_xyz(_frame, _container, boost::bind(&Log::dcp_log, dcpomatic_log.get(), _1, _2));
int noise_amount = 2;
int pixel_skip = 16;
while (true) {
* convert_to_xyz() again because compress_j2k() corrupts its xyz parameter.
*/
- xyz = convert_to_xyz (_frame, boost::bind(&Log::dcp_log, dcpomatic_log.get(), _1, _2));
+ xyz = convert_to_xyz(_frame, _container, boost::bind(&Log::dcp_log, dcpomatic_log.get(), _1, _2));
auto size = xyz->size ();
auto pixels = size.width * size.height;
dcpomatic::RNG rng(42);
class DCPVideo
{
public:
- DCPVideo (std::shared_ptr<const PlayerVideo>, int index, int dcp_fps, int bandwidth, Resolution r);
+ DCPVideo (std::shared_ptr<const PlayerVideo>, dcp::Size container, int index, int dcp_fps, int bandwidth, Resolution resolution);
DCPVideo (std::shared_ptr<const PlayerVideo>, cxml::ConstNodePtr);
DCPVideo (DCPVideo const&) = default;
bool same (std::shared_ptr<const DCPVideo> other) const;
- static std::shared_ptr<dcp::OpenJPEGImage> convert_to_xyz (std::shared_ptr<const PlayerVideo> frame, dcp::NoteHandler note);
+ static std::shared_ptr<dcp::OpenJPEGImage> convert_to_xyz(std::shared_ptr<const PlayerVideo> frame, dcp::Size container, dcp::NoteHandler note);
private:
void add_metadata (xmlpp::Element *) const;
std::shared_ptr<const PlayerVideo> _frame;
+ dcp::Size _container;
int _index; ///< frame index within the DCP's intrinsic duration
int _frames_per_second; ///< Frames per second that we will use for the DCP
int _j2k_bandwidth; ///< J2K bandwidth to use
map,
_output_audio_channels,
bind(&PlayerVideo::force, FFmpegFileEncoder::pixel_format(format)),
+ _film->frame_size(),
+ _film->frame_size(),
VideoRange::VIDEO,
Image::Alignment::PADDED,
false,
{
/* All our output formats are video range at the moment */
auto image = video->image (
- bind (&PlayerVideo::force, _pixel_format),
+ bind(&PlayerVideo::force, _pixel_format),
+ _video_frame_size,
+ _video_frame_size,
VideoRange::VIDEO,
false
);
LOG_DEBUG_ENCODE("Frame @ %1 FAKE", to_string(time));
_writer->fake_write (position, pv->eyes ());
frame_done ();
- } else if (pv->has_j2k() && !_film->reencode_j2k()) {
+ } else if (pv->has_j2k(_film->frame_size(), _film->frame_size()) && !_film->reencode_j2k()) {
LOG_DEBUG_ENCODE("Frame @ %1 J2K", to_string(time));
/* This frame already has J2K data, so just write it */
_writer->write (pv->j2k(), position, pv->eyes ());
LOG_TIMING ("add-frame-to-queue queue=%1", _queue.size ());
_queue.push_back (DCPVideo(
pv,
+ _film->frame_size(),
position,
_film->video_frame_rate(),
_film->j2k_bandwidth(),
return { std::max(a.x, b.x), std::max(a.y, b.y) };
}
+
+bool
+operator==(PixelQuanta const& a, PixelQuanta const& b)
+{
+ return a.x == b.x && a.y == b.y;
+}
+
+
+bool
+operator!=(PixelQuanta const& a, PixelQuanta const& b)
+{
+ return a.x != b.x || a.y != b.y;
+}
+
PixelQuanta max (PixelQuanta const& a, PixelQuanta const& b);
+bool operator==(PixelQuanta const& a, PixelQuanta const& b);
+bool operator!=(PixelQuanta const& a, PixelQuanta const& b);
#endif
{
if (property == VideoContentProperty::CROP) {
if (type == ChangeType::DONE) {
- auto const vcs = video_container_size();
boost::mutex::scoped_lock lm (_mutex);
for (auto const& i: _delay) {
- i.first->reset_metadata (_film, vcs);
+ i.first->reset_metadata(_film);
}
}
} else {
#include "j2k_image_proxy.h"
#include "player.h"
#include "player_video.h"
+#include "util.h"
#include "video_content.h"
#include <dcp/raw_convert.h>
extern "C" {
shared_ptr<const ImageProxy> in,
Crop crop,
boost::optional<double> fade,
- dcp::Size inter_size,
- dcp::Size out_size,
+ dcp::Size size_in_film,
+ PixelQuanta pixel_quanta,
Eyes eyes,
Part part,
optional<ColourConversion> colour_conversion,
: _in (in)
, _crop (crop)
, _fade (fade)
- , _inter_size (inter_size)
- , _out_size (out_size)
+ , _size_in_film(size_in_film)
+ , _pixel_quanta(pixel_quanta)
, _eyes (eyes)
, _part (part)
, _colour_conversion (colour_conversion)
_crop = Crop (node);
_fade = node->optional_number_child<double> ("Fade");
- _inter_size = dcp::Size (node->number_child<int> ("InterWidth"), node->number_child<int> ("InterHeight"));
- _out_size = dcp::Size (node->number_child<int> ("OutWidth"), node->number_child<int> ("OutHeight"));
+ _size_in_film = dcp::Size(node->number_child<int>("SizeInFilmWidth"), node->number_child<int>("SizeInFilmHeight"));
+ _pixel_quanta = PixelQuanta(node->number_child<int>("PixelQuantumX"), node->number_child<int>("PixelQuantumY"));
_eyes = static_cast<Eyes>(node->number_child<int>("Eyes"));
_part = static_cast<Part>(node->number_child<int>("Part"));
_video_range = static_cast<VideoRange>(node->number_child<int>("VideoRange"));
shared_ptr<Image>
-PlayerVideo::image (function<AVPixelFormat (AVPixelFormat)> pixel_format, VideoRange video_range, bool fast) const
+PlayerVideo::image(
+ function<AVPixelFormat (AVPixelFormat)> pixel_format,
+ dcp::Size display_container,
+ dcp::Size film_container,
+ VideoRange video_range,
+ bool fast
+ ) const
{
/* XXX: this assumes that image() and prepare() are only ever called with the same parameters (except crop, inter size, out size, fade) */
boost::mutex::scoped_lock lm (_mutex);
- if (!_image || _crop != _image_crop || _inter_size != _image_inter_size || _out_size != _image_out_size || _fade != _image_fade) {
- make_image (pixel_format, video_range, fast);
+ if (!_image || _crop != _image_crop || _size_in_film != _image_size_in_film || _pixel_quanta != _image_pixel_quanta || _fade != _image_fade) {
+ make_image(pixel_format, display_container, film_container, video_range, fast);
}
return _image;
}
shared_ptr<const Image>
-PlayerVideo::raw_image () const
+PlayerVideo::raw_image(dcp::Size display_container, dcp::Size film_container) const
{
- return _in->image(Image::Alignment::COMPACT, _inter_size).image;
+ auto const inter_size = scale_for_display(_size_in_film, display_container, film_container, _pixel_quanta);
+ return _in->image(Image::Alignment::COMPACT, inter_size).image;
}
* @param fast true to be fast at the expense of quality.
*/
void
-PlayerVideo::make_image (function<AVPixelFormat (AVPixelFormat)> pixel_format, VideoRange video_range, bool fast) const
+PlayerVideo::make_image (
+ function<AVPixelFormat (AVPixelFormat)> pixel_format,
+ dcp::Size display_container,
+ dcp::Size film_container,
+ VideoRange video_range,
+ bool fast
+ ) const
{
_image_crop = _crop;
- _image_inter_size = _inter_size;
- _image_out_size = _out_size;
+ _image_size_in_film = _size_in_film;
+ _image_pixel_quanta = _pixel_quanta;
_image_fade = _fade;
- auto prox = _in->image (Image::Alignment::PADDED, _inter_size);
+ auto const inter_size = scale_for_display(_size_in_film, display_container, film_container, _pixel_quanta);
+
+ auto prox = _in->image (Image::Alignment::PADDED, inter_size);
_error = prox.error;
auto total_crop = _crop;
}
_image = prox.image->crop_scale_window (
- total_crop, _inter_size, _out_size, yuv_to_rgb, _video_range, pixel_format (prox.image->pixel_format()), video_range, Image::Alignment::COMPACT, fast
+ total_crop, inter_size, display_container, yuv_to_rgb, _video_range, pixel_format(prox.image->pixel_format()), video_range, Image::Alignment::COMPACT, fast
);
if (_text) {
node->add_child("Fade")->add_child_text (raw_convert<string> (_fade.get ()));
}
_in->add_metadata (node->add_child ("In"));
- node->add_child("InterWidth")->add_child_text (raw_convert<string> (_inter_size.width));
- node->add_child("InterHeight")->add_child_text (raw_convert<string> (_inter_size.height));
- node->add_child("OutWidth")->add_child_text (raw_convert<string> (_out_size.width));
- node->add_child("OutHeight")->add_child_text (raw_convert<string> (_out_size.height));
+ node->add_child("SizeInFilmWidth")->add_child_text(raw_convert<string>(_size_in_film.width));
+ node->add_child("SizeInFilmHeight")->add_child_text(raw_convert<string>(_size_in_film.height));
+ node->add_child("PixelQuantumX")->add_child_text(raw_convert<string>(_pixel_quanta.x));
+ node->add_child("PixelQuantumY")->add_child_text(raw_convert<string>(_pixel_quanta.y));
node->add_child("Eyes")->add_child_text (raw_convert<string> (static_cast<int> (_eyes)));
node->add_child("Part")->add_child_text (raw_convert<string> (static_cast<int> (_part)));
node->add_child("VideoRange")->add_child_text(raw_convert<string>(static_cast<int>(_video_range)));
bool
-PlayerVideo::has_j2k () const
+PlayerVideo::has_j2k(dcp::Size display_container, dcp::Size film_container) const
{
/* XXX: maybe other things */
return false;
}
- return _crop == Crop() && _out_size == j2k->size() && _inter_size == j2k->size() && !_text && !_fade && !_colour_conversion;
+ auto const inter_size = scale_for_display(_size_in_film, display_container, film_container, _pixel_quanta);
+ return _crop == Crop() && film_container == j2k->size() && inter_size == j2k->size() && !_text && !_fade && !_colour_conversion;
}
}
-Position<int>
-PlayerVideo::inter_position () const
-{
- return Position<int> ((_out_size.width - _inter_size.width) / 2, (_out_size.height - _inter_size.height) / 2);
-}
-
-
/** @return true if this PlayerVideo is definitely the same as another, false if it is probably not */
bool
PlayerVideo::same (shared_ptr<const PlayerVideo> other) const
{
if (_crop != other->_crop ||
_fade != other->_fade ||
- _inter_size != other->_inter_size ||
- _out_size != other->_out_size ||
+ _size_in_film != other->_size_in_film ||
+ _pixel_quanta != other->_pixel_quanta ||
_eyes != other->_eyes ||
_part != other->_part ||
_colour_conversion != other->_colour_conversion ||
void
-PlayerVideo::prepare (function<AVPixelFormat (AVPixelFormat)> pixel_format, VideoRange video_range, Image::Alignment alignment, bool fast, bool proxy_only)
+PlayerVideo::prepare(
+ function<AVPixelFormat (AVPixelFormat)> pixel_format,
+ dcp::Size display_container,
+ dcp::Size film_container,
+ VideoRange video_range,
+ Image::Alignment alignment,
+ bool fast,
+ bool proxy_only
+ )
{
- _in->prepare (alignment, _inter_size);
+ auto const inter_size = scale_for_display(_size_in_film, display_container, film_container, _pixel_quanta);
+ _in->prepare(alignment, inter_size);
boost::mutex::scoped_lock lm (_mutex);
if (!_image && !proxy_only) {
- make_image (pixel_format, video_range, fast);
+ make_image(pixel_format, display_container, film_container, video_range, fast);
}
}
_in,
_crop,
_fade,
- _inter_size,
- _out_size,
+ _size_in_film,
+ _pixel_quanta,
_eyes,
_part,
_colour_conversion,
}
-/** Re-read crop, fade, inter/out size, colour conversion and video range from our content.
+/** Re-read crop, fade, colour conversion and video range from our content.
* @return true if this was possible, false if not.
*/
bool
-PlayerVideo::reset_metadata (shared_ptr<const Film> film, dcp::Size player_video_container_size)
+PlayerVideo::reset_metadata (shared_ptr<const Film> film)
{
auto content = _content.lock();
if (!content || !_video_frame) {
_crop = content->video->actual_crop();
_fade = content->video->fade(film, _video_frame.get());
- _inter_size = scale_for_display(
- content->video->scaled_size(film->frame_size()),
- player_video_container_size,
- film->frame_size(),
- content->video->pixel_quanta()
- );
- _out_size = player_video_container_size;
_colour_conversion = content->video->colour_conversion();
_video_range = content->video->range();
#include "colour_conversion.h"
#include "dcpomatic_time.h"
#include "image.h"
+#include "pixel_quanta.h"
#include "position.h"
#include "position_image.h"
#include "types.h"
std::shared_ptr<const ImageProxy> image,
Crop crop,
boost::optional<double> fade,
- dcp::Size inter_size,
- dcp::Size out_size,
+ dcp::Size size_in_film,
+ PixelQuanta pixel_quanta,
Eyes eyes,
Part part,
boost::optional<ColourConversion> colour_conversion,
return _text;
}
- void prepare (std::function<AVPixelFormat (AVPixelFormat)> pixel_format, VideoRange video_range, Image::Alignment alignment, bool fast, bool proxy_only);
- std::shared_ptr<Image> image (std::function<AVPixelFormat (AVPixelFormat)> pixel_format, VideoRange video_range, bool fast) const;
- std::shared_ptr<const Image> raw_image () const;
+ void prepare(
+ std::function<AVPixelFormat (AVPixelFormat)> pixel_format,
+ dcp::Size display_container,
+ dcp::Size film_container,
+ VideoRange video_range,
+ Image::Alignment alignment,
+ bool fast,
+ bool proxy_only
+ );
+
+ std::shared_ptr<Image> image(
+ std::function<AVPixelFormat (AVPixelFormat)> pixel_format,
+ dcp::Size display_container,
+ dcp::Size film_container,
+ VideoRange video_range,
+ bool fast
+ ) const;
+
+ std::shared_ptr<const Image> raw_image(
+ dcp::Size display_container,
+ dcp::Size film_container
+ ) const;
static AVPixelFormat force (AVPixelFormat);
static AVPixelFormat keep_xyz_or_rgb (AVPixelFormat);
void add_metadata (xmlpp::Node* node) const;
void write_to_socket (std::shared_ptr<Socket> socket) const;
- bool reset_metadata (std::shared_ptr<const Film> film, dcp::Size player_video_container_size);
+ bool reset_metadata(std::shared_ptr<const Film> film);
+
+ bool has_j2k(
+ dcp::Size display_container,
+ dcp::Size film_container
+ ) const;
- bool has_j2k () const;
std::shared_ptr<const dcp::Data> j2k () const;
Eyes eyes () const {
return _colour_conversion;
}
- /** @return Position of the content within the overall image once it has been scaled up */
- Position<int> inter_position () const;
-
- /** @return Size of the content within the overall image once it has been scaled up */
- dcp::Size inter_size () const {
- return _inter_size;
- }
-
- dcp::Size out_size () const {
- return _out_size;
- }
-
bool same (std::shared_ptr<const PlayerVideo> other) const;
size_t memory_used () const;
}
private:
- void make_image (std::function<AVPixelFormat (AVPixelFormat)> pixel_format, VideoRange video_range, bool fast) const;
+ void make_image(
+ std::function<AVPixelFormat (AVPixelFormat)> pixel_format,
+ dcp::Size display_container,
+ dcp::Size film_container,
+ VideoRange video_range,
+ bool fast
+ ) const;
std::shared_ptr<const ImageProxy> _in;
Crop _crop;
boost::optional<double> _fade;
- dcp::Size _inter_size;
- dcp::Size _out_size;
+ dcp::Size _size_in_film;
+ PixelQuanta _pixel_quanta;
Eyes _eyes;
Part _part;
boost::optional<ColourConversion> _colour_conversion;
mutable std::shared_ptr<Image> _image;
/** _crop that was used to make _image */
mutable Crop _image_crop;
- /** _inter_size that was used to make _image */
- mutable dcp::Size _image_inter_size;
- /** _out_size that was used to make _image */
- mutable dcp::Size _image_out_size;
+ /** _size_in_film that was used to make _image */
+ mutable dcp::Size _image_size_in_film;
+ /** _pixel_quanta that was used to make _image */
+ mutable PixelQuanta _image_pixel_quanta;
/** _fade that was used to make _image */
mutable boost::optional<double> _image_fade;
/** true if there was an error when decoding our image */
void
-VideoRingBuffers::reset_metadata (shared_ptr<const Film> film, dcp::Size player_video_container_size)
+VideoRingBuffers::reset_metadata (shared_ptr<const Film> film)
{
boost::mutex::scoped_lock lm (_mutex);
for (auto const& i: _data) {
- i.first->reset_metadata (film, player_video_container_size);
+ i.first->reset_metadata(film);
}
}
Frame size () const;
bool empty () const;
- void reset_metadata (std::shared_ptr<const Film> film, dcp::Size player_video_container_size);
+ void reset_metadata(std::shared_ptr<const Film> film);
std::pair<size_t, std::string> memory_used () const;
_state_timer.set ("get image");
- _image = player_video().first->image(boost::bind(&PlayerVideo::force, AV_PIX_FMT_RGB24), VideoRange::FULL, true);
+ _image = player_video().first->image(
+ boost::bind(&PlayerVideo::force, AV_PIX_FMT_RGB24),
+ VideoRange::FULL,
+ true
+ );
_state_timer.set ("ImageChanged");
_viewer->image_changed (player_video().first);
return;
}
+ auto film = _film.lock ();
+ if (!film) {
+ return;
+ }
+
/* We must copy the PlayerVideo here as we will call ::image() on it, potentially
with a different pixel_format than was used when ::prepare() was called.
*/
- _image = DCPVideo::convert_to_xyz (image->shallow_copy(), [](dcp::NoteType, string) {});
+ _image = DCPVideo::convert_to_xyz(image->shallow_copy(), film->frame_size(), [](dcp::NoteType, string) {});
_dirty = true;
Refresh ();
}
--- /dev/null
+/*
+ Copyright (C) 2022 Carl Hetherington <cth@carlh.net>
+
+ 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 <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "lib/colour_conversion.h"
+#include "lib/j2k_image_proxy.h"
+#include "lib/player_video.h"
+#include <dcp/openjpeg_image.h>
+#include <dcp/j2k_transcode.h>
+#include <dcp/mono_picture_frame.h>
+#include <boost/optional.hpp>
+#include <boost/test/unit_test.hpp>
+
+
+using std::make_shared;
+using std::weak_ptr;
+using boost::optional;
+
+
+BOOST_AUTO_TEST_CASE(player_video_j2k_passthrough_test)
+{
+ auto size = dcp::Size(4096, 2048);
+ auto image = make_shared<dcp::OpenJPEGImage>(size);
+ for (int x = 0; x < size.width; ++x) {
+ image->data(0)[x] = x;
+ image->data(1)[x] = x;
+ image->data(2)[x] = x;
+ }
+ for (int y = 1; y < size.height; ++y) {
+ memcpy(image->data(0) + y * size.width, image->data(0), size.width);
+ memcpy(image->data(1) + y * size.width, image->data(1), size.width);
+ memcpy(image->data(2) + y * size.width, image->data(2), size.width);
+ }
+ auto compressed = compress_j2k(image, 250000000LL, 24, false, true);
+ auto decompressed = decompress_j2k(compressed, 0);
+ auto frame = make_shared<dcp::MonoPictureFrame>(compressed.data(), compressed.size());
+ auto proxy = make_shared<J2KImageProxy>(frame, size, AV_PIX_FMT_XYZ12LE, optional<int>());
+ auto video = make_shared<PlayerVideo>(proxy, Crop(), optional<double>(), size, size, Eyes::BOTH, Part::WHOLE, optional<ColourConversion>(), VideoRange::FULL, weak_ptr<Content>(), optional<Frame>(), false);
+ auto out_image = video->image([](AVPixelFormat) { return AV_PIX_FMT_XYZ12LE; }, VideoRange::FULL, false);
+
+ BOOST_REQUIRE_EQUAL(out_image->size().width, size.width);
+ BOOST_REQUIRE_EQUAL(out_image->size().height, size.height);
+
+ for (int c = 0; c < 3; ++c) {
+ for (auto y = 0; y < size.height; ++y) {
+ auto in = decompressed->data(c) + y * size.width;
+ auto out = reinterpret_cast<uint16_t*>(out_image->data()[0] + y * out_image->stride()[0]) + c;
+ for (int x = 0; x < size.width; ++x) {
+ BOOST_REQUIRE_MESSAGE(*in == (*out >> 4), *in << " != " << *out << " at x=" << x << ", y=" << y << ", c=" << c);
+ ++in;
+ out += 3;
+ }
+ }
+ }
+}
+