From 4dc6cbedb4ef789c8c8ebd4c83095d0198050350 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20B=C3=BCnzli?= Date: Sun, 21 Jul 2013 23:06:57 +0200 Subject: better JPEG image resolution detection (for XPS) XPS extracts the resolution of a JPEG image from the image data. The current code however only reads the density values provided by JFIF metadata, while the XPS specification also allows for resolution to be in either EXIF metadata or Photoshop's APP13 chunk. This patch adds code for reading both kinds of metadata in order to get more consistent behavior with Microsoft's XPS Viewer. Documents for reproducing this issue: 2093*.xps, 2249*.xps, 2252*.xps, 2268*.xps and "jpeg exif resolution.xps". Another detail: The default resolution for JPEG images in XPS documents is 96 DPI and not 72 DPI. --- source/fitz/load-jpeg.c | 120 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 117 insertions(+), 3 deletions(-) (limited to 'source/fitz/load-jpeg.c') diff --git a/source/fitz/load-jpeg.c b/source/fitz/load-jpeg.c index b968c0cf..d3701a1e 100644 --- a/source/fitz/load-jpeg.c +++ b/source/fitz/load-jpeg.c @@ -43,6 +43,113 @@ static void skip_input_data(j_decompress_ptr cinfo, long num_bytes) } } +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; +} + +static int extract_exif_resolution(jpeg_saved_marker_ptr marker, int *xres, int *yres) +{ + int is_big_endian; + 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 + 2 > marker->data_length) + 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 0x11A: + if (type == 5 && value_off > offset && value_off + 8 <= marker->data_length) + 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 + 8 <= marker->data_length) + 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 || x_res > INT_MAX || y_res <= 0 || y_res > INT_MAX) + 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); + } + 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 */ && data + value_off <= data_end) + data_size = read_value(data + value_off - 4, 4, 1); + if (data_size < 0 || data + value_off + data_size > data_end) + 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; +} + void fz_load_jpeg_info(fz_context *ctx, unsigned char *rbuf, int rlen, int *xp, int *yp, int *xresp, int *yresp, fz_colorspace **cspacep) { @@ -67,6 +174,9 @@ fz_load_jpeg_info(fz_context *ctx, unsigned char *rbuf, int rlen, int *xp, int * 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); if (cinfo.num_components == 1) @@ -81,7 +191,11 @@ fz_load_jpeg_info(fz_context *ctx, unsigned char *rbuf, int rlen, int *xp, int * *xp = cinfo.image_width; *yp = cinfo.image_height; - if (cinfo.density_unit == 1) + if (extract_exif_resolution(cinfo.marker_list, xresp, yresp)) + /* 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; @@ -97,8 +211,8 @@ fz_load_jpeg_info(fz_context *ctx, unsigned char *rbuf, int rlen, int *xp, int * *yresp = 0; } - if (*xresp <= 0) *xresp = 72; - if (*yresp <= 0) *yresp = 72; + if (*xresp <= 0) *xresp = 96; + if (*yresp <= 0) *yresp = 96; } fz_always(ctx) { -- cgit v1.2.3