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

Wayne McDougall waynemcdougall at gmail.com
Wed Apr 20 12:32:38 CEST 2016


On 18 April 2016 at 21:49, Jean-Baptiste Kempf <jb at videolan.org> wrote:

> I am very sorry, but I cannot apply this patch. It is corrupted for me
> everytime, because of wrapping and line endings.
>

My apologies. I will try very hard to get this right.

Changes in this patch:

a) Made orientation automatic, with no option to disable
to address your mild concerns and Rémi's firm objections.

>
> Well, I'm surprised GetWLE and GetWBE are not enough for you, and
> hton16|32|64, who are actually swapping or not depending on the machine.
> We have many other formats, so I'm surprised we need something new
> here...
>
> b) I spent an hour looking at this, and can't see the existing functions
will apply.
So someone smarter than me will have to address this. I have made detailed
comments.

c) Amended the citation to LGPL code I modified, basing it on other VLC
code citations

define i_otag in the scope where you do need it.
>
> d) Have moved definitions, CONST and DEFINEs to be near their relevant code


> The rest starts looking really good... (mention it in NEWS?)
>
> e) included patch for NEWS

[PATCH Automatically orient JPEG image based on orientation tag, if present

---
 NEWS                 |   1 +
 modules/codec/jpeg.c | 225
++++++++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 225 insertions(+), 1 deletion(-)

diff --git a/NEWS b/NEWS
index 5233b81..dd37984 100644
--- a/NEWS
+++ b/NEWS
@@ -67,6 +67,7 @@ Decoder:
    supporting H.263, H.264/MPEG-4 AVC, MPEG-4 Part 2, and DV depending on
device
    and OS version
  * Support for the OggSpots video codec
+ * JPEG images correctly oriented using embedded orientation tag, if
present

 Demuxers:
  * Support HD-DVD .evo (H.264, VC-1, MPEG-2, PCM, AC-3, E-AC3, MLP, DTS)
diff --git a/modules/codec/jpeg.c b/modules/codec/jpeg.c
index fdb91c7..69c7a69 100644
--- a/modules/codec/jpeg.c
+++ b/modules/codec/jpeg.c
@@ -1,7 +1,7 @@
 /*****************************************************************************
  * 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>
  *
@@ -182,6 +182,220 @@ static int OpenDecoder(vlc_object_t *p_this)
 }

 /*
+ * The following two functions are used to return 16 and 32 bit values from
+ * the EXIF tag structure. That structure is borrowed from TIFF files and
may be
+ * in big endian or little endian format. The endian parameter tells us
which.
+ * Case Little Endian EXIF tag / Little Endian machine
+ *   - just memcpy the tag structure into the value to return
+ * Case Little Endian EXIF tag / Big Endian machine
+ *    - memcpy the tag structure into value, and bswap it
+ * Case Little Endian EXIF tag / Big Endian machine
+ *   - memcpy the tag structure into value, and bswap it
+ * Case Big Endian EXIF tag / Big Endian machine
+ *   - just memcpy the tag structure into the value to return
+ *
+ * While there are byte manipulation functions in vlc_common.h, none of
them
+ * address that format of the data supplied may be either big or little
endian.
+ *
+ * The claim is made that this is the best way to do it. Can you do better?
+*/
+
+#define G_LITTLE_ENDIAN     1234
+#define G_BIG_ENDIAN        4321
+
+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;
+}
+
+/*
+ * Look through the meta data in the libjpeg decompress structure to
determine
+ * if an EXIF Orientation tag is present. If so return its value (1-8),
+ * otherwise return 0
+ *
+ * This function is based on the function get_orientation in io-jpeg.c,
part of
+ * the GdkPixbuf library, licensed under LGPLv2+.
+ *   Copyright (C) 1999 Michael Zucchi, The Free Software Foundation
+*/
+LOCAL( int )
+jpeg_GetOrientation( j_decompress_ptr cinfo )
+{
+
+    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 */
+    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 */
+
+    const char leth[] = { 0x49, 0x49, 0x2a, 0x00 }; /* Little endian TIFF
header */
+    const char beth[] = { 0x4d, 0x4d, 0x00, 0x2a }; /* Big endian TIFF
header */
+
+    #define EXIF_JPEG_MARKER    0xE1
+    #define EXIF_IDENT_STRING   "Exif\000\000"
+    #define EXIF_ORIENT_TAGID   0x112
+
+    /* 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 == EXIF_ORIENT_TAGID )
+        {
+            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 */
+}
+
+/*
  * This function must be fed with a complete compressed frame.
  */
 static picture_t *DecodeBlock(decoder_t *p_dec, block_t **pp_block)
@@ -214,6 +428,7 @@ static picture_t *DecodeBlock(decoder_t *p_dec, block_t
**pp_block)

     jpeg_create_decompress(&p_sys->p_jpeg);
     jpeg_mem_src(&p_sys->p_jpeg, p_block->p_buffer, p_block->i_buffer);
+    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;
@@ -230,6 +445,14 @@ static picture_t *DecodeBlock(decoder_t *p_dec,
block_t **pp_block)
     p_dec->fmt_out.video.i_gmask = 0x0000ff00;
     p_dec->fmt_out.video.i_bmask = 0x00ff0000;

+    int i_otag; /* Orientation tag has valid range of 1-8. 1 is normal
orientation, 0 = unspecified = normal */
+    i_otag = jpeg_GetOrientation( &p_sys->p_jpeg );
+    if ( i_otag > 1 )
+    {
+        msg_Info( p_dec, "Orientation is %d", i_otag );
+        p_dec->fmt_out.video.orientation = ORIENT_FROM_EXIF( i_otag );
+    }
+
     /* Get a new picture */
     p_pic = decoder_NewPicture(p_dec);
     if (!p_pic)
-- 
2.7.4
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mailman.videolan.org/pipermail/vlc-devel/attachments/20160420/23f9e5d1/attachment.html>


More information about the vlc-devel mailing list