@@ -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 */
250278static 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+
275333unsigned
276334get_exif_orientation (const struct gpujpeg_orientation * orientation )
277335{
@@ -285,7 +343,8 @@ get_exif_orientation(const struct gpujpeg_orientation* orientation)
285343}
286344
287345static 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
310371static void
311372gpujpeg_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
326390void
327391gpujpeg_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) {
413645static void
414646exif_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 }
0 commit comments