Skip to content

Commit 590d1b8

Browse files
committed
Exif: unify handling
assume just unsinged or byte array It could be even just arrays of unsigned, but allowing C-strings for UNDEFINED and ASCII is more convenient.
1 parent 9d4abbf commit 590d1b8

File tree

1 file changed

+105
-121
lines changed

1 file changed

+105
-121
lines changed

src/gpujpeg_exif.c

Lines changed: 105 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131

3232
#include <assert.h> // for assert
3333
#include <ctype.h> // for isdigit
34+
#include <stddef.h>
3435
#include <stdint.h> // for uint8_t, uint32_t, uint16_t
3536
#include <stdio.h> // for printf
3637
#include <stdlib.h> // for size_t, NULL, free, abort, calloc
@@ -70,24 +71,26 @@ enum exif_tag_type {
7071
};
7172

7273
enum {
73-
T_NUMERIC = 1 << 0,
74-
T_UNSIGNED = 1 << 1,
74+
T_NUMERIC = 1 << 0, // single number in .uvalue
75+
T_UNSIGNED = 1 << 1, // number (or pair if rational) is unsigned
76+
T_BYTE_ARRAY = 1 << 2, // data stored in .csvalue
77+
T_RATIONAL = 1 << 3, // 2 items of .uvalue
7578
};
7679

77-
static const struct exif_tag_type_info_t
80+
static const struct
7881
{
7982
unsigned size;
8083
const char* name;
8184
unsigned type_flags;
8285
} exif_tag_type_info[] = {
83-
[ET_BYTE] = {1, "BYTE", T_NUMERIC|T_UNSIGNED},
84-
[ET_ASCII] = {0, "ASCII", 0 },
85-
[ET_SHORT] = {2, "SHORT", T_NUMERIC|T_UNSIGNED},
86-
[ET_LONG] = {4, "LONG", T_NUMERIC|T_UNSIGNED},
87-
[ET_RATIONAL] = {8, "RATIONAL", T_UNSIGNED},
88-
[ET_UNDEFINED] = {4, "UNDEFINED", },
89-
[ET_SLONG] = {4, "SLONG" , T_NUMERIC},
90-
[ET_SRATIONAL] = {8, "SRATIONAL", 0}
86+
[ET_BYTE] = {1, "BYTE", T_NUMERIC|T_UNSIGNED },
87+
[ET_ASCII] = {1, "ASCII", T_BYTE_ARRAY },
88+
[ET_SHORT] = {2, "SHORT", T_NUMERIC|T_UNSIGNED },
89+
[ET_LONG] = {4, "LONG", T_NUMERIC|T_UNSIGNED },
90+
[ET_RATIONAL] = {8, "RATIONAL", T_UNSIGNED|T_RATIONAL},
91+
[ET_UNDEFINED] = {1, "UNDEFINED", T_BYTE_ARRAY },
92+
[ET_SLONG] = {4, "SLONG" , T_NUMERIC },
93+
[ET_SRATIONAL] = {8, "SRATIONAL", T_RATIONAL },
9194
};
9295

