/*====================================================================* - Copyright (C) 2001 Leptonica. All rights reserved. - This software is distributed in the hope that it will be - useful, but with NO WARRANTY OF ANY KIND. - No author or distributor accepts responsibility to anyone for the - consequences of using this software, or for whether it serves any - particular purpose or works at all, unless he or she says so in - writing. Everyone is granted permission to copy, modify and - redistribute this source code, for commercial or non-commercial - purposes, with the following restrictions: (1) the origin of this - source code must not be misrepresented; (2) modified versions must - be plainly marked as such; and (3) this notice may not be removed - or altered from any source or modified source distribution. *====================================================================*/ /* * jpegio.c * * Read jpeg from file * PIX *pixReadJpeg() [ special top level ] * PIX *pixReadStreamJpeg() * * Write jpeg to file * l_int32 pixWriteJpeg() [ special top level ] * l_int32 pixWriteStreamJpeg() * * Extraction of jpeg header information * l_int32 extractJpegDataFromFile() * l_int32 extractJpegDataFromArray() * static l_int32 locateJpegImageParameters() * static l_int32 getNextJpegMarker() * static l_int32 getTwoByteParameter() * * Read/write to memory [not on windows] * PIX *pixReadMemJpeg() * l_int32 pixWriteMemJpeg() * * Static system helpers * static void jpeg_error_do_not_exit() * 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. * * 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. So I inserted setjmp(s) * in the reader and writer, wrote a static error handler that * does not exit, and set up the cinfo structure so that the * low-level jpeg library will call this error handler instead * of the default function error_exit(). */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include "allheaders.h" #ifdef HAVE_CONFIG_H #include "config_auto.h" #endif /* HAVE_CONFIG_H */ /* --------------------------------------------*/ #if HAVE_LIBJPEG /* defined in environ.h */ /* --------------------------------------------*/ #include <setjmp.h> #include "jpeglib.h" static void jpeg_error_do_not_exit(j_common_ptr cinfo); static l_uint8 jpeg_getc(j_decompress_ptr cinfo); static jmp_buf jpeg_jmpbuf; /* 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); /* Helpers for extraction of jpeg data */ static l_int32 locateJpegImageParameters(l_uint8 *, l_int32, l_int32 *); static l_int32 getNextJpegMarker(l_uint8 *, l_int32, l_int32 *); static l_int32 getTwoByteParameter(l_uint8 *, l_int32); #ifndef NO_CONSOLE_IO #define DEBUG_INFO 0 #endif /* ~NO_CONSOLE_IO */ /*---------------------------------------------------------------------* * Reading Jpeg * *---------------------------------------------------------------------*/ /*! * pixReadJpeg() * * Input: filename * colormap flag (0 means return RGB image if color; * 1 means create colormap and return 8 bpp * palette image if color) * reduction (scaling factor: 1, 2, 4 or 8) * &pnwarn (<optional return> number of warnings about * corrupted data) * Return: pix, or null on error * * Images reduced by factors of 2, 4 or 8 can be returned * significantly faster than full resolution images. * * The jpeg library will return warnings (or exit) if * the jpeg data is bad. Use this function if you want the * jpeg library to create an 8 bpp palette image, or to * tell if the jpeg data has been corrupted. For corrupt jpeg * data, there are two possible outcomes: * (1) a damaged pix will be returned, along with a nonzero * number of warnings, or * (2) for sufficiently serious problems, the library will attempt * to exit (caught by our error handler) and no pix will be returned. */ PIX * pixReadJpeg(const char *filename, l_int32 cmflag, l_int32 reduction, l_int32 *pnwarn) { FILE *fp; PIX *pix; PROCNAME("pixReadJpeg"); if (!filename) return (PIX *)ERROR_PTR("filename not defined", procName, NULL); if (pnwarn) *pnwarn = 0; /* init */ if (cmflag != 0 && cmflag != 1) cmflag = 0; /* default */ if (reduction != 1 && reduction != 2 && reduction != 4 && reduction != 8) return (PIX *)ERROR_PTR("reduction not in {1,2,4,8}", procName, NULL); if ((fp = fopenReadStream(filename)) == NULL) return (PIX *)ERROR_PTR("image file not found", procName, NULL); pix = pixReadStreamJpeg(fp, cmflag, reduction, pnwarn, 0); fclose(fp); if (!pix) return (PIX *)ERROR_PTR("image not returned", procName, NULL); return pix; } /*! * pixReadStreamJpeg() * * Input: stream * colormap flag (0 means return RGB image if color; * 1 means create colormap and return 8 bpp * palette image if color) * reduction (scaling factor: 1, 2, 4 or 8) * &pnwarn (<optional return> number of warnings) * hint: a bitwise OR of L_HINT_* values * Return: pix, or null on error * * Usage: see pixReadJpeg() */ PIX * pixReadStreamJpeg(FILE *fp, l_int32 cmflag, l_int32 reduction, l_int32 *pnwarn, l_int32 hint) { l_uint8 cyan, yellow, magenta, black, white; l_int32 rval, gval, bval; l_int32 i, j, k; 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; struct jpeg_error_mgr jerr; l_uint8 *comment = NULL; PROCNAME("pixReadStreamJpeg"); if (!fp) return (PIX *)ERROR_PTR("fp not defined", procName, NULL); if (pnwarn) *pnwarn = 0; /* init */ if (cmflag != 0 && cmflag != 1) cmflag = 0; /* default */ if (reduction != 1 && reduction != 2 && reduction != 4 && reduction != 8) return (PIX *)ERROR_PTR("reduction not in {1,2,4,8}", procName, NULL); if (BITS_IN_JSAMPLE != 8) /* set in jmorecfg.h */ return (PIX *)ERROR_PTR("BITS_IN_JSAMPLE != 8", procName, NULL); rewind(fp); pix = NULL; /* init */ if (setjmp(jpeg_jmpbuf)) { pixDestroy(&pix); FREE(rowbuffer); return (PIX *)ERROR_PTR("internal jpeg error", procName, NULL); } rowbuffer = NULL; cinfo.err = jpeg_std_error(&jerr); jerr.error_exit = jpeg_error_do_not_exit; /* catch error; do not exit! */ jpeg_create_decompress(&cinfo); cinfo.client_data = &comment; jpeg_set_marker_processor(&cinfo, JPEG_COM, jpeg_comment_callback); jpeg_stdio_src(&cinfo, fp); jpeg_read_header(&cinfo, TRUE); cinfo.scale_denom = reduction; if (hint & L_HINT_GRAY) cinfo.out_color_space = JCS_GRAYSCALE; jpeg_calc_output_dimensions(&cinfo); /* Allocate the image and a row buffer */ spp = cinfo.out_color_components; w = cinfo.output_width; h = cinfo.output_height; ycck = (cinfo.jpeg_color_space == JCS_YCCK && spp == 4 && cmflag == 0); cmyk = (cinfo.jpeg_color_space == JCS_CMYK && spp == 4 && cmflag == 0); if (spp != 1 && spp != 3 && !ycck && !cmyk) { if (comment) FREE(comment); return (PIX *)ERROR_PTR("spp must be 1 or 3, or YCCK or CMYK", procName, NULL); } if ((spp == 3 && cmflag == 0) || ycck || cmyk) { /* rgb or 4 bpp color */ rowbuffer = (JSAMPROW)CALLOC(sizeof(JSAMPLE), spp * w); pix = pixCreate(w, h, 32); } else { /* 8 bpp gray or colormapped */ rowbuffer = (JSAMPROW)CALLOC(sizeof(JSAMPLE), w); pix = pixCreate(w, h, 8); } if (!rowbuffer || !pix) { if (comment) FREE(comment); if (rowbuffer) FREE(rowbuffer); pixDestroy(&pix); return (PIX *)ERROR_PTR("rowbuffer or pix not made", procName, NULL); } if (comment) { pixSetText(pix, (char *)comment); FREE(comment); } if (spp == 1) /* Grayscale or colormapped */ jpeg_start_decompress(&cinfo); else { /* Color; spp == 3 or YCCK or CMYK */ if (cmflag == 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 */ if ((spp == 3 && cmflag == 0) || ycck || cmyk) { /* -- 24 bit color -- */ for (i = 0; i < h; i++) { if (jpeg_read_scanlines(&cinfo, &rowbuffer, (JDIMENSION)1) != 1) return (PIX *)ERROR_PTR("bad read scanline", procName, NULL); 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 are inverted on read-in, see the "Special color spaces" section of "Using the IJG JPEG Library" by Thomas G. Lane. */ for (j = k = 0; j < w; j++) { cyan = 255 - rowbuffer[k++]; magenta = 255 - rowbuffer[k++]; yellow = 255 - rowbuffer[k++]; white = rowbuffer[k++]; black = 255 - white; rval = 255 - (cyan * white) / 255 - black; gval = 255 - (magenta * white) / 255 - black; bval = 255 - (yellow * white) / 255 - black; 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 */ for (i = 0; i < h; i++) { if (jpeg_read_scanlines(&cinfo, &rowbuffer, (JDIMENSION)1) != 1) return (PIX *)ERROR_PTR("bad read scanline", procName, NULL); line = data + i * wpl; for (j = 0; j < w; j++) SET_DATA_BYTE(line, j, rowbuffer[j]); } } if (pnwarn) *pnwarn = cinfo.err->num_warnings; switch (cinfo.density_unit) { case 1: /* pixels per inch */ pixSetXRes(pix, cinfo.X_density); pixSetYRes(pix, cinfo.Y_density); break; case 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)); break; default: /* the pixel density may not be defined; ignore */ break; } jpeg_finish_decompress(&cinfo); jpeg_destroy_decompress(&cinfo); FREE(rowbuffer); return pix; } /*---------------------------------------------------------------------* * Writing Jpeg * *---------------------------------------------------------------------*/ /*! * pixWriteJpeg() * * Input: filename * pix * quality (1 - 100; 75 is default) * progressive (0 for baseline sequential; 1 for progressive) * Return: 0 if OK; 1 on error */ l_int32 pixWriteJpeg(const char *filename, PIX *pix, l_int32 quality, l_int32 progressive) { FILE *fp; PROCNAME("pixWriteJpeg"); if (!pix) return ERROR_INT("pix not defined", procName, 1); if (!filename) return ERROR_INT("filename not defined", procName, 1); if ((fp = fopen(filename, "wb+")) == NULL) return ERROR_INT("stream not opened", procName, 1); if (pixWriteStreamJpeg(fp, pix, quality, progressive)) { fclose(fp); return ERROR_INT("pix not written to stream", procName, 1); } fclose(fp); return 0; } /*! * pixWriteStreamJpeg() * * Input: stream * pix (8 or 32 bpp) * quality (1 - 100; 75 is default value; 0 is also default) * progressive (0 for baseline sequential; 1 for progressive) * Return: 0 if OK, 1 on error * * Notes: * (1) Under the covers, the library transforms rgb to a * luminence-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. * (2) Progressive encoding gives better compression, at the * expense of slower encoding and decoding. * (3) There are three possibilities: * * 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. * * 8 bpp colormapped image: convert each line to three * 8 bpp line images in the color line buffer, and * compress as three 8 bpp images. * (4) 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. See note in * pixWriteStreamPng() for an example. */ l_int32 pixWriteStreamJpeg(FILE *fp, PIX *pix, l_int32 quality, l_int32 progressive) { l_uint8 byteval; l_int32 xres, yres; l_int32 i, j, k; l_int32 w, h, d, wpl, spp, colorflg, rowsamples; l_int32 *rmap, *gmap, *bmap; l_uint32 *ppixel, *line, *data; JSAMPROW rowbuffer; PIXCMAP *cmap; struct jpeg_compress_struct cinfo; struct jpeg_error_mgr jerr; const char *text; PROCNAME("pixWriteStreamJpeg"); if (!fp) return ERROR_INT("stream not open", procName, 1); if (!pix) return ERROR_INT("pix not defined", procName, 1); rewind(fp); if (setjmp(jpeg_jmpbuf)) { FREE(rowbuffer); if (colorflg == 1) { FREE(rmap); FREE(gmap); FREE(bmap); } return ERROR_INT("internal jpeg error", procName, 1); } rowbuffer = NULL; rmap = NULL; gmap = NULL; bmap = NULL; w = pixGetWidth(pix); h = pixGetHeight(pix); d = pixGetDepth(pix); if (d != 8 && d != 24 && d != 32) return ERROR_INT("bpp must be 8, 24 or 32", procName, 1); if (quality <= 0) quality = 75; /* default */ if (d == 32 || d == 24) colorflg = 2; /* rgb; no colormap */ else if ((cmap = pixGetColormap(pix)) == NULL) colorflg = 0; /* 8 bpp grayscale; no colormap */ else { colorflg = 1; /* 8 bpp; colormap */ pixcmapToArrays(cmap, &rmap, &gmap, &bmap); } cinfo.err = jpeg_std_error(&jerr); jerr.error_exit = jpeg_error_do_not_exit; /* catch error; do not exit! */ jpeg_create_compress(&cinfo); jpeg_stdio_dest(&cinfo, fp); cinfo.image_width = w; cinfo.image_height = h; if (colorflg == 0) { cinfo.input_components = 1; cinfo.in_color_space = JCS_GRAYSCALE; } else { /* colorflg == 1 or 2 */ 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; 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; } jpeg_set_quality(&cinfo, quality, TRUE); if (progressive) { jpeg_simple_progression(&cinfo); } jpeg_start_compress(&cinfo, TRUE); if ((text = pixGetText(pix))) { jpeg_write_marker(&cinfo, JPEG_COM, (const JOCTET *)text, strlen(text)); } /* Allocate row buffer */ spp = cinfo.input_components; rowsamples = spp * w; if ((rowbuffer = (JSAMPROW)CALLOC(sizeof(JSAMPLE), rowsamples)) == NULL) return ERROR_INT("calloc fail for rowbuffer", procName, 1); data = pixGetData(pix); wpl = pixGetWpl(pix); for (i = 0; i < h; i++) { line = data + i * wpl; if (colorflg == 0) { /* 8 bpp gray */ for (j = 0; j < w; j++) rowbuffer[j] = GET_DATA_BYTE(line, j); } else if (colorflg == 1) { /* 8 bpp colormapped */ for (j = 0; j < w; j++) { byteval = GET_DATA_BYTE(line, j); rowbuffer[3 * j + COLOR_RED] = rmap[byteval]; rowbuffer[3 * j + COLOR_GREEN] = gmap[byteval]; rowbuffer[3 * j + COLOR_BLUE] = bmap[byteval]; } } else { /* colorflg == 2 */ if (d == 24) { /* See note 4 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); FREE(rowbuffer); if (colorflg == 1) { FREE(rmap); FREE(gmap); FREE(bmap); } jpeg_destroy_compress(&cinfo); return 0; } /*---------------------------------------------------------------------* * Extraction of jpeg header information * *---------------------------------------------------------------------*/ /*! * extractJpegDataFromFile() * * Input: filein * &data (<return> binary data consisting of the entire jpeg file) * &nbytes (<return> size of binary data) * &w (<optional return> image width) * &h (<optional return> image height) * &bps (<optional return> bits/sample; should be 8) * &spp (<optional return> samples/pixel; should be 1 or 3) * Return: 0 if OK, 1 on error */ l_int32 extractJpegDataFromFile(const char *filein, l_uint8 **pdata, l_int32 *pnbytes, l_int32 *pw, l_int32 *ph, l_int32 *pbps, l_int32 *pspp) { l_uint8 *data; l_int32 format, nbytes; FILE *fpin; PROCNAME("extractJpegDataFromFile"); if (!filein) return ERROR_INT("filein not defined", procName, 1); if (!pdata) return ERROR_INT("&data not defined", procName, 1); if (!pnbytes) return ERROR_INT("&nbytes not defined", procName, 1); if (!pw && !ph && !pbps && !pspp) return ERROR_INT("no output data requested", procName, 1); *pdata = NULL; *pnbytes = 0; if ((fpin = fopen(filein, "r")) == NULL) return ERROR_INT("filein not defined", procName, 1); format = findFileFormat(fpin); fclose(fpin); if (format != IFF_JFIF_JPEG) return ERROR_INT("filein not jfif jpeg", procName, 1); if ((data = arrayRead(filein, &nbytes)) == NULL) return ERROR_INT("inarray not made", procName, 1); *pnbytes = nbytes; *pdata = data; /* On error, free the data */ if (extractJpegDataFromArray(data, nbytes, pw, ph, pbps, pspp)) { FREE(data); *pdata = NULL; *pnbytes = 0; } return 0; } /*! * extractJpegDataFromArray() * * Input: data (binary data consisting of the entire jpeg file) * nbytes (size of binary data) * &w (<optional return> image width) * &h (<optional return> image height) * &bps (<optional return> bits/sample; should be 8) * &spp (<optional return> samples/pixel; should be 1, 3 or 4) * Return: 0 if OK, 1 on error */ l_int32 extractJpegDataFromArray(const void *data, l_int32 nbytes, l_int32 *pw, l_int32 *ph, l_int32 *pbps, l_int32 *pspp) { l_uint8 *data8; l_int32 imeta, msize, bps, w, h, spp; PROCNAME("extractJpegDataFromArray"); if (!data) return ERROR_INT("data not defined", procName, 1); if (!pw && !ph && !pbps && !pspp) return ERROR_INT("no output data requested", procName, 1); data8 = (l_uint8 *)data; /* Find where the image metadata begins in header: * 0xc0 is start of metadata for baseline DCT; * 0xc1 is start of metadata for extended sequential DCT; * ... */ imeta = 0; if (locateJpegImageParameters(data8, nbytes, &imeta)) return ERROR_INT("metadata not found", procName, 1); /* Save the metadata */ msize = getTwoByteParameter(data8, imeta); /* metadata size */ bps = data8[imeta + 2]; h = getTwoByteParameter(data8, imeta + 3); w = getTwoByteParameter(data8, imeta + 5); spp = data8[imeta + 7]; if (pbps) *pbps = bps; if (ph) *ph = h; if (pw) *pw = w; if (pspp) *pspp = spp; #if DEBUG_INFO fprintf(stderr, "w = %d, h = %d, bps = %d, spp = %d\n", w, h, bps, spp); fprintf(stderr, "imeta = %d, msize = %d\n", imeta, msize); #endif /* DEBUG_INFO */ /* Is the data obviously bad? */ if (h <= 0 || w <= 0 || bps != 8 || (spp != 1 && spp !=3 && spp != 4)) { fprintf(stderr, "h = %d, w = %d, bps = %d, spp = %d\n", h, w, bps, spp); return ERROR_INT("image parameters not valid", procName, 1); } return 0; } /* * locateJpegImageParameters() * * Input: inarray (binary jpeg) * size (of the data array) * &index (<return> location of image metadata) * Return: 0 if OK, 1 on error. Caller must check this! * * Notes: * (1) The parameters listed here appear to be the only jpeg flags * we need to worry about. It would have been nice to have * avoided the switch with all these parameters, but * unfortunately the parser for the jpeg header is set * to accept any old flag that's not on the approved list! * So we have to look for a flag that's not on the list * (and is not 0), and then interpret the size of the * data chunk and skip it. Sometimes such a chunk contains * a thumbnail version of the image, so if we don't skip it, * we will find a pair of bytes such as 0xffc0, followed * by small w and h dimensions. */ static l_int32 locateJpegImageParameters(l_uint8 *inarray, l_int32 size, l_int32 *pindex) { l_uint8 val; l_int32 index, skiplength; PROCNAME("locateJpegImageParameters"); if (!inarray) return ERROR_INT("inarray not defined", procName, 1); if (!pindex) return ERROR_INT("&index not defined", procName, 1); index = *pindex; while (1) { if (getNextJpegMarker(inarray, size, &index)) break; if ((val = inarray[index]) == 0) /* ignore if "escaped" */ continue; /* fprintf(stderr, " marker %x at %o, %d\n", val, index, index); */ switch(val) { case 0xc0: /* M_SOF0 */ case 0xc1: /* M_SOF1 */ case 0xc2: /* M_SOF2 */ case 0xc3: /* M_SOF3 */ case 0xc5: /* M_SOF5 */ case 0xc6: /* M_SOF6 */ case 0xc7: /* M_SOF7 */ case 0xc9: /* M_SOF9 */ case 0xca: /* M_SOF10 */ case 0xcd: /* M_SOF13 */ case 0xce: /* M_SOF14 */ case 0xcf: /* M_SOF15 */ *pindex = index + 1; /* found it */ return 0; case 0x01: /* M_TEM */ case 0xd0: /* M_RST0 */ case 0xd1: /* M_RST1 */ case 0xd2: /* M_RST2 */ case 0xd3: /* M_RST3 */ case 0xd4: /* M_RST4 */ case 0xd5: /* M_RST5 */ case 0xd6: /* M_RST6 */ case 0xd7: /* M_RST7 */ case 0xd8: /* M_SOI */ case 0xd9: /* M_EOI */ case 0xe0: /* M_APP0 */ case 0xee: /* M_APP14 */ break; default: skiplength = getTwoByteParameter(inarray, index + 1); index += skiplength; break; } } return 1; /* not found */ } /* * getNextJpegMarker() * * Input: array (jpeg data) * size (from current point to the end) * &index (<return> the last position searched. If it * is not at the end of the array, we return * the first byte that is not 0xff, after * having encountered at least one 0xff.) * Return: 0 if a marker is found, 1 if the end of the array is reached * * Notes: * (1) In jpeg, 0xff is used to mark the end of a data segment. * There may be more than one 0xff in succession. But not every * 0xff marks the end of a segment. It is possible, though * rare, that 0xff can occur within some data. In that case, * the marker is "escaped", by following it with 0x00. * (2) This function parses a jpeg data stream. It doesn't * _really_ get the next marker, because it doesn't check if * the 0xff is escaped. But the caller checks for this escape * condition, and ignores the marker if escaped. */ static l_int32 getNextJpegMarker(l_uint8 *array, l_int32 size, l_int32 *pindex) { l_uint8 val; l_int32 index; PROCNAME("getNextJpegMarker"); if (!array) return ERROR_INT("array not defined", procName, 1); if (!pindex) return ERROR_INT("&index not defined", procName, 1); index = *pindex; while (index < size) { /* skip to 0xff */ val = array[index++]; if (val == 0xff) break; } while (index < size) { /* skip repeated 0xff */ val = array[index++]; if (val != 0xff) break; } *pindex = index - 1; if (index >= size) return 1; else return 0; } static l_int32 getTwoByteParameter(l_uint8 *array, l_int32 index) { return (l_int32)((array[index]) << 8) + (l_int32)(array[index + 1]); } /*---------------------------------------------------------------------* * Read/write to memory * *---------------------------------------------------------------------*/ #if HAVE_FMEMOPEN #include "_stdio.h" /* extern FILE *open_memstream(char **data, size_t *size); */ /* extern FILE *fmemopen(void *data, size_t size, const char *mode); */ /*! * pixReadMemJpeg() * * Input: cdata (const; jpeg-encoded) * size (of data) * colormap flag (0 means return RGB image if color; * 1 means create colormap and return 8 bpp * palette image if color) * reduction (scaling factor: 1, 2, 4 or 8) * &pnwarn (<optional return> number of warnings) * hint (bitwise OR of L_HINT_* values; use 0 for no hint) * Return: pix, or null on error * * Notes: * (1) The @size byte of @data must be a null character. * (2) See pixReadJpeg() for usage. */ PIX * pixReadMemJpeg(const l_uint8 *cdata, size_t size, l_int32 cmflag, l_int32 reduction, l_int32 *pnwarn, l_int32 hint) { l_uint8 *data; FILE *fp; PIX *pix; PROCNAME("pixReadMemJpeg"); if (!cdata) return (PIX *)ERROR_PTR("cdata not defined", procName, NULL); data = (l_uint8 *)cdata; /* we're really not going to change this */ if ((fp = fmemopen(data, size, "r")) == NULL) return (PIX *)ERROR_PTR("stream not opened", procName, NULL); pix = pixReadStreamJpeg(fp, cmflag, reduction, pnwarn, hint); fclose(fp); return pix; } /*! * pixWriteMemJpeg() * * Input: &data (<return> data of tiff compressed image) * &size (<return> size of returned data) * pix * quality (1 - 100; 75 is default value; 0 is also default) * progressive (0 for baseline sequential; 1 for progressive) * Return: 0 if OK, 1 on error * * Notes: * (1) See pixWriteStreamJpeg() for usage. This version writes to * memory instead of to a file stream. */ l_int32 pixWriteMemJpeg(l_uint8 **pdata, size_t *psize, PIX *pix, l_int32 quality, l_int32 progressive) { l_int32 ret; FILE *fp; PROCNAME("pixWriteMemJpeg"); 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 ((fp = open_memstream((char **)pdata, psize)) == NULL) return ERROR_INT("stream not opened", procName, 1); ret = pixWriteStreamJpeg(fp, pix, quality, progressive); fclose(fp); return ret; } #else PIX * pixReadMemJpeg(const l_uint8 *cdata, size_t size, l_int32 cmflag, l_int32 reduction, l_int32 *pnwarn, l_int32 hint) { return (PIX *)ERROR_PTR( "jpeg read from memory not implemented on this platform", "pixReadMemJpeg", NULL); } l_int32 pixWriteMemJpeg(l_uint8 **pdata, size_t *psize, PIX *pix, l_int32 quality, l_int32 progressive) { return ERROR_INT( "jpeg write to memory not implemented on this platform", "pixWriteMemJpeg", 1); } #endif /* HAVE_FMEMOPEN */ /*---------------------------------------------------------------------* * Static helpers * *---------------------------------------------------------------------*/ /* The default jpeg error_exit() kills the process. * We don't want leptonica to allow this to happen. * If you want this default behavior, remove the * calls to this in the functions above. */ static void jpeg_error_do_not_exit(j_common_ptr cinfo) { (*cinfo->err->output_message) (cinfo); jpeg_destroy(cinfo); longjmp(jpeg_jmpbuf, 0); return; } /* 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++); } /* This function is required for reading jpeg comments, and * was contributed by Antony Dovgal. Why 'boolean'? See * note above the declaration. */ static boolean jpeg_comment_callback(j_decompress_ptr cinfo) { l_int32 length, i; l_uint32 c; l_uint8 **comment; comment = (l_uint8 **)cinfo->client_data; length = jpeg_getc(cinfo) << 8; length += jpeg_getc(cinfo); length -= 2; if (length <= 0) return 1; *comment = (l_uint8 *)MALLOC(length + 1); if (!(*comment)) return 0; for (i = 0; i < length; i++) { c = jpeg_getc(cinfo); (*comment)[i] = c; } (*comment)[length] = 0; return 1; } /* --------------------------------------------*/ #endif /* HAVE_LIBJPEG */ /* --------------------------------------------*/