summaryrefslogtreecommitdiff
blob: 58a9c36e04ff42578312196b76a7a06bc7a23091 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
/*====================================================================*
 -  Copyright (C) 2001 Leptonica.  All rights reserved.
 -
 -  Redistribution and use in source and binary forms, with or without
 -  modification, are permitted provided that the following conditions
 -  are met:
 -  1. Redistributions of source code must retain the above copyright
 -     notice, this list of conditions and the following disclaimer.
 -  2. Redistributions in binary form must reproduce the above
 -     copyright notice, this list of conditions and the following
 -     disclaimer in the documentation and/or other materials
 -     provided with the distribution.
 -
 -  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 -  ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 -  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 -  A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL ANY
 -  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 -  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 -  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 -  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 -  OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 -  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 -  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *====================================================================*/

/*!
 * \file jp2kio.c
 * <pre>
 *
 *    Read jp2k from file
 *          PIX                *pixReadJp2k()  [special top level]
 *          PIX                *pixReadStreamJp2k()
 *
 *    Write jp2k to file
 *          l_int32             pixWriteJp2k()  [special top level]
 *          l_int32             pixWriteStreamJp2k()
 *          static opj_image_t *pixConvertToOpjImage()
 *
 *    Read/write to memory
 *          PIX                *pixReadMemJp2k()
 *          l_int32             pixWriteMemJp2k()
 *
 *    Static functions from opj 2.0 to retain file stream interface
 *          static opj_stream_t  *opjCreateStream()
 *          [other static helpers]
 *
 *    Based on the OpenJPEG distribution:
 *        http://www.openjpeg.org/
 *    The ISO/IEC reference for jpeg2000 is:
 *        http://www.jpeg.org/public/15444-1annexi.pdf
 *
 *    Compressing to memory and decompressing from memory
 *    ---------------------------------------------------
 *    On systems like windows without fmemopen() and open_memstream(),
 *    we write data to a temp file and read it back for operations
 *    between pix and compressed-data, such as pixReadMemJp2k() and
 *    pixWriteMemJp2k().
 *
 *    Pdf can accept jp2k compressed strings directly
 *    -----------------------------------------------
 *    Transcoding (with the uncompress/compress cycle) is not required
 *    to wrap images that have already been compressed with jp2k in pdf,
 *    because the pdf format for jp2k includes the full string of the
 *    jp2k compressed images.  This is also true for jpeg compressed
 *    strings.
 *
 *    N.B.
 *    * This is based on the most recent openjpeg release: 2.1.
 *    * The openjpeg interface was massively changed from 1.X.  The debian
 *      distribution is way back at 1.3.  We have inquired but are unable
 *      to determine if or when a debian distribution will be built for 2.1.
 *    * For version 2.1, the openjpeg.h file is installed in an
 *      openjpeg-2.1 subdirectory, which is hard to support.
 *    * In openjpeg-2.1, reading is slow compared to jpeg or webp,
 *      and writing is very slow compared to jpeg or webp.  This is expected
 *      to improve significantly in future versions.
 *    * Reading and writing jp2k are supported here for 2.1.
 *      The high-level interface to openjpeg continues to change.
 *      From 2.0 to 2.1, the ability to interface to a C file stream
 *      was removed permanently.  Leptonica supports both file stream
 *      and memory buffer interfaces for every image I/O library, and
 *      it requires the libraries to support at least one of these.
 *      However, openjpeg-2.1 provides neither, so we have brought
 *      several static functions over from openjpeg-2.0 in order to
 *      retain the file stream interface.  See our static function
 *      opjCreateStream().
 *    * Specifying a quality factor for jpeg2000 requires caution.  Unlike
 *      jpeg and webp, which have a sensible scale that goes from 0 (very poor)
 *      to 100 (nearly lossless), kakadu and openjpeg use idiosyncratic and
 *      non-intuitive numbers.  kakadu uses "rate/distortion" numbers in
 *      a narrow range around 50,000; openjpeg (and our write interface)
 *      use SNR.  The visually apparent artifacts introduced by compression
 *      are strongly content-dependent and vary in a highly non-linear
 *      way with SNR.  We take SNR = 34 as default, roughly similar in
 *      quality to jpeg's default standard of 75.  For document images,
 *      SNR = 25 is very poor, whereas SNR = 45 is nearly lossless.  If you
 *      use the latter, you will pay dearly in the size of the compressed file.
 * </pre>
 */

