Bug 703753: Add detection of Orientation from Exif images.
authorRobin Watts <[email protected]>
Thu, 1 Apr 2021 12:04:07 +0000 (13:04 +0100)
committerFred Ross-Perry <[email protected]>
Fri, 2 Apr 2021 15:47:58 +0000 (08:47 -0700)
Add the concept of 'orientation' to fz_images. This is set to
the orientation defined in an image; currently only Exif format
images set this. This value is ignored by all internal rendering
functions.

The image document handler checks the orientation, and flips/rotates
the image as necessary when feeding it to the display functions.

include/mupdf/fitz/image.h
source/cbz/muimg.c
source/fitz/image-imp.h
source/fitz/image.c
source/fitz/load-jpeg.c

index 547e3ad4046ab85a6bfe6c83db36070dff6fee6c..822eab7787817a1eac596de79b9f698ac49ae816 100644 (file)
@@ -301,6 +301,7 @@ struct fz_image
        unsigned int invert_cmyk_jpeg:1;
        unsigned int decoded:1;
        unsigned int scalable:1;
+       uint8_t orientation;
        fz_image *mask;
        int xres; /* As given in the image, not necessarily as rendered */
        int yres; /* As given in the image, not necessarily as rendered */
@@ -322,6 +323,33 @@ struct fz_image
 */
 void fz_image_resolution(fz_image *image, int *xres, int *yres);
 
