JPEG loader: Parse EXIF data and handle orientation

This commit is contained in:
Kim Woelders 2021-09-23 15:07:58 +02:00
parent c5e0e786f9
commit b200f57e92
4 changed files with 287 additions and 7 deletions

View File

@ -88,7 +88,7 @@ id3_la_LDFLAGS = -module -avoid-version
id3_la_LIBADD = $(ID3_LIBS) $(top_builddir)/src/lib/libImlib2.la
id3_la_LIBTOOLFLAGS = --tag=disable-static
jpeg_la_SOURCES = loader_jpeg.c
jpeg_la_SOURCES = loader_jpeg.c exif.c exif.h
jpeg_la_CPPFLAGS = $(JPEG_CFLAGS) $(AM_CPPFLAGS)
jpeg_la_LDFLAGS = -module -avoid-version
jpeg_la_LIBADD = $(JPEG_LIBS) $(top_builddir)/src/lib/libImlib2.la

178
src/modules/loaders/exif.c Normal file
View File

@ -0,0 +1,178 @@
#include <stdio.h>
#include <string.h>
#include "exif.h"
#define EXIF_DEBUG 0
#if EXIF_DEBUG
#define D(...) printf(__VA_ARGS__)
#else
#define D(...)
#endif
// IFD types
#define IFD_TYPE_BYTE 1 // 8-bit unsigned integer
#define IFD_TYPE_ASCII 2 // 8-bit byte that contains a 7-bit ASCII code; the last byte must be NUL (binary zero)
#define IFD_TYPE_SHORT 3 // 16-bit (2-byte) unsigned integer
#define IFD_TYPE_LONG 4 // 32-bit (4-byte) unsigned integer
#define IFD_TYPE_RATIONAL 5 // Two LONGs: the first represents the numerator of a fraction; the second, the denominator
#define IFD_TYPE_SBYTE 6 // 8-bit signed (twos-complement) integer
#define IFD_TYPE_UNDEFINED 7 // 8-bit byte that may contain anything, depending on the definition of the field
#define IFD_TYPE_SSHORT 8 // 16-bit (2-byte) signed (twos-complement) integer
#define IFD_TYPE_SLONG 9 // 32-bit (4-byte) signed (twos-complement) integer
#define IFD_TYPE_SRATIONAL 10 // Two SLONGs: the first represents the numerator of a fraction, the second the denominator
#define IFD_TYPE_FLOAT 11 // Single precision (4-byte) IEEE format
#define IFD_TYPE_DOUBLE 12 // Double precision (8-byte) IEEE format
// IFD tags
#define TIFF_Orientation 0x0112
#define TIFF_ExifIFD 0x8769
#define TIFF_GpsIFD 0x8825
static unsigned int
get_u16(const unsigned char *p, int swap)
{
if (swap)
return p[0] << 8 | p[1];
else
return p[1] << 8 | p[0];
}
static unsigned int
get_u32(const unsigned char *p, int swap)
{
if (swap)
return p[0] << 24 | p[1] << 16 | p[2] << 8 | p[3];
else
return p[3] << 24 | p[2] << 16 | p[1] << 8 | p[0];
}
/*
* Parse TIFF IFD (Image File Directory)
*/
static void
tiff_parse_ifd(int lvl, const unsigned char *p, unsigned int len,
const unsigned char *ifd, int swap, ExifInfo * ei)
{
const unsigned char *pp = ifd;
unsigned int tag, type, cnt;
unsigned int iifd, nifd;
D("%s: len=%x(%d)\n", __func__, len, len);
if (pp + 2 - p > len)
{
D("Bad IFD offset\n");
return;
}
nifd = get_u16(pp, swap);
pp += 2;
for (iifd = 0; iifd < nifd; iifd++, pp += 12)
{
if (pp + 12 - p > len)
{
D("Bad offset, break\n");
return;
}
tag = get_u16(pp, swap);
type = get_u16(pp + 2, swap);
cnt = get_u32(pp + 4, swap);
#if EXIF_DEBUG
unsigned int val = get_u32(pp + 8, swap);
D("%*s %3d/%3d: tag=%04x type=%2d cnt=%5d val=%9d(0x%08x)\n",
4 * lvl, " ", iifd, nifd, tag, type, cnt, val, val);
#endif
switch (tag)
{
case TIFF_Orientation:
if (type == IFD_TYPE_SHORT && cnt == 1)
ei->orientation = get_u16(pp + 8, swap);
#if EXIF_DEBUG
break;
#else
return; // Done
#endif
#if EXIF_DEBUG
case TIFF_ExifIFD:
case TIFF_GpsIFD:
if (lvl > 0)
break;
tiff_parse_ifd(lvl + 1, p, len, p + val, swap, ei);
break;
#endif
}
}
}
/*
* Parse EXIF data (Exif.. are not part of the TIFF file)
*/
int
exif_parse(const void *data, unsigned int len, ExifInfo * ei)
{
const unsigned char *ptr = data;
int swap;
unsigned int word;
D("%s: len=%x(%d)\n", __func__, len, len);
if (memcmp(ptr, "Exif", 4) != 0)
{
D("Not EXIF data\n");
return 1;
}
ptr += 6; // ptr-> TIFF header
len -= 6;
word = ptr[0] << 8 | ptr[1];
if (word == 0x4949) // II
swap = 0;
else if (word == 0x4d4d) // MM
swap = 1;
else
return 1;
word = get_u16(ptr + 2, swap);
if (word != 42)
{
D("Bad TIFF version: %d\n", word);
return 1;
}
word = get_u32(ptr + 4, swap);
if (word > len)
{
D("Bad IFD offset: %d\n", word);
return 1;
}
tiff_parse_ifd(0, ptr, len, ptr + word, swap, ei);
switch (ei->orientation)
{
default:
case ORIENT_TOPLEFT:
case ORIENT_TOPRIGHT:
case ORIENT_BOTRIGHT:
case ORIENT_BOTLEFT:
ei->swap_wh = 0;
break;
case ORIENT_LEFTTOP:
case ORIENT_RIGHTTOP:
case ORIENT_RIGHTBOT:
case ORIENT_LEFTBOT:
ei->swap_wh = 1;
break;
}
D("Orientation: %d (swap w/h=%d)\n", ei->orientation, ei->swap_wh);
return len;
}