#ifdef HAVE_CONFIG_H
#include <config_auto.h>
#endif  /* HAVE_CONFIG_H */

#include <string.h>
#include "allheaders.h"

/* --------------------------------------------*/
#if  HAVE_LIBJP2K   /* defined in environ.h */
/* --------------------------------------------*/

    /* Leptonica supports versions 2.0 and newer */
#ifdef LIBJP2K_HEADER
#include LIBJP2K_HEADER
#else
#include <openjpeg.h>
#endif

    /* 2.0 didn't define OPJ_VERSION_MINOR. */
#ifndef OPJ_VERSION_MINOR
#define OPJ_VERSION_MINOR 0
#endif

    /* Static generator of opj_stream from file stream.
     * In 2.0.1, this functionality is provided by
     *    opj_stream_create_default_file_stream(),
     * but it was removed in 2.1.0. Because we must have either
     * a file stream or a memory interface to the compressed data,
     * it is necessary to recreate the stream interface here.  */
static opj_stream_t *opjCreateStream(FILE *fp, l_int32 is_read);

    /* Static converter pix --> opj_image.  Used for compressing pix,
     * because the codec works on data stored in their raster format. */
static opj_image_t *pixConvertToOpjImage(PIX *pix);

/*---------------------------------------------------------------------*
 *                        Callback event handlers                      *
 *---------------------------------------------------------------------*/
static void error_callback(const char *msg, void *client_data) {
  (void)client_data;
  fprintf(stdout, "[ERROR] %s", msg);
}

static void warning_callback(const char *msg, void *client_data) {
  (void)client_data;
  fprintf(stdout, "[WARNING] %s", msg);
}

static void info_callback(const char *msg, void *client_data) {
  (void)client_data;
  fprintf(stdout, "[INFO] %s", msg);
}


/*---------------------------------------------------------------------*
 *                 Read jp2k from file (special function)              *
 *---------------------------------------------------------------------*/
/*!
 * \brief   pixReadJp2k()
 *
 * \param[in]    filename
 * \param[in]    reduction   scaling factor: 1, 2, 4, 8, 16
 * \param[in]    box         [optional] for extracting a subregion, can be null
 * \param[in]    hint        a bitwise OR of L_JP2K_* values; 0 for default
 * \param[in]    debug       output callback messages, etc
 * \return  pix 8 or 32 bpp, or NULL on error
 *
 * <pre>
 * Notes:
 *      (1) This is a special function for reading jp2k files.
 *          The high-level pixReadStream() uses default values:
 *             %reduction = 1
 *             %box = NULL
 *      (2) This decodes at either full resolution or at a reduction by
 *          a power of 2.  The default value %reduction == 1 gives a full
 *          resolution image.  Use %reduction > 1 to get a reduced image.
 *          The actual values of %reduction that can be used on an image
 *          depend on the number of resolution levels chosen when the
 *          image was compressed.  Typical values might be 1, 2, 4, 8 and 16.
 *          Using a value representing a reduction level that was not
 *          stored when the file was written will fail with the message:
 *          "failed to read the header".
 *      (3) Use %box to decode only a part of the image.  The box is defined
 *          at full resolution.  It is reduced internally by %reduction,
 *          and clipping to the right and bottom of the image is automatic.
 *      (4) We presently only handle images with 8 bits/sample (bps).
 *          If the image has 16 bps, the read will fail.
 *      (5) There are 4 possible values of samples/pixel (spp).
 *          The values in brackets give the pixel values in the Pix:
 *           spp = 1  ==>  grayscale           [8 bpp grayscale]
 *           spp = 2  ==>  grayscale + alpha   [32 bpp rgba]
 *           spp = 3  ==>  rgb                 [32 bpp rgb]
 *           spp = 4  ==>  rgba                [32 bpp rgba]
 *      (6) The %hint parameter is reserved for future use.
 * </pre>
 */
PIX *
pixReadJp2k(const char  *filename,
            l_uint32     reduction,
            BOX         *box,
            l_int32      hint,
            l_int32      debug)
{
FILE     *fp;
PIX      *pix;

    PROCNAME("pixReadJp2k");

    if (!filename)
        return (PIX *)ERROR_PTR("filename not defined", procName, NULL);

    if ((fp = fopenReadStream(filename)) == NULL)
        return (PIX *)ERROR_PTR("image file not found", procName, NULL);
    pix = pixReadStreamJp2k(fp, reduction, box, hint, debug);
    fclose(fp);

    if (!pix)
        return (PIX *)ERROR_PTR("image not returned", procName, NULL);
    return pix;
}


