AmaltasCoder
AmaltasCoder

Reputation: 1123

Use libexif with libjpeg to set exif tags on an existing JPEG

In my C program I would like to use libexif along with libjpeg to set exif tags on an existing jpeg file present at a given path inputFilePath, and save the resulting jpeg to output path outputFilePath.

The input jpeg file is large (40000 X 40000 pixels) so loading the whole image in memory isn't preferable and shouldn't be needed.

I don't care about other existing Exif tags in the Jpeg, they may be removed.

I have read and tried the example provided with libexif which uses a fixed JPEG, but just can't figure out how to do the same for any JPEG.

Btw, I did get the following code which sets exif tags by loading the jpeg in-memory to work. It uses the libjpeg implementation provided in exif utility that comes along with libexif.

ExifEntry *entry;
ExifData *exif = exif_data_new();
if (!exif) {
  //Out of memory
}

/* Set the image options */
exif_data_set_option(exif, EXIF_DATA_OPTION_FOLLOW_SPECIFICATION);
exif_data_set_data_type(exif, EXIF_DATA_TYPE_COMPRESSED);
exif_data_set_byte_order(exif, FILE_BYTE_ORDER);

/* Create the mandatory EXIF fields with default data */
exif_data_fix(exif);

/* All these tags are created with default values by exif_data_fix() */
/* Change the data to the correct values for this image. */
entry = init_tag(exif, EXIF_IFD_EXIF, EXIF_TAG_PIXEL_X_DIMENSION);
exif_set_long(entry->data, FILE_BYTE_ORDER, w);

entry = init_tag(exif, EXIF_IFD_EXIF, EXIF_TAG_PIXEL_Y_DIMENSION);
exif_set_long(entry->data, FILE_BYTE_ORDER, h);

entry = init_tag(exif, EXIF_IFD_EXIF, EXIF_TAG_COLOR_SPACE);
exif_set_short(entry->data, FILE_BYTE_ORDER, 1);

/* Create a EXIF_TAG_USER_COMMENT tag. This one must be handled
 * differently because that tag isn't automatically created and
 * allocated by exif_data_fix(), nor can it be created using
 * exif_entry_initialize() so it must be explicitly allocated here.
 */
entry = create_tag(exif, EXIF_IFD_EXIF, EXIF_TAG_USER_COMMENT,
                   sizeof(ASCII_COMMENT) + sizeof(FILE_COMMENT) - 2);
/* Write the special header needed for a comment tag */
memcpy(entry->data, ASCII_COMMENT, sizeof(ASCII_COMMENT) - 1);
/* Write the actual comment text, without the trailing NUL character */
memcpy(entry->data + 8, FILE_COMMENT, sizeof(FILE_COMMENT) - 1);
/* create_tag() happens to set the format and components correctly for
 * EXIF_TAG_USER_COMMENT, so there is nothing more to do. */

JPEGData *jdata;
unsigned char *d = NULL;
unsigned int ds;
ExifLog *log = NULL;

/* Parse the JPEG file. */
jdata = jpeg_data_new();
jpeg_data_log(jdata, log);
jpeg_data_load_file(jdata, inputFilePath);

/* Make sure the EXIF data is not too big. */
exif_data_save_data(exif, &d, &ds);
if (ds) {
  free(d);
  if (ds > 0xffff)
    //Too much EXIF data
};

jpeg_data_set_exif_data(jdata, exif);

/* Save the modified image. */
jpeg_data_save_file(jdata, outputFilePath);
jpeg_data_unref(jdata);

Upvotes: 3

Views: 2916

Answers (1)

jladan
jladan

Reputation: 837

If you aren't re-compressing or editing the image, then you won't need libjpeg. It can be done with fopen and fputc.

There's a good description of the JPEG file structure and metadata from exiv2. Most jpeg files will start with 0xFFD8 (start of image), then an APP0 block for JFIF data (0xFF E0 <length> <data>). If there is an EXIF header, it's in the APP1 block (0xFF E1 <length> <data>).

The blocks in a JPEG file are formatted as

  • Marker (0xFF xx) where xx is En for APPn blocks
  • Content

    • 2 bytes - length of content in bytes including these 2 bytes
    • data

So, an outline of your program would be

  1. Copy the file until the APP1 block
  2. Write your APP1 block instead
  3. Copy the rest of the file

The EXIF header contents can be created with exif_data_save_data() in libexif.

Upvotes: 3

Related Questions