c++gdlibexif

Write Exif data back to image rotated by GD image library


I'm having trouble when try to attach original exif data back to image after rotating it by GD image.

How I rotate image

rotatedImage = gdImageRotateInterpolated(image, (360-rotationDeg)%360, 0);
imgBuffer = (unsigned char *)gdImageJpegPtr(rotatedImage, &imgSize, 85);

Then I have a function try to write extracted exif data from original image to rotated one by combine image buffer with exif buffer but I can't still see Exif data while inspecting the image. I'm using libexif

writeExifData(unsigned char* &imageBuffer, int &size, ExifData* exifData) {
    if (!exifData) {
        return;
    }
    TRACE("Image Size: %d", size);
    // save exif data into buffer
    unsigned char* buf = NULL;
    unsigned int bufSize = 0;
    exif_data_save_data(exifData, &buf, &bufSize);
    std::vector<unsigned char> combinedBuffer(size + bufSize);

    // Copy the image buffer
    std::copy(imageBuffer, imageBuffer + size, combinedBuffer.begin());

    // Copy the EXIF buffer
    std::copy(buf, buf + bufSize, combinedBuffer.begin() + size);

    unsigned char* newImageBuffer = (unsigned char*)malloc(combinedBuffer.size());

    memcpy(newImageBuffer, combinedBuffer.data(), combinedBuffer.size());
    imageBuffer = newImageBuffer;
    size = combinedBuffer.size();
}

Any other way that I can have new image buffer include all Exif data from original image?


Solution

  • The JPEG image format consists of a number of segments using tag-length-value encoding. Furthermore, APP1 data for EXIF is supposed to come right after the SOI marker in the file, which is the first two bytes of your JPEG.

    exif_save_data produces the "value" part but you need to wrap that in a segment yourself.

    Thus, you can do the following:

    struct jpeg_header {
      uint16_t soi;
      uint16_t app1;
      uint16_t exif_len;
    } jh = { htobe16(0xFFD8), htobe16(0xFFE1), htobe16(2 + bufSize) };
    
    unsigned char* newImageBuffer = (unsigned char*)malloc(sizeof(jh) + size - 2 /* duplicate SOI */);
    unsigned char* write = newImageBuffer;
    write = std::copy(reinterpret_cast<unsigned char *>(&jh), reinterpret_cast<unsigned char *>(&jh) + sizeof(jh), write);
    write = std::copy(buf, buf+bufSize, write);
    write = std::copy(imageBuffer+2, imageBuffer+size, write);
    
    return newImageBuffer;
    

    If you do not have access to htobe16 you can use htons as well.