/*!
 * \brief   pixReadStreamJp2k()
 *
 * \param[in]    fp          file stream
 * \param[in]    reduction   scaling factor: 1, 2, 4, 8
 * \param[in]    box         [optional] for extracting a subregion, can be null
 * \param[in]    hint        a bitwise OR of L_JP2K_* values; 0 for default
 * \param[in]    debug       output callback messages, etc
 * \return  pix 8 or 32 bpp, or NULL on error
 *
 * <pre>
 * Notes:
 *      (1) See pixReadJp2k() for usage.
 * </pre>
 */
PIX *
pixReadStreamJp2k(FILE     *fp,
                  l_uint32  reduction,
                  BOX      *box,
                  l_int32   hint,
                  l_int32   debug)
{
const char        *opjVersion;
l_int32            i, j, index, bx, by, bw, bh, val, rval, gval, bval, aval;
l_int32            w, h, wpl, bps, spp, xres, yres, reduce, prec, colorspace;
l_uint32           pixel;
l_uint32          *data, *line;
opj_dparameters_t  parameters;   /* decompression parameters */
opj_image_t       *image = NULL;
opj_codec_t       *l_codec = NULL;  /* handle to decompressor */
opj_stream_t      *l_stream = NULL;  /* opj stream */
PIX               *pix = NULL;

    PROCNAME("pixReadStreamJp2k");

    if (!fp)
        return (PIX *)ERROR_PTR("fp not defined", procName, NULL);

    opjVersion = opj_version();
    if (opjVersion[0] != '2') {
        L_ERROR("version is %s; must be 2.0 or higher\n", procName, opjVersion);
        return NULL;
    }
    if ((opjVersion[2] - 0x30) != OPJ_VERSION_MINOR) {
        L_ERROR("version %s: differs from minor = %d\n",
                procName, opjVersion, OPJ_VERSION_MINOR);
         return NULL;
     }

        /* Get the resolution and the bits/sample */
    rewind(fp);
    fgetJp2kResolution(fp, &xres, &yres);
    freadHeaderJp2k(fp, NULL, NULL, &bps, NULL);
    rewind(fp);

    if (bps > 8) {
        L_ERROR("found %d bps; can only handle 8 bps\n", procName, bps);
        return NULL;
    }

        /* Set decoding parameters to default values */
    opj_set_default_decoder_parameters(&parameters);

        /* Find and set the reduce parameter, which is log2(reduction).
         * Valid reductions are powers of 2, and are determined when the
         * compressed string is made.  A request for an invalid reduction
         * will cause an error in opj_read_header(), and no image will
         * be returned. */
    for (reduce = 0; (1L << reduce) < reduction; reduce++) { }
    if ((1L << reduce) != reduction) {
        L_ERROR("invalid reduction %d; not power of 2\n", procName, reduction);
        return NULL;
    }
    parameters.cp_reduce = reduce;

        /* Get a decoder handle */
    if ((l_codec = opj_create_decompress(OPJ_CODEC_JP2)) == NULL) {
        L_ERROR("failed to make the codec\n", procName);
        return NULL;
    }

        /* Catch and report events using callbacks */
    if (debug) {
        opj_set_info_handler(l_codec, info_callback, NULL);
        opj_set_warning_handler(l_codec, warning_callback, NULL);
        opj_set_error_handler(l_codec, error_callback, NULL);
    }

        /* Setup the decoding parameters using user parameters */
    if (!opj_setup_decoder(l_codec, &parameters)){
        L_ERROR("failed to set up decoder\n", procName);
        opj_destroy_codec(l_codec);
        return NULL;
    }

        /* Open decompression 'stream'.  In 2.0, we could call this:
         *    opj_stream_create_default_file_stream(fp, 1)
         * but the file stream interface was removed in 2.1. */
    if ((l_stream = opjCreateStream(fp, 1)) == NULL) {
        L_ERROR("failed to open the stream\n", procName);
        opj_destroy_codec(l_codec);
        return NULL;
    }

        /* Read the main header of the codestream and, if necessary,
         * the JP2 boxes */
    if(!opj_read_header(l_stream, l_codec, &image)){
        L_ERROR("failed to read the header\n", procName);
        opj_stream_destroy(l_stream);
        opj_destroy_codec(l_codec);
        opj_image_destroy(image);
        return NULL;
    }

        /* Set up to decode a rectangular region */
    if (box) {
        boxGetGeometry(box, &bx, &by, &bw, &bh);
        if (!opj_set_decode_area(l_codec, image, bx, by,
                                 bx + bw, by + bh)) {
            L_ERROR("failed to set the region for decoding\n", procName);
            opj_stream_destroy(l_stream);
            opj_destroy_codec(l_codec);
            opj_image_destroy(image);
            return NULL;
        }
    }

        /* Get the decoded image */
    if (!(opj_decode(l_codec, l_stream, image) &&
          opj_end_decompress(l_codec, l_stream))) {
        L_ERROR("failed to decode the image\n", procName);
        opj_destroy_codec(l_codec);
        opj_stream_destroy(l_stream);
        opj_image_destroy(image);
        return NULL;
    }

        /* Finished with the byte stream and the codec */
    opj_stream_destroy(l_stream);
    opj_destroy_codec(l_codec);

        /* Get the image parameters */
    spp = image->numcomps;
    w = image->comps[0].w;
    h = image->comps[0].h;
    prec = image->comps[0].prec;
    if (prec != bps)
        L_WARNING("precision %d != bps %d!\n", procName, prec, bps);
    if (debug) {
        L_INFO("w = %d, h = %d, bps = %d, spp = %d\n",
               procName, w, h, bps, spp);
        colorspace = image->color_space;
        if (colorspace == OPJ_CLRSPC_SRGB)
            L_INFO("colorspace is sRGB\n", procName);
        else if (colorspace == OPJ_CLRSPC_GRAY)
            L_INFO("colorspace is grayscale\n", procName);
        else if (colorspace == OPJ_CLRSPC_SYCC)
            L_INFO("colorspace is YUV\n", procName);
    }

        /* Convert the image to a pix */
    if (spp == 1)
        pix = pixCreate(w, h, 8);
    else
        pix = pixCreate(w, h, 32);
    pixSetInputFormat(pix, IFF_JP2);
    pixSetResolution(pix, xres, yres);
    data = pixGetData(pix);
    wpl = pixGetWpl(pix);
    index = 0;
    if (spp == 1) {
        for (i = 0; i < h; i++) {
            line = data + i * wpl;
            for (j = 0; j < w; j++) {
                val = image->comps[0].data[index];
                SET_DATA_BYTE(line, j, val);
                index++;
            }
        }
    } else if (spp == 2) {  /* convert to RGBA */
        for (i = 0; i < h; i++) {
            line = data + i * wpl;
            for (j = 0; j < w; j++) {
                val = image->comps[0].data[index];
                aval = image->comps[1].data[index];
                composeRGBAPixel(val, val, val, aval, &pixel);
                line[j] = pixel;
                index++;
            }
        }
    } else if (spp >= 3) {
        for (i = 0; i < h; i++) {
            line = data + i * wpl;
            for (j = 0; j < w; j++) {
                rval = image->comps[0].data[index];
                gval = image->comps[1].data[index];
                bval = image->comps[2].data[index];
                if (spp == 3) {
                    composeRGBPixel(rval, gval, bval, &pixel);
                } else {  /* spp == 4 */
                    aval = image->comps[3].data[index];
                    composeRGBAPixel(rval, gval, bval, aval, &pixel);
                }
                line[j] = pixel;
                index++;
            }
        }
    }

        /* Free the opj image data structure */
    opj_image_destroy(image);

    return pix;
}


