Skip to content

Commit 0dbd321

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 85d8a3e commit 0dbd321

File tree

3 files changed

+209
-3
lines changed

3 files changed

+209
-3
lines changed

src/gpujpeg_exif.c

Lines changed: 172 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,6 +152,7 @@ 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

152158
union value_u {
@@ -386,7 +392,7 @@ gpujpeg_writer_write_exif(struct gpujpeg_encoder* encoder)
386392
gpujpeg_writer_emit_byte(writer, 'M');
387393
gpujpeg_writer_emit_byte(writer, 'M');
388394

389-
gpujpeg_writer_emit_2byte(writer, 0x002a); // TIFF header
395+
gpujpeg_writer_emit_2byte(writer, TIFF_HDR_TAG); // TIFF header
390396
gpujpeg_writer_emit_4byte(writer, 0x08); // IFD offset - follows immediately
391397

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

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+
bool
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: 33 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,35 @@ 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 < 7 ) {
308+
WARN_MSG("Unexpected end of APP1 marker!\n");
309+
return;
310+
}
311+
312+
char type_tag[50];
313+
unsigned i = 0;
314+
const uint8_t*ptr = *image + 2;
315+
while ( *ptr != '\0' && ptr < image_end - 1 && i < sizeof type_tag ) {
316+
type_tag[i++] = *ptr++;
317+
}
318+
type_tag[i] = '\0';
319+
if ( strcmp(type_tag, "Exif") == 0 ) {
320+
if ( !gpujpeg_exif_parse(image, image_end, verbose) ) {
321+
gpujpeg_reader_skip_marker_content(image, image_end);
322+
return;
323+
}
324+
*color_space = GPUJPEG_YCBCR_BT601_256LVLS;
325+
*header_type = GPUJPEG_HEADER_EXIF;
326+
return;
327+
}
328+
WARN_MSG("Skipping unsupported APP1 marker \"%s\"!\n", type_tag);
329+
gpujpeg_reader_skip_marker_content(image, image_end);
330+
}
331+
302332
/**
303333
* Read Adobe APP13 marker (used as a segment info by GPUJPEG)
304334
*
@@ -1346,6 +1376,9 @@ gpujpeg_reader_read_common_markers(uint8_t** image, const uint8_t* image_end, in
13461376
*color_space = GPUJPEG_YCBCR_BT601_256LVLS;
13471377
}
13481378
break;
1379+
case GPUJPEG_MARKER_APP1:
1380+
gpujpeg_reader_read_app1(image, image_end, header_type, color_space, log_level);
1381+
break;
13491382
case GPUJPEG_MARKER_APP8:
13501383
if ( gpujpeg_reader_read_app8(image, image_end, color_space, header_type, log_level, in_spiff) != 0 ) {
13511384
return -1;
@@ -1356,7 +1389,6 @@ gpujpeg_reader_read_common_markers(uint8_t** image, const uint8_t* image_end, in
13561389
return -1;
13571390
}
13581391
break;
1359-
case GPUJPEG_MARKER_APP1:
13601392
case GPUJPEG_MARKER_APP2:
13611393
case GPUJPEG_MARKER_APP3:
13621394
case GPUJPEG_MARKER_APP4:

0 commit comments

Comments
 (0)