Skip to content

Commit a0a5b51

Browse files
committed
gpujpeg_reader: parse Exif APP1
There is not much data we can process but at least issue a warning if Orientation isn't upside-down, left-right (1).
1 parent 5772f73 commit a0a5b51

File tree

3 files changed

+197
-3
lines changed

3 files changed

+197
-3
lines changed

src/gpujpeg_exif.c

Lines changed: 163 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@
5757
#include "gpujpeg_util.h" // for ARR_SIZE
5858
#include "gpujpeg_writer.h" // for gpujpeg_writer, gpujpeg_writer...
5959

60+
#define MOD_NAME "[Exif] "
61+
6062
enum exif_tag_type {
6163
ET_NONE = 0,
6264
ET_BYTE = 1, ///< 8-bit unsigned integer
@@ -77,7 +79,7 @@ enum {
7779
T_RATIONAL = 1 << 3, // 2 items of .uvalue
7880
};
7981

80-
static const struct
82+
static const struct exif_tag_type_info_t
8183
{
8284
unsigned size;
8385
const char* name;
@@ -94,6 +96,7 @@ static const struct
9496
};
9597

9698
enum exif_tiff_tag {
99+
TAG_NONE,
97100
// 0th IFD TIFF Tags
98101
ETIFF_ORIENTATION, ///< Image resolution in width direction (recommended)
99102
ETIFF_XRESOLUTION, ///< Image resolution in width direction (mandatory)
@@ -111,6 +114,7 @@ enum exif_tiff_tag {
111114
EEXIF_COLOR_SPACE, ///< Color space information (mandatory)
112115
EEXIF_PIXEL_X_DIMENSION, ///< Valid image width (mandatory)
113116
EEXIF_PIXEL_Y_DIMENSION, ///< Valid image height (mandatory)
117+
NUM_TAGS
114118
};
115119

116120
const struct exif_tiff_tag_info_t {
@@ -119,6 +123,7 @@ const struct exif_tiff_tag_info_t {
119123
unsigned count;
120124
const char *name;
121125
} exif_tiff_tag_info[] = {
126+
[TAG_NONE] = {0, 0, 0, "Unknown" },
122127
[ETIFF_ORIENTATION] = {0x112, ET_SHORT, 1, "Orientation" },
123128
[ETIFF_XRESOLUTION] = {0x11A, ET_RATIONAL, 1, "XResolution" },
124129
[ETIFF_YRESOLUTION] = {0x11B, ET_RATIONAL, 1, "YResolution" },
@@ -147,8 +152,12 @@ enum {
147152
IFD_ITEM_SZ = 12,
148153
DPI_DEFAULT = 72,
149154
EEXIF_FIRST = 0x827A, // (Exposure time) first tag id of Exif Private Tags
155+
TIFF_HDR_TAG = 0x002a,
150156
};
151157

158+
////////////////////////////////////////////////////////////////////////////////
159+
// WRITER //
160+
////////////////////////////////////////////////////////////////////////////////
152161
union value_u {
153162
const uint32_t *uvalue;
154163
const char* csvalue; // ET_STRING (must be 0-terminated) or ET_UNDEFINED
@@ -386,7 +395,7 @@ gpujpeg_writer_write_exif(struct gpujpeg_encoder* encoder)
386395
gpujpeg_writer_emit_byte(writer, 'M');
387396
gpujpeg_writer_emit_byte(writer, 'M');
388397

389-
gpujpeg_writer_emit_2byte(writer, 0x002a); // TIFF header
398+
gpujpeg_writer_emit_2byte(writer, TIFF_HDR_TAG); // TIFF header
390399
gpujpeg_writer_emit_4byte(writer, 0x08); // IFD offset - follows immediately
391400

392401
gpujpeg_write_0th(encoder, start);
@@ -558,3 +567,155 @@ gpujpeg_exif_tags_destroy(struct gpujpeg_exif_tags* exif_tags)
558567
}
559568
free(exif_tags);
560569
}
570+
571+
////////////////////////////////////////////////////////////////////////////////
572+
// READER //
573+
////////////////////////////////////////////////////////////////////////////////
574+
static enum exif_tiff_tag
575+
get_tag_from_id(uint16_t tag_id)
576+
{
577+
for ( unsigned i = TAG_NONE + 1; i < NUM_TAGS; ++i ) {
578+
if ( exif_tiff_tag_info[i].id == tag_id ) {
579+
return i;
580+
}
581+
}
582+
return TAG_NONE;
583+
}
584+
585+
static uint8_t
586+
read_byte(uint8_t** image)
587+
{
588+
return *(*image)++;
589+
}
590+
static uint16_t
591+
read_2byte_be(uint8_t** image) {
592+
uint16_t ret = (*image)[0] << 8 | (*image)[1];
593+
*image += 2;
594+
return ret;
595+
}
596+
static uint32_t
597+
read_4byte_be(uint8_t** image) {
598+
uint32_t ret = (*image)[0] << 24 | (*image)[1] << 16 | (*image)[2] << 8 | (*image)[3];
599+
*image += 4;
600+
return ret;
601+
}
602+
static uint16_t
603+
read_2byte_le(uint8_t** image) {
604+
uint16_t ret = (*image)[1] << 8 | (*image)[0];
605+
*image += 2;
606+
return ret;
607+
}
608+
static uint32_t
609+
read_4byte_le(uint8_t** image) {
610+
uint32_t ret = (*image)[3] << 24 | (*image)[2] << 16 | (*image)[1] << 8 | (*image)[0];
611+
*image += 4;
612+
return ret;
613+
}
614+
615+
static void
616+
read_0th_ifd(uint8_t** image, const uint8_t* image_end, int verbose, uint16_t (*read_2byte)(uint8_t**),
617+
uint32_t (*read_4byte)(uint8_t**))
618+
{
619+
if ( *image + 2 > image_end ) {
620+
WARN_MSG("Unexpected end of file!\n");
621+
return;
622+
}
623+
size_t num_interop = read_2byte(image);
624+
if ( *image + num_interop * IFD_ITEM_SZ > image_end ) {
625+
WARN_MSG(MOD_NAME "Insufficient space to hold %zu IFD0 items!\n", num_interop);
626+
return;
627+
}
628+
DEBUG_MSG(verbose, "Found %zu IFD0 items.\n", num_interop);
629+
630+
for ( unsigned i = 0; i < num_interop; ++i ) {
631+
uint16_t tag_id = read_2byte(image);
632+
uint16_t type = read_2byte(image);
633+
uint32_t count = read_4byte(image);
634+
uint32_t val = read_4byte(image);
635+
unsigned size = 0;
636+
enum exif_tiff_tag tag = get_tag_from_id(tag_id);
637+
const char* type_name = "WRONG";
638+
if ( type < ET_END && exif_tag_type_info[type].name != NULL ) {
639+
type_name = exif_tag_type_info[type].name;
640+
if ( (exif_tag_type_info[type].type_flags & T_NUMERIC) != 0 ) {
641+
if (read_2byte == read_2byte_be) {
642+
val >>= 8 * exif_tag_type_info[type].size;
643+
}
644+
}
645+
size = exif_tag_type_info[type].size;
646+
}
647+
DEBUG_MSG(verbose, MOD_NAME "Found IFD0 tag %s (%#x) type %s: count=%u, %s=%#x\n", exif_tiff_tag_info[tag].name,
648+
tag_id, type_name, count, count * size <= 4 ? "value" : "offset", val);
649+
if ( tag == ETIFF_ORIENTATION && val != ETIFF_ORIENT_HORIZONTAL ) {
650+
WARN_MSG(MOD_NAME "Orientation %d not handled!\n", val);
651+
}
652+
}
653+
654+
DEBUG_MSG(verbose, MOD_NAME "Skipping data after IFD0 marker (eg. Exif SubIFD)\n");
655+
// TODO: Exif Private Tags SubIFD
656+
}
657+
658+
/**
659+
* parse the header
660+
*
661+
* Currently only the basic validity is cheecked. If verbosity is set to higher value,
662+
* the basic tags from 0th IFD are printed out (not Exif SubIFD).
663+
*
664+
* JPEG Orientation is checked and of not horizontal, warning is issued.
665+
*/
666+
void
667+
gpujpeg_exif_parse(uint8_t** image, const uint8_t* image_end, int verbose)
668+
{
669+
#define HANDLE_ERROR(...) \
670+
WARN_MSG(__VA_ARGS__); \
671+
*image = image_start + length; \
672+
return
673+
674+
enum {
675+
EXIF_HDR_MIN_LEN = 18, // with empty 0th IFD
676+
};
677+
assert(image_end - *image > 2);
678+
uint8_t *image_start = *image;
679+
uint16_t length = read_2byte_be(image);
680+
if (length > image_end - *image - 2) {
681+
HANDLE_ERROR("Unexpected end of file!\n");
682+
}
683+
if (length < EXIF_HDR_MIN_LEN) {
684+
HANDLE_ERROR("Insufficient Exif header length %u!\n", (unsigned)length);
685+
}
686+
uint8_t exif[5];
687+
for (int i = 0; i < 5; ++i) {
688+
exif[i] = read_byte(image);
689+
}
690+
assert(strncmp((char *) exif, "Exif", sizeof exif) == 0); // otherwise fn shouldn't be called
691+
read_byte(image); // drop (padding)
692+
693+
uint8_t* const base = *image;
694+
695+
uint16_t endian_tag = read_2byte_be(image);
696+
uint16_t (*read_2byte)(uint8_t **) = read_2byte_be;
697+
uint32_t (*read_4byte)(uint8_t **) = read_4byte_be;
698+
699+
if ( endian_tag == ('I' << 8 | 'I') ) {
700+
DEBUG_MSG(verbose, "Little endian Exif detected.\n");
701+
read_2byte = read_2byte_le;
702+
read_4byte = read_4byte_le;
703+
}
704+
else if ( endian_tag == ('M' << 8 | 'M') ) {
705+
DEBUG_MSG(verbose, "Big endian Exif detected.\n");
706+
}
707+
else {
708+
HANDLE_ERROR("Unexpected endianity!\n");
709+
}
710+
uint16_t tiff_hdr = read_2byte(image);
711+
if (tiff_hdr != TIFF_HDR_TAG) {
712+
HANDLE_ERROR("Wrong TIFF tag, expected 0x%04x!\n", TIFF_HDR_TAG);
713+
}
714+
715+
uint32_t offset = read_4byte(image); // 0th IFD offset
716+
*image = base + offset;
717+
read_0th_ifd(image, image_end, verbose, read_2byte, read_4byte);
718+
719+
*image = image_start + length;
720+
#undef HANDLE_ERROR
721+
}

src/gpujpeg_exif.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232

3333
#ifndef __cplusplus
3434
#include <stdbool.h>
35+
#include <stdint.h>
3536
#endif // not defined __cplusplus
3637

3738
struct gpujpeg_exif_tags;
@@ -45,4 +46,7 @@ gpujpeg_exif_add_tag(struct gpujpeg_exif_tags** exif_tags, const char *cfg);
4546
void
4647
gpujpeg_exif_tags_destroy(struct gpujpeg_exif_tags* exif_tags);
4748

49+
void
50+
gpujpeg_exif_parse(uint8_t** image, const uint8_t* image_end, int verbose);
51+
4852
#endif // defined GPUJPEG_EXIF_H_6755D546_2A90_46DF_9ECA_22F575C3E7E3

src/gpujpeg_reader.c

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
#include "../libgpujpeg/gpujpeg_decoder.h"
3838
#include "../libgpujpeg/gpujpeg_encoder.h" // for enum gpujpeg_header_type
3939
#include "gpujpeg_decoder_internal.h"
40+
#include "gpujpeg_exif.h"
4041
#include "gpujpeg_marker.h"
4142
#include "gpujpeg_reader.h"
4243
#include "gpujpeg_util.h"
@@ -299,6 +300,32 @@ gpujpeg_reader_read_app0(uint8_t** image, const uint8_t* image_end, enum gpujpeg
299300
return -1;
300301
}
301302

303+
static void
304+
gpujpeg_reader_read_app1(uint8_t** image, const uint8_t* image_end, enum gpujpeg_header_type* header_type,
305+
enum gpujpeg_color_space* color_space, int verbose)
306+
{
307+
if ( image_end - *image < 2 ) {
308+
ERROR_MSG("Unexpected end of APP1 marker!\n");
309+
return;
310+
}
311+
312+
uint8_t type_tag[50];
313+
unsigned i = 0;
314+
const uint8_t* ptr = *image + 2;
315+
while ( *ptr != '\0' && ptr < image_end && i < sizeof type_tag - 1 ) {
316+
type_tag[i++] = *ptr++;
317+
}
318+
type_tag[i] = '\0';
319+
if ( strcmp((char *) type_tag, "Exif") == 0 ) {
320+
*color_space = GPUJPEG_YCBCR_BT601_256LVLS;
321+
*header_type = GPUJPEG_HEADER_EXIF;
322+
gpujpeg_exif_parse(image, image_end, verbose);
323+
return;
324+
}
325+
WARN_MSG("Skipping unsupported APP1 marker \"%s\"!\n", type_tag);
326+
gpujpeg_reader_skip_marker_content(image, image_end);
327+
}
328+
302329
/**
303330
* Read Adobe APP13 marker (used as a segment info by GPUJPEG)
304331
*
@@ -1346,6 +1373,9 @@ gpujpeg_reader_read_common_markers(uint8_t** image, const uint8_t* image_end, in
13461373
*color_space = GPUJPEG_YCBCR_BT601_256LVLS;
13471374
}
13481375
break;
1376+
case GPUJPEG_MARKER_APP1:
1377+
gpujpeg_reader_read_app1(image, image_end, header_type, color_space, log_level);
1378+
break;
13491379
case GPUJPEG_MARKER_APP8:
13501380
if ( gpujpeg_reader_read_app8(image, image_end, color_space, header_type, log_level, in_spiff) != 0 ) {
13511381
return -1;
@@ -1356,7 +1386,6 @@ gpujpeg_reader_read_common_markers(uint8_t** image, const uint8_t* image_end, in
13561386
return -1;
13571387
}
13581388
break;
1359-
case GPUJPEG_MARKER_APP1:
13601389
case GPUJPEG_MARKER_APP2:
13611390
case GPUJPEG_MARKER_APP3:
13621391
case GPUJPEG_MARKER_APP4:

0 commit comments

Comments
 (0)