/*---------------------------------------------------------------------*
 *                        Write jp2k to file                           *
 *---------------------------------------------------------------------*/
/*!
 * \brief   pixWriteJp2k()
 *
 * \param[in]    filename
 * \param[in]    pix        any depth, cmap is OK
 * \param[in]    quality    SNR > 0; 0 for default (34); 100 for lossless
 * \param[in]    nlevels    resolution levels; <= 10; default = 5
 * \param[in]    hint       a bitwise OR of L_JP2K_* values; 0 for default
 * \param[in]    debug      output callback messages, etc
 * \return  0 if OK; 1 on error
 *
 * <pre>
 * Notes:
 *      (1) The %quality parameter is the SNR.  The useful range is narrow:
 *             SNR < 27  (terrible quality)
 *             SNR = 34  (default; approximately equivalent to jpeg quality 75)
 *             SNR = 40  (very high quality)
 *             SNR = 45  (nearly lossless)
 *          Use 0 for default; 100 for lossless.
 *      (2) The %nlevels parameter is the number of resolution levels
 *          to be written.  For example, with nlevels == 5, images with
 *          reduction factors of 1, 2, 4, 8 and 16 are encoded, and retrieval
 *          is done at the level requested when reading.  For default,
 *          use either 5 or 0.
 *      (3) The %hint parameter is not yet in use.
 *      (4) For now, we only support 1 "layer" for quality.
 * </pre>
 */