View File

@ -0,0 +1,21 @@
#ifndef EXIF_PARSE_H
#define EXIF_PARSE_H
#define ORIENT_TOPLEFT 1
#define ORIENT_TOPRIGHT 2
#define ORIENT_BOTRIGHT 3
#define ORIENT_BOTLEFT 4
#define ORIENT_LEFTTOP 5
#define ORIENT_RIGHTTOP 6
#define ORIENT_RIGHTBOT 7
#define ORIENT_LEFTBOT 8
typedef struct {
unsigned char orientation;
char swap_wh;
} ExifInfo;
int exif_parse(const void *data, unsigned int len,
ExifInfo * ei);
#endif /* EXIF_PARSE_H */

View File

@ -1,6 +1,14 @@
#include "loader_common.h"
#include <jpeglib.h>
#include <setjmp.h>
#include "exif.h"
#define DEBUG 0
#if DEBUG
#define D(fmt...) fprintf(stdout, "JPEG loader: " fmt)
#else
#define D(fmt...)
#endif
typedef struct {
struct jpeg_error_mgr jem;
@ -65,7 +73,8 @@ load2(ImlibImage * im, int load_data)
ImLib_JPEG_data jdata;
DATA8 *ptr, *line[16];
DATA32 *ptr2;
int x, y, l, scans;
int x, y, l, scans, inc;
ExifInfo ei = { 0 };
/* set up error handling */
jds.err = _jdata_init(&jdata);
@ -79,13 +88,38 @@ load2(ImlibImage * im, int load_data)
jpeg_create_decompress(&jds);
jpeg_stdio_src(&jds, im->fp);
jpeg_save_markers(&jds, JPEG_APP0 + 1, 256);
jpeg_read_header(&jds, TRUE);
im->w = w = jds.image_width;
im->h = h = jds.image_height;
/* Get orientation */
ei.orientation = ORIENT_TOPLEFT;
if (jds.marker_list)
{
jpeg_saved_marker_ptr m = jds.marker_list;
D("Markers: %p: m=%02x len=%d/%d\n", m,
m->marker, m->original_length, m->data_length);
exif_parse(m->data, m->data_length, &ei);
}
w = jds.image_width;
h = jds.image_height;
if (!IMAGE_DIMENSIONS_OK(w, h))
goto quit;
if (ei.swap_wh)
{
im->w = h;
im->h = w;
}
else
{
im->w = w;
im->h = h;
}
UNSET_FLAG(im->flags, F_HAS_ALPHA);
if (!load_data)
@ -127,6 +161,45 @@ load2(ImlibImage * im, int load_data)
{
ptr = line[y];
switch (ei.orientation)
{
default:
case ORIENT_TOPLEFT:
ptr2 = im->data + (l + y) * w;
inc = 1;
break;
case ORIENT_TOPRIGHT:
ptr2 = im->data + (l + y) * w + w - 1;
inc = -1;
break;
case ORIENT_BOTRIGHT:
ptr2 = im->data + (h - 1 - (l + y)) * w + w - 1;
inc = -1;
break;
case ORIENT_BOTLEFT:
ptr2 = im->data + (h - 1 - (l + y)) * w;
inc = 1;
break;
case ORIENT_LEFTTOP:
ptr2 = im->data + (l + y);
inc = h;
break;
case ORIENT_RIGHTTOP:
ptr2 = im->data + (h - 1 - (l + y));
inc = h;
break;
case ORIENT_RIGHTBOT:
ptr2 = im->data + (h - 1 - (l + y)) + (w - 1) * h;
inc = -h;
break;
case ORIENT_LEFTBOT:
ptr2 = im->data + (l + y) + (w - 1) * h;
inc = -h;
break;
}
D("l,s,y=%d,%d, %d - x,y=%4ld,%4ld\n", l, y, l + y,
(ptr2 - im->data) % im->w, (ptr2 - im->data) / im->w);
switch (jds.out_color_space)
{
default:
@ -136,7 +209,7 @@ load2(ImlibImage * im, int load_data)
{
*ptr2 = PIXEL_ARGB(0xff, ptr[0], ptr[0], ptr[0]);
ptr++;
ptr2++;
ptr2 += inc;
}
break;
case JCS_RGB:
@ -144,7 +217,7 @@ load2(ImlibImage * im, int load_data)
{
*ptr2 = PIXEL_ARGB(0xff, ptr[0], ptr[1], ptr[2]);
ptr += jds.output_components;
ptr2++;
ptr2 += inc;
}
break;
case JCS_CMYK:
@ -154,18 +227,26 @@ load2(ImlibImage * im, int load_data)
ptr[1] * ptr[3] / 255,
ptr[2] * ptr[3] / 255);
ptr += jds.output_components;
ptr2++;
ptr2 += inc;
}
break;
}
}
if (ei.orientation != ORIENT_TOPLEFT &&
ei.orientation != ORIENT_TOPRIGHT)
continue;
if (im->lc && __imlib_LoadProgressRows(im, l, scans))
{
rc = LOAD_BREAK;
goto quit;
}
}
if (ei.orientation != ORIENT_TOPLEFT && ei.orientation != ORIENT_TOPRIGHT)
{
if (im->lc)
__imlib_LoadProgressRows(im, 0, im->h);
}
jpeg_finish_decompress(&jds);