[vlc-devel] [PATCH] Automatically orient JPEG image based on orientation flag, if set

Wayne McDougall waynemcdougall at gmail.com
Fri Apr 15 01:50:02 CEST 2016


---
 modules/codec/jpeg.c | 1263
+++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 1257 insertions(+), 6 deletions(-)

diff --git a/modules/codec/jpeg.c b/modules/codec/jpeg.c
index cc7a58b..77233a7 100644
--- a/modules/codec/jpeg.c
+++ b/modules/codec/jpeg.c
@@ -1,9 +1,10 @@
 /*****************************************************************************
  * jpeg.c: jpeg decoder module making use of libjpeg.

*****************************************************************************
- * Copyright (C) 2013-2014 VLC authors and VideoLAN
+ * Copyright (C) 2013-2014,2016 VLC authors and VideoLAN
  *
  * Authors: Maxim Bublis <b at codemonkey.ru>
+ *          Wayne McDougall <waynemcdougall at gmail.com>
  *
  * This program is free software; you can redistribute it and/or modify it
  * under the terms of the GNU Lesser General Public License as published by
@@ -27,6 +28,11 @@
 #include <vlc_common.h>
 #include <vlc_plugin.h>
 #include <vlc_codec.h>
+
+/* Although this file really shouldn't have access to the library
internals,
+ * it's helpful to let it call jround_up() and jcopy_block_row().
+ */
+#define JPEG_INTERNALS
 #include <jpeglib.h>
 #include <setjmp.h>

@@ -50,6 +56,27 @@
 #define ENC_QUALITY_LONGTEXT N_( "Quality level " \
                                  "for encoding (this can enlarge or reduce
output image size)." )

+#define DEC_CFG_PREFIX "jpeg-"
+#define DEC_AUTOORIENT_TEXT N_( "Automatic Orientation" )
+#define DEC_AUTOORIENT_LONGTEXT N_( "Automatic rotation based on EXIF
orientation flag. " \
+                                    "Lossless, but will crop if JPEG has
unusual dimensions. Uses more RAM." )
+
+const char leth[] = {
+    0x49, 0x49, 0x2a, 0x00
+};                                              /* Little endian TIFF
header */
+const char beth[] = {
+    0x4d, 0x4d, 0x00, 0x2a
+};                                              /* Big endian TIFF header
*/
+const char types[] = {
+    0x00, 0x01, 0x01, 0x02, 0x04, 0x08, 0x00,
+    0x08, 0x00, 0x04, 0x08
+};                                              /* size in bytes for EXIF
types */
+
+#define G_LITTLE_ENDIAN     1234
+#define G_BIG_ENDIAN        4321
+
+#define EXIF_JPEG_MARKER    0xE1
+#define EXIF_IDENT_STRING   "Exif\000\000"

 /*
  * jpeg common descriptor
@@ -86,6 +113,33 @@ struct encoder_sys_t {
     int i_quality;
 };

+/*
+ * Codes for supported types of image transformations.
+ */
+
+typedef enum {
+    JXFORM_FLIP_H,              /* horizontal flip */
+    JXFORM_FLIP_V,              /* vertical flip */
+    JXFORM_TRANSPOSE,           /* transpose across UL-to-LR axis */
+    JXFORM_TRANSVERSE,          /* transpose across UR-to-LL axis */
+    JXFORM_ROT_90,              /* 90-degree clockwise rotation */
+    JXFORM_ROT_180,             /* 180-degree rotation */
+    JXFORM_ROT_270,             /* 270-degree clockwise (or 90 ccw) */
+} JXFORM_CODE;
+
+typedef struct {
+    /* Options: set by caller */
+    JXFORM_CODE transform;      /* image transform operator */
+
+    /* Internal workspace: caller should not touch these */
+    int num_components;         /* # of components in workspace */
+    jvirt_barray_ptr * workspace_coef_arrays; /* workspace for
transformations */
+    JDIMENSION output_width;    /* cropped destination dimensions */
+    JDIMENSION output_height;
+    int iMCU_sample_width;      /* destination iMCU size */
+    int iMCU_sample_height;
+} jpeg_transform_info;
+
 static const char * const ppsz_enc_options[] = {
     "quality",
     NULL
@@ -107,6 +161,8 @@ set_description( N_( "JPEG image decoder" ) )
 set_capability( "decoder", 1000 )
 set_callbacks( OpenDecoder, CloseDecoder )
 add_shortcut( "jpeg" )
+add_bool( DEC_CFG_PREFIX "autoorient", true,
+          DEC_AUTOORIENT_TEXT, DEC_AUTOORIENT_LONGTEXT, true )

 /* encoder submodule */
 add_submodule()
@@ -178,6 +234,1120 @@ static int OpenDecoder ( vlc_object_t * p_this )
     return VLC_SUCCESS;
 }

