Skip to content

Commit ed138f8

Browse files
committed
readded Exif custom tag writer
Removed by the commit 1510ad2 because it was originally thought that this will be incompatible with the newly added generic metadata handling. But (at least now) thouse 2 ways are not incompatible so keep the Exif custom tag writer for now. I do not won't to extend the Exif stuff much in future because it doesn't seem that there is much useful stuff in the Exif metadata so higher complexity may not hurt. + add Exif to README.md
1 parent 6ff7fbc commit ed138f8

File tree

8 files changed

+263
-16
lines changed

8 files changed

+263
-16
lines changed

NEWS.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
- provide orientation metadata by decoder
55
- SPIFF: read/write the orientation
6-
- Exif: remove custom tag storing API
76

87
2025-10-20 - 0.27.9
98
----------

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,9 @@ Features
5050

5151
- uses NVIDIA CUDA platform
5252
- baseline Huffman 8-bit coding
53-
- use of **JFIF** file format by default, **Adobe** and **SPIFF** is supported as well (used by encoder
54-
if JPEG internal color space is not representable by JFIF - eg. limited range **YCbCr BT.709** or **RGB**)
53+
- uses **JFIF** file format by default, **Adobe** or **SPIFF** is used
54+
by encoder if JPEG internal color space is not representable by JFIF -
55+
eg. limited range **YCbCr BT.709** or **RGB**, **Exif** also supported
5556
- use of _restart markers_ that allow fast parallel encoding/decoding
5657
- Encoder by default creates _non-interleaved_ stream, optionally it can produce
5758
an _interleaved_ stream (all components in one scan) or/and subsampled stream.

libgpujpeg/gpujpeg_encoder.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,8 @@ gpujpeg_encoder_suggest_restart_interval(const struct gpujpeg_image_parameters*
225225

226226
/// input image is vertically flipped (bottom-up): values @ref GPUJPEG_VAL_TRUE or @ref GPUJPEG_VAL_FALSE
227227
#define GPUJPEG_ENC_OPT_FLIPPED_BOOL "enc_opt_flipped"
228+
/// custom exif tag in format <key>:TYPE=<value>
229+
#define GPUJPEG_ENC_OPT_EXIF_TAG "enc_exif_tag"
228230
/// set image orientation - syntax "<name>=<deg>[-]" or "help"; only if header supports (Exif, SPIFF)
229231
#define GPUJPEG_ENC_OPT_METADATA "enc_metadata"
230232

src/gpujpeg_encoder.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -770,6 +770,10 @@ gpujpeg_encoder_set_option(struct gpujpeg_encoder* encoder, const char *opt, con
770770
if ( strcmp(opt, GPUJPEG_ENC_OPT_CHANNEL_REMAP) == 0 ) {
771771
return gpujpeg_opt_set_channel_remap(&encoder->coder, val, GPUJPEG_ENC_OPT_CHANNEL_REMAP);
772772
}
773+
if ( strcmp(opt, GPUJPEG_ENC_OPT_EXIF_TAG) == 0 ) {
774+
encoder->header_type = GPUJPEG_HEADER_EXIF;
775+
return gpujpeg_exif_add_tag(&encoder->writer->exif_tags, val) ? GPUJPEG_NOERR : GPUJPEG_ERROR;
776+
}
773777
if ( strcmp(opt, GPUJPEG_ENC_OPT_METADATA) == 0 ) {
774778
return add_metadata(&encoder->writer->metadata, val);
775779
}
@@ -786,6 +790,7 @@ gpujpeg_encoder_print_options() {
786790
"] - whether is the input image should be vertically flipped (prior encode)\n");
787791
printf("\t" GPUJPEG_ENC_OPT_CHANNEL_REMAP "=XYZ[W] - input channel mapping, eg. '210F' for GBRX,\n"
788792
"\t\t'210' for GBR; special placeholders 'F' and 'Z' to set a channel to all-ones or all-zeros\n");
793+
printf("\t" GPUJPEG_ENC_OPT_EXIF_TAG "=<key>=<value>|help - custom EXIF tag (use help for syntax)\n");
789794
printf("\t" GPUJPEG_ENC_OPT_METADATA "=<key>=<value>|help - set image metadata\n");
790795
}
791796

src/gpujpeg_exif.c

Lines changed: 243 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -244,18 +244,49 @@ struct tag_value
244244
union value_u value;
245245
};
246246

