Skip to content

Commit 3be5a2c

Browse files
committed
encoder: allow passing custom tags
1 parent b43e8a5 commit 3be5a2c

File tree

6 files changed

+184
-22
lines changed

6 files changed

+184
-22
lines changed

libgpujpeg/gpujpeg_encoder.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,9 @@ gpujpeg_encoder_suggest_restart_interval(const struct gpujpeg_image_parameters*
236236
/// input image is vertically flipped (bottom-up): values @ref GPUJPEG_VAL_TRUE or @ref GPUJPEG_VAL_FALSE
237237
#define GPUJPEG_ENC_OPT_FLIPPED_BOOL "enc_opt_flipped"
238238

239+
/// custom exif tag in format <key>:TYPE=<value>
240+
#define GPUJPEG_ENC_OPT_EXIF_TAG "enc_exif_tag"
241+
239242
/**
240243
* remap channel order
241244
*

src/gpujpeg_encoder.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
#include "gpujpeg_preprocessor.h"
4444
#include "gpujpeg_dct_cpu.h"
4545
#include "gpujpeg_dct_gpu.h"
46+
#include "gpujpeg_exif.h"
4647
#include "gpujpeg_huffman_cpu_encoder.h"
4748
#include "gpujpeg_huffman_gpu_encoder.h"
4849
#include "gpujpeg_marker.h"
@@ -741,6 +742,11 @@ gpujpeg_encoder_set_option(struct gpujpeg_encoder* encoder, const char *opt, con
741742
if ( strcmp(opt, GPUJPEG_ENC_OPT_CHANNEL_REMAP) == 0 ) {
742743
return gpujpeg_opt_set_channel_remap(&encoder->coder, val, GPUJPEG_ENC_OPT_CHANNEL_REMAP);
743744
}
745+
if ( strcmp(opt, GPUJPEG_ENC_OPT_EXIF_TAG) == 0 ) {
746+
encoder->header_type = GPUJPEG_HEADER_EXIF;
747+
return gpujpeg_exif_add_tag(&encoder->writer->exif_tags, val) ? GPUJPEG_NOERR : GPUJPEG_ERROR;
748+
749+
}
744750
ERROR_MSG("Invalid encoder option: %s!\n", opt);
745751
return GPUJPEG_ERROR;
746752
}
@@ -754,6 +760,7 @@ gpujpeg_encoder_print_options() {
754760
"] - whether is the input image should be vertically flipped (prior encode)\n");
755761
printf("\t" GPUJPEG_ENC_OPT_CHANNEL_REMAP "=XYZ[W] - input channel mapping, eg. '210F' for GBRX,\n"
756762
"\t\t'210' for GBR; special placeholders 'F' and 'Z' to set a channel to all-ones or all-zeros\n");
763+
printf("\t" GPUJPEG_ENC_OPT_EXIF_TAG "=<ID>:<type>=<value> - custom EXIF tag\n");
757764
}
758765

759766
/* Documented at declaration */

src/gpujpeg_exif.c

Lines changed: 159 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,18 @@
3434
#include <stdlib.h>
3535
#include <string.h>
3636

37+
#ifdef _WIN32
38+
#define strncasecmp _strnicmp
39+
#endif
40+
3741
#include "compat/endian.h"
3842
#include "gpujpeg_common_internal.h"
3943
#include "gpujpeg_encoder_internal.h"
4044
#include "gpujpeg_marker.h"
4145
#include "gpujpeg_writer.h"
4246

4347
enum exif_tag_type {
48+
ET_NONE = 0,
4449
ET_BYTE = 1, ///< 8-bit unsigned integer
4550
ET_ASCII = 2, ///< NULL-terminated ASCII string
4651
ET_SHORT = 3, ///< 16-bit unsigned integer
@@ -49,13 +54,29 @@ enum exif_tag_type {
4954
ET_UNDEFINED = 7, ///< An 8-bit byte that may take any value depending on the field definition.
5055
ET_SLONG = 9, ///< 32-bit signed integer
5156
ET_SRATIONAL = 10, ///< two SLONGs - first numerator, second denominator
57+
ET_END,
58+
};
59+
60+
enum {
61+
T_NUMERIC = 1 << 0,
62+
T_UNSIGNED = 1 << 1,
5263
};
5364

54-
const struct exif_tag_type_info_t
65+
static const struct exif_tag_type_info_t
5566
{
5667
unsigned size;
57-
} exif_tag_type_info[] = {[ET_BYTE] = {1}, [ET_ASCII] = {0}, [ET_SHORT] = {2}, [ET_LONG] = {4},
58-
[ET_RATIONAL] = {8}, [ET_UNDEFINED] = {4}, [ET_SLONG] = {4}, [ET_SRATIONAL] = {8}};
68+
const char* name;
69+
unsigned type_flags;
70+
} exif_tag_type_info[] = {
71+
[ET_BYTE] = {1, "BYTE", T_NUMERIC|T_UNSIGNED},
72+
[ET_ASCII] = {0, "ASCII", 0 },
73+
[ET_SHORT] = {2, "SHORT", T_NUMERIC|T_UNSIGNED},
74+
[ET_LONG] = {4, "LONG", T_NUMERIC|T_UNSIGNED},
75+
[ET_RATIONAL] = {8, "RATIONAL", T_UNSIGNED},
76+
[ET_UNDEFINED] = {4, "UNDEFINED", },
77+
[ET_SLONG] = {4, "SLONG" , T_NUMERIC},
78+
[ET_SRATIONAL] = {8, "SRATIONAL", 0}
79+
};
5980

6081
enum exif_tiff_tag {
6182
// 0th IFD TIFF Tags
@@ -100,6 +121,7 @@ enum {
100121
ETIFF_INCHES = 2,
101122
NEXT_IFD_PTR_SZ = 4,
102123
IFD_ITEM_SZ = 12,
124+
EEXIF_FIRST = 0x827A, // (Exposure time) first tag id of Exif Private Tags
103125
};
104126

105127
union value_u {
@@ -167,34 +189,33 @@ write_exif_emit_lt_4b_tag(struct gpujpeg_writer* writer, uint16_t tag, enum exif
167189
}
168190

169191
static void
170-
write_exif_tag(struct gpujpeg_writer* writer, enum exif_tiff_tag tag, union value_u val, const uint8_t* start,
171-
uint8_t** end)
192+
write_exif_tag(struct gpujpeg_writer* writer, enum exif_tag_type type, uint16_t tag_id, union value_u val,
193+
const uint8_t* start, uint8_t** end)
172194
{
173-
174-
const struct exif_tiff_tag_info_t* t = &exif_tiff_tag_info[tag];
175-
const unsigned size = exif_tag_type_info[t->type].size;
195+
assert(type < ET_END);
196+
const unsigned size = exif_tag_type_info[type].size;
176197

177198
// size for string is computed
178-
assert(size > 0 || t->type == ET_ASCII);
199+
assert(size > 0 || type == ET_ASCII);
179200

180-
if ( t->type == ET_ASCII ) {
181-
write_exif_emit_string_tag(writer, t->id, val.csvalue, start, end);
201+
if ( type == ET_ASCII ) {
202+
write_exif_emit_string_tag(writer, tag_id, val.csvalue, start, end);
182203
return;
183204
}
184-
if ( t->type == ET_UNDEFINED ) {
205+
if ( type == ET_UNDEFINED ) {
185206
assert(size == 4);
186-
gpujpeg_writer_emit_2byte(writer, t->id);
187-
gpujpeg_writer_emit_2byte(writer, t->type);
207+
gpujpeg_writer_emit_2byte(writer, tag_id);
208+
gpujpeg_writer_emit_2byte(writer, type);
188209
gpujpeg_writer_emit_4byte(writer, 4); // count - we have all 4 B, unsure if defined otherwise for other
189210
memcpy(writer->buffer_current, val.csvalue, 4);
190211
writer->buffer_current += 4;
191212
return;
192213
}
193214
if (size <= 4) {
194-
write_exif_emit_4bytes_tag(writer, t->id, t->type, size, val.uvalue);
215+
write_exif_emit_4bytes_tag(writer, tag_id, type, size, val.uvalue);
195216
return;
196217
}
197-
write_exif_emit_lt_4b_tag(writer, t->id, t->type, size, val, start, end);
218+
write_exif_emit_lt_4b_tag(writer, tag_id, type, size, val, start, end);
198219
}
199220

200221
struct tag_value
@@ -203,26 +224,67 @@ struct tag_value
203224
union value_u value;
204225
};
205226

227+
/// custom exif tag values
228+
struct custom_tag_value
229+
{
230+
uint16_t tag_id;
231+
enum exif_tag_type type;
232+
union value_u value;
233+
};
234+
enum { CT_TIFF, CT_EXIF, CT_NUM };
235+
/// custom exif tags given by user
236+
struct gpujpeg_exif_tags {
237+
struct custom_exif_tags
238+
{
239+
struct custom_tag_value *vals;
240+
size_t count;
241+
} tags[CT_NUM];
242+
};
243+
244+
static int
245+
ifd_sort(const void* a, const void* b)
246+
{
247+
const uint8_t* aa = a;
248+
const uint8_t *bb = b;
249+
int a_tag_id = aa[0] << 8 | aa[1];
250+
int b_tag_id = bb[0] << 8 | bb[1];
251+
return a_tag_id - b_tag_id;
252+
}
253+
206254
/**
207255
* @param tags array of tags, should be ordered awcending according to exif_tiff_tag_info_t.id
208256
*/
209257
static void
210258
gpujpeg_write_ifd(struct gpujpeg_writer* writer, const uint8_t* start, size_t count,
211-
const struct tag_value tags[])
259+
const struct tag_value tags[], const struct custom_exif_tags *custom_tags)
212260
{
213261
enum {
214262
EXIF_IFD_NUM_SZ = 2,
215263
};
216-
uint8_t *end = writer->buffer_current + EXIF_IFD_NUM_SZ + (count * IFD_ITEM_SZ) + NEXT_IFD_PTR_SZ;
217-
gpujpeg_writer_emit_2byte(writer, count); // IFD Item Count
264+
size_t count_all = count + custom_tags->count;
265+
uint8_t* end = writer->buffer_current + EXIF_IFD_NUM_SZ + (count_all * IFD_ITEM_SZ) + NEXT_IFD_PTR_SZ;
266+
gpujpeg_writer_emit_2byte(writer, count_all); // IFD Item Count
218267

268+
uint8_t *first_rec = writer->buffer_current;
269+
unsigned last_tag_id = 0;
219270
for ( unsigned i = 0; i < count; ++i ) {
220271
const struct tag_value* info = &tags[i];
221272
union value_u value = info->value;
222273
if ( info->tag == ETIFF_EXIF_IFD_POINTER ) {
223274
value.uvalue = end - start;
224275
}
225-
write_exif_tag(writer, info->tag, value, start, &end);
276+
const struct exif_tiff_tag_info_t* t = &exif_tiff_tag_info[info->tag];
277+
assert(t->id >= last_tag_id);
278+
last_tag_id = t->id;
279+
write_exif_tag(writer, t->type, t->id, value, start, &end);
280+
}
281+
if ( custom_tags != NULL ) { // add user custom tags
282+
for ( unsigned i = 0; i < custom_tags->count; ++i ) {
283+
write_exif_tag(writer, custom_tags->vals[i].type, custom_tags->vals[i].tag_id, custom_tags->vals[i].value,
284+
start, &end);
285+
}
286+
// ensure custom_tags are in-ordered
287+
qsort(first_rec, (writer->buffer_current - first_rec) / IFD_ITEM_SZ, IFD_ITEM_SZ, ifd_sort);
226288
}
227289
gpujpeg_writer_emit_4byte(writer, 0); // Next IFD Offset (none)
228290
writer->buffer_current = end; // jump after the section Value longer than 4Byte of 0th IFD
@@ -239,8 +301,10 @@ gpujpeg_write_0th(struct gpujpeg_encoder* encoder, const uint8_t* start)
239301
{ETIFF_YCBCR_POSITIONING, {.uvalue = ETIFF_CENTER}}, // center
240302
{ETIFF_EXIF_IFD_POINTER, {0} }, // value later; should be last
241303
};
304+
const struct custom_exif_tags* custom_tags =
305+
encoder->writer->exif_tags != NULL ? &encoder->writer->exif_tags->tags[CT_TIFF] : NULL;
242306

243-
gpujpeg_write_ifd(encoder->writer, start, ARR_SIZE(tags), tags);
307+
gpujpeg_write_ifd(encoder->writer, start, ARR_SIZE(tags), tags, custom_tags);
244308
}
245309

246310
static void gpujpeg_write_exif_ifd(struct gpujpeg_encoder* encoder, const uint8_t *start)
@@ -253,7 +317,10 @@ static void gpujpeg_write_exif_ifd(struct gpujpeg_encoder* encoder, const uint8_
253317
{EEXIF_PIXEL_X_DIMENSION, {encoder->coder.param_image.width} },
254318
{EEXIF_PIXEL_Y_DIMENSION, {encoder->coder.param_image.height}},
255319
};
256-
gpujpeg_write_ifd(encoder->writer, start, ARR_SIZE(tags), tags);
320+
const struct custom_exif_tags* custom_tags =
321+
encoder->writer->exif_tags != NULL ? &encoder->writer->exif_tags->tags[CT_EXIF] : NULL;
322+
323+
gpujpeg_write_ifd(encoder->writer, start, ARR_SIZE(tags), tags, custom_tags);
257324
}
258325

259326

@@ -300,3 +367,73 @@ gpujpeg_writer_write_exif(struct gpujpeg_encoder* encoder)
300367
length_p[1] = length;
301368
}
302369

