| /*====================================================================* |
| - 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 */ |
| /* --------------------------------------------*/ |
| |