l_ok
pixWriteJp2k(const char  *filename,
             PIX         *pix,
             l_int32      quality,
             l_int32      nlevels,
             l_int32      hint,
             l_int32      debug)
{
FILE  *fp;

    PROCNAME("pixWriteJp2k");

    if (!pix)
        return ERROR_INT("pix not defined", procName, 1);
    if (!filename)
        return ERROR_INT("filename not defined", procName, 1);

    if ((fp = fopenWriteStream(filename, "wb+")) == NULL)
        return ERROR_INT("stream not opened", procName, 1);

    if (pixWriteStreamJp2k(fp, pix, quality, nlevels, hint, debug)) {
        fclose(fp);
        return ERROR_INT("pix not written to stream", procName, 1);
    }

    fclose(fp);
    return 0;
}


/*!
 * \brief   pixWriteStreamJp2k()
 *
 * \param[in]    fp         file stream
 * \param[in]    pix        any depth, cmap is OK
 * \param[in]    quality    SNR > 0; 0 for default (34); 100 for lossless
 * \param[in]    nlevels    <= 10
 * \param[in]    hint       a bitwise OR of L_JP2K_* values; 0 for default
 * \param[in]    debug      output callback messages, etc
 * \return  0 if OK, 1 on error
 * <pre>
 * Notes:
 *      (1) See pixWriteJp2k() for usage.
 *      (2) For an encoder with more encoding options, see, e.g.,
 *    https://github.com/OpenJPEG/openjpeg/blob/master/tests/test_tile_encoder.c
 * </pre>
 */
