From a3a5eb33cf1be810eb3821c913bfd2d8b964336a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Eric=20Janouch?= Date: Thu, 8 Jun 2023 09:42:11 +0200 Subject: [PATCH] Unify non/enhanced JPEG loading code And in so doing, add missing warning redirection to JPEG Quant Smooth, as well as downscaling. We still heavily depend on libjpeg-turbo. --- fiv-io.c | 186 ++++++++++++++++++++++++------------------------------- 1 file changed, 82 insertions(+), 104 deletions(-) diff --git a/fiv-io.c b/fiv-io.c index 0540465..8f27b0b 100644 --- a/fiv-io.c +++ b/fiv-io.c @@ -19,20 +19,18 @@ #include #include +#include +#include #include #include +#include #include #include #include #include #include - #ifdef HAVE_JPEG_QS -#include -#include - -#include #include #endif // HAVE_JPEG_QS @@ -1454,95 +1452,13 @@ load_jpeg_finalize(cairo_surface_t *surface, bool cmyk, cairo_surface_mark_dirty(surface); } -static cairo_surface_t * -open_libjpeg_turbo( - const char *data, gsize len, const FivIoOpenContext *ctx, GError **error) -{ - // Note that there doesn't seem to be much of a point in using this - // simplified API anymore, because JPEG-QS needs the original libjpeg API. - // It's just more or less duplicated code which won't compile with - // the slow version of the library. - tjhandle dec = tjInitDecompress(); - if (!dec) { - set_error(error, tjGetErrorStr2(dec)); - return NULL; - } - - int width = 0, height = 0, subsampling = TJSAMP_444, colorspace = TJCS_RGB; - if (tjDecompressHeader3(dec, (const unsigned char *) data, len, - &width, &height, &subsampling, &colorspace)) { - set_error(error, tjGetErrorStr2(dec)); - tjDestroy(dec); - return NULL; - } - - bool use_cmyk = colorspace == TJCS_CMYK || colorspace == TJCS_YCCK; - int pixel_format = use_cmyk - ? TJPF_CMYK - : (G_BYTE_ORDER == G_LITTLE_ENDIAN ? TJPF_BGRX : TJPF_XRGB); - - // The limit of Cairo/pixman is 32767. but JPEG can go as high as 65535. - // Prevent Cairo from throwing an error, and make use of libjpeg's scaling. - // gdk-pixbuf circumvents this check, producing unrenderable surfaces. - const int max = 32767; - - int nfs = 0; - tjscalingfactor *fs = tjGetScalingFactors(&nfs), f = {0, 1}; - if (fs && (width > max || height > max)) { - for (int i = 0; i < nfs; i++) { - if (TJSCALED(width, fs[i]) <= max && - TJSCALED(height, fs[i]) <= max && - fs[i].num * f.denom > f.num * fs[i].denom) - f = fs[i]; - } - - add_warning(ctx, - "the image is too large, and had to be scaled by %d/%d", - f.num, f.denom); - width = TJSCALED(width, f); - height = TJSCALED(height, f); - } - - cairo_surface_t *surface = - cairo_image_surface_create(CAIRO_FORMAT_RGB24, width, height); - cairo_status_t surface_status = cairo_surface_status(surface); - if (surface_status != CAIRO_STATUS_SUCCESS) { - set_error(error, cairo_status_to_string(surface_status)); - cairo_surface_destroy(surface); - tjDestroy(dec); - return NULL; - } - - // Starting to modify pixel data directly. Probably an unnecessary call. - cairo_surface_flush(surface); - - int stride = cairo_image_surface_get_stride(surface); - if (tjDecompress2(dec, (const unsigned char *) data, len, - cairo_image_surface_get_data(surface), width, stride, height, - pixel_format, TJFLAG_ACCURATEDCT)) { - if (tjGetErrorCode(dec) == TJERR_WARNING) { - add_warning(ctx, "%s", tjGetErrorStr2(dec)); - } else { - set_error(error, tjGetErrorStr2(dec)); - cairo_surface_destroy(surface); - tjDestroy(dec); - return NULL; - } - } - - load_jpeg_finalize(surface, use_cmyk, ctx, data, len); - tjDestroy(dec); - return surface; -} - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -#ifdef HAVE_JPEG_QS - struct libjpeg_error_mgr { struct jpeg_error_mgr pub; jmp_buf buf; GError **error; + const FivIoOpenContext *ctx; }; static void @@ -1555,15 +1471,25 @@ libjpeg_error_exit(j_common_ptr cinfo) longjmp(err->buf, 1); } +static void +libjpeg_output_message(j_common_ptr cinfo) +{ + struct libjpeg_error_mgr *err = (struct libjpeg_error_mgr *) cinfo->err; + char buf[JMSG_LENGTH_MAX] = ""; + (*cinfo->err->format_message)(cinfo, buf); + add_warning(err->ctx, "%s", buf); +} + static cairo_surface_t * -open_libjpeg_enhanced( - const char *data, gsize len, const FivIoOpenContext *ctx, GError **error) +load_libjpeg_turbo(const char *data, gsize len, const FivIoOpenContext *ctx, + void (*loop)(struct jpeg_decompress_struct *, JSAMPARRAY), GError **error) { cairo_surface_t *volatile surface = NULL; - struct libjpeg_error_mgr jerr = {.error = error}; + struct libjpeg_error_mgr jerr = {.error = error, .ctx = ctx}; struct jpeg_decompress_struct cinfo = {.err = jpeg_std_error(&jerr.pub)}; jerr.pub.error_exit = libjpeg_error_exit; + jerr.pub.output_message = libjpeg_output_message; if (setjmp(jerr.buf)) { g_clear_pointer(&surface, cairo_surface_destroy); jpeg_destroy_decompress(&cinfo); @@ -1587,6 +1513,30 @@ open_libjpeg_enhanced( int width = cinfo.output_width; int height = cinfo.output_height; + // The limit of Cairo/pixman is 32767. but JPEG can go as high as 65535. + // Prevent Cairo from throwing an error, and make use of libjpeg's scaling. + // gdk-pixbuf circumvents this check, producing unrenderable surfaces. + const int max = 32767; + + int nfs = 0; + tjscalingfactor *fs = tjGetScalingFactors(&nfs), f = {0, 1}; + if (fs && (width > max || height > max)) { + for (int i = 0; i < nfs; i++) { + if (TJSCALED(width, fs[i]) <= max && + TJSCALED(height, fs[i]) <= max && + fs[i].num * f.denom > f.num * fs[i].denom) + f = fs[i]; + } + + add_warning(ctx, + "the image is too large, and had to be scaled by %d/%d", + f.num, f.denom); + width = TJSCALED(width, f); + height = TJSCALED(height, f); + cinfo.scale_num = f.num; + cinfo.scale_denom = f.denom; + } + surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24, width, height); cairo_status_t surface_status = cairo_surface_status(surface); if (surface_status != CAIRO_STATUS_SUCCESS) { @@ -1601,6 +1551,31 @@ open_libjpeg_enhanced( for (int i = 0; i < height; i++) lines[i] = surface_data + i * surface_stride; + // Slightly unfortunate generalization. + loop(&cinfo, lines); + + load_jpeg_finalize(surface, use_cmyk, ctx, data, len); + jpeg_destroy_decompress(&cinfo); + return surface; +} + +static void +load_libjpeg_simple( + struct jpeg_decompress_struct *cinfo, JSAMPARRAY lines) +{ + (void) jpeg_start_decompress(cinfo); + while (cinfo->output_scanline < cinfo->output_height) + (void) jpeg_read_scanlines(cinfo, lines + cinfo->output_scanline, + cinfo->output_height - cinfo->output_scanline); + (void) jpeg_finish_decompress(cinfo); +} + +#ifdef HAVE_JPEG_QS + +static void +load_libjpeg_enhanced( + struct jpeg_decompress_struct *cinfo, JSAMPARRAY lines) +{ // Go for the maximum quality setting. jpegqs_control_t opts = { .flags = JPEGQS_DIAGONALS | JPEGQS_JOINT_YUV | JPEGQS_UPSAMPLE_UV, @@ -1608,21 +1583,26 @@ open_libjpeg_enhanced( .niter = 3, }; - (void) jpegqs_start_decompress(&cinfo, &opts); - while (cinfo.output_scanline < cinfo.output_height) - (void) jpeg_read_scanlines(&cinfo, lines + cinfo.output_scanline, - cinfo.output_height - cinfo.output_scanline); - (void) jpegqs_finish_decompress(&cinfo); - - load_jpeg_finalize(surface, use_cmyk, ctx, data, len); - jpeg_destroy_decompress(&cinfo); - return surface; + (void) jpegqs_start_decompress(cinfo, &opts); + while (cinfo->output_scanline < cinfo->output_height) + (void) jpeg_read_scanlines(cinfo, lines + cinfo->output_scanline, + cinfo->output_height - cinfo->output_scanline); + (void) jpegqs_finish_decompress(cinfo); } #else -#define open_libjpeg_enhanced open_libjpeg_turbo +#define load_libjpeg_enhanced libjpeg_turbo_load_simple #endif +static cairo_surface_t * +open_libjpeg_turbo( + const char *data, gsize len, const FivIoOpenContext *ctx, GError **error) +{ + return load_libjpeg_turbo(data, len, ctx, + ctx->enhance ? load_libjpeg_enhanced : load_libjpeg_simple, + error); +} + // --- WebP -------------------------------------------------------------------- static const char * @@ -3248,9 +3228,7 @@ fiv_io_open_from_data( ctx, error); break; case WUFFS_BASE__FOURCC__JPEG: - surface = ctx->enhance - ? open_libjpeg_enhanced(data, len, ctx, error) - : open_libjpeg_turbo(data, len, ctx, error); + surface = open_libjpeg_turbo(data, len, ctx, error); break; case WUFFS_BASE__FOURCC__WEBP: surface = open_libwebp(data, len, ctx, error);