2010-07-31 06:52:47 +04:00
|
|
|
/*
|
|
|
|
* The Python Imaging Library.
|
|
|
|
* $Id$
|
|
|
|
*
|
|
|
|
* coder for JPEG data
|
|
|
|
*
|
|
|
|
* history:
|
|
|
|
* 1996-05-06 fl created
|
|
|
|
* 1996-07-16 fl don't drop last block of encoded data
|
|
|
|
* 1996-12-30 fl added quality and progressive settings
|
|
|
|
* 1997-01-08 fl added streamtype settings
|
|
|
|
* 1998-01-31 fl adapted to libjpeg 6a
|
|
|
|
* 1998-07-12 fl added YCbCr support
|
|
|
|
* 2001-04-16 fl added DPI write support
|
|
|
|
*
|
|
|
|
* Copyright (c) 1997-2001 by Secret Labs AB
|
|
|
|
* Copyright (c) 1995-1997 by Fredrik Lundh
|
|
|
|
*
|
|
|
|
* See the README file for details on usage and redistribution.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "Imaging.h"
|
|
|
|
|
2021-01-03 06:17:51 +03:00
|
|
|
#ifdef HAVE_LIBJPEG
|
2010-07-31 06:52:47 +04:00
|
|
|
|
2013-01-11 00:36:21 +04:00
|
|
|
#undef HAVE_PROTOTYPES
|
|
|
|
#undef HAVE_STDLIB_H
|
|
|
|
#undef HAVE_STDDEF_H
|
2010-07-31 06:52:47 +04:00
|
|
|
#undef UINT8
|
|
|
|
#undef UINT16
|
|
|
|
#undef UINT32
|
|
|
|
#undef INT16
|
|
|
|
#undef INT32
|
|
|
|
|
|
|
|
#include "Jpeg.h"
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------------- */
|
2013-05-15 07:45:07 +04:00
|
|
|
/* Suspending output handler */
|
2010-07-31 06:52:47 +04:00
|
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
|
|
|
|
METHODDEF(void)
|
2021-01-03 06:17:51 +03:00
|
|
|
stub(j_compress_ptr cinfo) { /* empty */ }
|
2010-07-31 06:52:47 +04:00
|
|
|
|
|
|
|
METHODDEF(boolean)
|
2021-01-03 06:17:51 +03:00
|
|
|
empty_output_buffer(j_compress_ptr cinfo) {
|
2010-07-31 06:52:47 +04:00
|
|
|
/* Suspension */
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
GLOBAL(void)
|
2021-01-03 06:17:51 +03:00
|
|
|
jpeg_buffer_dest(j_compress_ptr cinfo, JPEGDESTINATION *destination) {
|
|
|
|
cinfo->dest = (void *)destination;
|
2010-07-31 06:52:47 +04:00
|
|
|
|
|
|
|
destination->pub.init_destination = stub;
|
|
|
|
destination->pub.empty_output_buffer = empty_output_buffer;
|
|
|
|
destination->pub.term_destination = stub;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------------- */
|
2013-05-15 07:45:07 +04:00
|
|
|
/* Error handler */
|
2010-07-31 06:52:47 +04:00
|
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
|
|
|
|
METHODDEF(void)
|
2021-01-03 06:17:51 +03:00
|
|
|
error(j_common_ptr cinfo) {
|
|
|
|
JPEGERROR *error;
|
|
|
|
error = (JPEGERROR *)cinfo->err;
|
|
|
|
(*cinfo->err->output_message)(cinfo);
|
2013-05-15 07:45:07 +04:00
|
|
|
longjmp(error->setjmp_buffer, 1);
|
2010-07-31 06:52:47 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------------- */
|
2020-05-01 15:08:57 +03:00
|
|
|
/* Encoder */
|
2010-07-31 06:52:47 +04:00
|
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
|
|
|
|
int
|
2021-01-03 06:17:51 +03:00
|
|
|
ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
|
|
|
|
JPEGENCODERSTATE *context = (JPEGENCODERSTATE *)state->context;
|
2010-07-31 06:52:47 +04:00
|
|
|
int ok;
|
|
|
|
|
|
|
|
if (setjmp(context->error.setjmp_buffer)) {
|
2020-05-01 15:08:57 +03:00
|
|
|
/* JPEG error handler */
|
|
|
|
jpeg_destroy_compress(&context->cinfo);
|
|
|
|
state->errcode = IMAGING_CODEC_BROKEN;
|
|
|
|
return -1;
|
2010-07-31 06:52:47 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!state->state) {
|
2020-05-01 15:08:57 +03:00
|
|
|
/* Setup compression context (very similar to the decoder) */
|
|
|
|
context->cinfo.err = jpeg_std_error(&context->error.pub);
|
|
|
|
context->error.pub.error_exit = error;
|
|
|
|
jpeg_create_compress(&context->cinfo);
|
|
|
|
jpeg_buffer_dest(&context->cinfo, &context->destination);
|
2010-07-31 06:52:47 +04:00
|
|
|
|
|
|
|
context->extra_offset = 0;
|
|
|
|
|
2020-05-01 15:08:57 +03:00
|
|
|
/* Ready to encode */
|
|
|
|
state->state = 1;
|
2010-07-31 06:52:47 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Load the destination buffer */
|
|
|
|
context->destination.pub.next_output_byte = buf;
|
|
|
|
context->destination.pub.free_in_buffer = bytes;
|
|
|
|
|
|
|
|
switch (state->state) {
|
2020-05-01 15:08:57 +03:00
|
|
|
case 1:
|
|
|
|
|
|
|
|
context->cinfo.image_width = state->xsize;
|
|
|
|
context->cinfo.image_height = state->ysize;
|
|
|
|
|
|
|
|
switch (state->bits) {
|
|
|
|
case 8:
|
|
|
|
context->cinfo.input_components = 1;
|
|
|
|
context->cinfo.in_color_space = JCS_GRAYSCALE;
|
|
|
|
break;
|
|
|
|
case 24:
|
|
|
|
context->cinfo.input_components = 3;
|
2020-05-10 12:56:36 +03:00
|
|
|
if (strcmp(im->mode, "YCbCr") == 0) {
|
2020-05-01 15:08:57 +03:00
|
|
|
context->cinfo.in_color_space = JCS_YCbCr;
|
2020-05-10 12:56:36 +03:00
|
|
|
} else {
|
2020-05-01 15:08:57 +03:00
|
|
|
context->cinfo.in_color_space = JCS_RGB;
|
2020-05-10 12:56:36 +03:00
|
|
|
}
|
2020-05-01 15:08:57 +03:00
|
|
|
break;
|
|
|
|
case 32:
|
|
|
|
context->cinfo.input_components = 4;
|
|
|
|
context->cinfo.in_color_space = JCS_CMYK;
|
2021-01-03 06:17:51 +03:00
|
|
|
#ifdef JCS_EXTENSIONS
|
2020-05-10 12:56:36 +03:00
|
|
|
if (strcmp(context->rawmode, "RGBX") == 0) {
|
2020-05-01 15:08:57 +03:00
|
|
|
context->cinfo.in_color_space = JCS_EXT_RGBX;
|
2020-05-10 12:56:36 +03:00
|
|
|
}
|
2021-01-03 06:17:51 +03:00
|
|
|
#endif
|
2020-05-01 15:08:57 +03:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
state->errcode = IMAGING_CODEC_CONFIG;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Compressor configuration */
|
|
|
|
jpeg_set_defaults(&context->cinfo);
|
|
|
|
|
|
|
|
/* Use custom quantization tables */
|
|
|
|
if (context->qtables) {
|
|
|
|
int i;
|
|
|
|
int quality = 100;
|
|
|
|
int last_q = 0;
|
|
|
|
if (context->quality != -1) {
|
|
|
|
quality = context->quality;
|
|
|
|
}
|
|
|
|
for (i = 0; i < context->qtablesLen; i++) {
|
2021-01-03 06:17:51 +03:00
|
|
|
jpeg_add_quant_table(
|
|
|
|
&context->cinfo,
|
|
|
|
i,
|
|
|
|
&context->qtables[i * DCTSIZE2],
|
|
|
|
quality,
|
|
|
|
FALSE);
|
2020-05-01 15:08:57 +03:00
|
|
|
context->cinfo.comp_info[i].quant_tbl_no = i;
|
|
|
|
last_q = i;
|
|
|
|
}
|
|
|
|
if (context->qtablesLen == 1) {
|
2021-01-03 06:17:51 +03:00
|
|
|
// jpeg_set_defaults created two qtables internally, but we only
|
|
|
|
// wanted one.
|
|
|
|
jpeg_add_quant_table(
|
|
|
|
&context->cinfo, 1, &context->qtables[0], quality, FALSE);
|
2020-05-01 15:08:57 +03:00
|
|
|
}
|
|
|
|
for (i = last_q; i < context->cinfo.num_components; i++) {
|
|
|
|
context->cinfo.comp_info[i].quant_tbl_no = last_q;
|
|
|
|
}
|
|
|
|
} else if (context->quality != -1) {
|
2020-10-10 17:04:18 +03:00
|
|
|
jpeg_set_quality(&context->cinfo, context->quality, TRUE);
|
2020-05-01 15:08:57 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Set subsampling options */
|
2021-01-03 06:17:51 +03:00
|
|
|
switch (context->subsampling) {
|
|
|
|
case 0: /* 1x1 1x1 1x1 (4:4:4) : None */
|
2020-05-01 15:08:57 +03:00
|
|
|
{
|
|
|
|
context->cinfo.comp_info[0].h_samp_factor = 1;
|
|
|
|
context->cinfo.comp_info[0].v_samp_factor = 1;
|
|
|
|
context->cinfo.comp_info[1].h_samp_factor = 1;
|
|
|
|
context->cinfo.comp_info[1].v_samp_factor = 1;
|
|
|
|
context->cinfo.comp_info[2].h_samp_factor = 1;
|
|
|
|
context->cinfo.comp_info[2].v_samp_factor = 1;
|
|
|
|
break;
|
|
|
|
}
|
2021-01-03 06:17:51 +03:00
|
|
|
case 1: /* 2x1, 1x1, 1x1 (4:2:2) : Medium */
|
2020-05-01 15:08:57 +03:00
|
|
|
{
|
|
|
|
context->cinfo.comp_info[0].h_samp_factor = 2;
|
|
|
|
context->cinfo.comp_info[0].v_samp_factor = 1;
|
|
|
|
context->cinfo.comp_info[1].h_samp_factor = 1;
|
|
|
|
context->cinfo.comp_info[1].v_samp_factor = 1;
|
|
|
|
context->cinfo.comp_info[2].h_samp_factor = 1;
|
|
|
|
context->cinfo.comp_info[2].v_samp_factor = 1;
|
|
|
|
break;
|
|
|
|
}
|
2021-01-03 06:17:51 +03:00
|
|
|
case 2: /* 2x2, 1x1, 1x1 (4:2:0) : High */
|
2020-05-01 15:08:57 +03:00
|
|
|
{
|
|
|
|
context->cinfo.comp_info[0].h_samp_factor = 2;
|
|
|
|
context->cinfo.comp_info[0].v_samp_factor = 2;
|
|
|
|
context->cinfo.comp_info[1].h_samp_factor = 1;
|
|
|
|
context->cinfo.comp_info[1].v_samp_factor = 1;
|
|
|
|
context->cinfo.comp_info[2].h_samp_factor = 1;
|
|
|
|
context->cinfo.comp_info[2].v_samp_factor = 1;
|
|
|
|
break;
|
|
|
|
}
|
2021-01-03 06:17:51 +03:00
|
|
|
default: {
|
2020-05-01 15:08:57 +03:00
|
|
|
/* Use the lib's default */
|
|
|
|
break;
|
|
|
|
}
|
2021-01-03 06:17:51 +03:00
|
|
|
}
|
|
|
|
if (context->progressive) {
|
|
|
|
jpeg_simple_progression(&context->cinfo);
|
|
|
|
}
|
|
|
|
context->cinfo.smoothing_factor = context->smooth;
|
|
|
|
context->cinfo.optimize_coding = (boolean)context->optimize;
|
|
|
|
if (context->xdpi > 0 && context->ydpi > 0) {
|
|
|
|
context->cinfo.write_JFIF_header = TRUE;
|
|
|
|
context->cinfo.density_unit = 1; /* dots per inch */
|
|
|
|
context->cinfo.X_density = context->xdpi;
|
|
|
|
context->cinfo.Y_density = context->ydpi;
|
|
|
|
}
|
2020-05-01 15:08:57 +03:00
|
|
|
switch (context->streamtype) {
|
|
|
|
case 1:
|
|
|
|
/* tables only -- not yet implemented */
|
|
|
|
state->errcode = IMAGING_CODEC_CONFIG;
|
|
|
|
return -1;
|
|
|
|
case 2:
|
|
|
|
/* image only */
|
|
|
|
jpeg_suppress_tables(&context->cinfo, TRUE);
|
|
|
|
jpeg_start_compress(&context->cinfo, FALSE);
|
|
|
|
/* suppress extra section */
|
|
|
|
context->extra_offset = context->extra_size;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
/* interchange stream */
|
|
|
|
jpeg_start_compress(&context->cinfo, TRUE);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
state->state++;
|
|
|
|
/* fall through */
|
|
|
|
|
|
|
|
case 2:
|
|
|
|
// check for exif len + 'APP1' header bytes
|
2021-01-03 06:17:51 +03:00
|
|
|
if (context->rawExifLen + 5 > context->destination.pub.free_in_buffer) {
|
2020-05-01 15:08:57 +03:00
|
|
|
break;
|
|
|
|
}
|
2021-01-03 06:17:51 +03:00
|
|
|
// add exif header
|
|
|
|
if (context->rawExifLen > 0) {
|
|
|
|
jpeg_write_marker(
|
|
|
|
&context->cinfo,
|
|
|
|
JPEG_APP0 + 1,
|
|
|
|
(unsigned char *)context->rawExif,
|
|
|
|
context->rawExifLen);
|
2020-05-01 15:08:57 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
state->state++;
|
|
|
|
/* fall through */
|
|
|
|
case 3:
|
|
|
|
|
|
|
|
if (context->extra) {
|
|
|
|
/* copy extra buffer to output buffer */
|
|
|
|
unsigned int n = context->extra_size - context->extra_offset;
|
2020-05-10 12:56:36 +03:00
|
|
|
if (n > context->destination.pub.free_in_buffer) {
|
2020-05-01 15:08:57 +03:00
|
|
|
n = context->destination.pub.free_in_buffer;
|
2020-05-10 12:56:36 +03:00
|
|
|
}
|
2021-01-03 06:17:51 +03:00
|
|
|
memcpy(
|
|
|
|
context->destination.pub.next_output_byte,
|
|
|
|
context->extra + context->extra_offset,
|
|
|
|
n);
|
2020-05-01 15:08:57 +03:00
|
|
|
context->destination.pub.next_output_byte += n;
|
|
|
|
context->destination.pub.free_in_buffer -= n;
|
|
|
|
context->extra_offset += n;
|
2020-05-10 12:56:36 +03:00
|
|
|
if (context->extra_offset >= context->extra_size) {
|
2020-05-01 15:08:57 +03:00
|
|
|
state->state++;
|
2020-05-10 12:56:36 +03:00
|
|
|
} else {
|
2020-05-01 15:08:57 +03:00
|
|
|
break;
|
2020-05-10 12:56:36 +03:00
|
|
|
}
|
2020-05-11 00:19:52 +03:00
|
|
|
} else {
|
2010-07-31 06:52:47 +04:00
|
|
|
state->state++;
|
2020-05-11 00:19:52 +03:00
|
|
|
}
|
2020-05-01 15:08:57 +03:00
|
|
|
|
|
|
|
case 4:
|
2022-12-05 05:57:26 +03:00
|
|
|
|
2022-12-05 20:46:54 +03:00
|
|
|
if (context->comment) {
|
2022-12-05 05:57:26 +03:00
|
|
|
jpeg_write_marker(&context->cinfo, JPEG_COM, (unsigned char *)context->comment, context->comment_size);
|
|
|
|
}
|
|
|
|
state->state++;
|
|
|
|
|
|
|
|
case 5:
|
2021-01-03 06:17:51 +03:00
|
|
|
if (1024 > context->destination.pub.free_in_buffer) {
|
2020-05-01 15:08:57 +03:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
ok = 1;
|
|
|
|
while (state->y < state->ysize) {
|
2021-01-03 06:17:51 +03:00
|
|
|
state->shuffle(
|
|
|
|
state->buffer,
|
|
|
|
(UINT8 *)im->image[state->y + state->yoff] +
|
|
|
|
state->xoff * im->pixelsize,
|
|
|
|
state->xsize);
|
2020-05-01 15:08:57 +03:00
|
|
|
ok = jpeg_write_scanlines(&context->cinfo, &state->buffer, 1);
|
2020-05-10 12:56:36 +03:00
|
|
|
if (ok != 1) {
|
2020-05-01 15:08:57 +03:00
|
|
|
break;
|
2020-05-10 12:56:36 +03:00
|
|
|
}
|
2020-05-01 15:08:57 +03:00
|
|
|
state->y++;
|
|
|
|
}
|
|
|
|
|
2020-05-10 12:56:36 +03:00
|
|
|
if (ok != 1) {
|
2010-07-31 06:52:47 +04:00
|
|
|
break;
|
2020-05-10 12:56:36 +03:00
|
|
|
}
|
2020-05-01 15:08:57 +03:00
|
|
|
state->state++;
|
|
|
|
/* fall through */
|
2010-07-31 06:52:47 +04:00
|
|
|
|
2022-12-05 05:57:26 +03:00
|
|
|
case 6:
|
2020-05-01 15:08:57 +03:00
|
|
|
|
|
|
|
/* Finish compression */
|
2020-05-10 12:56:36 +03:00
|
|
|
if (context->destination.pub.free_in_buffer < 100) {
|
2020-05-01 15:08:57 +03:00
|
|
|
break;
|
2020-05-10 12:56:36 +03:00
|
|
|
}
|
2020-05-01 15:08:57 +03:00
|
|
|
jpeg_finish_compress(&context->cinfo);
|
|
|
|
|
|
|
|
/* Clean up */
|
2022-12-05 05:57:26 +03:00
|
|
|
if (context->comment) {
|
|
|
|
free(context->comment);
|
|
|
|
context->comment = NULL;
|
|
|
|
}
|
2020-05-01 15:08:57 +03:00
|
|
|
if (context->extra) {
|
|
|
|
free(context->extra);
|
|
|
|
context->extra = NULL;
|
|
|
|
}
|
|
|
|
if (context->rawExif) {
|
|
|
|
free(context->rawExif);
|
|
|
|
context->rawExif = NULL;
|
|
|
|
}
|
|
|
|
if (context->qtables) {
|
|
|
|
free(context->qtables);
|
|
|
|
context->qtables = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
jpeg_destroy_compress(&context->cinfo);
|
|
|
|
/* if (jerr.pub.num_warnings) return BROKEN; */
|
|
|
|
state->errcode = IMAGING_CODEC_END;
|
2013-05-15 09:29:31 +04:00
|
|
|
break;
|
2010-07-31 06:52:47 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Return number of bytes in output buffer */
|
|
|
|
return context->destination.pub.next_output_byte - buf;
|
|
|
|
}
|
|
|
|
|
2021-01-03 06:17:51 +03:00
|
|
|
const char *
|
|
|
|
ImagingJpegVersion(void) {
|
2010-07-31 06:52:47 +04:00
|
|
|
static char version[20];
|
|
|
|
sprintf(version, "%d.%d", JPEG_LIB_VERSION / 10, JPEG_LIB_VERSION % 10);
|
|
|
|
return version;
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|