247+
/// custom exif tag values
248+
struct custom_tag_value
249+
{
250+
uint16_t tag_id;
251+
enum exif_tag_type type;
252+
union value_u value;
253+
size_t val_count;
254+
};
255+
enum { CT_TIFF, CT_EXIF, CT_NUM };
256+
/// custom exif tags given by user
257+
struct gpujpeg_exif_tags {
258+
struct custom_exif_tags
259+
{
260+
struct custom_tag_value *vals;
261+
size_t count;
262+
} tags[CT_NUM];
263+
};
264+
265+
static int
266+
ifd_sort(const void* a, const void* b)
267+
{
268+
const uint8_t* aa = a;
269+
const uint8_t *bb = b;
270+
int a_tag_id = aa[0] << 8 | aa[1];
271+
int b_tag_id = bb[0] << 8 | bb[1];
272+
return a_tag_id - b_tag_id;
273+
}
274+
247275
/**
248276
* @param tags array of tags, should be ordered awcending according to exif_tiff_tag_info_t.id
249277
*/
250278
static void
251-
gpujpeg_write_ifd(struct gpujpeg_writer* writer, const uint8_t* start, size_t count, const struct tag_value tags[])
279+
gpujpeg_write_ifd(struct gpujpeg_writer* writer, const uint8_t* start, size_t count,
280+
const struct tag_value tags[], const struct custom_exif_tags *custom_tags)
252281
{
253282
enum {
254283
EXIF_IFD_NUM_SZ = 2,
255284
};
256-
uint8_t* end = writer->buffer_current + EXIF_IFD_NUM_SZ + (count * IFD_ITEM_SZ) + NEXT_IFD_PTR_SZ;
257-
gpujpeg_writer_emit_2byte(writer, count); // IFD Item Count
285+
size_t count_all = count + custom_tags->count;
286+
uint8_t* end = writer->buffer_current + EXIF_IFD_NUM_SZ + (count_all * IFD_ITEM_SZ) + NEXT_IFD_PTR_SZ;
287+
gpujpeg_writer_emit_2byte(writer, count_all); // IFD Item Count
258288

289+
uint8_t *first_rec = writer->buffer_current;
259290
unsigned last_tag_id = 0;
260291
for ( unsigned i = 0; i < count; ++i ) {
261292
const struct tag_value* info = &tags[i];
@@ -268,10 +299,37 @@ gpujpeg_write_ifd(struct gpujpeg_writer* writer, const uint8_t* start, size_t co
268299
last_tag_id = t->id;
269300
write_exif_tag(writer, t->type, t->id, exif_tiff_tag_info[info->tag].count, value, start, &end);
270301
}
302+
if ( custom_tags != NULL ) { // add user custom tags
303+
for ( unsigned i = 0; i < custom_tags->count; ++i ) {
304+
write_exif_tag(writer, custom_tags->vals[i].type, custom_tags->vals[i].tag_id,
305+
custom_tags->vals[i].val_count, custom_tags->vals[i].value, start, &end);
306+
}
307+
// ensure custom_tags are in-ordered
308+
qsort(first_rec, (writer->buffer_current - first_rec) / IFD_ITEM_SZ, IFD_ITEM_SZ, ifd_sort);
309+
}
271310
gpujpeg_writer_emit_4byte(writer, 0); // Next IFD Offset (none)
272311
writer->buffer_current = end; // jump after the section Value longer than 4Byte of 0th IFD
273312
}
274313

314+
/**
315+
* from tags remove the items that are overriden by custom_tags
316+
*/
317+
static size_t
318+
remove_overriden(size_t count, struct tag_value tags[STAT_ARR_DCL(count)],
319+
const struct custom_exif_tags custom_tags[STAT_ARR_DCL(1)])
320+
{
321+
for ( unsigned i = 0; i < custom_tags->count; ++i ) {
322+
for ( unsigned j = 0; j < count; ++j ) {
323+
if ( custom_tags->vals[i].tag_id == exif_tiff_tag_info[tags[j].tag].id ) {
324+
memmove(tags + j, tags + j + 1, (count - j - 1) * sizeof(tags[0]));
325+
count -= 1;
326+
break;
327+
}
328+
}
329+
}
330+
return count;
331+
}
332+
275333
unsigned
276334
get_exif_orientation(const struct gpujpeg_orientation* orientation)
277335
{
@@ -285,7 +343,8 @@ get_exif_orientation(const struct gpujpeg_orientation* orientation)
285343
}
286344

287345
static void
288-
gpujpeg_write_0th(struct gpujpeg_writer* writer, const uint8_t* start, const struct gpujpeg_image_metadata* metadata)
346+
gpujpeg_write_0th(struct gpujpeg_writer* writer, const uint8_t* start, const struct gpujpeg_image_metadata* metadata,
347+
const struct custom_exif_tags* custom_tags)
289348
{
290349
char date_time[] = " : : : : "; // unknown val by Exif 2.3
291350
time_t now = time(NULL);
@@ -304,12 +363,15 @@ gpujpeg_write_0th(struct gpujpeg_writer* writer, const uint8_t* start, const str
304363
if ( metadata->vals[GPUJPEG_METADATA_ORIENTATION].set ) {
305364
orientation = get_exif_orientation(&metadata->vals[GPUJPEG_METADATA_ORIENTATION].orient);
306365
}
307-
gpujpeg_write_ifd(writer, start, ARR_SIZE(tags), tags);
366+
size_t tag_count = ARR_SIZE(tags);
367+
tag_count = remove_overriden(tag_count, tags, custom_tags);
368+
gpujpeg_write_ifd(writer, start, tag_count, tags, custom_tags);
308369
}
309370

310371
static void
311372
gpujpeg_write_exif_ifd(struct gpujpeg_writer* writer, const uint8_t* start,
312-
const struct gpujpeg_image_parameters* param_image)
373+
const struct gpujpeg_image_parameters* param_image,
374+
const struct custom_exif_tags* custom_tags)
313375
{
314376
struct tag_value tags[] = {
315377
{EEXIF_EXIF_VERSION, {.csvalue = "0230"} }, // 2.30
@@ -319,14 +381,16 @@ gpujpeg_write_exif_ifd(struct gpujpeg_writer* writer, const uint8_t* start,
319381
{EEXIF_PIXEL_X_DIMENSION, {.uvalue = (uint32_t[]){param_image->width}} },
320382
{EEXIF_PIXEL_Y_DIMENSION, {.uvalue = (uint32_t[]){param_image->height}}},
321383
};
322-
gpujpeg_write_ifd(writer, start, ARR_SIZE(tags), tags);
384+
size_t tag_count = ARR_SIZE(tags);
385+
tag_count = remove_overriden(tag_count, tags, custom_tags);
386+
gpujpeg_write_ifd(writer, start, ARR_SIZE(tags), tags, custom_tags);
323387
}
324388

325389
/// writes EXIF APP1 marker
326390
void
327391
gpujpeg_writer_write_exif(struct gpujpeg_writer* writer, const struct gpujpeg_parameters* param,
328392
const struct gpujpeg_image_parameters* param_image,
329-
const struct gpujpeg_image_metadata* metadata)
393+
const struct gpujpeg_image_metadata* metadata, const struct gpujpeg_exif_tags* custom_tags)
330394
{
331395
if ( param->color_space_internal != GPUJPEG_YCBCR_BT601_256LVLS ) {
332396
WARN_MSG("[Exif] Color space %s currently not recorded, assumed %s (report)\n",
@@ -357,15 +421,183 @@ gpujpeg_writer_write_exif(struct gpujpeg_writer* writer, const struct gpujpeg_pa
357421
gpujpeg_writer_emit_2byte(writer, TIFF_HDR_TAG); // TIFF header
358422
gpujpeg_writer_emit_4byte(writer, 0x08); // IFD offset - follows immediately
359423

360-
gpujpeg_write_0th(writer, start, metadata);
361-
gpujpeg_write_exif_ifd(writer, start, param_image);
424+
const struct custom_exif_tags* tiff_tags = &(const struct custom_exif_tags){.count = 0};
425+
const struct custom_exif_tags* exif_tags = &(const struct custom_exif_tags){.count = 0};
426+
if ( custom_tags != NULL ) {
427+
tiff_tags = &custom_tags->tags[CT_TIFF];
428+
exif_tags = &custom_tags->tags[CT_EXIF];
429+
}
430+
431+
gpujpeg_write_0th(writer, start, metadata, tiff_tags);
432+
gpujpeg_write_exif_ifd(writer, start, param_image, exif_tags);
362433

363434
// set the marker length
364435
size_t length = writer->buffer_current - length_p;
365436
length_p[0] = length >> 8;
366437
length_p[1] = length;
367438
}
368439

440+
static bool
441+
get_numeric_tag_type(char** endptr, long* tag_id, enum exif_tag_type* type)
442+
{
443+
*tag_id = strtol(*endptr, endptr, 0);
444+
if ( **endptr != ':' ) {
445+
ERROR_MSG("Error parsing Exif tag ID or missing type!\n");
446+
return false;
447+
}
448+
*endptr += 1;
449+
for ( unsigned i = ET_NONE + 1; i < ET_END; ++i ) {
450+
if ( exif_tag_type_info[i].name == NULL ) { // unset/invalid type
451+
continue;
452+
}
453+
size_t len = strlen(exif_tag_type_info[i].name);
454+
if ( strncasecmp(*endptr, exif_tag_type_info[i].name, len) == 0 ) {
455+
*type = i;
456+
*endptr += len;
457+
break;
458+
}
459+
}
460+
if ( type == ET_NONE ) {
461+
ERROR_MSG("Error parsing Exif tag type!\n");
462+
return false;
463+
}
464+
if ( **endptr != '=' ) {
465+
ERROR_MSG("Error parsing Exif - missing value!\n");
466+
return false;
467+
}
468+
return true;
469+
}
470+
471+
static void
472+
usage()
473+
{
474+
printf("Exif value syntax:\n"
475+
"\t" GPUJPEG_ENC_OPT_EXIF_TAG "=<ID>:<type>=<value>\n"
476+
"\t" GPUJPEG_ENC_OPT_EXIF_TAG "=<name>=<value>\n"
477+
"\t\tname must be a tag name known to GPUJPEG\n");
478+
printf("\n");
479+
printf("If mulitple numeric values required, separate with a comma; rationals are in format num/den.\n");
480+
printf("UNDEFINED and ASCII should be raw strings.\n");
481+
printf("\n");
482+
printf("recognized tag name (type, count):\n");
483+
for ( unsigned i = 0; i < ARR_SIZE(exif_tiff_tag_info); ++i ) {
484+
printf("\t- %s (%s, %u)\n", exif_tiff_tag_info[i].name, exif_tag_type_info[exif_tiff_tag_info[i].type].name,
485+
exif_tiff_tag_info[i].count);
486+
}
487+
}
488+
489+
/**
490+
* add user-provided Exif tag
491+
*/
492+
bool
493+
gpujpeg_exif_add_tag(struct gpujpeg_exif_tags** exif_tags, const char* cfg)
494+
{
495+
if (strcmp(cfg, "help") == 0) {
496+
usage();
497+
return false;
498+
}
499+
500+
char *endptr = (char *) cfg;
501+
long tag_id = 0;
502+
enum exif_tag_type type = ET_NONE;
503+
504+
if (isdigit(*endptr)) {
505+
if ( !get_numeric_tag_type(&endptr, &tag_id, &type) ) {
506+
return false;
507+
}
508+
} else {
509+
for (unsigned i = 0; i < ARR_SIZE(exif_tiff_tag_info); ++i) {
510+
size_t len = strlen(exif_tiff_tag_info[i].name);
511+
if ( strncasecmp(endptr, exif_tiff_tag_info[i].name, len) == 0 ) {
512+
tag_id = exif_tiff_tag_info[i].id;
513+
type = exif_tiff_tag_info[i].type;
514+
endptr += len;
515+
}
516+
}
517+
if (*endptr != '=') {
518+
ERROR_MSG("[Exif] Wrong tag name or missing value!\n");
519+
return false;
520+
}
521+
}
522+
523+
endptr += 1;
524+
void* val_alloc = NULL;
525+
size_t val_count = 0;
526+
if ( (exif_tag_type_info[type].type_flags & T_BYTE_ARRAY) != 0 ) {
527+
char* val_str = strdup(endptr);
528+
val_alloc = val_str;
529+
val_count = strlen(val_str);
530+
endptr += val_count;
531+
if (type == ET_ASCII) {
532+
val_count += 1; // include '\0'
533+
}
534+
}
535+
else {
536+
do {
537+
if ( *endptr == ',' ) {
538+
endptr += 1;
539+
}
540+
if ( (exif_tag_type_info[type].type_flags & T_NUMERIC) != 0U ) {
541+
unsigned long long val = strtoull(endptr, &endptr, 0);
542+
val_count += 1;
543+
uint32_t* val_a = realloc(val_alloc, val_count * sizeof *val_a);
544+
val_a[val_count - 1] = val;
545+
val_alloc = val_a;
546+
}
547+
else if ( (exif_tag_type_info[type].type_flags & T_RATIONAL) != 0U ) {
548+
unsigned long long num = strtoull(endptr, &endptr, 0);
549+
if ( *endptr != '/' ) {
550+
ERROR_MSG("[Exif] Malformed rational, expected '/', got '%c'!\n", *endptr);
551+
}
552+
endptr += 1;
553+
unsigned long long den = strtoull(endptr, &endptr, 0);
554+
val_count += 1;
555+
uint32_t* val_a = realloc(val_alloc, val_count * 2 * sizeof *val_a);
556+
val_a[2 * (val_count - 1)] = num;
557+
val_a[(2 * (val_count - 1)) + 1] = den;
558+
val_alloc = val_a;
559+
}
560+
} while ( *endptr == ',' );
561+
}
562+
563+
if ( *endptr != '\0' ) {
564+
free(val_alloc);
565+
ERROR_MSG("Trainling data in Exif value: %s\n", endptr);
566+
return false;
567+
}
568+
569+
if (*exif_tags == NULL) {
570+
*exif_tags = calloc(1, sizeof **exif_tags);
571+
}
572+
573+
int table_idx = tag_id < EEXIF_FIRST ? CT_TIFF : CT_EXIF;
574+
size_t new_size = (*exif_tags)->tags[table_idx].count += 1;
575+
(*exif_tags)->tags[table_idx].vals = realloc((*exif_tags)->tags[table_idx].vals,
576+
new_size * sizeof (*exif_tags)->tags[table_idx].vals[0]);
577+
(*exif_tags)->tags[table_idx].vals[new_size - 1].tag_id = tag_id;
578+
(*exif_tags)->tags[table_idx].vals[new_size - 1].type = type;
579+
assert(val_alloc != NULL);
580+
(*exif_tags)->tags[table_idx].vals[new_size - 1].value.uvalue = val_alloc;
581+
(*exif_tags)->tags[table_idx].vals[new_size - 1].val_count = val_count;
582+
583+
return true;
584+
}
585+
586+
void
587+
gpujpeg_exif_tags_destroy(struct gpujpeg_exif_tags* exif_tags)
588+
{
589+
if ( exif_tags == NULL ) {
590+
return;
591+
}
592+
for ( unsigned i = 0; i < CT_NUM; ++i ) {
593+
for (unsigned j = 0; i < exif_tags->tags[i].count; ++i) {
594+
free((void *) exif_tags->tags[i].vals[j].value.uvalue);
595+
}
596+
free(exif_tags->tags[i].vals);
597+
}
598+
free(exif_tags);
599+
}
600+
369601
////////////////////////////////////////////////////////////////////////////////
370602
// READER //
371603
////////////////////////////////////////////////////////////////////////////////
@@ -413,7 +645,7 @@ read_4byte_le(uint8_t** image) {
413645
static void
414646
exif_orieintation_to_metadata(unsigned val, struct gpujpeg_image_metadata* parsed)
415647
{
416-
if (val == 0 || val > 8) {
648+
if ( val == 0 || val > 8 ) {
417649
WARN_MSG(MOD_NAME "Flawed orientation value %d! Should be 1-8...\n", val);
418650
return;
419651
}

src/gpujpeg_exif.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,13 @@ struct gpujpeg_writer;
4242
void
4343
gpujpeg_writer_write_exif(struct gpujpeg_writer* writer, const struct gpujpeg_parameters* param,
4444
const struct gpujpeg_image_parameters* param_image,
45-
const struct gpujpeg_image_metadata* metadata);
45+
const struct gpujpeg_image_metadata* metadata,
46+
const struct gpujpeg_exif_tags* custom_tags);
47+
48+
bool
49+
gpujpeg_exif_add_tag(struct gpujpeg_exif_tags** exif_tags, const char *cfg);
50+
void
51+
gpujpeg_exif_tags_destroy(struct gpujpeg_exif_tags* exif_tags);
4652

4753
void
4854
gpujpeg_exif_parse(uint8_t** image, const uint8_t* image_end, int verbose, struct gpujpeg_image_metadata* metadata);

0 commit comments

Comments
 (0)