+/**
+       Request the natural orientation of an image.
+
+       This is for images (such as JPEG) that can contain internal
+       specifications of rotation/flips. This is ignored by all the
+       internal decode/rendering routines, but can be used by callers
+       (such as the image document handler) to respect such
+       specifications.
+
+       The values used by MuPDF are as follows, with the equivalent
+       Exif specifications given for information:
+
+       0: Undefined
+       1: 0 degree ccw rotation. (Exif = 1)
+       2: 90 degree ccw rotation. (Exif = 8)
+       3: 180 degree ccw rotation. (Exif = 3)
+       4: 270 degree ccw rotation. (Exif = 6)
+       5: flip on X. (Exif = 2)
+       6: flip on X, then rotate ccw by 90 degrees. (Exif = 5)
+       7: flip on X, then rotate ccw by 180 degrees. (Exif = 4)
+       8: flip on X, then rotate ccw by 270 degrees. (Exif = 7)
+*/
+uint8_t fz_image_orientation(fz_context *ctx, fz_image *image);
+
+fz_matrix
+fz_image_orientation_matrix(fz_context *ctx, fz_image *image);
+
 /**
        Retrieve the underlying compressed data for an image.
 
index cf077bc9ac99c73e388cf51b4e5ac330da315db9..0d17017ae0f0ba1c2a8276aa5973f40990c8a6bf 100644 (file)
@@ -40,11 +40,20 @@ img_bound_page(fz_context *ctx, fz_page *page_)
        fz_image *image = page->image;
        int xres, yres;
        fz_rect bbox;
+       uint8_t orientation = fz_image_orientation(ctx, page->image);
 
        fz_image_resolution(image, &xres, &yres);
        bbox.x0 = bbox.y0 = 0;
-       bbox.x1 = image->w * DPI / xres;
-       bbox.y1 = image->h * DPI / yres;
+       if (orientation == 0 || (orientation & 1) == 1)
+       {
+               bbox.x1 = image->w * DPI / xres;
+               bbox.y1 = image->h * DPI / yres;
+       }
+       else
+       {
+               bbox.y1 = image->w * DPI / xres;
+               bbox.x1 = image->h * DPI / yres;
+       }
        return bbox;
 }
 
@@ -55,11 +64,22 @@ img_run_page(fz_context *ctx, fz_page *page_, fz_device *dev, fz_matrix ctm, fz_
        fz_image *image = page->image;
        int xres, yres;
        float w, h;
+       uint8_t orientation = fz_image_orientation(ctx, page->image);
+       fz_matrix immat = fz_image_orientation_matrix(ctx, page->image);
 
        fz_image_resolution(image, &xres, &yres);
-       w = image->w * DPI / xres;
-       h = image->h * DPI / yres;
-       ctm = fz_pre_scale(ctm, w, h);
+       if (orientation == 0 || (orientation & 1) == 1)
+       {
+               w = image->w * DPI / xres;
+               h = image->h * DPI / yres;
+       }
+       else
+       {
+               h = image->w * DPI / xres;
+               w = image->h * DPI / yres;
+       }
+       immat = fz_post_scale(immat, w, h);
+       ctm = fz_concat(immat, ctm);
        fz_fill_image(ctx, dev, image, ctm, 1, fz_default_color_params);
 }
 
index a880db272d89bd1a16060200cda12e0086dbe576..2f6a75acf088aad3f3388b228ebb9a9553a6ca09 100644 (file)
@@ -10,7 +10,7 @@ fz_pixmap *fz_load_bmp(fz_context *ctx, const unsigned char *data, size_t size);
 fz_pixmap *fz_load_pnm(fz_context *ctx, const unsigned char *data, size_t size);
 fz_pixmap *fz_load_jbig2(fz_context *ctx, const unsigned char *data, size_t size);
 
-void fz_load_jpeg_info(fz_context *ctx, const unsigned char *data, size_t size, int *w, int *h, int *xres, int *yres, fz_colorspace **cspace);
+void fz_load_jpeg_info(fz_context *ctx, const unsigned char *data, size_t size, int *w, int *h, int *xres, int *yres, fz_colorspace **cspace, uint8_t *orientation);
 void fz_load_jpx_info(fz_context *ctx, const unsigned char *data, size_t size, int *w, int *h, int *xres, int *yres, fz_colorspace **cspace);
 void fz_load_png_info(fz_context *ctx, const unsigned char *data, size_t size, int *w, int *h, int *xres, int *yres, fz_colorspace **cspace);
 void fz_load_tiff_info(fz_context *ctx, const unsigned char *data, size_t size, int *w, int *h, int *xres, int *yres, fz_colorspace **cspace);
index 00a71ba26c6f32ce1cf32cd9c9bb6a2672f9e6ea..1c8f8cdc911f904380ae655925d82daf8c186cfb 100644 (file)
@@ -1035,6 +1035,7 @@ fz_new_image_from_buffer(fz_context *ctx, fz_buffer *buffer)
        fz_image *image = NULL;
        int type;
        int bpc;
+       uint8_t orientation = 0;
 
        if (len < 8)
                fz_throw(ctx, FZ_ERROR_GENERIC, "unknown image file format");
@@ -1050,7 +1051,7 @@ fz_new_image_from_buffer(fz_context *ctx, fz_buffer *buffer)
                fz_load_jpx_info(ctx, buf, len, &w, &h, &xres, &yres, &cspace);
                break;
        case FZ_IMAGE_JPEG:
-               fz_load_jpeg_info(ctx, buf, len, &w, &h, &xres, &yres, &cspace);
+               fz_load_jpeg_info(ctx, buf, len, &w, &h, &xres, &yres, &cspace, &orientation);
                break;
        case FZ_IMAGE_PNG:
                fz_load_png_info(ctx, buf, len, &w, &h, &xres, &yres, &cspace);
@@ -1083,6 +1084,7 @@ fz_new_image_from_buffer(fz_context *ctx, fz_buffer *buffer)
                if (type == FZ_IMAGE_JPEG)
                        bc->params.u.jpeg.color_transform = -1;
                image = fz_new_image_from_compressed_buffer(ctx, w, h, bpc, cspace, xres, yres, 0, 0, NULL, NULL, bc, NULL);
+               image->orientation = orientation;
        }
        fz_always(ctx)
                fz_drop_colorspace(ctx, cspace);
@@ -1150,6 +1152,63 @@ fz_image_resolution(fz_image *image, int *xres, int *yres)
        }
 }
 
+uint8_t fz_image_orientation(fz_context *ctx, fz_image *image)
+{
+       return image ? image->orientation : 0;
+}
+
+fz_matrix fz_image_orientation_matrix(fz_context *ctx, fz_image *image)
+{
+       fz_matrix m;
+
+       switch (image ? image->orientation : 0)
+       {
+       case 0:
+       case 1: /* 0 degree rotation */
+               m.a =  1; m.b =  0;
+               m.c =  0; m.d =  1;
+               m.e =  0; m.f =  0;
+               break;
+       case 2: /* 90 degree ccw */
+               m.a =  0; m.b = -1;
+               m.c =  1; m.d =  0;
+               m.e =  0; m.f =  1;
+               break;
+       case 3: /* 180 degree ccw */
+               m.a = -1; m.b =  0;
+               m.c =  0; m.d = -1;
+               m.e =  1; m.f =  1;
+               break;
+       case 4: /* 270 degree ccw */
+               m.a =  0; m.b =  1;
+               m.c = -1; m.d =  0;
+               m.e =  1; m.f =  0;
+               break;
+       case 5: /* flip on X */
+               m.a = -1; m.b = 0;
+               m.c =  0; m.d = 1;
+               m.e =  1; m.f = 0;
+               break;
+       case 6: /* flip on X, then rotate ccw by 90 degrees */
+               m.a =  0; m.b =  1;
+               m.c =  1; m.d =  0;
+               m.e =  0; m.f =  0;
+               break;
+       case 7: /* flip on X, then rotate ccw by 180 degrees */
+               m.a =  1; m.b =  0;
+               m.c =  0; m.d = -1;
+               m.e =  0; m.f =  1;
+               break;
+       case 8: /* flip on X, then rotate ccw by 270 degrees */
+               m.a =  0; m.b = -1;
+               m.c = -1; m.d =  0;
+               m.e =  1; m.f =  1;
+               break;
+       }
+
+       return m;
+}
+
 typedef struct fz_display_list_image_s
 {
        fz_image super;
index d90b4e356c75e4e0c9d85c43326616d89a2ef529..fc4c2e28883b937817639a4637faf53798d3f708 100644 (file)
@@ -194,9 +194,28 @@ static fz_colorspace *extract_icc_profile(fz_context *ctx, jpeg_saved_marker_ptr
 #endif
 }
 
-static int extract_exif_resolution(jpeg_saved_marker_ptr marker, int *xres, int *yres)
+/* Returns true if <x> 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)
 {
-       int is_big_endian;
+       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;
@@ -225,6 +244,13 @@ static int extract_exif_resolution(jpeg_saved_marker_ptr marker, int *xres, int
                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);
@@ -354,7 +380,7 @@ fz_load_jpeg(fz_context *ctx, const unsigned char *rbuf, size_t rlen)
 
                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))
+               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 */;
@@ -420,7 +446,7 @@ fz_load_jpeg(fz_context *ctx, const unsigned char *rbuf, size_t rlen)
 }
 
 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)
+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;
@@ -428,6 +454,8 @@ fz_load_jpeg_info(fz_context *ctx, const unsigned char *rbuf, size_t rlen, int *
        fz_colorspace *icc = NULL;
 
        *cspacep = NULL;
+       if (orientation)
+               *orientation = NULL;
 
        cinfo.mem = NULL;
        cinfo.global_state = 0;
@@ -469,7 +497,7 @@ fz_load_jpeg_info(fz_context *ctx, const unsigned char *rbuf, size_t rlen, int *
                if (!*cspacep)
                        fz_throw(ctx, FZ_ERROR_GENERIC, "cannot determine colorspace");
 
-               if (extract_exif_resolution(cinfo.marker_list, xresp, yresp))
+               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 */;