2 Copyright (C) 2012-2021 Carl Hetherington <cth@carlh.net>
4 This file is part of libdcp.
6 libdcp is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 libdcp is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with libdcp. If not, see <http://www.gnu.org/licenses/>.
19 In addition, as a special exception, the copyright holders give
20 permission to link the code of portions of this program with the
21 OpenSSL library under certain conditions as described in each
22 individual source file, and distribute linked combinations
25 You must obey the GNU General Public License in all respects
26 for all of the code used other than OpenSSL. If you modify
27 file(s) with this exception, you may extend this exception to your
28 version of the file(s), but you are not obligated to do so. If you
29 do not wish to do so, delete this exception statement from your
30 version. If you delete this exception statement from all source
31 files in the program, then also delete it here.
35 /** @file src/j2k_transcode.cc
36 * @brief Methods to encode and decode JPEG2000
40 #include "array_data.h"
41 #include "j2k_transcode.h"
42 #include "exceptions.h"
43 #include "openjpeg_image.h"
44 #include "dcp_assert.h"
45 #include "compose.hpp"
54 using std::shared_ptr;
55 using boost::shared_array;
59 shared_ptr<dcp::OpenJPEGImage>
60 dcp::decompress_j2k (Data const& data, int reduce)
62 return dcp::decompress_j2k (data.data(), data.size(), reduce);
66 shared_ptr<dcp::OpenJPEGImage>
67 dcp::decompress_j2k (shared_ptr<const Data> data, int reduce)
69 return dcp::decompress_j2k (data->data(), data->size(), reduce);
76 ReadBuffer (uint8_t const * data, int64_t size)
82 OPJ_SIZE_T read (void* buffer, OPJ_SIZE_T nb_bytes)
84 int64_t N = min (nb_bytes, _size - _offset);
85 memcpy (buffer, _data + _offset, N);
91 uint8_t const * _data;
98 read_function (void* buffer, OPJ_SIZE_T nb_bytes, void* data)
100 return reinterpret_cast<ReadBuffer*>(data)->read (buffer, nb_bytes);
105 read_free_function (void* data)
107 delete reinterpret_cast<ReadBuffer*>(data);
112 decompress_error_callback (char const * msg, void *)
114 throw J2KDecompressionError (msg);
119 compress_error_callback (char const * msg, void *)
121 throw MiscError (msg);
125 shared_ptr<dcp::OpenJPEGImage>
126 dcp::decompress_j2k (uint8_t const * data, int64_t size, int reduce)
128 DCP_ASSERT (reduce >= 0);
130 uint8_t const jp2_magic[] = {
141 auto format = OPJ_CODEC_J2K;
142 if (size >= int (sizeof (jp2_magic)) && memcmp (data, jp2_magic, sizeof (jp2_magic)) == 0) {
143 format = OPJ_CODEC_JP2;
146 auto decoder = opj_create_decompress (format);
148 boost::throw_exception (ReadError ("could not create JPEG2000 decompresser"));
150 opj_dparameters_t parameters;
151 opj_set_default_decoder_parameters (¶meters);
152 parameters.cp_reduce = reduce;
153 opj_setup_decoder (decoder, ¶meters);
155 auto stream = opj_stream_default_create (OPJ_TRUE);
157 throw MiscError ("could not create JPEG2000 stream");
160 opj_set_error_handler(decoder, decompress_error_callback, 00);
162 opj_stream_set_read_function (stream, read_function);
163 auto buffer = new ReadBuffer (data, size);
164 opj_stream_set_user_data (stream, buffer, read_free_function);
165 opj_stream_set_user_data_length (stream, size);
167 opj_image_t* image = 0;
168 opj_read_header (stream, decoder, &image);
169 if (opj_decode (decoder, stream, image) == OPJ_FALSE) {
170 opj_destroy_codec (decoder);
171 opj_stream_destroy (stream);
172 if (format == OPJ_CODEC_J2K) {
173 boost::throw_exception (ReadError (String::compose ("could not decode JPEG2000 codestream of %1 bytes.", size)));
175 boost::throw_exception (ReadError (String::compose ("could not decode JP2 file of %1 bytes.", size)));
179 opj_destroy_codec (decoder);
180 opj_stream_destroy (stream);
182 image->x1 = rint (float(image->x1) / pow (2.0f, reduce));
183 image->y1 = rint (float(image->y1) / pow (2.0f, reduce));
184 return std::make_shared<OpenJPEGImage>(image);
191 /* XXX: is there a better strategy for this? */
192 #define MAX_J2K_SIZE (1024 * 1024 * 2)
194 : _data (shared_array<uint8_t>(new uint8_t[MAX_J2K_SIZE]), MAX_J2K_SIZE)
200 OPJ_SIZE_T write (void* buffer, OPJ_SIZE_T nb_bytes)
202 DCP_ASSERT ((_offset + nb_bytes) < MAX_J2K_SIZE);
203 memcpy (_data.data() + _offset, buffer, nb_bytes);
205 if (_offset > OPJ_SIZE_T(_data.size())) {
206 _data.set_size (_offset);
211 OPJ_BOOL seek (OPJ_SIZE_T nb_bytes)
217 ArrayData data () const
229 write_function (void* buffer, OPJ_SIZE_T nb_bytes, void* data)
231 return reinterpret_cast<WriteBuffer*>(data)->write(buffer, nb_bytes);
236 write_free_function (void* data)
238 delete reinterpret_cast<WriteBuffer*>(data);
243 seek_function (OPJ_OFF_T nb_bytes, void* data)
245 return reinterpret_cast<WriteBuffer*>(data)->seek(nb_bytes);
251 dcp::compress_j2k (shared_ptr<const OpenJPEGImage> xyz, int bandwidth, int frames_per_second, bool threed, bool fourk, string comment)
253 /* get a J2K compressor handle */
254 auto encoder = opj_create_compress (OPJ_CODEC_J2K);
255 if (encoder == nullptr) {
256 throw MiscError ("could not create JPEG2000 encoder");
259 if (comment.empty()) {
260 /* asdcplib complains with "Illegal data size" when reading frames encoded with an empty comment */
261 throw MiscError("compress_j2k comment can not be an empty string");
264 opj_set_error_handler (encoder, compress_error_callback, 0);
266 /* Set encoding parameters to default values */
267 opj_cparameters_t parameters;
268 opj_set_default_encoder_parameters (¶meters);
270 parameters.numresolution = 7;
272 parameters.rsiz = fourk ? OPJ_PROFILE_CINEMA_4K : OPJ_PROFILE_CINEMA_2K;
273 parameters.cp_comment = strdup (comment.c_str());
276 parameters.max_cs_size = (bandwidth / 8) / frames_per_second;
278 /* In 3D we have only half the normal bandwidth per eye */
279 parameters.max_cs_size /= 2;
281 parameters.max_comp_size = parameters.max_cs_size / 1.25;
282 parameters.tcp_numlayers = 1;
283 parameters.tcp_mct = 1;
284 #ifdef LIBDCP_HAVE_NUMGBITS
285 parameters.numgbits = fourk ? 2 : 1;
288 /* Setup the encoder parameters using the current image and user parameters */
289 opj_setup_encoder (encoder, ¶meters, xyz->opj_image());
291 #ifndef LIBDCP_HAVE_NUMGBITS
292 string numgbits = String::compose("GUARD_BITS=%1", fourk ? 2 : 1);
293 char const* extra_options[] = { numgbits.c_str(), nullptr };
294 opj_encoder_set_extra_options(encoder, extra_options);
297 auto stream = opj_stream_default_create (OPJ_FALSE);
299 opj_destroy_codec (encoder);
300 free (parameters.cp_comment);
301 throw MiscError ("could not create JPEG2000 stream");
304 opj_stream_set_write_function (stream, write_function);
305 opj_stream_set_seek_function (stream, seek_function);
306 WriteBuffer* buffer = new WriteBuffer ();
307 opj_stream_set_user_data (stream, buffer, write_free_function);
309 if (!opj_start_compress (encoder, xyz->opj_image(), stream)) {
310 opj_stream_destroy (stream);
311 opj_destroy_codec (encoder);
312 free (parameters.cp_comment);
313 if ((errno & 0x61500) == 0x61500) {
314 /* We've had one of the magic error codes from our patched openjpeg */
315 boost::throw_exception (StartCompressionError (errno & 0xff));
317 boost::throw_exception (StartCompressionError ());
321 if (!opj_encode (encoder, stream)) {
322 opj_stream_destroy (stream);
323 opj_destroy_codec (encoder);
324 free (parameters.cp_comment);
325 throw MiscError ("JPEG2000 encoding failed");
328 if (!opj_end_compress (encoder, stream)) {
329 opj_stream_destroy (stream);
330 opj_destroy_codec (encoder);
331 free (parameters.cp_comment);
332 throw MiscError ("could not end JPEG2000 encoding");
335 ArrayData enc (buffer->data ());
337 opj_stream_destroy (stream);
338 opj_destroy_codec (encoder);
339 free (parameters.cp_comment);