+/* Start of code used to Auto Orient JPEG */
+
+LOCAL( unsigned short )
+de_get16( void * ptr, uint endian ) {
+    unsigned short val;
+
+    memcpy( &val, ptr, sizeof( val ) );
+    if ( endian == G_BIG_ENDIAN )
+    {
+        #ifndef WORDS_BIGENDIAN
+        val = bswap16( val );
+        #endif
+    }
+    else
+    {
+        #ifdef WORDS_BIGENDIAN
+        val = bswap16( val );
+        #endif
+    }
+    return val;
+}
+
+LOCAL( unsigned int )
+de_get32( void * ptr, uint endian ) {
+    unsigned int val;
+
+    memcpy( &val, ptr, sizeof( val ) );
+    if ( endian == G_BIG_ENDIAN )
+    {
+        #ifndef WORDS_BIGENDIAN
+        val = bswap32( val );
+        #endif
+    }
+    else
+    {
+        #ifdef WORDS_BIGENDIAN
+        val = bswap32( val );
+        #endif
+    }
+    return val;
+}
+
+/* jpeg_GetOrientation function based on code from GdkPixbuf library -
JPEG image loader
+ *
http://src.gnu-darwin.org/ports/x11-toolkits/gtk20/work/gtk+-2.12.3/gdk-pixbuf/io-jpeg.c
+ *
+ * Copyright (C) 1999 Michael Zucchi
+ * Copyright (C) 1999 The Free Software Foundation
+ */
+
+LOCAL( int )
+jpeg_GetOrientation( j_decompress_ptr cinfo ) {
+    /* This function looks through the meta data in the libjpeg decompress
structure to
+       determine if an EXIF Orientation tag is present and if so return
its value (1-8).
+       If no EXIF Orientation tag is found 0 (zero) is returned. */
+
+    uint i;                    /* index into working buffer */
+    ushort tag_type;           /* endianed tag type extracted from tiff
header */
+    uint ret;                  /* Return value */
+    uint offset;               /* de-endianed offset in various situations
*/
+    uint tags;                 /* number of tags in current ifd */
+    uint type;                 /* de-endianed type of tag used as index
into types[] */
+    uint count;                /* de-endianed count of elements in a tag */
+    uint tiff = 0;             /* offset to active tiff header */
+    uint endian = 0;           /* detected endian of data */
+
+    jpeg_saved_marker_ptr exif_marker;      /* Location of the Exif APP1
marker */
+    jpeg_saved_marker_ptr cmarker;          /* Location to check for Exif
APP1 marker */
+
+    /* check for Exif marker (also called the APP1 marker) */
+    exif_marker = NULL;
+    cmarker = cinfo->marker_list;
+
+
+
+    while ( cmarker )
+    {
+        if ( cmarker->marker == EXIF_JPEG_MARKER )
+        {
+            /* The Exif APP1 marker should contain a unique
+               identification string ("Exif\0\0"). Check for it. */
+            if ( !memcmp( cmarker->data, EXIF_IDENT_STRING, 6 ) )
+            {
+                exif_marker = cmarker;
+            }
+        }
+        cmarker = cmarker->next;
+    }
+
+    /* Did we find the Exif APP1 marker? */
+    if ( exif_marker == NULL )
+        return 0;
+
+    /* Do we have enough data? */
+    if ( exif_marker->data_length < 32 )
+        return 0;
+
+    /* Check for TIFF header and catch endianess */
+    i = 0;
+
+    /* Just skip data until TIFF header - it should be within 16 bytes
from marker start.
+       Normal structure relative to APP1 marker -
+            0x0000: APP1 marker entry = 2 bytes
+            0x0002: APP1 length entry = 2 bytes
+            0x0004: Exif Identifier entry = 6 bytes
+            0x000A: Start of TIFF header (Byte order entry) - 4 bytes
+                    - This is what we look for, to determine endianess.
+            0x000E: 0th IFD offset pointer - 4 bytes
+
+            exif_marker->data points to the first data after the APP1
marker
+            and length entries, which is the exif identification string.
+            The TIFF header should thus normally be found at i=6, below,
+            and the pointer to IFD0 will be at 6+4 = 10.
+    */
+
+    while ( i < 16 )
+    {
+        /* Little endian TIFF header */
+        if ( memcmp( &exif_marker->data[i], leth, 4 ) == 0 )
+        {
+            endian = G_LITTLE_ENDIAN;
+        }
+        /* Big endian TIFF header */
+        else
+        if ( memcmp( &exif_marker->data[i], beth, 4 ) == 0 )
+        {
+            endian = G_BIG_ENDIAN;
+        }
+        /* Keep looking through buffer */
+        else
+        {
+            i++;
+            continue;
+        }
+        /* We have found either big or little endian TIFF header */
+        tiff = i;
+        break;
+    }
+
+    /* So did we find a TIFF header or did we just hit end of buffer? */
+    if ( tiff == 0 )
+        return 0;
+
+    /* Read out the offset pointer to IFD0 */
+    offset = de_get32( &exif_marker->data[i] + 4, endian );
+    i = i + offset;
+
+    /* Check that we still are within the buffer and can read the tag
count */
+
+    if ( ( i + 2 ) > exif_marker->data_length )
+        return 0;
+
+    /* Find out how many tags we have in IFD0. As per the TIFF spec, the
first
+       two bytes of the IFD contain a count of the number of tags. */
+    tags = de_get16( &exif_marker->data[i], endian );
+    i = i + 2;
+
+    /* Check that we still have enough data for all tags to check. The tags
+       are listed in consecutive 12-byte blocks. The tag ID, type, size,
and
+       a pointer to the actual value, are packed into these 12 byte
entries. */
+    if ( ( i + tags * 12 ) > exif_marker->data_length )
+        return 0;
+
+    /* Check through IFD0 for tags of interest */
+    while ( tags-- )
+    {
+        tag_type = de_get16( &exif_marker->data[i], endian );
+        /* Is this the orientation tag? */
+        if ( tag_type == 0x112 )
+        {
+            type = de_get16( &exif_marker->data[i + 2], endian );
+            count = de_get32( &exif_marker->data[i + 4], endian );
+
+            /* Check that type and count fields are OK. The orientation
field
+               will consist of a single (count=1) 2-byte integer (type=3).
*/
+            if ( type != 3 || count != 1 )
+                return 0;
+
+            /* Return the orientation value. Within the 12-byte block, the
+               pointer to the actual data is at offset 8. */
+            ret = de_get16( &exif_marker->data[i + 8], endian );
+            return ret <= 8 ? ret : 0;
+        }
+        /* move the pointer to the next 12-byte tag field. */
+        i = i + 12;
+    }
+
+    return 0;     /* No EXIF Orientation tag found */
+}
+
+/* Following Auto Orient code heavily based on code from transupp.c by the
IJG */
+
+/* Trim off any partial iMCUs on the indicated destination edge */
+
+LOCAL( void )
+trim_right_edge( jpeg_transform_info * info, JDIMENSION full_width ) {
+    JDIMENSION MCU_cols;
+
+    MCU_cols = info->output_width / info->iMCU_sample_width;
+    if ( MCU_cols > 0 && MCU_cols ==
+         full_width / info->iMCU_sample_width )
+        info->output_width = MCU_cols * info->iMCU_sample_width;
+}
+
+LOCAL( void )
+trim_bottom_edge( jpeg_transform_info * info, JDIMENSION full_height ) {
+    JDIMENSION MCU_rows;
+
+    MCU_rows = info->output_height / info->iMCU_sample_height;
+    if ( MCU_rows > 0 && MCU_rows ==
+         full_height / info->iMCU_sample_height )
+        info->output_height = MCU_rows * info->iMCU_sample_height;
+}
+
+/* Request any required workspace.
+ *
+ * This routine figures out the size that the output image will be
+ * (which implies that all the transform parameters must be set before
+ * it is called).
+ *
+ * We allocate the workspace virtual arrays from the source decompression
+ * object, so that all the arrays (both the original data and the
workspace)
+ * will be taken into account while making memory management decisions.
+ * Hence, this routine must be called after jpeg_read_header (which reads
+ * the image dimensions) and before jpeg_read_coefficients (which realizes
+ * the source's virtual arrays).
+ *
+ */
+
+LOCAL( boolean )
+jtransform_request_workspace( j_decompress_ptr srcinfo,
+                              jpeg_transform_info * info ) {
+    jvirt_barray_ptr * coef_arrays;
+    boolean need_workspace, transpose_it;
+    jpeg_component_info * compptr;
+    JDIMENSION width_in_iMCUs, height_in_iMCUs;
+    JDIMENSION width_in_blocks, height_in_blocks;
+    int ci, h_samp_factor, v_samp_factor;
+
+    /* Process all the components */
+    info->num_components = srcinfo->num_components;
+
+    /* Compute output image dimensions and related values. */
+    jpeg_core_output_dimensions( srcinfo );
+
+    /* If there is only one output component, force the iMCU size to be 1;
+     * else use the source iMCU size.
+     */
+    switch ( info->transform )
+    {
+    case JXFORM_TRANSPOSE:
+    case JXFORM_TRANSVERSE:
+    case JXFORM_ROT_90:
+    case JXFORM_ROT_270:
+        info->output_width = srcinfo->output_height;
+        info->output_height = srcinfo->output_width;
+        if ( info->num_components == 1 )
+        {
+            info->iMCU_sample_width = srcinfo->min_DCT_v_scaled_size;
+            info->iMCU_sample_height = srcinfo->min_DCT_h_scaled_size;
+        }
+        else
+        {
+            info->iMCU_sample_width =
+                srcinfo->max_v_samp_factor *
srcinfo->min_DCT_v_scaled_size;
+            info->iMCU_sample_height =
+                srcinfo->max_h_samp_factor *
srcinfo->min_DCT_h_scaled_size;
+        }
+        break;
+    default:
+        info->output_width = srcinfo->output_width;
+        info->output_height = srcinfo->output_height;
+        if ( info->num_components == 1 )
+        {
+            info->iMCU_sample_width = srcinfo->min_DCT_h_scaled_size;
+            info->iMCU_sample_height = srcinfo->min_DCT_v_scaled_size;
+        }
+        else
+        {
+            info->iMCU_sample_width =
+                srcinfo->max_h_samp_factor *
srcinfo->min_DCT_h_scaled_size;
+            info->iMCU_sample_height =
+                srcinfo->max_v_samp_factor *
srcinfo->min_DCT_v_scaled_size;
+        }
+        break;
+    }
+
+    /* Figure out whether we need workspace arrays,
+     * and if so whether they are transposed relative to the source.
+     */
+    need_workspace = FALSE;
+    transpose_it = FALSE;
+    switch ( info->transform )
+    {
+    case JXFORM_FLIP_H:
+        trim_right_edge( info, srcinfo->output_width );
+        /* do_flip_h doesn't need a workspace array */
+        break;
+    case JXFORM_FLIP_V:
+        trim_bottom_edge( info, srcinfo->output_height );
+        /* Need workspace arrays having same dimensions as source image. */
+        need_workspace = TRUE;
+        break;
+    case JXFORM_TRANSPOSE:
+        /* transpose does NOT have to trim anything */
+        /* Need workspace arrays having transposed dimensions. */
+        need_workspace = TRUE;
+        transpose_it = TRUE;
+        break;
+    case JXFORM_TRANSVERSE:
+        trim_right_edge( info, srcinfo->output_height );
+        trim_bottom_edge( info, srcinfo->output_width );
+        /* Need workspace arrays having transposed dimensions. */
+        need_workspace = TRUE;
+        transpose_it = TRUE;
+        break;
+    case JXFORM_ROT_90:
+        trim_right_edge( info, srcinfo->output_height );
+        /* Need workspace arrays having transposed dimensions. */
+        need_workspace = TRUE;
+        transpose_it = TRUE;
+        break;
+    case JXFORM_ROT_180:
+        trim_right_edge( info, srcinfo->output_width );
+        trim_bottom_edge( info, srcinfo->output_height );
+        /* Need workspace arrays having same dimensions as source image. */
+        need_workspace = TRUE;
+        break;
+    case JXFORM_ROT_270:
+        trim_bottom_edge( info, srcinfo->output_width );
+        /* Need workspace arrays having transposed dimensions. */
+        need_workspace = TRUE;
+        transpose_it = TRUE;
+        break;
+    }
+
+    /* Allocate workspace if needed.
+
+     * Note that we allocate arrays padded out to the next iMCU boundary,
+     * so that transform routines need not worry about missing edge blocks.
+     */
+    if ( need_workspace )
+    {
+        coef_arrays = (jvirt_barray_ptr *)
+                      ( *srcinfo->mem->alloc_small )(
(j_common_ptr)srcinfo, JPOOL_IMAGE,
+                                                      sizeof(
jvirt_barray_ptr ) * info->num_components );
+        width_in_iMCUs = (JDIMENSION)
+                         jdiv_round_up( (long)info->output_width,
+                                        (long)info->iMCU_sample_width );
+        height_in_iMCUs = (JDIMENSION)
+                          jdiv_round_up( (long)info->output_height,
+                                         (long)info->iMCU_sample_height );
+        for ( ci = 0; ci < info->num_components; ci++ )
+        {
+            compptr = srcinfo->comp_info + ci;
+            if ( info->num_components == 1 )
+            {
+                /* we're going to force samp factors to 1x1 in this case */
+                h_samp_factor = v_samp_factor = 1;
+            }
+            else
+            if ( transpose_it )
+            {
+                h_samp_factor = compptr->v_samp_factor;
+                v_samp_factor = compptr->h_samp_factor;
+            }
+            else
+            {
+                h_samp_factor = compptr->h_samp_factor;
+                v_samp_factor = compptr->v_samp_factor;
+            }
+            width_in_blocks = width_in_iMCUs * h_samp_factor;
+            height_in_blocks = height_in_iMCUs * v_samp_factor;
+            coef_arrays[ci] = ( *srcinfo->mem->request_virt_barray )
+                                  ( (j_common_ptr)srcinfo, JPOOL_IMAGE,
FALSE,
+                                  width_in_blocks, height_in_blocks,
(JDIMENSION)v_samp_factor );
+        }
+        info->workspace_coef_arrays = coef_arrays;
+    }
+    else
+        info->workspace_coef_arrays = NULL;
+
+    return TRUE;
+}
+
+/* Transpose destination image parameters */
+
+LOCAL( void )
+transpose_critical_parameters( j_compress_ptr dstinfo ) {
+    int tblno, i, j, ci, itemp;
+    jpeg_component_info * compptr;
+    JQUANT_TBL * qtblptr;
+    JDIMENSION jtemp;
+    UINT16 qtemp;
+
+    /* Transpose image dimensions */
+    jtemp = dstinfo->image_width;
+    dstinfo->image_width = dstinfo->image_height;
+    dstinfo->image_height = jtemp;
+    itemp = dstinfo->min_DCT_h_scaled_size;
+    dstinfo->min_DCT_h_scaled_size = dstinfo->min_DCT_v_scaled_size;
+
+    dstinfo->min_DCT_v_scaled_size = itemp;
+
+    /* Transpose sampling factors */
+    for ( ci = 0; ci < dstinfo->num_components; ci++ )
+    {
+        compptr = dstinfo->comp_info + ci;
+        itemp = compptr->h_samp_factor;
+        compptr->h_samp_factor = compptr->v_samp_factor;
+        compptr->v_samp_factor = itemp;
+    }
+
+    /* Transpose quantization tables */
+    for ( tblno = 0; tblno < NUM_QUANT_TBLS; tblno++ )
+    {
+        qtblptr = dstinfo->quant_tbl_ptrs[tblno];
+        if ( qtblptr != NULL )
+        {
+            for ( i = 0; i < DCTSIZE; i++ )
+            {
+                for ( j = 0; j < i; j++ )
+                {
+                    qtemp = qtblptr->quantval[i * DCTSIZE + j];
+                    qtblptr->quantval[i * DCTSIZE + j] =
qtblptr->quantval[j * DCTSIZE + i];
+                    qtblptr->quantval[j * DCTSIZE + i] = qtemp;
+                }
+            }
+        }
+    }
+}
+
+/* Adjust output image parameters as needed.
+ *
+ * This must be called after jpeg_copy_critical_parameters()
+ * and before jpeg_write_coefficients().
+ *
+ * The return value is the set of virtual coefficient arrays to be written
+ * (either the ones allocated by jtransform_request_workspace, or the
+ * original source data arrays).  The caller will need to pass this value
+ * to jpeg_write_coefficients().
+ */
+
+LOCAL( jvirt_barray_ptr * )
+jtransform_adjust_parameters( j_compress_ptr dstinfo,
+                              jvirt_barray_ptr * src_coef_arrays,
+                              jpeg_transform_info * info ) {
+    /* Correct the destination's image dimensions as necessary
+     * for rotate/flip operations.
+     */
+    dstinfo->jpeg_width = info->output_width;
+    dstinfo->jpeg_height = info->output_height;
+
+    if ( info->transform == JXFORM_ROT_270 )
+    {
+        transpose_critical_parameters( dstinfo );
+    }
+
+    /* Return the appropriate output data set */
+    if ( info->workspace_coef_arrays != NULL )
+        return info->workspace_coef_arrays;
+    return src_coef_arrays;
+}
+
+LOCAL( void )
+do_flip_h( j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
jvirt_barray_ptr * src_coef_arrays ) {
+/* Horizontal flip; done in-place, so no separate dest array is required.
+ */
+    JDIMENSION MCU_cols, comp_width, blk_x, blk_y;
+    int ci, k, offset_y;
+    JBLOCKARRAY buffer;
+    JCOEFPTR ptr1, ptr2;
+    JCOEF temp1, temp2;
+    jpeg_component_info * compptr;
+
+    /* Horizontal mirroring of DCT blocks is accomplished by swapping
+     * pairs of blocks in-place.  Within a DCT block, we perform horizontal
+     * mirroring by changing the signs of odd-numbered columns.
+     * Partial iMCUs at the right edge are left untouched.
+     */
+    MCU_cols = srcinfo->output_width /
+               ( dstinfo->max_h_samp_factor *
dstinfo->min_DCT_h_scaled_size );
+
+    for ( ci = 0; ci < dstinfo->num_components; ci++ )
+    {
+        compptr = dstinfo->comp_info + ci;
+        comp_width = MCU_cols * compptr->h_samp_factor;
+        for ( blk_y = 0; blk_y < compptr->height_in_blocks;
+              blk_y += compptr->v_samp_factor )
+        {
+            buffer = ( *srcinfo->mem->access_virt_barray )
+                         ( (j_common_ptr)srcinfo, src_coef_arrays[ci],
blk_y,
+                         (JDIMENSION)compptr->v_samp_factor, TRUE );
+            for ( offset_y = 0; offset_y < compptr->v_samp_factor;
offset_y++ )
+            {
+                /* Do the mirroring */
+                for ( blk_x = 0; blk_x * 2 < comp_width; blk_x++ )
+                {
+                    ptr1 = buffer[offset_y][blk_x];
+                    ptr2 = buffer[offset_y][comp_width - blk_x - 1];
+                    /* this unrolled loop doesn't need to know which row
it's on... */
+                    for ( k = 0; k < DCTSIZE2; k += 2 )
+                    {
+                        temp1 = *ptr1; /* swap even column */
+                        temp2 = *ptr2;
+                        *ptr1++ = temp2;
+                        *ptr2++ = temp1;
+                        temp1 = *ptr1; /* swap odd column with sign change
*/
+                        temp2 = *ptr2;
+                        *ptr1++ = -temp2;
+                        *ptr2++ = -temp1;
+                    }
+                }
+            }
+        }
+    }
+}
+
+LOCAL( void )
+do_flip_v( j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+           jvirt_barray_ptr * src_coef_arrays,
+           jvirt_barray_ptr * dst_coef_arrays ) {
+/* Vertical flip */
+    JDIMENSION MCU_rows, comp_height, dst_blk_x, dst_blk_y;
+    int ci, i, j, offset_y;
+    JBLOCKARRAY src_buffer, dst_buffer;
+    JBLOCKROW src_row_ptr, dst_row_ptr;
+    JCOEFPTR src_ptr, dst_ptr;
+    jpeg_component_info * compptr;
+
+    /* We output into a separate array because we can't touch different
+     * rows of the source virtual array simultaneously.  Otherwise, this
+     * is a pretty straightforward analog of horizontal flip.
+     * Within a DCT block, vertical mirroring is done by changing the signs
+     * of odd-numbered rows.
+     * Partial iMCUs at the bottom edge are copied verbatim.
+     */
+    MCU_rows = srcinfo->output_height /
+               ( dstinfo->max_v_samp_factor *
dstinfo->min_DCT_v_scaled_size );
+
+    for ( ci = 0; ci < dstinfo->num_components; ci++ )
+    {
+        compptr = dstinfo->comp_info + ci;
+        comp_height = MCU_rows * compptr->v_samp_factor;
+        for ( dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks;
+              dst_blk_y += compptr->v_samp_factor )
+        {
+            dst_buffer = ( *srcinfo->mem->access_virt_barray )
+                             ( (j_common_ptr)srcinfo, dst_coef_arrays[ci],
dst_blk_y,
+                             (JDIMENSION)compptr->v_samp_factor, TRUE );
+            if ( dst_blk_y < comp_height )
+            {
+                /* Row is within the mirrorable area. */
+                src_buffer = ( *srcinfo->mem->access_virt_barray )
+                                 ( (j_common_ptr)srcinfo,
src_coef_arrays[ci],
+                                 comp_height - dst_blk_y -
+                                 (JDIMENSION)compptr->v_samp_factor,
+                                 (JDIMENSION)compptr->v_samp_factor, FALSE
);
+            }
+            else
+            {
+                /* Bottom-edge blocks will be copied verbatim. */
+                src_buffer = ( *srcinfo->mem->access_virt_barray )
+                                 ( (j_common_ptr)srcinfo,
src_coef_arrays[ci],
+                                 dst_blk_y,
+                                 (JDIMENSION)compptr->v_samp_factor, FALSE
);
+            }
+            for ( offset_y = 0; offset_y < compptr->v_samp_factor;
offset_y++ )
+            {
+                if ( dst_blk_y < comp_height )
+                {
+                    /* Row is within the mirrorable area. */
+                    dst_row_ptr = dst_buffer[offset_y];
+                    src_row_ptr = src_buffer[compptr->v_samp_factor -
offset_y - 1];
+                    for ( dst_blk_x = 0; dst_blk_x <
compptr->width_in_blocks;
+                          dst_blk_x++ )
+                    {
+                        dst_ptr = dst_row_ptr[dst_blk_x];
+                        src_ptr = src_row_ptr[dst_blk_x];
+                        for ( i = 0; i < DCTSIZE; i += 2 )
+                        {
+                            /* copy even row */
+                            for ( j = 0; j < DCTSIZE; j++ )
+                                *dst_ptr++ = *src_ptr++;
+                            /* copy odd row with sign change */
+                            for ( j = 0; j < DCTSIZE; j++ )
+                                *dst_ptr++ = -*src_ptr++;
+                        }
+                    }
+                }
+                else
+                {
+                    /* Just copy row verbatim. */
+                    jcopy_block_row( src_buffer[offset_y],
+                                     dst_buffer[offset_y],
+                                     compptr->width_in_blocks );
+                }
+            }
+        }
+    }
+}
+
+LOCAL( void )
+do_transpose( j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+              jvirt_barray_ptr * src_coef_arrays,
+              jvirt_barray_ptr * dst_coef_arrays ) {
+/* Transpose source into destination */
+    JDIMENSION dst_blk_x, dst_blk_y;
+    int ci, i, j, offset_x, offset_y;
+    JBLOCKARRAY src_buffer, dst_buffer;
+    JCOEFPTR src_ptr, dst_ptr;
+    jpeg_component_info * compptr;
+
+    /* Transposing pixels within a block just requires transposing the
+     * DCT coefficients.
+     * Partial iMCUs at the edges require no special treatment; we simply
+     * process all the available DCT blocks for every component.
+     */
+    for ( ci = 0; ci < dstinfo->num_components; ci++ )
+    {
+        compptr = dstinfo->comp_info + ci;
+        for ( dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks;
+              dst_blk_y += compptr->v_samp_factor )
+        {
+            dst_buffer = ( *srcinfo->mem->access_virt_barray )
+                             ( (j_common_ptr)srcinfo, dst_coef_arrays[ci],
dst_blk_y,
+                             (JDIMENSION)compptr->v_samp_factor, TRUE );
+            for ( offset_y = 0; offset_y < compptr->v_samp_factor;
offset_y++ )
+            {
+                for ( dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks;
+                      dst_blk_x += compptr->h_samp_factor )
+                {
+                    src_buffer = ( *srcinfo->mem->access_virt_barray )
+                                     ( (j_common_ptr)srcinfo,
src_coef_arrays[ci],
+                                     dst_blk_x,
+                                     (JDIMENSION)compptr->h_samp_factor,
FALSE );
+                    for ( offset_x = 0; offset_x < compptr->h_samp_factor;
offset_x++ )
+                    {
+                        dst_ptr = dst_buffer[offset_y][dst_blk_x +
offset_x];
+                        src_ptr = src_buffer[offset_x][dst_blk_y +
offset_y];
+                        for ( i = 0; i < DCTSIZE; i++ )
+                            for ( j = 0; j < DCTSIZE; j++ )
+                                dst_ptr[j * DCTSIZE + i] = src_ptr[i *
DCTSIZE + j];
+                    }
+                }
+            }
+        }
+    }
+}
+
+LOCAL( void )
+do_rot_90( j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+           jvirt_barray_ptr * src_coef_arrays,
+           jvirt_barray_ptr * dst_coef_arrays ) {
+/* 90 degree rotation is equivalent to
+ *   1. Transposing the image;
+ *   2. Horizontal mirroring.
+ * These two steps are merged into a single processing routine.
+ */
+    JDIMENSION MCU_cols, comp_width, dst_blk_x, dst_blk_y;
+    int ci, i, j, offset_x, offset_y;
+    JBLOCKARRAY src_buffer, dst_buffer;
+    JCOEFPTR src_ptr, dst_ptr;
+    jpeg_component_info * compptr;
+
+    /* Because of the horizontal mirror step, we can't process partial
iMCUs
+     * at the (output) right edge properly.  They just get transposed and
+     * not mirrored.
+     */
+    MCU_cols = srcinfo->output_height /
+               ( dstinfo->max_h_samp_factor *
dstinfo->min_DCT_h_scaled_size );
+
+    for ( ci = 0; ci < dstinfo->num_components; ci++ )
+    {
+        compptr = dstinfo->comp_info + ci;
+        comp_width = MCU_cols * compptr->h_samp_factor;
+        for ( dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks;
+              dst_blk_y += compptr->v_samp_factor )
+        {
+            dst_buffer = ( *srcinfo->mem->access_virt_barray )
+                             ( (j_common_ptr)srcinfo, dst_coef_arrays[ci],
dst_blk_y,
+                             (JDIMENSION)compptr->v_samp_factor, TRUE );
+            for ( offset_y = 0; offset_y < compptr->v_samp_factor;
offset_y++ )
+            {
+                for ( dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks;
+                      dst_blk_x += compptr->h_samp_factor )
+                {
+                    if ( dst_blk_x < comp_width )
+                    {
+                        /* Block is within the mirrorable area. */
+                        src_buffer = ( *srcinfo->mem->access_virt_barray )
+                                         ( (j_common_ptr)srcinfo,
src_coef_arrays[ci],
+                                         comp_width - dst_blk_x -
+
(JDIMENSION)compptr->h_samp_factor,
+
(JDIMENSION)compptr->h_samp_factor, FALSE );
+                    }
+                    else
+                    {
+                        /* Edge blocks are transposed but not mirrored. */
+                        src_buffer = ( *srcinfo->mem->access_virt_barray )
+                                         ( (j_common_ptr)srcinfo,
src_coef_arrays[ci],
+                                         dst_blk_x,
+
(JDIMENSION)compptr->h_samp_factor, FALSE );
+                    }
+                    for ( offset_x = 0; offset_x < compptr->h_samp_factor;
offset_x++ )
+                    {
+                        dst_ptr = dst_buffer[offset_y][dst_blk_x +
offset_x];
+                        if ( dst_blk_x < comp_width )
+                        {
+                            /* Block is within the mirrorable area. */
+                            src_ptr = src_buffer[compptr->h_samp_factor -
offset_x - 1]
+                                      [dst_blk_y + offset_y];
+                            for ( i = 0; i < DCTSIZE; i++ )
+                            {
+                                for ( j = 0; j < DCTSIZE; j++ )
+                                    dst_ptr[j * DCTSIZE + i] = src_ptr[i *
DCTSIZE + j];
+                                i++;
+                                for ( j = 0; j < DCTSIZE; j++ )
+                                    dst_ptr[j * DCTSIZE + i] = -src_ptr[i
* DCTSIZE + j];
+                            }
+                        }
+                        else
+                        {
+                            /* Edge blocks are transposed but not
mirrored. */
+                            src_ptr = src_buffer[offset_x]
+                                      [dst_blk_y + offset_y];
+                            for ( i = 0; i < DCTSIZE; i++ )
+                                for ( j = 0; j < DCTSIZE; j++ )
+                                    dst_ptr[j * DCTSIZE + i] = src_ptr[i *
DCTSIZE + j];
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
+LOCAL( void )
+do_rot_270( j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+            jvirt_barray_ptr * src_coef_arrays,
+            jvirt_barray_ptr * dst_coef_arrays ) {
+/* 270 degree rotation is equivalent to
+ *   1. Horizontal mirroring;
+ *   2. Transposing the image.
+ * These two steps are merged into a single processing routine.
+ */
+    JDIMENSION MCU_rows, comp_height, dst_blk_x, dst_blk_y;
+    int ci, i, j, offset_x, offset_y;
+    JBLOCKARRAY src_buffer, dst_buffer;
+    JCOEFPTR src_ptr, dst_ptr;
+    jpeg_component_info * compptr;
+
+    /* Because of the horizontal mirror step, we can't process partial
iMCUs
+     * at the (output) bottom edge properly.  They just get transposed and
+     * not mirrored.
+     */
+    MCU_rows = srcinfo->output_width /
+               ( dstinfo->max_v_samp_factor *
dstinfo->min_DCT_v_scaled_size );
+
+    for ( ci = 0; ci < dstinfo->num_components; ci++ )
+    {
+        compptr = dstinfo->comp_info + ci;
+        comp_height = MCU_rows * compptr->v_samp_factor;
+        for ( dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks;
+              dst_blk_y += compptr->v_samp_factor )
+        {
+            dst_buffer = ( *srcinfo->mem->access_virt_barray )
+                             ( (j_common_ptr)srcinfo, dst_coef_arrays[ci],
dst_blk_y,
+                             (JDIMENSION)compptr->v_samp_factor, TRUE );
+            for ( offset_y = 0; offset_y < compptr->v_samp_factor;
offset_y++ )
+            {
+                for ( dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks;
+                      dst_blk_x += compptr->h_samp_factor )
+                {
+                    src_buffer = ( *srcinfo->mem->access_virt_barray )
+                                     ( (j_common_ptr)srcinfo,
src_coef_arrays[ci],
+                                     dst_blk_x,
+                                     (JDIMENSION)compptr->h_samp_factor,
FALSE );
+                    for ( offset_x = 0; offset_x < compptr->h_samp_factor;
offset_x++ )
+                    {
+                        dst_ptr = dst_buffer[offset_y][dst_blk_x +
offset_x];
+                        if ( dst_blk_y < comp_height )
+                        {
+                            /* Block is within the mirrorable area. */
+                            src_ptr = src_buffer[offset_x]
+                                      [comp_height - dst_blk_y - offset_y
- 1];
+                            for ( i = 0; i < DCTSIZE; i++ )
+                            {
+                                for ( j = 0; j < DCTSIZE; j++ )
+                                {
+                                    dst_ptr[j * DCTSIZE + i] = src_ptr[i *
DCTSIZE + j];
+                                    j++;
+                                    dst_ptr[j * DCTSIZE + i] = -src_ptr[i
* DCTSIZE + j];
+                                }
+                            }
+                        }
+                        else
+                        {
+                            /* Edge blocks are transposed but not
mirrored. */
+                            src_ptr = src_buffer[offset_x]
+                                      [dst_blk_y + offset_y];
+                            for ( i = 0; i < DCTSIZE; i++ )
+                                for ( j = 0; j < DCTSIZE; j++ )
+                                    dst_ptr[j * DCTSIZE + i] = src_ptr[i *
DCTSIZE + j];
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
+LOCAL( void )
+do_rot_180( j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+            jvirt_barray_ptr * src_coef_arrays,
+            jvirt_barray_ptr * dst_coef_arrays ) {
+/* 180 degree rotation is equivalent to
+ *   1. Vertical mirroring;
+ *   2. Horizontal mirroring.
+ * These two steps are merged into a single processing routine.
+ */
+    JDIMENSION MCU_cols, MCU_rows, comp_width, comp_height, dst_blk_x,
dst_blk_y;
+    int ci, i, j, offset_y;
+    JBLOCKARRAY src_buffer, dst_buffer;
+    JBLOCKROW src_row_ptr, dst_row_ptr;
+    JCOEFPTR src_ptr, dst_ptr;
+    jpeg_component_info * compptr;
+
+    MCU_cols = srcinfo->output_width /
+               ( dstinfo->max_h_samp_factor *
dstinfo->min_DCT_h_scaled_size );
+    MCU_rows = srcinfo->output_height /
+               ( dstinfo->max_v_samp_factor *
dstinfo->min_DCT_v_scaled_size );
+
+    for ( ci = 0; ci < dstinfo->num_components; ci++ )
+    {
+        compptr = dstinfo->comp_info + ci;
+        comp_width = MCU_cols * compptr->h_samp_factor;
+        comp_height = MCU_rows * compptr->v_samp_factor;
+        for ( dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks;
+              dst_blk_y += compptr->v_samp_factor )
+        {
+            dst_buffer = ( *srcinfo->mem->access_virt_barray )
+                             ( (j_common_ptr)srcinfo, dst_coef_arrays[ci],
dst_blk_y,
+                             (JDIMENSION)compptr->v_samp_factor, TRUE );
+            if ( dst_blk_y < comp_height )
+            {
+                /* Row is within the vertically mirrorable area. */
+                src_buffer = ( *srcinfo->mem->access_virt_barray )
+                                 ( (j_common_ptr)srcinfo,
src_coef_arrays[ci],
+                                 comp_height - dst_blk_y -
+                                 (JDIMENSION)compptr->v_samp_factor,
+                                 (JDIMENSION)compptr->v_samp_factor, FALSE
);
+            }
+            else
+            {
+                /* Bottom-edge rows are only mirrored horizontally. */
+                src_buffer = ( *srcinfo->mem->access_virt_barray )
+                                 ( (j_common_ptr)srcinfo,
src_coef_arrays[ci],
+                                 dst_blk_y,
+                                 (JDIMENSION)compptr->v_samp_factor, FALSE
);
+            }
+            for ( offset_y = 0; offset_y < compptr->v_samp_factor;
offset_y++ )
+            {
+                dst_row_ptr = dst_buffer[offset_y];
+                if ( dst_blk_y < comp_height )
+                {
+                    /* Row is within the mirrorable area. */
+                    src_row_ptr = src_buffer[compptr->v_samp_factor -
offset_y - 1];
+                    for ( dst_blk_x = 0; dst_blk_x <
compptr->width_in_blocks; dst_blk_x++ )
+                    {
+                        dst_ptr = dst_row_ptr[dst_blk_x];
+                        if ( dst_blk_x < comp_width )
+                        {
+                            /* Process the blocks that can be mirrored
both ways. */
+                            src_ptr = src_row_ptr[comp_width - dst_blk_x -
1];
+                            for ( i = 0; i < DCTSIZE; i += 2 )
+                            {
+                                /* For even row, negate every odd column.
*/
+                                for ( j = 0; j < DCTSIZE; j += 2 )
+                                {
+                                    *dst_ptr++ = *src_ptr++;
+                                    *dst_ptr++ = -*src_ptr++;
+                                }
+                                /* For odd row, negate every even column.
*/
+                                for ( j = 0; j < DCTSIZE; j += 2 )
+                                {
+                                    *dst_ptr++ = -*src_ptr++;
+                                    *dst_ptr++ = *src_ptr++;
+                                }
+                            }
+                        }
+                        else
+                        {
+                            /* Any remaining right-edge blocks are only
mirrored vertically. */
+                            src_ptr = src_row_ptr[dst_blk_x];
+                            for ( i = 0; i < DCTSIZE; i += 2 )
+                            {
+                                for ( j = 0; j < DCTSIZE; j++ )
+                                    *dst_ptr++ = *src_ptr++;
+                                for ( j = 0; j < DCTSIZE; j++ )
+                                    *dst_ptr++ = -*src_ptr++;
+                            }
+                        }
+                    }
+                }
+                else
+                {
+                    /* Remaining rows are just mirrored horizontally. */
+                    src_row_ptr = src_buffer[offset_y];
+                    for ( dst_blk_x = 0; dst_blk_x <
compptr->width_in_blocks; dst_blk_x++ )
+                    {
+                        if ( dst_blk_x < comp_width )
+                        {
+                            /* Process the blocks that can be mirrored. */
+                            dst_ptr = dst_row_ptr[dst_blk_x];
+                            src_ptr = src_row_ptr[comp_width - dst_blk_x -
1];
+                            for ( i = 0; i < DCTSIZE2; i += 2 )
+                            {
+                                *dst_ptr++ = *src_ptr++;
+                                *dst_ptr++ = -*src_ptr++;
+                            }
+                        }
+                        else
+                        {
+                            /* Any remaining right-edge blocks are only
copied. */
+                            jcopy_block_row( src_row_ptr + dst_blk_x,
+                                             dst_row_ptr + dst_blk_x,
+                                             (JDIMENSION)1 );
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
+LOCAL( void )
+do_transverse( j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+               jvirt_barray_ptr * src_coef_arrays,
+               jvirt_barray_ptr * dst_coef_arrays ) {
+/* Transverse transpose is equivalent to
+ *   1. 180 degree rotation;
+ *   2. Transposition;
+ * or
+ *   1. Horizontal mirroring;
+ *   2. Transposition;
+ *   3. Horizontal mirroring.
+ * These steps are merged into a single processing routine.
+ */
+    JDIMENSION MCU_cols, MCU_rows, comp_width, comp_height, dst_blk_x,
dst_blk_y;
+    int ci, i, j, offset_x, offset_y;
+    JBLOCKARRAY src_buffer, dst_buffer;
+    JCOEFPTR src_ptr, dst_ptr;
+    jpeg_component_info * compptr;
+
+    MCU_cols = srcinfo->output_height /
+               ( dstinfo->max_h_samp_factor *
dstinfo->min_DCT_h_scaled_size );
+    MCU_rows = srcinfo->output_width /
+               ( dstinfo->max_v_samp_factor *
dstinfo->min_DCT_v_scaled_size );
+
+    for ( ci = 0; ci < dstinfo->num_components; ci++ )
+    {
+        compptr = dstinfo->comp_info + ci;
+        comp_width = MCU_cols * compptr->h_samp_factor;
+        comp_height = MCU_rows * compptr->v_samp_factor;
+        for ( dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks;
+              dst_blk_y += compptr->v_samp_factor )
+        {
+            dst_buffer = ( *srcinfo->mem->access_virt_barray )
+                             ( (j_common_ptr)srcinfo, dst_coef_arrays[ci],
dst_blk_y,
+                             (JDIMENSION)compptr->v_samp_factor, TRUE );
+            for ( offset_y = 0; offset_y < compptr->v_samp_factor;
offset_y++ )
+            {
+                for ( dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks;
+                      dst_blk_x += compptr->h_samp_factor )
+                {
+                    if ( dst_blk_x < comp_width )
+                    {
+                        /* Block is within the mirrorable area. */
+                        src_buffer = ( *srcinfo->mem->access_virt_barray )
+                                         ( (j_common_ptr)srcinfo,
src_coef_arrays[ci],
+                                         comp_width - dst_blk_x -
+
(JDIMENSION)compptr->h_samp_factor,
+
(JDIMENSION)compptr->h_samp_factor, FALSE );
+                    }
+                    else
+                    {
+                        src_buffer = ( *srcinfo->mem->access_virt_barray )
+                                         ( (j_common_ptr)srcinfo,
src_coef_arrays[ci],
+                                         dst_blk_x,
+
(JDIMENSION)compptr->h_samp_factor, FALSE );
+                    }
+                    for ( offset_x = 0; offset_x < compptr->h_samp_factor;
offset_x++ )
+                    {
+                        dst_ptr = dst_buffer[offset_y][dst_blk_x +
offset_x];
+                        if ( dst_blk_y < comp_height )
+                        {
+                            if ( dst_blk_x < comp_width )
+                            {
+                                /* Block is within the mirrorable area. */
+                                src_ptr =
src_buffer[compptr->h_samp_factor - offset_x - 1]
+                                          [comp_height - dst_blk_y -
offset_y - 1];
+                                for ( i = 0; i < DCTSIZE; i++ )
+                                {
+                                    for ( j = 0; j < DCTSIZE; j++ )
+                                    {
+                                        dst_ptr[j * DCTSIZE + i] =
src_ptr[i * DCTSIZE + j];
+                                        j++;
+                                        dst_ptr[j * DCTSIZE + i] =
-src_ptr[i * DCTSIZE + j];
+                                    }
+                                    i++;
+                                    for ( j = 0; j < DCTSIZE; j++ )
+                                    {
+                                        dst_ptr[j * DCTSIZE + i] =
-src_ptr[i * DCTSIZE + j];
+                                        j++;
+                                        dst_ptr[j * DCTSIZE + i] =
src_ptr[i * DCTSIZE + j];
+                                    }
+                                }
+                            }
+                            else
+                            {
+                                /* Right-edge blocks are mirrored in y
only */
+                                src_ptr = src_buffer[offset_x]
+                                          [comp_height - dst_blk_y -
offset_y - 1];
+                                for ( i = 0; i < DCTSIZE; i++ )
+                                {
+                                    for ( j = 0; j < DCTSIZE; j++ )
+                                    {
+                                        dst_ptr[j * DCTSIZE + i] =
src_ptr[i * DCTSIZE + j];
+                                        j++;
+                                        dst_ptr[j * DCTSIZE + i] =
-src_ptr[i * DCTSIZE + j];
+                                    }
+                                }
+                            }
+                        }
+                        else
+                        {
+                            if ( dst_blk_x < comp_width )
+                            {
+                                /* Bottom-edge blocks are mirrored in x
only */
+                                src_ptr =
src_buffer[compptr->h_samp_factor - offset_x - 1]
+                                          [dst_blk_y + offset_y];
+                                for ( i = 0; i < DCTSIZE; i++ )
+                                {
+                                    for ( j = 0; j < DCTSIZE; j++ )
+                                        dst_ptr[j * DCTSIZE + i] =
src_ptr[i * DCTSIZE + j];
+                                    i++;
+                                    for ( j = 0; j < DCTSIZE; j++ )
+                                        dst_ptr[j * DCTSIZE + i] =
-src_ptr[i * DCTSIZE + j];
+                                }
+                            }
+                            else
+                            {
+                                /* At lower right corner, just transpose,
no mirroring */
+                                src_ptr = src_buffer[offset_x]
+                                          [dst_blk_y + offset_y];
+                                for ( i = 0; i < DCTSIZE; i++ )
+                                    for ( j = 0; j < DCTSIZE; j++ )
+                                        dst_ptr[j * DCTSIZE + i] =
src_ptr[i * DCTSIZE + j];
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
+/* Execute the actual transformation, if any.
+ *
+ * This must be called *after* jpeg_write_coefficients, because it depends
+ * on jpeg_write_coefficients to have computed subsidiary values such as
+ * the per-component width and height fields in the destination object.
+ *
+ * Note that some transformations will modify the source data arrays!
+ */
+
+LOCAL( void )
+jtransform_execute_transform( j_decompress_ptr srcinfo,
+                              j_compress_ptr dstinfo,
+                              jvirt_barray_ptr * src_coef_arrays,
+                              jpeg_transform_info * info ) {
+    jvirt_barray_ptr * dst_coef_arrays = info->workspace_coef_arrays;
+
+    /* Note: conditions tested here should match those in switch statement
+     * in jtransform_request_workspace()
+     */
+    switch ( info->transform )
+    {
+    case JXFORM_FLIP_H:
+        do_flip_h( srcinfo, dstinfo, src_coef_arrays );
+        break;
+    case JXFORM_FLIP_V:
+        do_flip_v( srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays );
+        break;
+    case JXFORM_TRANSPOSE:
+        do_transpose( srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays );
+        break;
+    case JXFORM_TRANSVERSE:
+        do_transverse( srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays
);
+        break;
+    case JXFORM_ROT_90:
+        do_rot_90( srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays );
+        break;
+    case JXFORM_ROT_180:
+        do_rot_180( srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays );
+        break;
+    case JXFORM_ROT_270:
+        do_rot_270( srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays );
+        break;
+    }
+}
+
+/* End of code to handle Auto Orient */
+
 /*
  * This function must be fed with a complete compressed frame.
  */
@@ -186,8 +1356,15 @@ static picture_t * DecodeBlock ( decoder_t * p_dec,
block_t * * pp_block )
     decoder_sys_t * p_sys = p_dec->p_sys;
     block_t * p_block;
     picture_t * p_pic = 0;
-
+    bool b_autoorient;
+    int i_otag;
     JSAMPARRAY p_row_pointers = NULL;
+    jpeg_transform_info transformoption;
+    struct jpeg_compress_struct dinfo = { 0 };
+    unsigned char * p_dmem = NULL;
+    unsigned long l_dmem_size = 0;
+    jvirt_barray_ptr * src_coef_arrays;
+    jvirt_barray_ptr * dst_coef_arrays;

     if ( !pp_block || !*pp_block )
     {
@@ -209,12 +1386,77 @@ static picture_t * DecodeBlock ( decoder_t * p_dec,
block_t * * pp_block )
         goto error;
     }

+    /*
+        We only initialise dinfo if we need to rotate a jpeg.
+        Make sure we don't try to destroy an uninitialised structure
+        if an error occurs
+    */
+    dinfo.mem = NULL;
+
     jpeg_create_decompress( &p_sys->p_jpeg );
     jpeg_mem_src( &p_sys->p_jpeg, p_block->p_buffer, p_block->i_buffer );
-    jpeg_read_header( &p_sys->p_jpeg, TRUE );
+    b_autoorient = var_CreateGetBool( p_dec, "jpeg-autoorient" );
+
+    if ( b_autoorient )
+    {
+        jpeg_save_markers( &p_sys->p_jpeg, EXIF_JPEG_MARKER, 0xffff );
+    }

+    jpeg_read_header( &p_sys->p_jpeg, TRUE );
     p_sys->p_jpeg.out_color_space = JCS_RGB;

+    if ( b_autoorient )
+    {
+        i_otag = jpeg_GetOrientation( &p_sys->p_jpeg );
+        if ( i_otag > 1 ) /* Do we need to orient the image? */
+        {
+            msg_Info( p_dec, "Orientation is %d", i_otag );
+            switch ( i_otag )
+            {
+            case 2:
+                transformoption.transform = JXFORM_FLIP_H;
+                break;
+            case 3:
+                transformoption.transform = JXFORM_ROT_180;
+                break;
+            case 4:
+                transformoption.transform = JXFORM_FLIP_V;
+                break;
+            case 5:
+                transformoption.transform = JXFORM_TRANSPOSE;
+                break;
+            case 6:
+                transformoption.transform = JXFORM_ROT_90;
+                break;
+            case 7:
+                transformoption.transform = JXFORM_TRANSVERSE;
+                break;
+            case 8:
+            default:
+                transformoption.transform = JXFORM_ROT_270;
+            }
+            dinfo.err = jpeg_std_error( &p_sys->err );
+            jpeg_create_compress( &dinfo );
+            jpeg_copy_critical_parameters( &p_sys->p_jpeg, &dinfo );
+            jtransform_request_workspace( &p_sys->p_jpeg, &transformoption
);
+            src_coef_arrays = jpeg_read_coefficients( &p_sys->p_jpeg );
+            dst_coef_arrays = jtransform_adjust_parameters( &dinfo,
src_coef_arrays, &transformoption );
+            jpeg_mem_dest( &dinfo, &p_dmem, &l_dmem_size );
+            jpeg_write_coefficients( &dinfo, dst_coef_arrays );
+            jtransform_execute_transform( &p_sys->p_jpeg, &dinfo,
src_coef_arrays, &transformoption );
+            jpeg_finish_compress( &dinfo );
+            jpeg_abort_decompress( &p_sys->p_jpeg );
+
+            /* Assign any change in height/width */
+            p_sys->p_jpeg.image_width = dinfo.image_width;
+            p_sys->p_jpeg.image_height = dinfo.image_height;
+
+            /* Restart processing with orientated image data */
+            jpeg_mem_src( &p_sys->p_jpeg, p_dmem, l_dmem_size );
+            jpeg_read_header( &p_sys->p_jpeg, TRUE );
+        }
+    }
+
     jpeg_start_decompress( &p_sys->p_jpeg );

     /* Set output properties */
@@ -236,10 +1478,12 @@ static picture_t * DecodeBlock ( decoder_t * p_dec,
block_t * * pp_block )

     /* Decode picture */
     p_row_pointers = malloc( sizeof( JSAMPROW ) *
p_sys->p_jpeg.output_height );
+
     if ( !p_row_pointers )
     {
         goto error;
     }
+
     for ( unsigned i = 0; i < p_sys->p_jpeg.output_height; i++ )
     {
         p_row_pointers[i] = p_pic->p->p_pixels + p_pic->p->i_pitch * i;
@@ -253,19 +1497,26 @@ static picture_t * DecodeBlock ( decoder_t * p_dec,
block_t * * pp_block )
     }

     jpeg_finish_decompress( &p_sys->p_jpeg );
+    jpeg_destroy_compress( &dinfo );
     jpeg_destroy_decompress( &p_sys->p_jpeg );
+    if ( l_dmem_size )
+    {
+        free( p_dmem );
+    }
     free( p_row_pointers );
-
     p_pic->date = p_block->i_pts > VLC_TS_INVALID ? p_block->i_pts :
p_block->i_dts;
-
     block_Release( p_block );
     return p_pic;

  error:

+    jpeg_destroy_compress( &dinfo );
     jpeg_destroy_decompress( &p_sys->p_jpeg );
+    if ( l_dmem_size )
+    {
+        free( p_dmem );
+    }
     free( p_row_pointers );
-
     block_Release( p_block );
     return NULL;
 }
-- 
2.7.4
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mailman.videolan.org/pipermail/vlc-devel/attachments/20160415/740fa074/attachment-0001.html>


More information about the vlc-devel mailing list