l_ok
pixWriteStreamJp2k(FILE    *fp,
                   PIX     *pix,
                   l_int32  quality,
                   l_int32  nlevels,
                   l_int32  hint,
                   l_int32  debug)
{
l_int32            w, h, d, success;
l_float32          snr;
const char        *opjVersion;
PIX               *pixs;
opj_cparameters_t  parameters;   /* compression parameters */
opj_stream_t      *l_stream = NULL;
opj_codec_t*       l_codec = NULL;;
opj_image_t       *image = NULL;

    PROCNAME("pixWriteStreamJp2k");

    if (!fp)
        return ERROR_INT("stream not open", procName, 1);
    if (!pix)
        return ERROR_INT("pix not defined", procName, 1);

    snr = (l_float32)quality;
    if (snr <= 0) snr = 34.0;   /* default */
    if (snr < 27)
        L_WARNING("SNR = %d < 27; very low\n", procName, (l_int32)snr);
    if (snr == 100) snr = 0;  /* for lossless */
    if (snr > 45) {
        L_WARNING("SNR > 45; using lossless encoding\n", procName);
        snr = 0;
    }

    if (nlevels <= 0) nlevels = 5;  /* default */
    if (nlevels > 10) {
        L_WARNING("nlevels = %d > 10; setting to 10\n", procName, nlevels);
        nlevels = 10;
    }

    opjVersion = opj_version();
    if (opjVersion[0] != '2') {
        L_ERROR("version is %s; must be 2.0 or higher\n", procName, opjVersion);
        return 1;
    }
    if ((opjVersion[2] - 0x30) != OPJ_VERSION_MINOR) {
        L_ERROR("version %s: differs from minor = %d\n",
                procName, opjVersion, OPJ_VERSION_MINOR);
         return 1;
     }

        /* Remove colormap if it exists; result is 8 or 32 bpp */
    pixGetDimensions(pix, &w, &h, &d);
    if (d == 24) {
        pixs = pixConvert24To32(pix);
    } else if (d == 32) {
        pixs = pixClone(pix);
    } else if (pixGetColormap(pix) == NULL) {
        pixs = pixConvertTo8(pix, 0);
    } else {  /* colormap */
        L_INFO("removing colormap; may be better to compress losslessly\n",
               procName);
        pixs = pixRemoveColormap(pix, REMOVE_CMAP_BASED_ON_SRC);
    }

        /* Convert to opj image format. */
    pixSetPadBits(pixs, 0);
    image = pixConvertToOpjImage(pixs);
    pixDestroy(&pixs);

        /* Set encoding parameters to default values.
         * We use one layer with the input SNR. */
    opj_set_default_encoder_parameters(&parameters);
    parameters.cp_fixed_quality = 1;
    parameters.cp_disto_alloc = 0;
    parameters.cp_fixed_alloc =  0;
    parameters.tcp_distoratio[0] = snr;
    parameters.tcp_numlayers = 1;
    parameters.numresolution = nlevels;

        /* Create comment for codestream */
    if (parameters.cp_comment == NULL) {
        const char comment1[] = "Created by Leptonica, version ";
        const char comment2[] = "; using OpenJPEG, version ";
        size_t len1 = strlen(comment1);
        size_t len2 = strlen(comment2);
        char *version1 = getLeptonicaVersion();
        const char *version2 = opj_version();
        len1 += len2 + strlen(version1) + strlen(version2) + 1;
        parameters.cp_comment = (char *)LEPT_MALLOC(len1);
        snprintf(parameters.cp_comment, len1, "%s%s%s%s", comment1, version1,
                 comment2, version2);
        LEPT_FREE(version1);
    }

        /* Get the encoder handle */
    if ((l_codec = opj_create_compress(OPJ_CODEC_JP2)) == NULL) {
        opj_image_destroy(image);
        LEPT_FREE(parameters.cp_comment);
        return ERROR_INT("failed to get the encoder handle\n", procName, 1);
    }

        /* Catch and report events using callbacks */
    if (debug) {
        opj_set_info_handler(l_codec, info_callback, NULL);
        opj_set_warning_handler(l_codec, warning_callback, NULL);
        opj_set_error_handler(l_codec, error_callback, NULL);
    }

        /* Set up the encoder */
    if (!opj_setup_encoder(l_codec, &parameters, image)) {
        opj_destroy_codec(l_codec);
        opj_image_destroy(image);
        LEPT_FREE(parameters.cp_comment);
        return ERROR_INT("failed to set up the encoder\n", procName, 1);
    }

        /* Open a compression stream for writing.  In 2.0 we could use this:
         *     opj_stream_create_default_file_stream(fp, 0)
         * but the file stream interface was removed in 2.1.  */
    rewind(fp);
    if ((l_stream = opjCreateStream(fp, 0)) == NULL) {
        opj_destroy_codec(l_codec);
        opj_image_destroy(image);
        LEPT_FREE(parameters.cp_comment);
        return ERROR_INT("failed to open l_stream\n", procName, 1);
    }

        /* Encode the image */
    if (!opj_start_compress(l_codec, image, l_stream)) {
        opj_stream_destroy(l_stream);
        opj_destroy_codec(l_codec);
        opj_image_destroy(image);
        LEPT_FREE(parameters.cp_comment);
        return ERROR_INT("opj_start_compress failed\n", procName, 1);
    }
    if (!opj_encode(l_codec, l_stream)) {
        opj_stream_destroy(l_stream);
        opj_destroy_codec(l_codec);
        opj_image_destroy(image);
        LEPT_FREE(parameters.cp_comment);
        return ERROR_INT("opj_encode failed\n", procName, 1);
    }
    success = opj_end_compress(l_codec, l_stream);

        /* Clean up */
    opj_stream_destroy(l_stream);
    opj_destroy_codec(l_codec);
    opj_image_destroy(image);
    LEPT_FREE(parameters.cp_comment);
    if (success)
        return 0;
    else
        return ERROR_INT("opj_end_compress failed\n", procName, 1);
}


/*!
 * \brief   pixConvertToOpjImage()
 *
 * \param[in]    pix     8 or 32 bpp
 * \return  opj_image, or NULL on error
 *
 * <pre>
 * Notes:
 *      (1) Input pix is 8 bpp grayscale, 32 bpp rgb, or 32 bpp rgba.
 *      (2) Gray + alpha pix are all represented as rgba.
 * </pre>
 */
