blob: 5ef8c99679cb749f65d3bf1123b30d1f72dbb876 [file] [log] [blame]
/*====================================================================*
- 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 */
/* --------------------------------------------*/