Skip to content

Commit 93cd539

Browse files
committed
tmp
1 parent bea94b8 commit 93cd539

File tree

7 files changed

+260
-14
lines changed

7 files changed

+260
-14
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
----------

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)