370+
/**
371+
* add user-provided Exif tag
372+
*/
373+
bool
374+
gpujpeg_exif_add_tag(struct gpujpeg_exif_tags** exif_tags, const char* cfg)
375+
{
376+
char *endptr = (char *) cfg;
377+
long tag_id = strtol(cfg, &endptr, 0);
378+
if (*endptr != ':') {
379+
ERROR_MSG("Error parsing Exif tag ID or missing type!\n");
380+
return false;
381+
}
382+
endptr += 1;
383+
384+
enum exif_tag_type type = ET_NONE;
385+
for (unsigned i = ET_NONE + 1; i < ET_END; ++i) {
386+
if ( exif_tag_type_info[i].name == NULL) { // unset/invalid type
387+
continue;
388+
}
389+
size_t len = strlen(exif_tag_type_info[i].name);
390+
if (strncasecmp(endptr, exif_tag_type_info[i].name, len) == 0 ) {
391+
type = i;
392+
endptr += len;
393+
break;
394+
}
395+
}
396+
if (type == ET_NONE) {
397+
ERROR_MSG("Error parsing Exif tag type!\n");
398+
return false;
399+
}
400+
if (*endptr != '=') {
401+
ERROR_MSG("Error parsing Exif - missing value!\n");
402+
return false;
403+
}
404+
unsigned numeric_unsigned = T_NUMERIC | T_UNSIGNED;
405+
if ((exif_tag_type_info[type].type_flags & numeric_unsigned) != numeric_unsigned) {
406+
ERROR_MSG("Only unsigned integers currently supported!\n");
407+
return false;
408+
}
409+
endptr += 1;
410+
unsigned long long val = strtoull(endptr, &endptr, 0);
411+
if (*endptr != '\0') {
412+
ERROR_MSG("Trainling data in Exif value!\n");
413+
return false;
414+
}
415+
416+
if (*exif_tags == NULL) {
417+
*exif_tags = calloc(1, sizeof **exif_tags);
418+
}
419+
420+
int table_idx = tag_id < EEXIF_FIRST ? CT_TIFF : CT_EXIF;
421+
size_t new_size = (*exif_tags)->tags[table_idx].count += 1;
422+
(*exif_tags)->tags[table_idx].vals = realloc((*exif_tags)->tags[table_idx].vals,
423+
new_size * sizeof (*exif_tags)->tags[table_idx].vals[0]);
424+
(*exif_tags)->tags[table_idx].vals[new_size - 1].tag_id = tag_id;
425+
(*exif_tags)->tags[table_idx].vals[new_size - 1].type = type;
426+
(*exif_tags)->tags[table_idx].vals[new_size - 1].value.uvalue = val;
427+
428+
return true;
429+
}
430+
431+
void
432+
gpujpeg_exif_tags_destroy(struct gpujpeg_exif_tags* exif_tags)
433+
{
434+
if (exif_tags == NULL) {
435+
return;
436+
}
437+
free(exif_tags->tags[0].vals);
438+
free(exif_tags->tags[1].vals);
439+
}

