diff mupdf-source/thirdparty/leptonica/src/jpegio.c @ 2:b50eed0cc0ef upstream

ADD: MuPDF v1.26.7: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.4. The directory name has changed: no version number in the expanded directory now.
author Franz Glasner <fzglas.hg@dom66.de>
date Mon, 15 Sep 2025 11:43:07 +0200
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mupdf-source/thirdparty/leptonica/src/jpegio.c	Mon Sep 15 11:43:07 2025 +0200
@@ -0,0 +1,1283 @@
+/*====================================================================*
+ -  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 jpegio.c
+ * <pre>
+ *
+ *    Read jpeg from file
+ *          PIX             *pixReadJpeg()  [special top level]
+ *          PIX             *pixReadStreamJpeg()
+ *
+ *    Read jpeg metadata from file
+ *          l_int32          readHeaderJpeg()
+ *          l_int32          freadHeaderJpeg()
+ *          l_int32          fgetJpegResolution()
+ *          l_int32          fgetJpegComment()
+ *
+ *    Write jpeg to file
+ *          l_int32          pixWriteJpeg()  [special top level]
+ *          l_int32          pixWriteStreamJpeg()
+ *
+ *    Read/write to memory
+ *          PIX             *pixReadMemJpeg()
+ *          l_int32          readHeaderMemJpeg()
+ *          l_int32          readResolutionMemJpeg()
+ *          l_int32          pixWriteMemJpeg()
+ *
+ *    Setting special flag for chroma sampling on write
+ *          l_int32          pixSetChromaSampling()
+ *
+ *    Static system helpers
+ *          static void      jpeg_error_catch_all_1()
+ *          static void      jpeg_error_catch_all_2()
+ *          static l_uint8   jpeg_getc()
+ *          static l_int32   jpeg_comment_callback()
+ *
+ *    Documentation: libjpeg.doc can be found, along with all
+ *    source code, at ftp://ftp.uu.net/graphics/jpeg
+ *    Download and untar the file:  jpegsrc.v6b.tar.gz
+ *    A good paper on jpeg can also be found there: wallace.ps.gz
+ *
+ *    The functions in libjpeg make it very simple to compress
+ *    and decompress images.  On input (decompression from file),
+ *    3 component color images can be read into either an 8 bpp Pix
+ *    with a colormap or a 32 bpp Pix with RGB components.  For output
+ *    (compression to file), all color Pix, whether 8 bpp with a
+ *    colormap or 32 bpp, are written compressed as a set of three
+ *    8 bpp (rgb) images.
+ *
+ *    Low-level error handling
+ *    ------------------------
+ *    The default behavior of the jpeg library is to call exit.
+ *    This is often undesirable, and the caller should make the
+ *    decision when to abort a process.  To prevent the jpeg library
+ *    from calling exit(), setjmp() has been inserted into all
+ *    readers and writers, and the cinfo struct has been set up so that
+ *    the low-level jpeg library will call a special error handler
+ *    that doesn't exit, instead of the default function error_exit().
+ *
+ *    To avoid race conditions and make these functions thread-safe in
+ *    the rare situation where calls to two threads are simultaneously
+ *    failing on bad jpegs, we insert a local copy of the jmp_buf struct
+ *    into the cinfo.client_data field, and use this on longjmp.
+ *    For extracting the jpeg comment, we have the added complication
+ *    that the client_data field must also return the jpeg comment,
+ *    and we use a different error handler.
+ *
+ *    How to avoid subsampling the chroma channels
+ *    --------------------------------------------
+ *    By default, the U,V (chroma) channels use 2x2 subsampling (aka 4.2.0).
+ *    Higher quality for color, using full resolution (4.4.4) for the chroma,
+ *    is obtained by setting a field in the pix before writing:
+ *        pixSetChromaSampling(pix, L_NO_CHROMA_SAMPLING_JPEG);
+ *    The field can be reset for default 4.2.0 subsampling with
+ *        pixSetChromaSampling(pix, 0);
+ *
+ *    How to extract just the luminance channel in reading RGB
+ *    --------------------------------------------------------
+ *    For higher resolution and faster decoding of an RGB image, you
+ *    can extract just the 8 bpp luminance channel, using pixReadJpeg(),
+ *    where you use L_JPEG_READ_LUMINANCE for the %hint arg.
+ *
+ *    How to continue to read if the data is corrupted
+ *    ------------------------------------------------
+ *    By default, if data is corrupted we make every effort to fail
+ *    to return a pix.  (Failure is not always possible with bad
+ *    data, because in some situations, such as during arithmetic
+ *    decoding, the low-level jpeg library will not abort or raise
+ *    a warning.)  To attempt to ignore warnings and get a pix when data
+ *    is corrupted, use L_JPEG_CONTINUE_WITH_BAD_DATA in the %hint arg.
+ *
+ *    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 pixReadMemJpeg() and
+ *    pixWriteMemJpeg().
+ * </pre>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config_auto.h>
+#endif  /* HAVE_CONFIG_H */
+
+#include <string.h>
+#include "allheaders.h"
+#include "pix_internal.h"
+
+/* --------------------------------------------*/
+#if  HAVE_LIBJPEG   /* defined in environ.h */
+/* --------------------------------------------*/
+
+#include <setjmp.h>
+
+    /* jconfig.h makes the error of setting
+     *   #define HAVE_STDLIB_H
+     * which conflicts with config_auto.h (where it is set to 1) and results
+     * for some gcc compiler versions in a warning.  The conflict is harmless
+     * but we suppress it by undefining the variable. */
+#undef HAVE_STDLIB_H
+#include "jpeglib.h"
+
+static void jpeg_error_catch_all_1(j_common_ptr cinfo);
+static void jpeg_error_catch_all_2(j_common_ptr cinfo);
+static l_uint8 jpeg_getc(j_decompress_ptr cinfo);
+
+    /* Note: 'boolean' is defined in jmorecfg.h.  We use it explicitly
+     * here because for Windows where __MINGW32__ is defined,
+     * the prototype for jpeg_comment_callback() is given as
+     * returning a boolean.  */
+static boolean jpeg_comment_callback(j_decompress_ptr cinfo);
+
+    /* This is saved in the client_data field of cinfo, and used both
+     * to retrieve the comment from its callback and to handle
+     * exceptions with a longjmp. */
+struct callback_data {
+    jmp_buf   jmpbuf;
+    l_uint8  *comment;
+};
+
+#ifndef  NO_CONSOLE_IO
+#define  DEBUG_INFO      0
+#endif  /* ~NO_CONSOLE_IO */
+
+
+/*---------------------------------------------------------------------*
+ *                 Read jpeg from file (special function)              *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief   pixReadJpeg()
+ *
+ * \param[in]    filename
+ * \param[in]    cmapflag   0 for no colormap in returned pix;
+ *                          1 to return an 8 bpp cmapped pix if spp = 3 or 4
+ * \param[in]    reduction  scaling factor: 1, 2, 4 or 8
+ * \param[out]   pnwarn     [optional] number of warnings about
+ *                          corrupted data
+ * \param[in]    hint       a bitwise OR of L_JPEG_* values; 0 for default
+ * \return  pix, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) This is a special function for reading jpeg files.
+ *      (2) Use this if you want the jpeg library to create
+ *          an 8 bpp colormapped image.
+ *      (3) Images reduced by factors of 2, 4 or 8 can be returned
+ *          significantly faster than full resolution images.
+ *      (4) If the jpeg data is bad, depending on the severity of the
+ *          data corruption one of two things will happen:
+ *          (a) 0 or more warnings are generated, or
+ *          (b) the library will immediately attempt to exit. This is
+ *              caught by our error handler and no pix will be returned.
+ *          If data corruption causes a warning, the default action
+ *          is to abort the read. The reason is that malformed jpeg
+ *          data sequences exist that prevent termination of the read.
+ *          To allow the decoding to continue after corrupted data is
+ *          encountered, include L_JPEG_CONTINUE_WITH_BAD_DATA in %hint.
+ *      (5) The possible hint values are given in the enum in imageio.h:
+ *            * L_JPEG_READ_LUMINANCE
+ *            * L_JPEG_CONTINUE_WITH_BAD_DATA
+ *          Default (0) is to do neither, and to fail on warning of data
+ *          corruption.
+ * </pre>
+ */
+PIX *
+pixReadJpeg(const char  *filename,
+            l_int32      cmapflag,
+            l_int32      reduction,
+            l_int32     *pnwarn,
+            l_int32      hint)
+{
+l_int32   ret;
+l_uint8  *comment;
+FILE     *fp;
+PIX      *pix;
+
+    if (pnwarn) *pnwarn = 0;
+    if (!filename)
+        return (PIX *)ERROR_PTR("filename not defined", __func__, NULL);
+    if (cmapflag != 0 && cmapflag != 1)
+        cmapflag = 0;  /* default */
+    if (reduction != 1 && reduction != 2 && reduction != 4 && reduction != 8)
+        return (PIX *)ERROR_PTR("reduction not in {1,2,4,8}", __func__, NULL);
+
+    if ((fp = fopenReadStream(filename)) == NULL)
+        return (PIX *)ERROR_PTR_1("image file not found",
+                                  filename, __func__, NULL);
+    pix = pixReadStreamJpeg(fp, cmapflag, reduction, pnwarn, hint);
+    if (pix) {
+        ret = fgetJpegComment(fp, &comment);
+        if (!ret && comment)
+            pixSetText(pix, (char *)comment);
+        LEPT_FREE(comment);
+    }
+    fclose(fp);
+
+    if (!pix)
+        return (PIX *)ERROR_PTR_1("image not returned",
+                                  filename, __func__, NULL);
+    return pix;
+}
+
+
+/*!
+ * \brief   pixReadStreamJpeg()
+ *
+ * \param[in]    fp         file stream
+ * \param[in]    cmapflag   0 for no colormap in returned pix;
+ *                          1 to return an 8 bpp cmapped pix if spp = 3 or 4
+ * \param[in]    reduction  scaling factor: 1, 2, 4 or 8
+ * \param[out]   pnwarn     [optional] number of warnings
+ * \param[in]    hint       a bitwise OR of L_JPEG_* values; 0 for default
+ * \return  pix, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) For usage, see pixReadJpeg().
+ *      (2) The jpeg comment, if it exists, is not stored in the pix.
+ * </pre>
+ */
+PIX *
+pixReadStreamJpeg(FILE     *fp,
+                  l_int32   cmapflag,
+                  l_int32   reduction,
+                  l_int32  *pnwarn,
+                  l_int32   hint)
+{
+l_int32                        cyan, yellow, magenta, black, nwarn;
+l_int32                        i, j, k, rval, gval, bval;
+l_int32                        nlinesread, abort_on_warning;
+l_int32                        w, h, wpl, spp, ncolors, cindex, ycck, cmyk;
+l_uint32                      *data;
+l_uint32                      *line, *ppixel;
+JSAMPROW                       rowbuffer;
+PIX                           *pix;
+PIXCMAP                       *cmap;
+struct jpeg_decompress_struct  cinfo = { 0 };
+struct jpeg_error_mgr          jerr = { 0 };
+jmp_buf                        jmpbuf;  /* must be local to the function */
+
+    if (pnwarn) *pnwarn = 0;
+    if (!fp)
+        return (PIX *)ERROR_PTR("fp not defined", __func__, NULL);
+    if (cmapflag != 0 && cmapflag != 1)
+        cmapflag = 0;  /* default */
+    if (reduction != 1 && reduction != 2 && reduction != 4 && reduction != 8)
+        return (PIX *)ERROR_PTR("reduction not in {1,2,4,8}", __func__, NULL);
+
+    if (BITS_IN_JSAMPLE != 8)  /* set in jmorecfg.h */
+        return (PIX *)ERROR_PTR("BITS_IN_JSAMPLE != 8", __func__, NULL);
+
+    rewind(fp);
+    pix = NULL;
+    rowbuffer = NULL;
+
+        /* Modify the jpeg error handling to catch fatal errors  */
+    cinfo.err = jpeg_std_error(&jerr);
+    jerr.error_exit = jpeg_error_catch_all_1;
+    cinfo.client_data = (void *)&jmpbuf;
+    if (setjmp(jmpbuf)) {
+        jpeg_destroy_decompress(&cinfo);
+        pixDestroy(&pix);
+        LEPT_FREE(rowbuffer);
+        return (PIX *)ERROR_PTR("internal jpeg error", __func__, NULL);
+    }
+
+        /* Initialize jpeg structs for decompression */
+    jpeg_create_decompress(&cinfo);
+    jpeg_stdio_src(&cinfo, fp);
+    jpeg_read_header(&cinfo, TRUE);
+    cinfo.scale_denom = reduction;
+    cinfo.scale_num = 1;
+    jpeg_calc_output_dimensions(&cinfo);
+    if (hint & L_JPEG_READ_LUMINANCE) {
+        cinfo.out_color_space = JCS_GRAYSCALE;
+        spp = 1;
+        L_INFO("reading luminance channel only\n", __func__);
+    } else {
+        spp = cinfo.out_color_components;
+    }
+
+        /* Allocate the image and a row buffer */
+    w = cinfo.output_width;
+    h = cinfo.output_height;
+    ycck = (cinfo.jpeg_color_space == JCS_YCCK && spp == 4 && cmapflag == 0);
+    cmyk = (cinfo.jpeg_color_space == JCS_CMYK && spp == 4 && cmapflag == 0);
+    if (spp != 1 && spp != 3 && !ycck && !cmyk) {
+        jpeg_destroy_decompress(&cinfo);
+        return (PIX *)ERROR_PTR("spp must be 1 or 3, or YCCK or CMYK",
+                                __func__, NULL);
+    }
+    if ((spp == 3 && cmapflag == 0) || ycck || cmyk) {  /* rgb or 4 bpp color */
+        rowbuffer = (JSAMPROW)LEPT_CALLOC(sizeof(JSAMPLE), (size_t)spp * w);
+        pix = pixCreate(w, h, 32);
+    } else {  /* 8 bpp gray or colormapped */
+        rowbuffer = (JSAMPROW)LEPT_CALLOC(sizeof(JSAMPLE), w);
+        pix = pixCreate(w, h, 8);
+    }
+    if (!rowbuffer || !pix) {
+        LEPT_FREE(rowbuffer);
+        rowbuffer = NULL;
+        pixDestroy(&pix);
+        jpeg_destroy_decompress(&cinfo);
+        return (PIX *)ERROR_PTR("rowbuffer or pix not made", __func__, NULL);
+    }
+    pixSetInputFormat(pix, IFF_JFIF_JPEG);
+
+        /* Initialize decompression.
+         * Set up a colormap for color quantization if requested.
+         * Arithmetic coding is rarely used on the jpeg data, but if it
+         * is, jpeg_start_decompress() handles the decoding.
+         * With corrupted encoded data, this can take an arbitrarily
+         * long time, and fuzzers are finding examples.  Unfortunately,
+         * there is no way to get a callback from an error in this phase. */
+    if (spp == 1) {  /* Grayscale or colormapped */
+        jpeg_start_decompress(&cinfo);
+    } else {        /* Color; spp == 3 or YCCK or CMYK */
+        if (cmapflag == 0) {   /* 24 bit color in 32 bit pix or YCCK/CMYK */
+            cinfo.quantize_colors = FALSE;
+            jpeg_start_decompress(&cinfo);
+        } else {      /* Color quantize to 8 bits */
+            cinfo.quantize_colors = TRUE;
+            cinfo.desired_number_of_colors = 256;
+            jpeg_start_decompress(&cinfo);
+
+                /* Construct a pix cmap */
+            cmap = pixcmapCreate(8);
+            ncolors = cinfo.actual_number_of_colors;
+            for (cindex = 0; cindex < ncolors; cindex++) {
+                rval = cinfo.colormap[0][cindex];
+                gval = cinfo.colormap[1][cindex];
+                bval = cinfo.colormap[2][cindex];
+                pixcmapAddColor(cmap, rval, gval, bval);
+            }
+            pixSetColormap(pix, cmap);
+        }
+    }
+    wpl  = pixGetWpl(pix);
+    data = pixGetData(pix);
+
+        /* Decompress.  It appears that jpeg_read_scanlines() always
+         * returns 1 when you ask for one scanline, but we test anyway.
+         * During decoding of scanlines, warnings are issued if corrupted
+         * data is found.  The default behavior is to abort reading
+         * when a warning is encountered.  By setting the hint to have
+         * the same bit set as in L_JPEG_CONTINUE_WITH_BAD_DATA, e.g.,
+         *       hint = hint | L_JPEG_CONTINUE_WITH_BAD_DATA
+         * reading will continue after warnings, in an attempt to return
+         * the (possibly corrupted) image. */
+    abort_on_warning = (hint & L_JPEG_CONTINUE_WITH_BAD_DATA) ? 0 : 1;
+    for (i = 0; i < h; i++) {
+        nlinesread = jpeg_read_scanlines(&cinfo, &rowbuffer, (JDIMENSION)1);
+        nwarn = cinfo.err->num_warnings;
+        if (nlinesread == 0 || (abort_on_warning && nwarn > 0)) {
+            L_ERROR("read error at scanline %d; nwarn = %d\n",
+                    __func__, i, nwarn);
+            pixDestroy(&pix);
+            jpeg_destroy_decompress(&cinfo);
+            LEPT_FREE(rowbuffer);
+            rowbuffer = NULL;
+            if (pnwarn) *pnwarn = nwarn;
+            return (PIX *)ERROR_PTR("bad data", __func__, NULL);
+        }
+
+            /* -- 24 bit color -- */
+        if ((spp == 3 && cmapflag == 0) || ycck || cmyk) {
+            ppixel = data + i * wpl;
+            if (spp == 3) {
+                for (j = k = 0; j < w; j++) {
+                    SET_DATA_BYTE(ppixel, COLOR_RED, rowbuffer[k++]);
+                    SET_DATA_BYTE(ppixel, COLOR_GREEN, rowbuffer[k++]);
+                    SET_DATA_BYTE(ppixel, COLOR_BLUE, rowbuffer[k++]);
+                    ppixel++;
+                }
+            } else {
+                    /* This is a conversion from CMYK -> RGB that ignores
+                       color profiles, and is invoked when the image header
+                       claims to be in CMYK or YCCK colorspace.  If in YCCK,
+                       libjpeg may be doing YCCK -> CMYK under the hood.
+                       To understand why the colors need to be inverted on
+                       read-in for the Adobe marker, see the "Special
+                       color spaces" section of "Using the IJG JPEG
+                       Library" by Thomas G. Lane:
+                         http://www.jpegcameras.com/libjpeg/libjpeg-3.html#ss3.1
+                       The non-Adobe conversion is equivalent to:
+                           rval = black - black * cyan / 255
+                           ...
+                       The Adobe conversion is equivalent to:
+                           rval = black - black * (255 - cyan) / 255
+                           ...
+                       Note that cyan is the complement to red, and we
+                       are subtracting the complement color (weighted
+                       by black) from black.  For Adobe conversions,
+                       where they've already inverted the CMY but not
+                       the K, we have to invert again.  The results
+                       must be clipped to [0 ... 255]. */
+                for (j = k = 0; j < w; j++) {
+                    cyan = rowbuffer[k++];
+                    magenta = rowbuffer[k++];
+                    yellow = rowbuffer[k++];
+                    black = rowbuffer[k++];
+                    if (cinfo.saw_Adobe_marker) {
+                        rval = (black * cyan) / 255;
+                        gval = (black * magenta) / 255;
+                        bval = (black * yellow) / 255;
+                    } else {
+                        rval = black * (255 - cyan) / 255;
+                        gval = black * (255 - magenta) / 255;
+                        bval = black * (255 - yellow) / 255;
+                    }
+                    rval = L_MIN(L_MAX(rval, 0), 255);
+                    gval = L_MIN(L_MAX(gval, 0), 255);
+                    bval = L_MIN(L_MAX(bval, 0), 255);
+                    composeRGBPixel(rval, gval, bval, ppixel);
+                    ppixel++;
+                }
+            }
+        } else {    /* 8 bpp grayscale or colormapped pix */
+            line = data + i * wpl;
+            for (j = 0; j < w; j++)
+                SET_DATA_BYTE(line, j, rowbuffer[j]);
+        }
+    }
+
+        /* If the pixel density is neither 1 nor 2, it may not be defined.
+         * In that case, don't set the resolution.  */
+    if (cinfo.density_unit == 1) {  /* pixels per inch */
+        pixSetXRes(pix, cinfo.X_density);
+        pixSetYRes(pix, cinfo.Y_density);
+    } else if (cinfo.density_unit == 2) {  /* pixels per centimeter */
+        pixSetXRes(pix, (l_int32)((l_float32)cinfo.X_density * 2.54 + 0.5));
+        pixSetYRes(pix, (l_int32)((l_float32)cinfo.Y_density * 2.54 + 0.5));
+    }
+
+    if (cinfo.output_components != spp)
+        lept_stderr("output spp = %d, spp = %d\n",
+                    cinfo.output_components, spp);
+
+    jpeg_finish_decompress(&cinfo);
+    jpeg_destroy_decompress(&cinfo);
+    LEPT_FREE(rowbuffer);
+    rowbuffer = NULL;
+    if (pnwarn) *pnwarn = nwarn;
+    if (nwarn > 0)
+        L_WARNING("%d warning(s) of bad data\n", __func__, nwarn);
+    return pix;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                     Read jpeg metadata from file                    *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief   readHeaderJpeg()
+ *
+ * \param[in]    filename
+ * \param[out]   pw     [optional]
+ * \param[out]   ph     [optional]
+ * \param[out]   pspp   [optional] samples/pixel
+ * \param[out]   pycck  [optional] 1 if ycck color space; 0 otherwise
+ * \param[out]   pcmyk  [optional] 1 if cmyk color space; 0 otherwise
+ * \return  0 if OK, 1 on error
+ */
+l_ok
+readHeaderJpeg(const char  *filename,
+               l_int32     *pw,
+               l_int32     *ph,
+               l_int32     *pspp,
+               l_int32     *pycck,
+               l_int32     *pcmyk)
+{
+l_int32  ret;
+FILE    *fp;
+
+    if (pw) *pw = 0;
+    if (ph) *ph = 0;
+    if (pspp) *pspp = 0;
+    if (pycck) *pycck = 0;
+    if (pcmyk) *pcmyk = 0;
+    if (!filename)
+        return ERROR_INT("filename not defined", __func__, 1);
+    if (!pw && !ph && !pspp && !pycck && !pcmyk)
+        return ERROR_INT("no results requested", __func__, 1);
+
+    if ((fp = fopenReadStream(filename)) == NULL)
+        return ERROR_INT_1("image file not found", filename, __func__, 1);
+    ret = freadHeaderJpeg(fp, pw, ph, pspp, pycck, pcmyk);
+    fclose(fp);
+    return ret;
+}
+
+
+/*!
+ * \brief   freadHeaderJpeg()
+ *
+ * \param[in]    fp     file stream
+ * \param[out]   pw     [optional]
+ * \param[out]   ph     [optional]
+ * \param[out]   pspp   [optional]  samples/pixel
+ * \param[out]   pycck  [optional]  1 if ycck color space; 0 otherwise
+ * \param[out]   pcmyk  [optional]  1 if cmyk color space; 0 otherwise
+ * \return  0 if OK, 1 on error
+ */
+l_ok
+freadHeaderJpeg(FILE     *fp,
+                l_int32  *pw,
+                l_int32  *ph,
+                l_int32  *pspp,
+                l_int32  *pycck,
+                l_int32  *pcmyk)
+{
+l_int32                        spp, w, h;
+struct jpeg_decompress_struct  cinfo = { 0 };
+struct jpeg_error_mgr          jerr = { 0 };
+jmp_buf                        jmpbuf;  /* must be local to the function */
+
+    if (pw) *pw = 0;
+    if (ph) *ph = 0;
+    if (pspp) *pspp = 0;
+    if (pycck) *pycck = 0;
+    if (pcmyk) *pcmyk = 0;
+    if (!fp)
+        return ERROR_INT("stream not defined", __func__, 1);
+    if (!pw && !ph && !pspp && !pycck && !pcmyk)
+        return ERROR_INT("no results requested", __func__, 1);
+
+    rewind(fp);
+
+        /* Modify the jpeg error handling to catch fatal errors  */
+    cinfo.err = jpeg_std_error(&jerr);
+    cinfo.client_data = (void *)&jmpbuf;
+    jerr.error_exit = jpeg_error_catch_all_1;
+    if (setjmp(jmpbuf))
+        return ERROR_INT("internal jpeg error", __func__, 1);
+
+        /* Initialize the jpeg structs for reading the header */
+    jpeg_create_decompress(&cinfo);
+    jpeg_stdio_src(&cinfo, fp);
+    jpeg_read_header(&cinfo, TRUE);
+    jpeg_calc_output_dimensions(&cinfo);
+    spp = cinfo.out_color_components;
+    w = cinfo.output_width;
+    h = cinfo.output_height;
+    if (w < 1 || h < 1 || spp < 1 || spp > 4) {
+        jpeg_destroy_decompress(&cinfo);
+        rewind(fp);
+        return ERROR_INT("bad jpeg image parameters", __func__, 1);
+    }
+
+    if (pspp) *pspp = spp;
+    if (pw) *pw = cinfo.output_width;
+    if (ph) *ph = cinfo.output_height;
+    if (pycck) *pycck =
+        (cinfo.jpeg_color_space == JCS_YCCK && spp == 4);
+    if (pcmyk) *pcmyk =
+        (cinfo.jpeg_color_space == JCS_CMYK && spp == 4);
+
+    jpeg_destroy_decompress(&cinfo);
+    rewind(fp);
+    return 0;
+}
+
+
+/*
+ * \brief   fgetJpegResolution()
+ *
+ * \param[in]    fp             file stream
+ * \param[out]   pxres, pyres   resolutions
+ * \return   0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) If neither resolution field is set, this is not an error;
+ *          the returned resolution values are 0 (designating 'unknown').
+ *      (2) Side-effect: this rewinds the stream.
+ * </pre>
+ */
+l_int32
+fgetJpegResolution(FILE     *fp,
+                   l_int32  *pxres,
+                   l_int32  *pyres)
+{
+struct jpeg_decompress_struct  cinfo = { 0 };
+struct jpeg_error_mgr          jerr = { 0 };
+jmp_buf                        jmpbuf;  /* must be local to the function */
+
+    if (pxres) *pxres = 0;
+    if (pyres) *pyres = 0;
+    if (!pxres || !pyres)
+        return ERROR_INT("&xres and &yres not both defined", __func__, 1);
+    if (!fp)
+        return ERROR_INT("stream not opened", __func__, 1);
+
+    rewind(fp);
+
+        /* Modify the jpeg error handling to catch fatal errors  */
+    cinfo.err = jpeg_std_error(&jerr);
+    cinfo.client_data = (void *)&jmpbuf;
+    jerr.error_exit = jpeg_error_catch_all_1;
+    if (setjmp(jmpbuf))
+        return ERROR_INT("internal jpeg error", __func__, 1);
+
+        /* Initialize the jpeg structs for reading the header */
+    jpeg_create_decompress(&cinfo);
+    jpeg_stdio_src(&cinfo, fp);
+    jpeg_read_header(&cinfo, TRUE);
+
+        /* It is common for the input resolution to be omitted from the
+         * jpeg file.  If density_unit is not 1 or 2, simply return 0. */
+    if (cinfo.density_unit == 1) {  /* pixels/inch */
+        *pxres = cinfo.X_density;
+        *pyres = cinfo.Y_density;
+    } else if (cinfo.density_unit == 2) {  /* pixels/cm */
+        *pxres = (l_int32)((l_float32)cinfo.X_density * 2.54 + 0.5);
+        *pyres = (l_int32)((l_float32)cinfo.Y_density * 2.54 + 0.5);
+    }
+
+    jpeg_destroy_decompress(&cinfo);
+    rewind(fp);
+    return 0;
+}
+
+
+/*
+ * \brief   fgetJpegComment()
+ *
+ * \param[in]    fp        file stream opened for read
+ * \param[out]   pcomment  comment
+ * \return   0 if OK; 1 on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) Side-effect: this rewinds the stream.
+ * </pre>
+ */
+l_int32
+fgetJpegComment(FILE      *fp,
+                l_uint8  **pcomment)
+{
+struct jpeg_decompress_struct  cinfo = { 0 };
+struct jpeg_error_mgr          jerr = { 0 };
+struct callback_data           cb_data = { 0 };  /* contains local jmp_buf */
+
+    if (!pcomment)
+        return ERROR_INT("&comment not defined", __func__, 1);
+    *pcomment = NULL;
+    if (!fp)
+        return ERROR_INT("stream not opened", __func__, 1);
+
+    rewind(fp);
+
+        /* Modify the jpeg error handling to catch fatal errors  */
+    cinfo.err = jpeg_std_error(&jerr);
+    jerr.error_exit = jpeg_error_catch_all_2;
+    cb_data.comment = NULL;
+    cinfo.client_data = (void *)&cb_data;
+    if (setjmp(cb_data.jmpbuf)) {
+        LEPT_FREE(cb_data.comment);
+        return ERROR_INT("internal jpeg error", __func__, 1);
+    }
+
+        /* Initialize the jpeg structs for reading the header */
+    jpeg_create_decompress(&cinfo);
+    jpeg_set_marker_processor(&cinfo, JPEG_COM, jpeg_comment_callback);
+    jpeg_stdio_src(&cinfo, fp);
+    jpeg_read_header(&cinfo, TRUE);
+
+        /* Save the result */
+    *pcomment = cb_data.comment;
+    jpeg_destroy_decompress(&cinfo);
+    rewind(fp);
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                             Writing Jpeg                            *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief   pixWriteJpeg()
+ *
+ * \param[in]    filename
+ * \param[in]    pix           any depth; cmap is OK
+ * \param[in]    quality       1 - 100; 75 is default
+ * \param[in]    progressive   0 for baseline sequential; 1 for progressive
+ * \return  0 if OK; 1 on error
+ */
+l_ok
+pixWriteJpeg(const char  *filename,
+             PIX         *pix,
+             l_int32      quality,
+             l_int32      progressive)
+{
+FILE  *fp;
+
+    if (!pix)
+        return ERROR_INT("pix not defined", __func__, 1);
+    if (!filename)
+        return ERROR_INT("filename not defined", __func__, 1);
+
+    if ((fp = fopenWriteStream(filename, "wb+")) == NULL)
+        return ERROR_INT_1("stream not opened", filename, __func__, 1);
+
+    if (pixWriteStreamJpeg(fp, pix, quality, progressive)) {
+        fclose(fp);
+        return ERROR_INT_1("pix not written to stream", filename, __func__, 1);
+    }
+
+    fclose(fp);
+    return 0;
+}
+
+
+/*!
+ * \brief   pixWriteStreamJpeg()
+ *
+ * \param[in]    fp           file stream
+ * \param[in]    pixs         any depth; cmap is OK
+ * \param[in]    quality      1 - 100; 75 is default value; 0 is also default
+ * \param[in]    progressive  0 for baseline sequential; 1 for progressive
+ * \return  0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) Progressive encoding gives better compression, at the
+ *          expense of slower encoding and decoding.
+ *      (2) Standard chroma subsampling is 2x2 on both the U and V
+ *          channels.  For highest quality, use no subsampling; this
+ *          option is set by pixSetChromaSampling(pix, 0).
+ *      (3) The only valid pixel depths in leptonica are 1, 2, 4, 8, 16
+ *          and 32 bpp.  However, it is possible, and in some cases desirable,
+ *          to write out a jpeg file using an rgb pix that has 24 bpp.
+ *          This can be created by appending the raster data for a 24 bpp
+ *          image (with proper scanline padding) directly to a 24 bpp
+ *          pix that was created without a data array.
+ *      (4) There are two compression paths in this function:
+ *          * Grayscale image, no colormap: compress as 8 bpp image.
+ *          * rgb full color image: copy each line into the color
+ *            line buffer, and compress as three 8 bpp images.
+ *      (5) Under the covers, the jpeg library transforms rgb to a
+ *          luminance-chromaticity triple, each component of which is
+ *          also 8 bits, and compresses that.  It uses 2 Huffman tables,
+ *          a higher resolution one (with more quantization levels)
+ *          for luminosity and a lower resolution one for the chromas.
+ * </pre>
+ */
+l_ok
+pixWriteStreamJpeg(FILE    *fp,
+                   PIX     *pixs,
+                   l_int32  quality,
+                   l_int32  progressive)
+{
+l_int32                      xres, yres;
+l_int32                      i, j, k;
+l_int32                      w, h, d, wpl, spp, colorflag, rowsamples;
+l_uint32                    *ppixel, *line, *data;
+JSAMPROW                     rowbuffer;
+PIX                         *pix;
+struct jpeg_compress_struct  cinfo = { 0 };
+struct jpeg_error_mgr        jerr = { 0 };
+char                        *text;
+jmp_buf                      jmpbuf;  /* must be local to the function */
+
+    if (!fp)
+        return ERROR_INT("stream not open", __func__, 1);
+    if (!pixs)
+        return ERROR_INT("pixs not defined", __func__, 1);
+    if (quality <= 0) quality = 75;  /* default */
+    if (quality > 100) {
+        L_ERROR("invalid jpeg quality; setting to 75\n", __func__);
+        quality = 75;
+    }
+
+        /* If necessary, convert the pix so that it can be jpeg compressed.
+         * The colormap is removed based on the source, so if the colormap
+         * has only gray colors, the image will be compressed with spp = 1. */
+    pixGetDimensions(pixs, &w, &h, &d);
+    pix = NULL;
+    if (pixGetColormap(pixs) != NULL) {
+        L_INFO("removing colormap; may be better to compress losslessly\n",
+               __func__);
+        pix = pixRemoveColormap(pixs, REMOVE_CMAP_BASED_ON_SRC);
+    } else if (d >= 8 && d != 16) {  /* normal case; no rewrite */
+        pix = pixClone(pixs);
+    } else if (d < 8 || d == 16) {
+        L_INFO("converting from %d to 8 bpp\n", __func__, d);
+        pix = pixConvertTo8(pixs, 0);  /* 8 bpp, no cmap */
+    } else {
+        L_ERROR("unknown pix type with d = %d and no cmap\n", __func__, d);
+        return 1;
+    }
+    if (!pix)
+        return ERROR_INT("pix not made", __func__, 1);
+    pixSetPadBits(pix, 0);
+
+    rewind(fp);
+    rowbuffer = NULL;
+
+        /* Modify the jpeg error handling to catch fatal errors  */
+    cinfo.err = jpeg_std_error(&jerr);
+    cinfo.client_data = (void *)&jmpbuf;
+    jerr.error_exit = jpeg_error_catch_all_1;
+    if (setjmp(jmpbuf)) {
+        LEPT_FREE(rowbuffer);
+        pixDestroy(&pix);
+        return ERROR_INT("internal jpeg error", __func__, 1);
+    }
+
+        /* Initialize the jpeg structs for compression */
+    jpeg_create_compress(&cinfo);
+    jpeg_stdio_dest(&cinfo, fp);
+    cinfo.image_width  = w;
+    cinfo.image_height = h;
+
+        /* Set the color space and number of components */
+    d = pixGetDepth(pix);
+    if (d == 8) {
+        colorflag = 0;    /* 8 bpp grayscale; no cmap */
+        cinfo.input_components = 1;
+        cinfo.in_color_space = JCS_GRAYSCALE;
+    } else {  /* d == 32 || d == 24 */
+        colorflag = 1;    /* rgb */
+        cinfo.input_components = 3;
+        cinfo.in_color_space = JCS_RGB;
+    }
+
+    jpeg_set_defaults(&cinfo);
+
+        /* Setting optimize_coding to TRUE seems to improve compression
+         * by approx 2-4 percent, and increases comp time by approx 20%. */
+    cinfo.optimize_coding = FALSE;
+
+        /* Set resolution in pixels/in (density_unit: 1 = in, 2 = cm) */
+    xres = pixGetXRes(pix);
+    yres = pixGetYRes(pix);
+    if ((xres != 0) && (yres != 0)) {
+        cinfo.density_unit = 1;  /* designates pixels per inch */
+        cinfo.X_density = xres;
+        cinfo.Y_density = yres;
+    }
+
+        /* Set the quality and progressive parameters */
+    jpeg_set_quality(&cinfo, quality, TRUE);
+    if (progressive)
+        jpeg_simple_progression(&cinfo);
+
+        /* Set the chroma subsampling parameters.  This is done in
+         * YUV color space.  The Y (intensity) channel is never subsampled.
+         * The standard subsampling is 2x2 on both the U and V channels.
+         * Notation on this is confusing.  For a nice illustrations, see
+         *   http://en.wikipedia.org/wiki/Chroma_subsampling
+         * The standard subsampling is written as 4:2:0.
+         * We allow high quality where there is no subsampling on the
+         * chroma channels: denoted as 4:4:4.  */
+    if (pixs->special == L_NO_CHROMA_SAMPLING_JPEG) {
+        cinfo.comp_info[0].h_samp_factor = 1;
+        cinfo.comp_info[0].v_samp_factor = 1;
+        cinfo.comp_info[1].h_samp_factor = 1;
+        cinfo.comp_info[1].v_samp_factor = 1;
+        cinfo.comp_info[2].h_samp_factor = 1;
+        cinfo.comp_info[2].v_samp_factor = 1;
+    }
+
+    jpeg_start_compress(&cinfo, TRUE);
+
+        /* Cap the text the length limit, 65533, for JPEG_COM payload.
+         * Just to be safe, subtract 100 to cover the Adobe name space.  */
+    if ((text = pixGetText(pix)) != NULL) {
+        if (strlen(text) > 65433) {
+            L_WARNING("text is %zu bytes; clipping to 65433\n",
+                   __func__, strlen(text));
+            text[65433] = '\0';
+        }
+        jpeg_write_marker(&cinfo, JPEG_COM, (const JOCTET *)text, strlen(text));
+    }
+
+        /* Allocate row buffer */
+    spp = cinfo.input_components;
+    rowsamples = spp * w;
+    if ((rowbuffer = (JSAMPROW)LEPT_CALLOC(sizeof(JSAMPLE), rowsamples))
+        == NULL) {
+        pixDestroy(&pix);
+        return ERROR_INT("calloc fail for rowbuffer", __func__, 1);
+    }
+
+    data = pixGetData(pix);
+    wpl  = pixGetWpl(pix);
+    for (i = 0; i < h; i++) {
+        line = data + i * wpl;
+        if (colorflag == 0) {        /* 8 bpp gray */
+            for (j = 0; j < w; j++)
+                rowbuffer[j] = GET_DATA_BYTE(line, j);
+        } else {  /* colorflag == 1 */
+            if (d == 24) {  /* See note 3 above; special case of 24 bpp rgb */
+                jpeg_write_scanlines(&cinfo, (JSAMPROW *)&line, 1);
+            } else {  /* standard 32 bpp rgb */
+                ppixel = line;
+                for (j = k = 0; j < w; j++) {
+                    rowbuffer[k++] = GET_DATA_BYTE(ppixel, COLOR_RED);
+                    rowbuffer[k++] = GET_DATA_BYTE(ppixel, COLOR_GREEN);
+                    rowbuffer[k++] = GET_DATA_BYTE(ppixel, COLOR_BLUE);
+                    ppixel++;
+                }
+            }
+        }
+        if (d != 24)
+            jpeg_write_scanlines(&cinfo, &rowbuffer, 1);
+    }
+    jpeg_finish_compress(&cinfo);
+
+    pixDestroy(&pix);
+    LEPT_FREE(rowbuffer);
+    rowbuffer = NULL;
+    jpeg_destroy_compress(&cinfo);
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                         Read/write to memory                        *
+ *---------------------------------------------------------------------*/
+
+/*!
+ * \brief   pixReadMemJpeg()
+ *
+ * \param[in]    data       const; jpeg-encoded
+ * \param[in]    size       of data
+ * \param[in]    cmflag     colormap flag 0 means return RGB image if color;
+ *                          1 means create a colormap and return
+ *                          an 8 bpp colormapped image if color
+ * \param[in]    reduction  scaling factor: 1, 2, 4 or 8
+ * \param[out]   pnwarn     [optional] number of warnings
+ * \param[in]    hint       a bitwise OR of L_JPEG_* values; 0 for default
+ * \return  pix, or NULL on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) The %size byte of %data must be a null character.
+ *      (2) The only hint flag so far is L_JPEG_READ_LUMINANCE,
+ *          given in the enum in imageio.h.
+ *      (3) See pixReadJpeg() for usage.
+ * </pre>
+ */
+PIX *
+pixReadMemJpeg(const l_uint8  *data,
+               size_t          size,
+               l_int32         cmflag,
+               l_int32         reduction,
+               l_int32        *pnwarn,
+               l_int32         hint)
+{
+l_int32   ret;
+l_uint8  *comment;
+FILE     *fp;
+PIX      *pix;
+
+    if (pnwarn) *pnwarn = 0;
+    if (!data)
+        return (PIX *)ERROR_PTR("data not defined", __func__, NULL);
+
+    if ((fp = fopenReadFromMemory(data, size)) == NULL)
+        return (PIX *)ERROR_PTR("stream not opened", __func__, NULL);
+    pix = pixReadStreamJpeg(fp, cmflag, reduction, pnwarn, hint);
+    if (pix) {
+        ret = fgetJpegComment(fp, &comment);
+        if (!ret && comment) {
+            pixSetText(pix, (char *)comment);
+            LEPT_FREE(comment);
+        }
+    }
+    fclose(fp);
+    if (!pix) L_ERROR("pix not read\n", __func__);
+    return pix;
+}
+
+
+/*!
+ * \brief   readHeaderMemJpeg()
+ *
+ * \param[in]    data    const; jpeg-encoded
+ * \param[in]    size    of data
+ * \param[out]   pw      [optional] width
+ * \param[out]   ph      [optional] height
+ * \param[out]   pspp    [optional] samples/pixel
+ * \param[out]   pycck   [optional] 1 if ycck color space; 0 otherwise
+ * \param[out]   pcmyk   [optional] 1 if cmyk color space; 0 otherwise
+ * \return  0 if OK, 1 on error
+ */
+l_ok
+readHeaderMemJpeg(const l_uint8  *data,
+                  size_t          size,
+                  l_int32        *pw,
+                  l_int32        *ph,
+                  l_int32        *pspp,
+                  l_int32        *pycck,
+                  l_int32        *pcmyk)
+{
+l_int32  ret;
+FILE    *fp;
+
+    if (pw) *pw = 0;
+    if (ph) *ph = 0;
+    if (pspp) *pspp = 0;
+    if (pycck) *pycck = 0;
+    if (pcmyk) *pcmyk = 0;
+    if (!data)
+        return ERROR_INT("data not defined", __func__, 1);
+    if (!pw && !ph && !pspp && !pycck && !pcmyk)
+        return ERROR_INT("no results requested", __func__, 1);
+
+    if ((fp = fopenReadFromMemory(data, size)) == NULL)
+        return ERROR_INT("stream not opened", __func__, 1);
+    ret = freadHeaderJpeg(fp, pw, ph, pspp, pycck, pcmyk);
+    fclose(fp);
+    return ret;
+}
+
+
+/*!
+ * \brief   readResolutionMemJpeg()
+ *
+ * \param[in]   data    const; jpeg-encoded
+ * \param[in]   size    of data
+ * \param[out]  pxres   [optional]
+ * \param[out]  pyres   [optional]
+ * \return  0 if OK, 1 on error
+ */
+l_ok
+readResolutionMemJpeg(const l_uint8  *data,
+                      size_t          size,
+                      l_int32        *pxres,
+                      l_int32        *pyres)
+{
+l_int32  ret;
+FILE    *fp;
+
+    if (pxres) *pxres = 0;
+    if (pyres) *pyres = 0;
+    if (!data)
+        return ERROR_INT("data not defined", __func__, 1);
+    if (!pxres && !pyres)
+        return ERROR_INT("no results requested", __func__, 1);
+
+    if ((fp = fopenReadFromMemory(data, size)) == NULL)
+        return ERROR_INT("stream not opened", __func__, 1);
+    ret = fgetJpegResolution(fp, pxres, pyres);
+    fclose(fp);
+    return ret;
+}
+
+
+/*!
+ * \brief   pixWriteMemJpeg()
+ *
+ * \param[out]   pdata        data of jpeg compressed image
+ * \param[out]   psize        size of returned data
+ * \param[in]    pix          any depth; cmap is OK
+ * \param[in]    quality      1 - 100; 75 is default value; 0 is also default
+ * \param[in]    progressive  0 for baseline sequential; 1 for progressive
+ * \return  0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) See pixWriteStreamJpeg() for usage.  This version writes to
+ *          memory instead of to a file stream.
+ * </pre>
+ */
+l_ok
+pixWriteMemJpeg(l_uint8  **pdata,
+                size_t    *psize,
+                PIX       *pix,
+                l_int32    quality,
+                l_int32    progressive)
+{
+l_int32  ret;
+FILE    *fp;
+
+    if (pdata) *pdata = NULL;
+    if (psize) *psize = 0;
+    if (!pdata)
+        return ERROR_INT("&data not defined", __func__, 1 );
+    if (!psize)
+        return ERROR_INT("&size not defined", __func__, 1 );
+    if (!pix)
+        return ERROR_INT("&pix not defined", __func__, 1 );
+
+#if HAVE_FMEMOPEN
+    if ((fp = open_memstream((char **)pdata, psize)) == NULL)
+        return ERROR_INT("stream not opened", __func__, 1);
+    ret = pixWriteStreamJpeg(fp, pix, quality, progressive);
+    fputc('\0', fp);
+    fclose(fp);
+    if (*psize > 0) *psize = *psize - 1;
+#else
+    L_INFO("no fmemopen API --> work-around: write to temp file\n", __func__);
+  #ifdef _WIN32
+    if ((fp = fopenWriteWinTempfile()) == NULL)
+        return ERROR_INT("tmpfile stream not opened", __func__, 1);
+  #else
+    if ((fp = tmpfile()) == NULL)
+        return ERROR_INT("tmpfile stream not opened", __func__, 1);
+  #endif  /* _WIN32 */
+    ret = pixWriteStreamJpeg(fp, pix, quality, progressive);
+    rewind(fp);
+    *pdata = l_binaryReadStream(fp, psize);
+    fclose(fp);
+#endif  /* HAVE_FMEMOPEN */
+    return ret;
+}
+
+
+/*---------------------------------------------------------------------*
+ *           Setting special flag for chroma sampling on write         *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief   pixSetChromaSampling()
+ *
+ * \param[in]    pix
+ * \param[in]    sampling    1 for subsampling; 0 for no subsampling
+ * \return  0 if OK, 1 on error
+ *
+ * <pre>
+ * Notes:
+ *      (1) The default is for 2x2 chroma subsampling because the files are
+ *          considerably smaller and the appearance is typically satisfactory.
+ *          To get full resolution output in the chroma channels for
+ *          jpeg writing, call this with %sampling == 0.
+ * </pre>
+ */
+l_ok
+pixSetChromaSampling(PIX     *pix,
+                     l_int32  sampling)
+{
+    if (!pix)
+        return ERROR_INT("pix not defined", __func__, 1 );
+    if (sampling)
+        pixSetSpecial(pix, 0);  /* default */
+    else
+        pixSetSpecial(pix, L_NO_CHROMA_SAMPLING_JPEG);
+    return 0;
+}
+
+
+/*---------------------------------------------------------------------*
+ *                        Static system helpers                        *
+ *---------------------------------------------------------------------*/
+/*!
+ * \brief   jpeg_error_catch_all_1()
+ *
+ *  Notes:
+ *      (1) The default jpeg error_exit() kills the process, but we
+ *          never want a call to leptonica to kill a process.  If you
+ *          do want this behavior, remove the calls to these error handlers.
+ *      (2) This is used where cinfo->client_data holds only jmpbuf.
+ */
+static void
+jpeg_error_catch_all_1(j_common_ptr cinfo)
+{
+    jmp_buf *pjmpbuf = (jmp_buf *)cinfo->client_data;
+    (*cinfo->err->output_message) (cinfo);
+    jpeg_destroy(cinfo);
+    longjmp(*pjmpbuf, 1);
+}
+
+/*!
+ * \brief   jpeg_error_catch_all_2()
+ *
+ *  Notes:
+ *      (1) This is used where cinfo->client_data needs to hold both
+ *          the jmpbuf and the jpeg comment data.
+ *      (2) On error, the comment data will be freed by the caller.
+ */
+static void
+jpeg_error_catch_all_2(j_common_ptr cinfo)
+{
+struct callback_data  *pcb_data;
+
+    pcb_data = (struct callback_data *)cinfo->client_data;
+    (*cinfo->err->output_message) (cinfo);
+    jpeg_destroy(cinfo);
+    longjmp(pcb_data->jmpbuf, 1);
+}
+
+/* This function was borrowed from libjpeg */
+static l_uint8
+jpeg_getc(j_decompress_ptr cinfo)
+{
+struct jpeg_source_mgr *datasrc;
+
+    datasrc = cinfo->src;
+    if (datasrc->bytes_in_buffer == 0) {
+        if (! (*datasrc->fill_input_buffer) (cinfo)) {
+            return 0;
+        }
+    }
+    datasrc->bytes_in_buffer--;
+    return GETJOCTET(*datasrc->next_input_byte++);
+}
+
+/*!
+ * \brief   jpeg_comment_callback()
+ *
+ *  Notes:
+ *      (1) This is used to read the jpeg comment (JPEG_COM).
+ *          See the note above the declaration for why it returns
+ *          a "boolean".
+ */
+static boolean
+jpeg_comment_callback(j_decompress_ptr cinfo)
+{
+l_int32                length, i;
+l_uint8               *comment;
+struct callback_data  *pcb_data;
+
+        /* Get the size of the comment */
+    length = jpeg_getc(cinfo) << 8;
+    length += jpeg_getc(cinfo);
+    length -= 2;
+    if (length <= 0)
+        return 1;
+
+        /* Extract the comment from the file */
+    if ((comment = (l_uint8 *)LEPT_CALLOC(length + 1, sizeof(l_uint8))) == NULL)
+        return 0;
+    for (i = 0; i < length; i++)
+        comment[i] = jpeg_getc(cinfo);
+
+        /* Save the comment and return */
+    pcb_data = (struct callback_data *)cinfo->client_data;
+    if (pcb_data->comment) {  /* clear before overwriting previous comment */
+        LEPT_FREE(pcb_data->comment);
+        pcb_data->comment = NULL;
+    }
+    pcb_data->comment = comment;
+    return 1;
+}
+
+/* --------------------------------------------*/
+#endif  /* HAVE_LIBJPEG */
+/* --------------------------------------------*/