1 // Copyright 2008-present Contributors to the OpenImageIO project.
2 // SPDX-License-Identifier: BSD-3-Clause
3 // https://github.com/OpenImageIO/oiio/blob/master/LICENSE.md
4
5 #include <vector>
6
7 #include "openjpeg.h"
8
9 #include <OpenImageIO/filesystem.h>
10 #include <OpenImageIO/fmath.h>
11 #include <OpenImageIO/imageio.h>
12
13
14 OIIO_PLUGIN_NAMESPACE_BEGIN
15
16
17 static void
openjpeg_error_callback(const char * msg,void * data)18 openjpeg_error_callback(const char* msg, void* data)
19 {
20 if (ImageOutput* input = (ImageOutput*)data) {
21 if (!msg || !msg[0])
22 msg = "Unknown OpenJpeg error";
23 input->errorf("%s", msg);
24 }
25 }
26
27
28 static void
openjpeg_dummy_callback(const char *,void *)29 openjpeg_dummy_callback(const char* /*msg*/, void* /*data*/)
30 {
31 }
32
33
34
35 class Jpeg2000Output final : public ImageOutput {
36 public:
Jpeg2000Output()37 Jpeg2000Output() { init(); }
~Jpeg2000Output()38 virtual ~Jpeg2000Output() { close(); }
format_name(void) const39 virtual const char* format_name(void) const override { return "jpeg2000"; }
supports(string_view feature) const40 virtual int supports(string_view feature) const override
41 {
42 return (feature == "alpha");
43 // FIXME: we should support Exif/IPTC, but currently don't.
44 }
45 virtual bool open(const std::string& name, const ImageSpec& spec,
46 OpenMode mode = Create) override;
47 virtual bool close() override;
48 virtual bool write_scanline(int y, int z, TypeDesc format, const void* data,
49 stride_t xstride) override;
50 virtual bool write_tile(int x, int y, int z, TypeDesc format,
51 const void* data, stride_t xstride,
52 stride_t ystride, stride_t zstride) override;
53
54 private:
55 std::string m_filename;
56 FILE* m_file;
57 opj_cparameters_t m_compression_parameters;
58 opj_image_t* m_image;
59 opj_codec_t* m_codec;
60 opj_stream_t* m_stream;
61 unsigned int m_dither;
62 bool m_convert_alpha; //< Do we deassociate alpha?
63 std::vector<unsigned char> m_tilebuffer;
64 std::vector<unsigned char> m_scratch;
65
init(void)66 void init(void)
67 {
68 m_file = NULL;
69 m_image = NULL;
70 m_codec = NULL;
71 m_stream = NULL;
72 m_convert_alpha = true;
73 }
74
75 opj_image_t* create_jpeg2000_image();
76
77 void init_components(opj_image_cmptparm_t* components, int precision);
78
79 opj_codec_t* create_compressor();
80
destroy_compressor()81 void destroy_compressor()
82 {
83 if (m_codec) {
84 opj_destroy_codec(m_codec);
85 m_codec = NULL;
86 }
87 }
88
destroy_stream()89 void destroy_stream()
90 {
91 if (m_stream) {
92 opj_stream_destroy(m_stream);
93 m_stream = NULL;
94 }
95 }
96
97 bool save_image();
98
99 template<typename T> void write_scanline(int y, int z, const void* data);
100
101 void setup_cinema_compression(OPJ_RSIZ_CAPABILITIES p_rsizCap);
102
103 void setup_compression_params();
104
105 OPJ_PROG_ORDER get_progression_order(const std::string& progression_order);
106 };
107
108
109 // Obligatory material to make this a recognizeable imageio plugin
110 OIIO_PLUGIN_EXPORTS_BEGIN
111
112 OIIO_EXPORT ImageOutput*
jpeg2000_output_imageio_create()113 jpeg2000_output_imageio_create()
114 {
115 return new Jpeg2000Output;
116 }
117
118 OIIO_EXPORT const char* jpeg2000_output_extensions[] = { "jp2", "j2k",
119 nullptr };
120
121 OIIO_PLUGIN_EXPORTS_END
122
123
124 bool
open(const std::string & name,const ImageSpec & spec,OpenMode mode)125 Jpeg2000Output::open(const std::string& name, const ImageSpec& spec,
126 OpenMode mode)
127 {
128 if (mode != Create) {
129 errorf("%s does not support subimages or MIP levels", format_name());
130 return false;
131 }
132
133 m_filename = name;
134 m_spec = spec;
135
136 // Check for things this format doesn't support
137 if (m_spec.width < 1 || m_spec.height < 1) {
138 errorf("Image resolution must be at least 1x1, you asked for %d x %d",
139 m_spec.width, m_spec.height);
140 return false;
141 }
142 if (m_spec.depth < 1)
143 m_spec.depth = 1;
144 if (m_spec.depth > 1) {
145 errorf("%s does not support volume images (depth > 1)", format_name());
146 return false;
147 }
148
149 if (m_spec.nchannels != 1 && m_spec.nchannels != 3
150 && m_spec.nchannels != 4) {
151 errorf("%s does not support %d-channel images\n", format_name(),
152 m_spec.nchannels);
153 return false;
154 }
155
156 // If not uint8 or uint16, default to uint8
157 if (m_spec.format != TypeDesc::UINT8 && m_spec.format != TypeDesc::UINT16)
158 m_spec.set_format(TypeDesc::UINT8);
159
160 m_dither = (m_spec.format == TypeDesc::UINT8)
161 ? m_spec.get_int_attribute("oiio:dither", 0)
162 : 0;
163 m_convert_alpha = m_spec.alpha_channel != -1
164 && !m_spec.get_int_attribute("oiio:UnassociatedAlpha", 0);
165
166 m_file = Filesystem::fopen(m_filename, "wb");
167 if (m_file == NULL) {
168 errorf("Could not open \"%s\"", m_filename);
169 return false;
170 }
171
172 // If user asked for tiles -- which this format doesn't support, emulate
173 // it by buffering the whole image.
174 if (m_spec.tile_width && m_spec.tile_height)
175 m_tilebuffer.resize(m_spec.image_bytes());
176
177 m_image = create_jpeg2000_image();
178 return true;
179 }
180
181
182
183 template<class T>
184 static void
deassociateAlpha(T * data,int size,int channels,int alpha_channel,float gamma)185 deassociateAlpha(T* data, int size, int channels, int alpha_channel,
186 float gamma)
187 {
188 unsigned int max = std::numeric_limits<T>::max();
189 if (gamma == 1) {
190 for (int x = 0; x < size; ++x, data += channels)
191 if (data[alpha_channel])
192 for (int c = 0; c < channels; c++)
193 if (c != alpha_channel) {
194 unsigned int f = data[c];
195 f = (f * max) / data[alpha_channel];
196 data[c] = (T)std::min(max, f);
197 }
198 } else {
199 for (int x = 0; x < size; ++x, data += channels)
200 if (data[alpha_channel]) {
201 // See associateAlpha() for an explanation.
202 float alpha_deassociate = pow((float)max / data[alpha_channel],
203 gamma);
204 for (int c = 0; c < channels; c++)
205 if (c != alpha_channel)
206 data[c] = static_cast<T>(std::min(
207 max, (unsigned int)(data[c] * alpha_deassociate)));
208 }
209 }
210 }
211
212
213
214 bool
write_scanline(int y,int z,TypeDesc format,const void * data,stride_t xstride)215 Jpeg2000Output::write_scanline(int y, int z, TypeDesc format, const void* data,
216 stride_t xstride)
217 {
218 y -= m_spec.y;
219 if (y > m_spec.height) {
220 errorf("Attempt to write too many scanlines to %s", m_filename);
221 return false;
222 }
223
224 m_spec.auto_stride(xstride, format, spec().nchannels);
225 const void* origdata = data;
226 data = to_native_scanline(format, data, xstride, m_scratch, m_dither, y, z);
227 if (data == origdata) {
228 m_scratch.assign((unsigned char*)data,
229 (unsigned char*)data + m_spec.scanline_bytes());
230 data = &m_scratch[0];
231 }
232
233 // JPEG-2000 specifically dictates unassociated (un-"premultiplied") alpha
234 if (m_convert_alpha) {
235 if (m_spec.format == TypeDesc::UINT16)
236 deassociateAlpha((unsigned short*)data, m_spec.width,
237 m_spec.nchannels, m_spec.alpha_channel, 2.2f);
238 else
239 deassociateAlpha((unsigned char*)data, m_spec.width,
240 m_spec.nchannels, m_spec.alpha_channel, 2.2f);
241 }
242
243 if (m_spec.format == TypeDesc::UINT8)
244 write_scanline<uint8_t>(y, z, data);
245 else
246 write_scanline<uint16_t>(y, z, data);
247
248 if (y == m_spec.height - 1)
249 save_image();
250
251 return true;
252 }
253
254
255
256 bool
write_tile(int x,int y,int z,TypeDesc format,const void * data,stride_t xstride,stride_t ystride,stride_t zstride)257 Jpeg2000Output::write_tile(int x, int y, int z, TypeDesc format,
258 const void* data, stride_t xstride, stride_t ystride,
259 stride_t zstride)
260 {
261 // Emulate tiles by buffering the whole image
262 return copy_tile_to_image_buffer(x, y, z, format, data, xstride, ystride,
263 zstride, &m_tilebuffer[0]);
264 }
265
266
267
268 bool
close()269 Jpeg2000Output::close()
270 {
271 if (!m_stream) { // Already closed
272 return true;
273 }
274
275 bool ok = true;
276 if (m_spec.tile_width) {
277 // We've been emulating tiles; now dump as scanlines.
278 OIIO_ASSERT(m_tilebuffer.size());
279 ok &= write_scanlines(m_spec.y, m_spec.y + m_spec.height, 0,
280 m_spec.format, &m_tilebuffer[0]);
281 std::vector<unsigned char>().swap(m_tilebuffer);
282 }
283
284 if (m_image) {
285 opj_image_destroy(m_image);
286 m_image = NULL;
287 }
288 destroy_compressor();
289 destroy_stream();
290 if (m_file) {
291 fclose(m_file);
292 m_file = NULL;
293 }
294 return ok;
295 }
296
297
298
299 bool
save_image()300 Jpeg2000Output::save_image()
301 {
302 m_codec = create_compressor();
303 if (!m_codec)
304 return false;
305
306 opj_set_error_handler(m_codec, openjpeg_error_callback, this);
307 opj_set_warning_handler(m_codec, openjpeg_dummy_callback, NULL);
308 opj_set_info_handler(m_codec, openjpeg_dummy_callback, NULL);
309
310 opj_setup_encoder(m_codec, &m_compression_parameters, m_image);
311
312 #if defined(OPJ_VERSION_MAJOR)
313 // OpenJpeg >= 2.1
314 m_stream = opj_stream_create_default_file_stream(m_filename.c_str(), false);
315 #else
316 // OpenJpeg 2.0: need to open a stream ourselves
317 m_file = Filesystem::fopen(m_filename, "wb");
318 m_stream = opj_stream_create_default_file_stream(m_file, false);
319 #endif
320 if (!m_stream) {
321 errorf("Failed write jpeg2000::save_image");
322 return false;
323 }
324
325 if (!opj_start_compress(m_codec, m_image, m_stream)
326 || !opj_encode(m_codec, m_stream)
327 || !opj_end_compress(m_codec, m_stream)) {
328 errorf("Failed write jpeg2000::save_image");
329 return false;
330 }
331
332 return true;
333 }
334
335
336 opj_image_t*
create_jpeg2000_image()337 Jpeg2000Output::create_jpeg2000_image()
338 {
339 setup_compression_params();
340
341 OPJ_COLOR_SPACE color_space = OPJ_CLRSPC_SRGB;
342 if (m_spec.nchannels == 1)
343 color_space = OPJ_CLRSPC_GRAY;
344
345 int precision = 16;
346 const ParamValue* prec = m_spec.find_attribute("oiio:BitsPerSample",
347 TypeDesc::INT);
348 if (prec)
349 precision = *(int*)prec->data();
350 else if (m_spec.format == TypeDesc::UINT8
351 || m_spec.format == TypeDesc::INT8)
352 precision = 8;
353
354 const int MAX_COMPONENTS = 4;
355 opj_image_cmptparm_t component_params[MAX_COMPONENTS];
356 init_components(component_params, precision);
357
358 m_image = opj_image_create(m_spec.nchannels, &component_params[0],
359 color_space);
360
361 m_image->x0 = m_compression_parameters.image_offset_x0;
362 m_image->y0 = m_compression_parameters.image_offset_y0;
363 m_image->x1 = m_compression_parameters.image_offset_x0
364 + (m_spec.width - 1) * m_compression_parameters.subsampling_dx
365 + 1;
366 m_image->y1
367 = m_compression_parameters.image_offset_y0
368 + (m_spec.height - 1) * m_compression_parameters.subsampling_dy + 1;
369
370 #if 0
371 // FIXME: I seem to get crashes with OpenJpeg 2.x in the presence of ICC
372 // profiles. I have no idea why. It seems like losing the ability to
373 // write ICC profiles is the lesser evil compared to either restricting
374 // ourselves to OpenJpeg 1.5 or living with crashes. I'm at the limit of
375 // my knowledge of OpenJPEG, which frankly has a poor API and abysmal
376 // documentation. So I'll leave the repair of this for later. If
377 // someboody comes along that desperately needs JPEG2000 and ICC
378 // profiles, maybe they will be motivated enough to track down the
379 // problem.
380 const ParamValue *icc = m_spec.find_attribute ("ICCProfile");
381 if (icc && icc->type().basetype == TypeDesc::UINT8 && icc->type().arraylen > 0) {
382 m_image->icc_profile_len = icc->type().arraylen;
383 m_image->icc_profile_buf = (unsigned char *) icc->data();
384 }
385 #endif
386
387 return m_image;
388 }
389
390
391 inline void
init_components(opj_image_cmptparm_t * components,int precision)392 Jpeg2000Output::init_components(opj_image_cmptparm_t* components, int precision)
393 {
394 memset(components, 0x00, m_spec.nchannels * sizeof(opj_image_cmptparm_t));
395 for (int i = 0; i < m_spec.nchannels; i++) {
396 components[i].dx = m_compression_parameters.subsampling_dx;
397 components[i].dy = m_compression_parameters.subsampling_dy;
398 components[i].w = m_spec.width;
399 components[i].h = m_spec.height;
400 components[i].prec = precision;
401 components[i].bpp = precision;
402 components[i].sgnd = 0;
403 }
404 }
405
406
407 opj_codec_t*
create_compressor()408 Jpeg2000Output::create_compressor()
409 {
410 std::string ext = Filesystem::extension(m_filename);
411 opj_codec_t* compressor = NULL;
412 if (ext == ".j2k")
413 compressor = opj_create_compress(OPJ_CODEC_J2K);
414 else if (ext == ".jp2")
415 compressor = opj_create_compress(OPJ_CODEC_JP2);
416
417 return compressor;
418 }
419
420
421
422 template<typename T>
423 void
write_scanline(int y,int,const void * data)424 Jpeg2000Output::write_scanline(int y, int /*z*/, const void* data)
425 {
426 int bits = sizeof(T) * 8;
427 const T* scanline = static_cast<const T*>(data);
428 const size_t scanline_pos = (y - m_spec.y) * m_spec.width;
429 for (int i = 0, j = 0; i < m_spec.width; i++) {
430 for (int c = 0; c < m_spec.nchannels; ++c) {
431 unsigned int val = scanline[j++];
432 if (bits != int(m_image->comps[c].prec))
433 val = bit_range_convert(val, bits, m_image->comps[c].prec);
434 m_image->comps[c].data[scanline_pos + i] = val;
435 }
436 }
437 }
438
439
440
441 void
setup_cinema_compression(OPJ_RSIZ_CAPABILITIES p_rsizCap)442 Jpeg2000Output::setup_cinema_compression(OPJ_RSIZ_CAPABILITIES p_rsizCap)
443 {
444 m_compression_parameters.tile_size_on = false;
445 m_compression_parameters.cp_tdx = 1;
446 m_compression_parameters.cp_tdy = 1;
447
448 m_compression_parameters.tp_flag = 'C';
449 m_compression_parameters.tp_on = 1;
450
451 m_compression_parameters.cp_tx0 = 0;
452 m_compression_parameters.cp_ty0 = 0;
453 m_compression_parameters.image_offset_x0 = 0;
454 m_compression_parameters.image_offset_y0 = 0;
455
456 m_compression_parameters.cblockw_init = 32;
457 m_compression_parameters.cblockh_init = 32;
458 m_compression_parameters.csty |= 0x01;
459
460 m_compression_parameters.prog_order = OPJ_CPRL;
461
462 m_compression_parameters.roi_compno = -1;
463
464 m_compression_parameters.subsampling_dx = 1;
465 m_compression_parameters.subsampling_dy = 1;
466
467 m_compression_parameters.irreversible = 1;
468
469 m_compression_parameters.cp_rsiz = p_rsizCap;
470 if (p_rsizCap == OPJ_CINEMA4K) {
471 m_compression_parameters.cp_cinema = OPJ_CINEMA4K_24;
472 m_compression_parameters.POC[0].tile = 1;
473 m_compression_parameters.POC[0].resno0 = 0;
474 m_compression_parameters.POC[0].compno0 = 0;
475 m_compression_parameters.POC[0].layno1 = 1;
476 m_compression_parameters.POC[0].resno1
477 = m_compression_parameters.numresolution - 1;
478 m_compression_parameters.POC[0].compno1 = 3;
479 m_compression_parameters.POC[0].prg1 = OPJ_CPRL;
480 m_compression_parameters.POC[1].tile = 1;
481 m_compression_parameters.POC[1].resno0
482 = m_compression_parameters.numresolution - 1;
483 m_compression_parameters.POC[1].compno0 = 0;
484 m_compression_parameters.POC[1].layno1 = 1;
485 m_compression_parameters.POC[1].resno1
486 = m_compression_parameters.numresolution;
487 m_compression_parameters.POC[1].compno1 = 3;
488 m_compression_parameters.POC[1].prg1 = OPJ_CPRL;
489 } else if (p_rsizCap == OPJ_CINEMA2K) {
490 m_compression_parameters.cp_cinema = OPJ_CINEMA2K_24;
491 }
492 }
493
494
495 void
setup_compression_params()496 Jpeg2000Output::setup_compression_params()
497 {
498 opj_set_default_encoder_parameters(&m_compression_parameters);
499 m_compression_parameters.tcp_rates[0] = 0;
500 m_compression_parameters.tcp_numlayers++;
501 m_compression_parameters.cp_disto_alloc = 1;
502
503 const ParamValue* is_cinema2k = m_spec.find_attribute("jpeg2000:Cinema2K",
504 TypeDesc::UINT);
505 if (is_cinema2k)
506 setup_cinema_compression(OPJ_CINEMA2K);
507
508 const ParamValue* is_cinema4k = m_spec.find_attribute("jpeg2000:Cinema4K",
509 TypeDesc::UINT);
510 if (is_cinema4k)
511 setup_cinema_compression(OPJ_CINEMA4K);
512
513 const ParamValue* initial_cb_width
514 = m_spec.find_attribute("jpeg2000:InitialCodeBlockWidth",
515 TypeDesc::UINT);
516 if (initial_cb_width && initial_cb_width->data())
517 m_compression_parameters.cblockw_init
518 = *(unsigned int*)initial_cb_width->data();
519
520 const ParamValue* initial_cb_height
521 = m_spec.find_attribute("jpeg2000:InitialCodeBlockHeight",
522 TypeDesc::UINT);
523 if (initial_cb_height && initial_cb_height->data())
524 m_compression_parameters.cblockh_init
525 = *(unsigned int*)initial_cb_height->data();
526
527 const ParamValue* progression_order
528 = m_spec.find_attribute("jpeg2000:ProgressionOrder", TypeDesc::STRING);
529 if (progression_order && progression_order->data()) {
530 std::string prog_order((const char*)progression_order->data());
531 m_compression_parameters.prog_order = get_progression_order(prog_order);
532 }
533
534 const ParamValue* compression_mode
535 = m_spec.find_attribute("jpeg2000:CompressionMode", TypeDesc::INT);
536 if (compression_mode && compression_mode->data())
537 m_compression_parameters.mode = *(int*)compression_mode->data();
538 }
539
540 OPJ_PROG_ORDER
get_progression_order(const std::string & progression_order)541 Jpeg2000Output::get_progression_order(const std::string& progression_order)
542 {
543 if (progression_order == "LRCP")
544 return OPJ_LRCP;
545 else if (progression_order == "RLCP")
546 return OPJ_RLCP;
547 else if (progression_order == "RPCL")
548 return OPJ_RPCL;
549 else if (progression_order == "PCRL")
550 return OPJ_PCRL;
551 else if (progression_order == "PCRL")
552 return OPJ_CPRL;
553 return OPJ_PROG_UNKNOWN;
554 }
555
556 OIIO_PLUGIN_NAMESPACE_END
557