mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-11-16 06:36:49 +03:00
4d7372bfd0
We already support streamtype=2 to skip producing JPEG tables, but streamtype=1, which skips everything but the tables, was never implemented. The streamtype=1 stub code dates to Git pre-history, so it's not immediately clear why. Implement the missing support. jpeg_write_tables() can't resume after a full output buffer (it fails with JERR_CANT_SUSPEND), so it might seem that Pillow needs to pre-compute the necessary buffer size. However, in the normal case of producing an interchange stream, the tables are written via the same libjpeg codepath during the first jpeg_write_scanlines() call, and table writes aren't resumable there either. Thus, any buffer large enough for the normal case will also be large enough for a tables-only file. The streamtype option isn't documented and this commit doesn't change that. It does add a test though. Co-authored-by: Andrew Murray <radarhere@users.noreply.github.com>
356 lines
12 KiB
C
356 lines
12 KiB
C
/*
|
|
* 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"
|
|
|
|
#ifdef HAVE_LIBJPEG
|
|
|
|
#undef HAVE_PROTOTYPES
|
|
#undef HAVE_STDLIB_H
|
|
#undef HAVE_STDDEF_H
|
|
#undef UINT8
|
|
#undef UINT16
|
|
#undef UINT32
|
|
#undef INT16
|
|
#undef INT32
|
|
|
|
#include "Jpeg.h"
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Suspending output handler */
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
METHODDEF(void)
|
|
stub(j_compress_ptr cinfo) { /* empty */ }
|
|
|
|
METHODDEF(boolean)
|
|
empty_output_buffer(j_compress_ptr cinfo) {
|
|
/* Suspension */
|
|
return FALSE;
|
|
}
|
|
|
|
GLOBAL(void)
|
|
jpeg_buffer_dest(j_compress_ptr cinfo, JPEGDESTINATION *destination) {
|
|
cinfo->dest = (void *)destination;
|
|
|
|
destination->pub.init_destination = stub;
|
|
destination->pub.empty_output_buffer = empty_output_buffer;
|
|
destination->pub.term_destination = stub;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Error handler */
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
METHODDEF(void)
|
|
error(j_common_ptr cinfo) {
|
|
JPEGERROR *error;
|
|
error = (JPEGERROR *)cinfo->err;
|
|
(*cinfo->err->output_message)(cinfo);
|
|
longjmp(error->setjmp_buffer, 1);
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Encoder */
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
int
|
|
ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
|
|
JPEGENCODERSTATE *context = (JPEGENCODERSTATE *)state->context;
|
|
int ok;
|
|
|
|
if (setjmp(context->error.setjmp_buffer)) {
|
|
/* JPEG error handler */
|
|
jpeg_destroy_compress(&context->cinfo);
|
|
state->errcode = IMAGING_CODEC_BROKEN;
|
|
return -1;
|
|
}
|
|
|
|
if (!state->state) {
|
|
/* 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);
|
|
|
|
context->extra_offset = 0;
|
|
|
|
/* Ready to encode */
|
|
state->state = 1;
|
|
}
|
|
|
|
/* Load the destination buffer */
|
|
context->destination.pub.next_output_byte = buf;
|
|
context->destination.pub.free_in_buffer = bytes;
|
|
|
|
switch (state->state) {
|
|
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;
|
|
if (strcmp(im->mode, "YCbCr") == 0) {
|
|
context->cinfo.in_color_space = JCS_YCbCr;
|
|
} else {
|
|
context->cinfo.in_color_space = JCS_RGB;
|
|
}
|
|
break;
|
|
case 32:
|
|
context->cinfo.input_components = 4;
|
|
context->cinfo.in_color_space = JCS_CMYK;
|
|
#ifdef JCS_EXTENSIONS
|
|
if (strcmp(context->rawmode, "RGBX") == 0) {
|
|
context->cinfo.in_color_space = JCS_EXT_RGBX;
|
|
}
|
|
#endif
|
|
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++) {
|
|
jpeg_add_quant_table(
|
|
&context->cinfo,
|
|
i,
|
|
&context->qtables[i * DCTSIZE2],
|
|
quality,
|
|
FALSE);
|
|
context->cinfo.comp_info[i].quant_tbl_no = i;
|
|
last_q = i;
|
|
}
|
|
if (context->qtablesLen == 1) {
|
|
// jpeg_set_defaults created two qtables internally, but we only
|
|
// wanted one.
|
|
jpeg_add_quant_table(
|
|
&context->cinfo, 1, &context->qtables[0], quality, FALSE);
|
|
}
|
|
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) {
|
|
jpeg_set_quality(&context->cinfo, context->quality, TRUE);
|
|
}
|
|
|
|
/* Set subsampling options */
|
|
switch (context->subsampling) {
|
|
case 0: /* 1x1 1x1 1x1 (4:4:4) : None */
|
|
{
|
|
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;
|
|
}
|
|
case 1: /* 2x1, 1x1, 1x1 (4:2:2) : Medium */
|
|
{
|
|
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;
|
|
}
|
|
case 2: /* 2x2, 1x1, 1x1 (4:2:0) : High */
|
|
{
|
|
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;
|
|
}
|
|
default: {
|
|
/* Use the lib's default */
|
|
break;
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
switch (context->streamtype) {
|
|
case 1:
|
|
/* tables only */
|
|
jpeg_write_tables(&context->cinfo);
|
|
goto cleanup;
|
|
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
|
|
if (context->rawExifLen + 5 > context->destination.pub.free_in_buffer) {
|
|
break;
|
|
}
|
|
// add exif header
|
|
if (context->rawExifLen > 0) {
|
|
jpeg_write_marker(
|
|
&context->cinfo,
|
|
JPEG_APP0 + 1,
|
|
(unsigned char *)context->rawExif,
|
|
context->rawExifLen);
|
|
}
|
|
|
|
state->state++;
|
|
/* fall through */
|
|
case 3:
|
|
|
|
if (context->extra) {
|
|
/* copy extra buffer to output buffer */
|
|
unsigned int n = context->extra_size - context->extra_offset;
|
|
if (n > context->destination.pub.free_in_buffer) {
|
|
n = context->destination.pub.free_in_buffer;
|
|
}
|
|
memcpy(
|
|
context->destination.pub.next_output_byte,
|
|
context->extra + context->extra_offset,
|
|
n);
|
|
context->destination.pub.next_output_byte += n;
|
|
context->destination.pub.free_in_buffer -= n;
|
|
context->extra_offset += n;
|
|
if (context->extra_offset >= context->extra_size) {
|
|
state->state++;
|
|
} else {
|
|
break;
|
|
}
|
|
} else {
|
|
state->state++;
|
|
}
|
|
|
|
case 4:
|
|
|
|
if (context->comment) {
|
|
jpeg_write_marker(&context->cinfo, JPEG_COM, (unsigned char *)context->comment, context->comment_size);
|
|
}
|
|
state->state++;
|
|
|
|
case 5:
|
|
if (1024 > context->destination.pub.free_in_buffer) {
|
|
break;
|
|
}
|
|
|
|
ok = 1;
|
|
while (state->y < state->ysize) {
|
|
state->shuffle(
|
|
state->buffer,
|
|
(UINT8 *)im->image[state->y + state->yoff] +
|
|
state->xoff * im->pixelsize,
|
|
state->xsize);
|
|
ok = jpeg_write_scanlines(&context->cinfo, &state->buffer, 1);
|
|
if (ok != 1) {
|
|
break;
|
|
}
|
|
state->y++;
|
|
}
|
|
|
|
if (ok != 1) {
|
|
break;
|
|
}
|
|
state->state++;
|
|
/* fall through */
|
|
|
|
case 6:
|
|
|
|
/* Finish compression */
|
|
if (context->destination.pub.free_in_buffer < 100) {
|
|
break;
|
|
}
|
|
jpeg_finish_compress(&context->cinfo);
|
|
|
|
cleanup:
|
|
/* Clean up */
|
|
if (context->comment) {
|
|
free(context->comment);
|
|
context->comment = NULL;
|
|
}
|
|
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;
|
|
break;
|
|
}
|
|
|
|
/* Return number of bytes in output buffer */
|
|
return context->destination.pub.next_output_byte - buf;
|
|
}
|
|
|
|
const char *
|
|
ImagingJpegVersion(void) {
|
|
static char version[20];
|
|
sprintf(version, "%d.%d", JPEG_LIB_VERSION / 10, JPEG_LIB_VERSION % 10);
|
|
return version;
|
|
}
|
|
|
|
#endif
|