summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon Bünzli <zeniko@gmail.com>2013-07-21 23:06:57 +0200
committerRobin Watts <robin.watts@artifex.com>2013-08-28 13:35:54 +0100
commit4dc6cbedb4ef789c8c8ebd4c83095d0198050350 (patch)
tree4333bbdebb98b4c1a725f9404a0f3112a94256b2
parent1c431eb5477388dbcf94b24a382efc1fb63b639a (diff)
downloadmupdf-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.c120
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)
{