static opj_image_t *
pixConvertToOpjImage(PIX  *pix)
{
l_int32               i, j, k, w, h, d, spp, wpl;
OPJ_COLOR_SPACE       colorspace;
l_int32              *ir = NULL;
l_int32              *ig = NULL;
l_int32              *ib = NULL;
l_int32              *ia = NULL;
l_uint32             *line, *data;
opj_image_t          *image;
opj_image_cmptparm_t  cmptparm[4];

    PROCNAME("pixConvertToOpjImage");

    if (!pix)
        return (opj_image_t *)ERROR_PTR("pix not defined", procName, NULL);
    pixGetDimensions(pix, &w, &h, &d);
    if (d != 8 && d != 32) {
        L_ERROR("invalid depth: %d\n", procName, d);
        return NULL;
    }

        /* Allocate the opj_image. */
    spp = pixGetSpp(pix);
    memset(&cmptparm[0], 0, 4 * sizeof(opj_image_cmptparm_t));
    for (i = 0; i < spp; i++) {
        cmptparm[i].prec = 8;
        cmptparm[i].bpp = 8;
        cmptparm[i].sgnd = 0;
        cmptparm[i].dx = 1;
        cmptparm[i].dy = 1;
        cmptparm[i].w = w;
        cmptparm[i].h = h;
    }
    colorspace = (spp == 1) ? OPJ_CLRSPC_GRAY : OPJ_CLRSPC_SRGB;
    if ((image = opj_image_create(spp, &cmptparm[0], colorspace)) == NULL)
        return (opj_image_t *)ERROR_PTR("image not made", procName, NULL);
    image->x0 = 0;
    image->y0 = 0;
    image->x1 = w;
    image->y1 = h;

        /* Set the component pointers */
    ir = image->comps[0].data;
    if (spp > 1) {
        ig = image->comps[1].data;
        ib = image->comps[2].data;
    }
    if(spp == 4)
        ia = image->comps[3].data;

        /* Transfer the data from the pix */
    data = pixGetData(pix);
    wpl = pixGetWpl(pix);
    for (i = 0, k = 0; i < h; i++) {
        line = data + i * wpl;
        for (j = 0; j < w; j++, k++) {
            if (spp == 1) {
                ir[k] = GET_DATA_BYTE(line, j);
            } else if (spp > 1) {
                ir[k] = GET_DATA_BYTE(line + j, COLOR_RED);
                ig[k] = GET_DATA_BYTE(line + j, COLOR_GREEN);
                ib[k] = GET_DATA_BYTE(line + j, COLOR_BLUE);
            }
            if (spp == 4)
                ia[k] = GET_DATA_BYTE(line + j, L_ALPHA_CHANNEL);
        }
    }

    return image;
}


/*---------------------------------------------------------------------*
 *                         Read/write to memory                        *
 *---------------------------------------------------------------------*/
/*!
 * \brief   pixReadMemJp2k()
 *
 * \param[in]    data        const; jpeg-encoded
 * \param[in]    size        of data
 * \param[in]    reduction   scaling factor: 1, 2, 4, 8
 * \param[in]    box         [optional] for extracting a subregion, can be null
 * \param[in]    hint        a bitwise OR of L_JP2K_* values; 0 for default
 * \param[in]    debug       output callback messages, etc
 * \return  pix, or NULL on error
 *
 * <pre>
 * Notes:
 *      (1) This crashes when reading through the fmemopen cookie.
 *          Until we can fix this, we use the file-based work-around.
 *          And fixing this may take some time, because the basic
 *          stream interface is no longer supported in openjpeg.
 *      (2) See pixReadJp2k() for usage.
 * </pre>
 */
PIX *
pixReadMemJp2k(const l_uint8  *data,
               size_t          size,
               l_uint32        reduction,
               BOX            *box,
               l_int32         hint,
               l_int32         debug)
{
FILE     *fp;
PIX      *pix;

    PROCNAME("pixReadMemJp2k");

    if (!data)
        return (PIX *)ERROR_PTR("data not defined", procName, NULL);

    if ((fp = fopenReadFromMemory(data, size)) == NULL)
        return (PIX *)ERROR_PTR("stream not opened", procName, NULL);
    pix = pixReadStreamJp2k(fp, reduction, box, hint, debug);
    fclose(fp);
    if (!pix) L_ERROR("pix not read\n", procName);
    return pix;
}


/*!
 * \brief   pixWriteMemJp2k()
 *
 * \param[out]   pdata     data of jpeg compressed image
 * \param[out]   psize     size of returned data
 * \param[in]    pix       8 or 32 bpp
 * \param[in]    quality   SNR > 0; 0 for default (34); 100 for lossless
 * \param[in]    nlevels   0 for default
 * \param[in]    hint      a bitwise OR of L_JP2K_* values; 0 for default
 * \param[in]    debug     output callback messages, etc
 * \return  0 if OK, 1 on error
 *
 * <pre>
 * Notes:
 *      (1) See pixWriteJp2k() for usage.  This version writes to
 *          memory instead of to a file stream.
 * </pre>
 */
