// Copyright (C) 2004-2023 Artifex Software, Inc. // // This file is part of MuPDF. // // MuPDF is free software: you can redistribute it and/or modify it under the // terms of the GNU Affero General Public License as published by the Free // Software Foundation, either version 3 of the License, or (at your option) // any later version. // // MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more // details. // // You should have received a copy of the GNU Affero General Public License // along with MuPDF. If not, see // // Alternative licensing terms are available from the licensor. // For commercial licensing, see or contact // Artifex Software, Inc., 1305 Grant Avenue - Suite 200, Novato, // CA 94945, U.S.A., +1(415)492-9861, for further information. #include "mupdf/fitz.h" #include #include #include #include #include #ifdef SHARE_JPEG #define JZ_CTX_FROM_CINFO(c) (fz_context *)((c)->client_data) static void fz_jpg_mem_init(j_common_ptr cinfo, fz_context *ctx) { cinfo->client_data = ctx; } #define fz_jpg_mem_term(cinfo) #else /* SHARE_JPEG */ typedef void * backing_store_ptr; #include "jmemcust.h" #define JZ_CTX_FROM_CINFO(c) (fz_context *)(GET_CUST_MEM_DATA(c)->priv) static void * fz_jpg_mem_alloc(j_common_ptr cinfo, size_t size) { fz_context *ctx = JZ_CTX_FROM_CINFO(cinfo); return fz_malloc_no_throw(ctx, size); } static void fz_jpg_mem_free(j_common_ptr cinfo, void *object, size_t size) { fz_context *ctx = JZ_CTX_FROM_CINFO(cinfo); fz_free(ctx, object); } static void fz_jpg_mem_init(j_common_ptr cinfo, fz_context *ctx) { jpeg_cust_mem_data *custmptr; custmptr = fz_malloc_struct(ctx, jpeg_cust_mem_data); if (!jpeg_cust_mem_init(custmptr, (void *) ctx, NULL, NULL, NULL, fz_jpg_mem_alloc, fz_jpg_mem_free, fz_jpg_mem_alloc, fz_jpg_mem_free, NULL)) { fz_free(ctx, custmptr); fz_throw(ctx, FZ_ERROR_GENERIC, "cannot initialize custom JPEG memory handler"); } cinfo->client_data = custmptr; } static void fz_jpg_mem_term(j_common_ptr cinfo) { if (cinfo->client_data) { fz_context *ctx = JZ_CTX_FROM_CINFO(cinfo); fz_free(ctx, cinfo->client_data); cinfo->client_data = NULL; } } #endif /* SHARE_JPEG */ static void output_message(j_common_ptr cinfo) { /* swallow message */ } static void error_exit(j_common_ptr cinfo) { char msg[JMSG_LENGTH_MAX]; fz_context *ctx = JZ_CTX_FROM_CINFO(cinfo); cinfo->err->format_message(cinfo, msg); fz_throw(ctx, FZ_ERROR_GENERIC, "jpeg error: %s", msg); } static void init_source(j_decompress_ptr cinfo) { /* nothing to do */ } static void term_source(j_decompress_ptr cinfo) { /* nothing to do */ } static boolean fill_input_buffer(j_decompress_ptr cinfo) { static unsigned char eoi[2] = { 0xFF, JPEG_EOI }; struct jpeg_source_mgr *src = cinfo->src; src->next_input_byte = eoi; src->bytes_in_buffer = 2; return 1; } static void skip_input_data(j_decompress_ptr cinfo, long num_bytes) { struct jpeg_source_mgr *src = cinfo->src; if (num_bytes > 0) { size_t skip = (size_t)num_bytes; /* size_t may be 64bit */ if (skip > src->bytes_in_buffer) skip = (size_t)src->bytes_in_buffer; src->next_input_byte += skip; src->bytes_in_buffer -= skip; } } static inline int read_value(const unsigned char *data, int bytes, int is_big_endian) { int value = 0; if (!is_big_endian) data += bytes; for (; bytes > 0; bytes--) value = (value << 8) | (is_big_endian ? *data++ : *--data); return value; } enum { MAX_ICC_PARTS = 256 }; static fz_colorspace *extract_icc_profile(fz_context *ctx, jpeg_saved_marker_ptr init_marker, int output_components, fz_colorspace *colorspace) { #if FZ_ENABLE_ICC const char idseq[] = { 'I', 'C', 'C', '_', 'P', 'R', 'O', 'F', 'I', 'L', 'E', '\0'}; jpeg_saved_marker_ptr marker = init_marker; fz_buffer *buf = NULL; fz_colorspace *icc; int part = 1; int parts = MAX_ICC_PARTS; const unsigned char *data; size_t size; fz_var(buf); if (init_marker == NULL) return colorspace; fz_try(ctx) { while (part < parts && marker != NULL) { for (marker = init_marker; marker != NULL; marker = marker->next) { if (marker->marker != JPEG_APP0 + 2) continue; if (marker->data_length < nelem(idseq) + 2) continue; if (memcmp(marker->data, idseq, nelem(idseq))) continue; if (marker->data[nelem(idseq)] != part) continue; if (parts == MAX_ICC_PARTS) parts = marker->data[nelem(idseq) + 1]; else if (marker->data[nelem(idseq) + 1] != parts) fz_warn(ctx, "inconsistent number of icc profile chunks in jpeg"); if (part > parts) { fz_warn(ctx, "skipping out of range icc profile chunk in jpeg"); continue; } data = marker->data + 14; size = marker->data_length - 14; if (!buf) buf = fz_new_buffer_from_copied_data(ctx, data, size); else fz_append_data(ctx, buf, data, size); part++; break; } } if (buf) { icc = fz_new_icc_colorspace(ctx, fz_colorspace_type(ctx, colorspace), 0, NULL, buf); fz_drop_colorspace(ctx, colorspace); colorspace = icc; } } fz_always(ctx) fz_drop_buffer(ctx, buf); fz_catch(ctx) fz_warn(ctx, "ignoring embedded ICC profile in JPEG"); return colorspace; #else return colorspace; #endif } /* Returns true if can be represented as an integer without overflow. * * We can't use comparisons such as 'return x < INT_MAX' because INT_MAX is * not safely convertible to float - it ends up as INT_MAX+1 so the comparison * doesn't do what we want. * * Instead we do a round-trip conversion and return true if this differs by * less than 1. This relies on high adjacent float values that differ by more * than 1, actually being exact integers, so the round-trip doesn't change the * value. */ static int float_can_be_int(float x) { return fabsf(x - (float)(int) x) < 1; } static uint8_t exif_orientation_to_mupdf[9] = { 0, 1, 5, 3, 7, 6, 4, 8, 2 }; static int extract_exif_resolution(jpeg_saved_marker_ptr marker, int *xres, int *yres, uint8_t *orientation) { int is_big_endian, orient; const unsigned char *data; unsigned int offset, ifd_len, res_type = 0; float x_res = 0, y_res = 0; if (!marker || marker->marker != JPEG_APP0 + 1 || marker->data_length < 14) return 0; data = (const unsigned char *)marker->data; if (read_value(data, 4, 1) != 0x45786966 /* Exif */ || read_value(data + 4, 2, 1) != 0x0000) return 0; if (read_value(data + 6, 4, 1) == 0x49492A00) is_big_endian = 0; else if (read_value(data + 6, 4, 1) == 0x4D4D002A) is_big_endian = 1; else return 0; offset = read_value(data + 10, 4, is_big_endian) + 6; if (offset < 14 || offset > marker->data_length - 2) return 0; ifd_len = read_value(data + offset, 2, is_big_endian); for (offset += 2; ifd_len > 0 && offset + 12 < marker->data_length; ifd_len--, offset += 12) { int tag = read_value(data + offset, 2, is_big_endian); int type = read_value(data + offset + 2, 2, is_big_endian); int count = read_value(data + offset + 4, 4, is_big_endian); unsigned int value_off = read_value(data + offset + 8, 4, is_big_endian) + 6; switch (tag) { case 0x112: if (type == 3 && count == 1) { orient = read_value(data + offset + 8, 2, is_big_endian); if (orient >= 1 && orient <= 8 && orientation) *orientation = exif_orientation_to_mupdf[orient]; } break; case 0x11A: if (type == 5 && value_off > offset && value_off <= marker->data_length - 8) x_res = 1.0f * read_value(data + value_off, 4, is_big_endian) / read_value(data + value_off + 4, 4, is_big_endian); break; case 0x11B: if (type == 5 && value_off > offset && value_off <= marker->data_length - 8) y_res = 1.0f * read_value(data + value_off, 4, is_big_endian) / read_value(data + value_off + 4, 4, is_big_endian); break; case 0x128: if (type == 3 && count == 1) res_type = read_value(data + offset + 8, 2, is_big_endian); break; } } if (x_res <= 0 || !float_can_be_int(x_res) || y_res <= 0 || !float_can_be_int(y_res)) return 0; if (res_type == 2) { *xres = (int)x_res; *yres = (int)y_res; } else if (res_type == 3) { *xres = (int)(x_res * 254 / 100); *yres = (int)(y_res * 254 / 100); } else { *xres = 0; *yres = 0; } return 1; } static int extract_app13_resolution(jpeg_saved_marker_ptr marker, int *xres, int *yres) { const unsigned char *data, *data_end; if (!marker || marker->marker != JPEG_APP0 + 13 || marker->data_length < 42 || strcmp((const char *)marker->data, "Photoshop 3.0") != 0) { return 0; } data = (const unsigned char *)marker->data; data_end = data + marker->data_length; for (data += 14; data + 12 < data_end; ) { int data_size = -1; int tag = read_value(data + 4, 2, 1); int value_off = 11 + read_value(data + 6, 2, 1); if (value_off % 2 == 1) value_off++; if (read_value(data, 4, 1) == 0x3842494D /* 8BIM */ && value_off <= data_end - data) data_size = read_value(data + value_off - 4, 4, 1); if (data_size < 0 || data_size > data_end - data - value_off) return 0; if (tag == 0x3ED && data_size == 16) { *xres = read_value(data + value_off, 2, 1); *yres = read_value(data + value_off + 8, 2, 1); return 1; } if (data_size % 2 == 1) data_size++; data += value_off + data_size; } return 0; } static void invert_cmyk(unsigned char *p, int n) { int i; for (i = 0; i < n; ++i) p[i] = 255 - p[i]; } fz_pixmap * fz_load_jpeg(fz_context *ctx, const unsigned char *rbuf, size_t rlen) { struct jpeg_decompress_struct cinfo; struct jpeg_error_mgr err; struct jpeg_source_mgr src; unsigned char *row[1], *sp, *dp; fz_colorspace *colorspace = NULL; unsigned int x; int k; size_t stride; fz_pixmap *image = NULL; fz_var(colorspace); fz_var(image); fz_var(row); row[0] = NULL; cinfo.mem = NULL; cinfo.global_state = 0; cinfo.err = jpeg_std_error(&err); err.output_message = output_message; err.error_exit = error_exit; cinfo.client_data = NULL; fz_jpg_mem_init((j_common_ptr)&cinfo, ctx); fz_try(ctx) { jpeg_create_decompress(&cinfo); cinfo.src = &src; src.init_source = init_source; src.fill_input_buffer = fill_input_buffer; src.skip_input_data = skip_input_data; src.resync_to_restart = jpeg_resync_to_restart; src.term_source = term_source; src.next_input_byte = rbuf; src.bytes_in_buffer = rlen; jpeg_save_markers(&cinfo, JPEG_APP0+1, 0xffff); jpeg_save_markers(&cinfo, JPEG_APP0+13, 0xffff); jpeg_read_header(&cinfo, 1); jpeg_start_decompress(&cinfo); if (cinfo.output_components == 1) colorspace = fz_keep_colorspace(ctx, fz_device_gray(ctx)); else if (cinfo.output_components == 3) colorspace = fz_keep_colorspace(ctx, fz_device_rgb(ctx)); else if (cinfo.output_components == 4) colorspace = fz_keep_colorspace(ctx, fz_device_cmyk(ctx)); colorspace = extract_icc_profile(ctx, cinfo.marker_list, cinfo.output_components, colorspace); if (!colorspace) fz_throw(ctx, FZ_ERROR_GENERIC, "cannot determine colorspace"); image = fz_new_pixmap(ctx, colorspace, cinfo.output_width, cinfo.output_height, NULL, 0); if (extract_exif_resolution(cinfo.marker_list, &image->xres, &image->yres, NULL)) /* XPS prefers EXIF resolution to JFIF density */; else if (extract_app13_resolution(cinfo.marker_list, &image->xres, &image->yres)) /* XPS prefers APP13 resolution to JFIF density */; else if (cinfo.density_unit == 1) { image->xres = cinfo.X_density; image->yres = cinfo.Y_density; } else if (cinfo.density_unit == 2) { image->xres = cinfo.X_density * 254 / 100; image->yres = cinfo.Y_density * 254 / 100; } if (image->xres <= 0) image->xres = 96; if (image->yres <= 0) image->yres = 96; fz_clear_pixmap(ctx, image); row[0] = fz_malloc(ctx, (size_t)cinfo.output_components * cinfo.output_width); dp = image->samples; stride = image->stride - image->w * (size_t)image->n; while (cinfo.output_scanline < cinfo.output_height) { jpeg_read_scanlines(&cinfo, row, 1); // Invert CMYK polarity for some CMYK images (see comment in filter-dct for details). if (cinfo.out_color_space == JCS_CMYK && cinfo.Adobe_transform == 2) invert_cmyk(row[0], image->stride); sp = row[0]; for (x = 0; x < cinfo.output_width; x++) { for (k = 0; k < cinfo.output_components; k++) *dp++ = *sp++; } dp += stride; } } fz_always(ctx) { fz_drop_colorspace(ctx, colorspace); fz_free(ctx, row[0]); row[0] = NULL; /* We call jpeg_abort rather than the more usual * jpeg_finish_decompress here. This has the same effect, * but doesn't spew warnings if we didn't read enough data etc. * Annoyingly jpeg_abort can throw */ fz_try(ctx) jpeg_abort((j_common_ptr)&cinfo); fz_catch(ctx) { /* Ignore any errors here */ } jpeg_destroy_decompress(&cinfo); fz_jpg_mem_term((j_common_ptr)&cinfo); } fz_catch(ctx) { fz_drop_pixmap(ctx, image); fz_rethrow(ctx); } return image; } void fz_load_jpeg_info(fz_context *ctx, const unsigned char *rbuf, size_t rlen, int *xp, int *yp, int *xresp, int *yresp, fz_colorspace **cspacep, uint8_t *orientation) { struct jpeg_decompress_struct cinfo; struct jpeg_error_mgr err; struct jpeg_source_mgr src; fz_colorspace *icc = NULL; *cspacep = NULL; if (orientation) *orientation = 0; cinfo.mem = NULL; cinfo.global_state = 0; cinfo.err = jpeg_std_error(&err); err.error_exit = error_exit; cinfo.client_data = NULL; fz_jpg_mem_init((j_common_ptr)&cinfo, ctx); fz_try(ctx) { jpeg_create_decompress(&cinfo); cinfo.src = &src; src.init_source = init_source; src.fill_input_buffer = fill_input_buffer; src.skip_input_data = skip_input_data; src.resync_to_restart = jpeg_resync_to_restart; src.term_source = term_source; src.next_input_byte = rbuf; src.bytes_in_buffer = rlen; jpeg_save_markers(&cinfo, JPEG_APP0+1, 0xffff); jpeg_save_markers(&cinfo, JPEG_APP0+13, 0xffff); jpeg_save_markers(&cinfo, JPEG_APP0+2, 0xffff); jpeg_read_header(&cinfo, 1); *xp = cinfo.image_width; *yp = cinfo.image_height; if (cinfo.num_components == 1) *cspacep = fz_keep_colorspace(ctx, fz_device_gray(ctx)); else if (cinfo.num_components == 3) *cspacep = fz_keep_colorspace(ctx, fz_device_rgb(ctx)); else if (cinfo.num_components == 4) *cspacep = fz_keep_colorspace(ctx, fz_device_cmyk(ctx)); *cspacep = extract_icc_profile(ctx, cinfo.marker_list, cinfo.num_components, *cspacep); if (!*cspacep) fz_throw(ctx, FZ_ERROR_GENERIC, "cannot determine colorspace"); if (extract_exif_resolution(cinfo.marker_list, xresp, yresp, orientation)) /* XPS prefers EXIF resolution to JFIF density */; else if (extract_app13_resolution(cinfo.marker_list, xresp, yresp)) /* XPS prefers APP13 resolution to JFIF density */; else if (cinfo.density_unit == 1) { *xresp = cinfo.X_density; *yresp = cinfo.Y_density; } else if (cinfo.density_unit == 2) { *xresp = cinfo.X_density * 254 / 100; *yresp = cinfo.Y_density * 254 / 100; } else { *xresp = 0; *yresp = 0; } if (*xresp <= 0) *xresp = 96; if (*yresp <= 0) *yresp = 96; } fz_always(ctx) { jpeg_destroy_decompress(&cinfo); fz_jpg_mem_term((j_common_ptr)&cinfo); } fz_catch(ctx) { fz_drop_colorspace(ctx, icc); fz_rethrow(ctx); } }