9396
enum exif_tiff_tag {
@@ -112,23 +115,24 @@ enum exif_tiff_tag {
112115
const struct exif_tiff_tag_info_t {
113116
uint16_t id;
114117
enum exif_tag_type type;
118+
unsigned count;
115119
const char *name;
116120
} exif_tiff_tag_info[] = {
117-
[ETIFF_ORIENTATION] = {0x112, ET_SHORT, "Orientation" },
118-
[ETIFF_XRESOLUTION] = {0x11A, ET_RATIONAL, "XResolution" },
119-
[ETIFF_YRESOLUTION] = {0x11B, ET_RATIONAL, "YResolution" },
120-
[ETIFF_RESOLUTION_UNIT] = {0x128, ET_SHORT, "ResolutionUnit" },
121-
[ETIFF_SOFTWARE] = {0x131, ET_ASCII, "Sofware" },
122-
[ETIFF_DATE_TIME] = {0x132, ET_ASCII, "DateTime" },
123-
[ETIFF_YCBCR_POSITIONING] = {0x213, ET_SHORT, "YCbCrPositioning" },
124-
[ETIFF_EXIF_IFD_POINTER] = {0x8769, ET_LONG, "Exif IFD Pointer"},
121+
[ETIFF_ORIENTATION] = {0x112, ET_SHORT, 1, "Orientation" },
122+
[ETIFF_XRESOLUTION] = {0x11A, ET_RATIONAL, 1, "XResolution" },
123+
[ETIFF_YRESOLUTION] = {0x11B, ET_RATIONAL, 1, "YResolution" },
124+
[ETIFF_RESOLUTION_UNIT] = {0x128, ET_SHORT, 1, "ResolutionUnit" },
125+
[ETIFF_SOFTWARE] = {0x131, ET_ASCII, 0, "Sofware" },
126+
[ETIFF_DATE_TIME] = {0x132, ET_ASCII, 20, "DateTime" },
127+
[ETIFF_YCBCR_POSITIONING] = {0x213, ET_SHORT, 1, "YCbCrPositioning"},
128+
[ETIFF_EXIF_IFD_POINTER] = {0x8769, ET_LONG, 1, "Exif IFD Pointer"},
125129
// Exif SubIFD
126-
[EEXIF_EXIF_VERSION] = {0x9000, ET_UNDEFINED, "ExifVersion" },
127-
[EEXIF_COMPONENTS_CONFIGURATION] = {0x9101, ET_UNDEFINED, "ComponentConfiguration"},
128-
[EEXIF_FLASHPIX_VERSION] = {0xA000, ET_UNDEFINED, "FlashPixVersion" },
129-
[EEXIF_COLOR_SPACE] = {0xA001, ET_SHORT, "ColorSpace" },
130-
[EEXIF_PIXEL_X_DIMENSION] = {0xA002, ET_SHORT, "PixelXDimension" }, // type can be also LONG
131-
[EEXIF_PIXEL_Y_DIMENSION] = {0xA003, ET_SHORT, "PixelYDimension" }, // ditto
130+
[EEXIF_EXIF_VERSION] = {0x9000, ET_UNDEFINED, 4, "ExifVersion" },
131+
[EEXIF_COMPONENTS_CONFIGURATION] = {0x9101, ET_UNDEFINED, 4, "ComponentConfiguration"},
132+
[EEXIF_FLASHPIX_VERSION] = {0xA000, ET_UNDEFINED, 4, "FlashPixVersion" },
133+
[EEXIF_COLOR_SPACE] = {0xA001, ET_SHORT, 1, "ColorSpace" },
134+
[EEXIF_PIXEL_X_DIMENSION] = {0xA002, ET_SHORT, 1, "PixelXDimension" }, // type can be also LONG
135+
[EEXIF_PIXEL_Y_DIMENSION] = {0xA003, ET_SHORT, 1, "PixelYDimension" }, // ditto
132136
};
133137

134138
// misc constants
@@ -144,97 +148,70 @@ enum {
144148
};
145149

146150
union value_u {
147-
uint32_t uvalue;
148-
const char* csvalue; // ET_STRING or ET_UNKNOWN
149-
struct
150-
{
151-
uint32_t uvalue_num;
152-
uint32_t uvalue_den;
153-
} urational;
151+
const uint32_t *uvalue;
152+
const char* csvalue; // ET_STRING (must be 0-terminated) or ET_UNDEFINED
154153
};
155154

156155
static void
157-
write_exif_emit_string_tag(struct gpujpeg_writer* writer, uint16_t tag, const char* str, const uint8_t* start,
158-
uint8_t** end)
156+
write_exif_tag(struct gpujpeg_writer* writer, enum exif_tag_type type, uint16_t tag_id, unsigned count,
157+
union value_u val, const uint8_t* start, uint8_t** end)
159158
{
160-
const size_t count = strlen(str) + 1;
161-
gpujpeg_writer_emit_2byte(writer, tag);
162-
gpujpeg_writer_emit_2byte(writer, ET_ASCII);
163-
gpujpeg_writer_emit_4byte(writer, count);
164-
gpujpeg_writer_emit_4byte(writer, *end - start);;
165-
memcpy(*end, str, count);
166-
*end += count;
167-
}
159+
assert(type < ET_END);
160+
unsigned size = exif_tag_type_info[type].size;
168161

169-
static void
170-
write_exif_emit_4bytes_tag(struct gpujpeg_writer* writer, uint16_t tag, enum exif_tag_type type, uint32_t size,
171-
uint32_t val)
172-
{
173-
assert(size <= 4);
174-
gpujpeg_writer_emit_2byte(writer, tag);
162+
if ( type == ET_ASCII ) {
163+
if ( count == 0 ) {
164+
count = strlen(val.csvalue) + 1;
165+
}
166+
else {
167+
assert(count == strlen(val.csvalue) + 1);
168+
}
169+
}
170+
assert(count > 0);
171+
assert(size > 0);
172+
173+
gpujpeg_writer_emit_2byte(writer, tag_id);
175174
gpujpeg_writer_emit_2byte(writer, type);
176-
gpujpeg_writer_emit_4byte(writer, 1);
175+
gpujpeg_writer_emit_4byte(writer, count);
177176

178-
for (unsigned i = size; i > 0; --i) { // Exif 4.6.2 - left aligned value; we use big endian
179-
gpujpeg_writer_emit_byte(writer, (val >> 8 * (i - 1)) & 0xFFU);
177+
// we actually store rational numbers as a pair
178+
if ( (exif_tag_type_info[type].type_flags & T_RATIONAL) != 0 ) {
179+
count *= 2;
180+
size /= 2;
180181
}
181-
for (unsigned i = size; i < 4; ++i) {
182-
gpujpeg_writer_emit_byte(writer, 0);
182+
183+
const bool val_longer_than_4b = size * count > 4;
184+
uint8_t* return_pos = NULL;
185+
if ( val_longer_than_4b ) {
186+
gpujpeg_writer_emit_4byte(writer, *end - start);
187+
return_pos = writer->buffer_current;
188+
writer->buffer_current = *end;
183189
}
184-
}
185190

186-
static void
187-
write_exif_emit_lt_4b_tag(struct gpujpeg_writer* writer, uint16_t tag, enum exif_tag_type type, uint32_t size,
188-
union value_u val, const uint8_t* start,
189-
uint8_t** end)
190-
{
191-
gpujpeg_writer_emit_2byte(writer, tag);
192-
gpujpeg_writer_emit_2byte(writer, type);
193-
gpujpeg_writer_emit_4byte(writer, 1);
194-
gpujpeg_writer_emit_4byte(writer, *end - start);;
195-
196-
if (type == ET_RATIONAL) {
197-
assert(size == 8);
198-
uint32_t num = htobe32(val.urational.uvalue_num);
199-
uint32_t den = htobe32(val.urational.uvalue_den);
200-
memcpy(*end, &num, sizeof num);
201-
memcpy(*end + 4, &den, sizeof den);
191+
if ( (exif_tag_type_info[type].type_flags & T_BYTE_ARRAY) != 0 ) {
192+
assert(size == 1);
193+
for ( unsigned i = 0; i < count; ++i ) {
194+
gpujpeg_writer_emit_byte(writer, val.csvalue[i]);
195+
}
202196
}
203197
else {
204-
abort();
198+
for ( unsigned c = 0; c < count; ++c ) {
199+
for ( unsigned i = 0; i < size; ++i ) { // Exif 4.6.2 - left aligned value; we use big endian
200+
gpujpeg_writer_emit_byte(writer, (val.uvalue[c] >> 8 * (size - i - 1)) & 0xFFU);
201+
}
202+
}
205203
}
206204

207-
*end += size;
208-
}
209-
210-
static void
211-
write_exif_tag(struct gpujpeg_writer* writer, enum exif_tag_type type, uint16_t tag_id, union value_u val,
212-
const uint8_t* start, uint8_t** end)
213-
{
214-
assert(type < ET_END);
215-
const unsigned size = exif_tag_type_info[type].size;
216-
217-
// size for string is computed
218-
assert(size > 0 || type == ET_ASCII);
219-
220-
if ( type == ET_ASCII ) {
221-
write_exif_emit_string_tag(writer, tag_id, val.csvalue, start, end);
222-
return;
205+
if ( val_longer_than_4b ) {
206+
*end += (size_t)size * count;
207+
writer->buffer_current = return_pos;
223208
}
224-
if ( type == ET_UNDEFINED ) {
225-
assert(size == 4);
226-
gpujpeg_writer_emit_2byte(writer, tag_id);
227-
gpujpeg_writer_emit_2byte(writer, type);
228-
gpujpeg_writer_emit_4byte(writer, 4); // count - we have all 4 B, unsure if defined otherwise for other
229-
memcpy(writer->buffer_current, val.csvalue, 4);
230-
writer->buffer_current += 4;
231-
return;
232-
}
233-
if (size <= 4) {
234-
write_exif_emit_4bytes_tag(writer, tag_id, type, size, val.uvalue);
235-
return;
209+
else {
210+
// padding
211+
for ( unsigned i = size * count; i < 4; ++i ) {
212+
gpujpeg_writer_emit_byte(writer, 0);
213+
}
236214
}
237-
write_exif_emit_lt_4b_tag(writer, tag_id, type, size, val, start, end);
238215
}
239216

240217
struct tag_value
@@ -290,17 +267,17 @@ gpujpeg_write_ifd(struct gpujpeg_writer* writer, const uint8_t* start, size_t co
290267
const struct tag_value* info = &tags[i];
291268
union value_u value = info->value;
292269
if ( info->tag == ETIFF_EXIF_IFD_POINTER ) {
293-
value.uvalue = end - start;
270+
value.uvalue = (uint32_t[]) {end - start};
294271
}
295272
const struct exif_tiff_tag_info_t* t = &exif_tiff_tag_info[info->tag];
296273
assert(t->id >= last_tag_id);
297274
last_tag_id = t->id;
298-
write_exif_tag(writer, t->type, t->id, value, start, &end);
275+
write_exif_tag(writer, t->type, t->id, exif_tiff_tag_info[info->tag].count, value, start, &end);
299276
}
300277
if ( custom_tags != NULL ) { // add user custom tags
301278
for ( unsigned i = 0; i < custom_tags->count; ++i ) {
302-
write_exif_tag(writer, custom_tags->vals[i].type, custom_tags->vals[i].tag_id, custom_tags->vals[i].value,
303-
start, &end);
279+
write_exif_tag(writer, custom_tags->vals[i].type, custom_tags->vals[i].tag_id, 1,
280+
custom_tags->vals[i].value, start, &end);
304281
}
305282
// ensure custom_tags are in-ordered
306283
qsort(first_rec, (writer->buffer_current - first_rec) / IFD_ITEM_SZ, IFD_ITEM_SZ, ifd_sort);
@@ -335,13 +312,13 @@ gpujpeg_write_0th(struct gpujpeg_encoder* encoder, const uint8_t* start)
335312
struct tm buf;
336313
(void) strftime(date_time, sizeof date_time, "%Y:%m:%d %H:%M:%S", localtime_s(&now, &buf));
337314
struct tag_value tags[] = {
338-
{ETIFF_ORIENTATION, {.uvalue = ETIFF_ORIENT_HORIZONTAL}},
339-
{ETIFF_XRESOLUTION, {.urational = {DPI_DEFAULT, 1}} },
340-
{ETIFF_YRESOLUTION, {.urational = {DPI_DEFAULT, 1}} },
341-
{ETIFF_RESOLUTION_UNIT, {.uvalue = ETIFF_INCHES} },
342-
{ETIFF_SOFTWARE, {.csvalue = "GPUJPEG"} },
343-
{ETIFF_DATE_TIME , {.csvalue = date_time} },
344-
{ETIFF_YCBCR_POSITIONING, {.uvalue = ETIFF_CENTER} },
315+
{ETIFF_ORIENTATION, {.uvalue = (uint32_t[]){ETIFF_ORIENT_HORIZONTAL}}},
316+
{ETIFF_XRESOLUTION, {.uvalue = (uint32_t[]){DPI_DEFAULT, 1}} },
317+
{ETIFF_YRESOLUTION, {.uvalue = (uint32_t[]){DPI_DEFAULT, 1}} },
318+
{ETIFF_RESOLUTION_UNIT, {.uvalue = (uint32_t[]){ETIFF_INCHES}} },
319+
{ETIFF_SOFTWARE, {.csvalue = "GPUJPEG"} },
320+
{ETIFF_DATE_TIME , {.csvalue = date_time} },
321+
{ETIFF_YCBCR_POSITIONING, {.uvalue = (uint32_t[]){ETIFF_CENTER}} },
345322
{ETIFF_EXIF_IFD_POINTER, {0} }, // value will be set later
346323
};
347324
size_t tag_count = ARR_SIZE(tags);
@@ -357,12 +334,12 @@ gpujpeg_write_0th(struct gpujpeg_encoder* encoder, const uint8_t* start)
357334
static void gpujpeg_write_exif_ifd(struct gpujpeg_encoder* encoder, const uint8_t *start)
358335
{
359336
struct tag_value tags[] = {
360-
{EEXIF_EXIF_VERSION, {.csvalue = "0230"} }, // 2.30
361-
{EEXIF_COMPONENTS_CONFIGURATION, {.csvalue = "\1\2\3\0"} }, // YCbCr
362-
{EEXIF_FLASHPIX_VERSION, {.csvalue = "0100"} }, // "0100"
363-
{EEXIF_COLOR_SPACE, {.uvalue = ETIFF_SRGB} },
364-
{EEXIF_PIXEL_X_DIMENSION, {encoder->coder.param_image.width} },
365-
{EEXIF_PIXEL_Y_DIMENSION, {encoder->coder.param_image.height}},
337+
{EEXIF_EXIF_VERSION, {.csvalue = "0230"} }, // 2.30
338+
{EEXIF_COMPONENTS_CONFIGURATION, {.csvalue = "\1\2\3\0"} }, // YCbCr
339+
{EEXIF_FLASHPIX_VERSION, {.csvalue = "0100"} },
340+
{EEXIF_COLOR_SPACE, {.uvalue = (uint32_t[]){ETIFF_SRGB}} },
341+
{EEXIF_PIXEL_X_DIMENSION, {.uvalue = (uint32_t[]){encoder->coder.param_image.width}} },
342+
{EEXIF_PIXEL_Y_DIMENSION, {.uvalue = (uint32_t[]){encoder->coder.param_image.height}}},
366343
};
367344
size_t tag_count = ARR_SIZE(tags);
368345
const struct custom_exif_tags* custom_tags = NULL;
@@ -518,17 +495,24 @@ gpujpeg_exif_add_tag(struct gpujpeg_exif_tags** exif_tags, const char* cfg)
518495
new_size * sizeof (*exif_tags)->tags[table_idx].vals[0]);
519496
(*exif_tags)->tags[table_idx].vals[new_size - 1].tag_id = tag_id;
520497
(*exif_tags)->tags[table_idx].vals[new_size - 1].type = type;
521-
(*exif_tags)->tags[table_idx].vals[new_size - 1].value.uvalue = val;
498+
uint32_t *val_a = malloc(sizeof *val_a);
499+
*val_a = val;
500+
(*exif_tags)->tags[table_idx].vals[new_size - 1].value.uvalue = val_a;
522501

523502
return true;
524503
}
525504

526505
void
527506
gpujpeg_exif_tags_destroy(struct gpujpeg_exif_tags* exif_tags)
528507
{
529-
if (exif_tags == NULL) {
508+
if ( exif_tags == NULL ) {
530509
return;
531510
}
532-
free(exif_tags->tags[0].vals);
533-
free(exif_tags->tags[1].vals);
511+
for ( unsigned i = 0; i < CT_NUM; ++i ) {
512+
for (unsigned j = 0; i < exif_tags->tags[i].count; ++i) {
513+
free((void *) exif_tags->tags[i].vals[j].value.uvalue);
514+
}
515+
free(exif_tags->tags[i].vals);
516+
}
517+
free(exif_tags);
534518
}

0 commit comments

Comments
 (0)