l_ok
pixWriteMemJp2k(l_uint8  **pdata,
                size_t    *psize,
                PIX       *pix,
                l_int32    quality,
                l_int32    nlevels,
                l_int32    hint,
                l_int32    debug)
{
l_int32  ret;
FILE    *fp;

    PROCNAME("pixWriteMemJp2k");

    if (pdata) *pdata = NULL;
    if (psize) *psize = 0;
    if (!pdata)
        return ERROR_INT("&data not defined", procName, 1 );
    if (!psize)
        return ERROR_INT("&size not defined", procName, 1 );
    if (!pix)
        return ERROR_INT("&pix not defined", procName, 1 );

#if HAVE_FMEMOPEN
    if ((fp = open_memstream((char **)pdata, psize)) == NULL)
        return ERROR_INT("stream not opened", procName, 1);
    ret = pixWriteStreamJp2k(fp, pix, quality, nlevels, hint, debug);
#else
    L_INFO("work-around: writing to a temp file\n", procName);
  #ifdef _WIN32
    if ((fp = fopenWriteWinTempfile()) == NULL)
        return ERROR_INT("tmpfile stream not opened", procName, 1);
  #else
    if ((fp = tmpfile()) == NULL)
        return ERROR_INT("tmpfile stream not opened", procName, 1);
  #endif  /* _WIN32 */
    ret = pixWriteStreamJp2k(fp, pix, quality, nlevels, hint, debug);
    rewind(fp);
    *pdata = l_binaryReadStream(fp, psize);
#endif  /* HAVE_FMEMOPEN */
    fclose(fp);
    return ret;
}


/*---------------------------------------------------------------------*
 *    Static functions from opj 2.0 to retain file stream interface    *
 *---------------------------------------------------------------------*/
static l_uint64
opj_get_user_data_length(FILE *fp) {
    OPJ_OFF_T length = 0;
    fseek(fp, 0, SEEK_END);
    length = (OPJ_OFF_T)ftell(fp);
    fseek(fp, 0, SEEK_SET);
    return (l_uint64)length;
}

static OPJ_SIZE_T
opj_read_from_file(void *p_buffer, OPJ_SIZE_T p_nb_bytes, FILE *fp) {
    OPJ_SIZE_T l_nb_read = fread(p_buffer, 1, p_nb_bytes, fp);
    return l_nb_read ? l_nb_read : (OPJ_SIZE_T) - 1;
}

static OPJ_SIZE_T
opj_write_from_file(void *p_buffer, OPJ_SIZE_T p_nb_bytes, FILE *fp)
{
    return fwrite(p_buffer, 1, p_nb_bytes, fp);
}

static OPJ_OFF_T
opj_skip_from_file(OPJ_OFF_T offset, FILE *fp) {
    if (fseek(fp, offset, SEEK_CUR)) {
        return -1;
    }
    return offset;
}

static l_int32
opj_seek_from_file(OPJ_OFF_T offset, FILE *fp) {
    if (fseek(fp, offset, SEEK_SET)) {
        return 0;
    }
    return 1;
}

    /* Static generator of opj_stream from file stream */
static opj_stream_t *
opjCreateStream(FILE    *fp,
                l_int32  is_read_stream)
{
opj_stream_t  *l_stream;

    PROCNAME("opjCreateStream");

    if (!fp)
        return (opj_stream_t *)ERROR_PTR("fp not defined", procName, NULL);

    l_stream = opj_stream_create(OPJ_J2K_STREAM_CHUNK_SIZE, is_read_stream);
    if (!l_stream)
        return (opj_stream_t *)ERROR_PTR("stream not made", procName, NULL);

#if OPJ_VERSION_MINOR == 0
    opj_stream_set_user_data(l_stream, fp);
#else
    opj_stream_set_user_data(l_stream, fp,
                             (opj_stream_free_user_data_fn)NULL);
#endif
    opj_stream_set_user_data_length(l_stream, opj_get_user_data_length(fp));
    opj_stream_set_read_function(l_stream,
                                 (opj_stream_read_fn)opj_read_from_file);
    opj_stream_set_write_function(l_stream,
                                  (opj_stream_write_fn)opj_write_from_file);
    opj_stream_set_skip_function(l_stream,
                                 (opj_stream_skip_fn)opj_skip_from_file);
    opj_stream_set_seek_function(l_stream,
                                 (opj_stream_seek_fn)opj_seek_from_file);

    return l_stream;
}

/* --------------------------------------------*/
#endif  /* HAVE_LIBJP2K */
/* --------------------------------------------*/