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