diff options
author | Simon Bünzli <zeniko@gmail.com> | 2013-07-21 23:06:57 +0200 |
---|---|---|
committer | Robin Watts <robin.watts@artifex.com> | 2013-08-28 13:35:54 +0100 |
commit | 4dc6cbedb4ef789c8c8ebd4c83095d0198050350 (patch) | |
tree | 4333bbdebb98b4c1a725f9404a0f3112a94256b2 | |
parent | 1c431eb5477388dbcf94b24a382efc1fb63b639a (diff) | |
download | mupdf-4dc6cbedb4ef789c8c8ebd4c83095d0198050350.tar.xz |
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.
-rw-r--r-- | source/fitz/load-jpeg.c | 120 |
1 files changed, 117 insertions, 3 deletions
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) { |