src/gpujpeg_exif.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,19 @@
3030
#ifndef GPUJPEG_EXIF_H_6755D546_2A90_46DF_9ECA_22F575C3E7E3
3131
#define GPUJPEG_EXIF_H_6755D546_2A90_46DF_9ECA_22F575C3E7E3
3232

33+
#ifndef __cplusplus
34+
#include <stdbool.h>
35+
#endif // not defined __cplusplus
36+
37+
struct gpujpeg_exif_tags;
38+
3339
struct gpujpeg_encoder;
3440
void
3541
gpujpeg_writer_write_exif(struct gpujpeg_encoder* encoder);
3642

43+
bool
44+
gpujpeg_exif_add_tag(struct gpujpeg_exif_tags** exif_tags, const char *cfg);
45+
void
46+
gpujpeg_exif_tags_destroy(struct gpujpeg_exif_tags* exif_tags);
47+
3748
#endif // defined GPUJPEG_EXIF_H_6755D546_2A90_46DF_9ECA_22F575C3E7E3

src/gpujpeg_writer.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ gpujpeg_writer_destroy(struct gpujpeg_writer* writer)
9494
{
9595
assert(writer != NULL);
9696
free_buffer(writer);
97+
gpujpeg_exif_tags_destroy(writer->exif_tags);
9798
free(writer);
9899
return 0;
99100
}

src/gpujpeg_writer.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ extern "C" {
4141

4242
/** JPEG encoder structure predeclaration */
4343
struct gpujpeg_encoder;
44+
struct gpujpeg_exif_tags;
4445
struct gpujpeg_image_parameters;
4546

4647
/** JPEG writer structure */
@@ -62,6 +63,8 @@ struct gpujpeg_writer
6263
uint8_t* segment_info_position;
6364
// Segment info current segment index
6465
int segment_info_index;
66+
67+
struct gpujpeg_exif_tags *exif_tags;
6568
};
6669

6770
/**

0 commit comments

Comments
 (0)