/* Copyright (C) 2015-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 "image_proxy.h" #include #include #include #include #include namespace dcp { class MonoJ2KPictureFrame; class StereoJ2KPictureFrame; } template class J2KImageProxy : public ImageProxy { public: J2KImageProxy(boost::filesystem::path path, dcp::Size, AVPixelFormat pixel_format) : _data(path) , _size (size) , _pixel_format (pixel_format) , _error (false) { /* ::image assumes 16bpp */ DCPOMATIC_ASSERT (_pixel_format == AV_PIX_FMT_RGB48 || _pixel_format == AV_PIX_FMT_XYZ12LE); } J2KImageProxy ( dcp::MonoJ2KPictureFrame frame, dcp::Size, AVPixelFormat pixel_format, boost::optional forced_reduction ) : _data (frame) , _size (size) , _pixel_format (pixel_format) , _forced_reduction (forced_reduction) , _error (false) { /* ::image assumes 16bpp */ DCPOMATIC_ASSERT (_pixel_format == AV_PIX_FMT_RGB48 || _pixel_format == AV_PIX_FMT_XYZ12LE); } J2KImageProxy ( dcp::StereoJ2KPictureFrame frame, dcp::Size size, dcp::Eye eye, AVPixelFormat pixel_format, boost::optional forced_reduction ) : _data (eye == dcp::Eye::LEFT ? frame->left() : frame->right()) , _size (size) , _eye (eye) , _pixel_format (pixel_format) , _forced_reduction (forced_reduction) , _error (false) { /* ::image assumes 16bpp */ DCPOMATIC_ASSERT (_pixel_format == AV_PIX_FMT_RGB48 || _pixel_format == AV_PIX_FMT_XYZ12LE); } J2KImageProxy (std::shared_ptr xml, std::shared_ptr socket) { _size = dcp::Size (xml->number_child("Width"), xml->number_child("Height")); if (xml->optional_number_child("Eye")) { _eye = static_cast(xml->number_child("Eye")); } auto data = make_shared(xml->number_child("Size")); /* This only matters when we are using J2KImageProxy for the preview, which will never use this constructor (which is only used for passing data to encode servers). So we can put anything in here. It's a bit of a hack. */ _pixel_format = AV_PIX_FMT_XYZ12LE; socket->read(data->data(), data->size()); _data = data; } /* For tests */ J2KImageProxy(dcp::ArrayData data, dcp::Size size, AVPixelFormat pixel_format) : _data(data) , _size(size) , _pixel_format(pixel_format) , _error(false) { /* ::image assumes 16bpp */ DCPOMATIC_ASSERT (_pixel_format == AV_PIX_FMT_RGB48 || _pixel_format == AV_PIX_FMT_XYZ12LE); } Result image ( Image::Alignment alignment, boost::optional size = boost::optional () ) const override { int const r = prepare (alignment, target_size); /* I think this is safe without a lock on mutex. _image is guaranteed to be set up when prepare() has happened. */ return Result(_image, r, _error); } void add_metadata(xmlpp::Element*) const override { cxml::add_text_child(element, "Type", N_("J2K")); cxml::add_text_child(element, "Width", raw_convert(_size.width)); cxml::add_text_child(element, "Height", raw_convert(_size.height)); if (_eye) { cxml::add_text_child(element, "Eye", raw_convert(static_cast(_eye.get()))); } cxml::add_text_child(element, "Size", raw_convert(_data.size())); } void write_to_socket (std::shared_ptr override) const override { socket->write(_data.data(), _data.size()); } /** @return true if our image is definitely the same as another, false if it is probably not */ bool same(std::shared_ptr) const override { auto jp = dynamic_pointer_cast(other); if (!jp) { return false; } return _data == *jp->_data; } int prepare(Image::Alignment alignment, boost::optional = boost::optional()) const override { boost::mutex::scoped_lock lm (_mutex); if (_image && target_size == _target_size) { DCPOMATIC_ASSERT (_reduce); return *_reduce; } int reduce = 0; if (_forced_reduction) { reduce = *_forced_reduction; } else { while (target_size && (_size.width / pow(2, reduce)) > target_size->width && (_size.height / pow(2, reduce)) > target_size->height) { ++reduce; } --reduce; reduce = max (0, reduce); } try { /* XXX: should check that potentially trashing _data here doesn't matter */ auto decompressed = dcp::decompress_j2k (const_cast(_data->data()), _data->size(), reduce); _image = make_shared(_pixel_format, decompressed->size(), alignment); int const shift = 16 - decompressed->precision (0); /* Copy data in whatever format (sRGB or XYZ) into our Image; I'm assuming the data is 12-bit either way. */ int const width = decompressed->size().width; int p = 0; int* decomp_0 = decompressed->data (0); int* decomp_1 = decompressed->data (1); int* decomp_2 = decompressed->data (2); for (int y = 0; y < decompressed->size().height; ++y) { auto q = reinterpret_cast(_image->data()[0] + y * _image->stride()[0]); for (int x = 0; x < width; ++x) { *q++ = decomp_0[p] << shift; *q++ = decomp_1[p] << shift; *q++ = decomp_2[p] << shift; ++p; } } } catch (dcp::J2KDecompressionError& e) { _image = make_shared(_pixel_format, _size, alignment); _image->make_black (); _error = true; } _target_size = target_size; _reduce = reduce; return reduce; } std::shared_ptr j2k () const { return _data; } dcp::Size size () const { return _size; } boost::optional eye () const { return _eye; } size_t memory_used () const override { size_t m = _data->size(); if (_image) { /* 3 components, 16-bits per pixel */ m += 3 * 2 * _image->size().width * _image->size().height; } return m; } private: Frame _data; dcp::Size _size; boost::optional _eye; mutable std::shared_ptr _image; mutable boost::optional _target_size; mutable boost::optional _reduce; AVPixelFormat _pixel_format; mutable boost::mutex _mutex; boost::optional _forced_reduction; /** true if an error occurred while decoding the JPEG2000 data, false if not */ mutable bool _error; };