Skip to content

Commit

Permalink
Added EXIF output support for JPEG, WebP and AVIF format writers.
Browse files Browse the repository at this point in the history
Reading of EXIF data is, however, only currently supported by the JPEG input reader, so no TIFF EXIF input support for the moment.
  • Loading branch information
ruven committed Oct 28, 2024
1 parent 20644e1 commit d084cf7
Show file tree
Hide file tree
Showing 11 changed files with 149 additions and 14 deletions.
5 changes: 5 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
28/10/2024:
- Added EXIF output support for JPEG, WebP and AVIF format writers. Reading of EXIF data is, however, only
currently supported by the JPEG input reader, so no TIFF EXIF input support for the moment.


27/10/2024:
- Added ability to inject ICC profile and XMP metadata into pre-encoded tiles returned from TIFF images. These
are essentially raw bitstreams with no container. Works for both JPEG and WebP formats.
Expand Down
19 changes: 19 additions & 0 deletions src/AVIFCompressor.cc
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ unsigned int AVIFCompressor::Compress( RawTile& rawtile ){
// Add ICC profile and XMP metadata to our image
writeICCProfile();
writeXMPMetadata();
writeExifMetadata();


if( (OK=avifEncoderAddImage( encoder, avif, 1, AVIF_ADD_IMAGE_FLAG_SINGLE )) != AVIF_RESULT_OK ){
Expand Down Expand Up @@ -238,3 +239,21 @@ void AVIFCompressor::writeXMPMetadata()
}
#endif
}



/// Write EXIF metadata
void AVIFCompressor::writeExifMetadata()
{
size_t len = exif.size();
if( len == 0 ) return;

#if AVIF_VERSION_MAJOR < 1
// No return from version < 1
avifImageSetMetadataExif( avif, (const uint8_t*) exif.c_str(), len );
#else
if( avifImageSetMetadataExif( avif, (const uint8_t*) exif.c_str(), len ) != AVIF_RESULT_OK ){
throw string( "AVIFCompressor :: Error adding EXIF metadata" );
}
#endif
}
3 changes: 3 additions & 0 deletions src/AVIFCompressor.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ class AVIFCompressor : public Compressor {
/// Write XMP metadata
void writeXMPMetadata();

/// Write EXIF metadata
void writeExifMetadata();


public:

Expand Down
9 changes: 9 additions & 0 deletions src/CVT.cc
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,15 @@ void CVT::send( Session* session ){
compressor->embedXMPMetadata( true );
}

// Always embed EXIF metadata in CVT function
if( (*session->image)->getMetadata("exif").size() > 0 ){
if( session->loglevel >= 3 ){
*(session->logfile) << "CVT :: Embedding EXIF metadata with size "
<< (*session->image)->getMetadata("exif").size() << " bytes" << endl;
}
compressor->embedExifMetadata( true );
}


// Initialise our output compression object
compressor->InitCompression( complete_image, resampled_height );
Expand Down
25 changes: 24 additions & 1 deletion src/Compressor.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ class Compressor {
bool embedXMP;
std::string xmp;

// EXIF metadata
bool embedEXIF;
std::string exif;

/// Write metadata
virtual void writeMetadata() {};

Expand All @@ -74,6 +78,9 @@ class Compressor {
/// Write XMP metadata
virtual void writeXMPMetadata() {};

/// Write EXIF metadata
virtual void writeExifMetadata() {};


public:

Expand All @@ -88,7 +95,8 @@ class Compressor {
dpi_y( 0 ),
dpi_units( 0 ),
embedICC( false ),
embedXMP( false ) {};
embedXMP( false ),
embedEXIF( false ) {};


virtual ~Compressor() {};
Expand Down Expand Up @@ -125,6 +133,9 @@ class Compressor {
/** @param embed Whether XMP metadata should be embedded */
inline void embedXMPMetadata( const bool embed ){ this->embedXMP = embed; }

/// Embed EXIF metadata
/** @param embed Whether EXIF metadata should be embedded */
inline void embedExifMetadata( const bool embed ){ this->embedEXIF = embed; }

/// Set general metadata
/** @param metadata Metadata list */
Expand All @@ -146,9 +157,21 @@ class Compressor {
xmp = it->second;
this->metadata.erase( it );
}

// Extract EXIF chunk if it exists and remove from list
it = this->metadata.find("exif");
if( it != this->metadata.end() ){
exif = it->second;
this->metadata.erase( it );
}

};


/// Set quality level
/** @param quality quality level */
virtual void setQuality( int quality ) {};


/// Initialise strip based compression
/** If we are doing a strip based encoding, we need to first initialise
Expand Down
39 changes: 34 additions & 5 deletions src/JPEGCompressor.cc
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ inline double round(double r) { return (r > 0.0) ? floor(r + 0.5) : ceil(r - 0.5
#define XMP_PREFIX "http://ns.adobe.com/xap/1.0/%c%s"
#define XMP_PREFIX_SIZE 29

// EXIF definitions
#define EXIF_PREFIX "Exif\0\0"
#define EXIF_PREFIX_SIZE 6


/* IIPImage version of the JPEG error_exit function. We want to pass control back
Expand Down Expand Up @@ -82,7 +85,7 @@ extern "C" {
* way of doing this, but this seems to work :/
*/
void setup_error_functions( jpeg_compress_struct *a ){
a->err->error_exit = iip_error_exit;
a->err->error_exit = iip_error_exit;
}
}

Expand Down Expand Up @@ -185,7 +188,8 @@ void JPEGCompressor::InitCompression( const RawTile& rawtile, unsigned int strip
// Calculate our metadata storage requirements
unsigned int metadata_size =
(icc.size()>0 ? (icc.size()+ICC_OVERHEAD_LEN) : 0) +
(xmp.size()>0 ? (xmp.size()+XMP_PREFIX_SIZE) : 0);
(xmp.size()>0 ? (xmp.size()+XMP_PREFIX_SIZE) : 0) +
(exif.size()>0 ? (exif.size()+EXIF_PREFIX_SIZE) : 0);

// Allocate enough memory for our header and metadata
unsigned long output_size = metadata_size + MX;
Expand Down Expand Up @@ -225,7 +229,10 @@ void JPEGCompressor::InitCompression( const RawTile& rawtile, unsigned int strip

// Add XMP metadata
writeXMPMetadata();


// Add EXIF metadata
writeExifMetadata();

// Copy the encoded JPEG header data to a separate buffer
size_t datacount = dest->source_size - dest->pub.free_in_buffer;
header_size = datacount;
Expand Down Expand Up @@ -337,7 +344,8 @@ unsigned int JPEGCompressor::Compress( RawTile& rawtile )
// Calculate our metadata storage requirements
unsigned int metadata_size =
(icc.size()>0 ? (icc.size()+ICC_OVERHEAD_LEN) : 0) +
(xmp.size()>0 ? (xmp.size()+XMP_PREFIX_SIZE) : 0);
(xmp.size()>0 ? (xmp.size()+XMP_PREFIX_SIZE) : 0) +
(exif.size()>0 ? (exif.size()+EXIF_PREFIX_SIZE) : 0);

// Allocate enough memory for our compressed output data
// - compressed images at overly high quality factors can be larger than raw data
Expand Down Expand Up @@ -373,6 +381,9 @@ unsigned int JPEGCompressor::Compress( RawTile& rawtile )
// Add XMP metadata
writeXMPMetadata();

// Add EXIF metadata
writeExifMetadata();


// Compress the image line by line
JSAMPROW row[1];
Expand Down Expand Up @@ -516,9 +527,27 @@ void JPEGCompressor::writeXMPMetadata()



void JPEGCompressor::writeExifMetadata()
{
// Skip if EXIF embedding is disabled or no EXIF present
if( !embedEXIF || exif.empty() ) return;

size_t len = exif.size();

// Need to add prefix (need do this with memcpy because of null bytes)
char exifstr[65536];
memcpy( exifstr, EXIF_PREFIX, EXIF_PREFIX_SIZE );
memcpy( exifstr+EXIF_PREFIX_SIZE, exif.c_str(), len );

// Write out our marker
jpeg_write_marker( &cinfo, JPEG_APP0+1, (const JOCTET*) exifstr, len+EXIF_PREFIX_SIZE );
}



void JPEGCompressor::injectMetadata( RawTile& rawtile )
{
if( (!embedICC && !embedXMP) || (icc.empty() && xmp.empty()) ) return;
if( (!embedICC && !embedXMP &&!embedEXIF) || (icc.empty() && xmp.empty() && exif.empty()) ) return;

// Initialize our compression structure
InitCompression( rawtile, 0 );
Expand Down
3 changes: 3 additions & 0 deletions src/JPEGCompressor.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ class JPEGCompressor: public Compressor{
/// Write XMP metadata
void writeXMPMetadata();

/// Write EXIF metadata
void writeExifMetadata();


public:

Expand Down
23 changes: 21 additions & 2 deletions src/PNGCompressor.cc
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,8 @@ void PNGCompressor::InitCompression( const RawTile& rawtile, unsigned int strip_
// Calculate our metadata storage requirements
unsigned int metadata_size =
(icc.size()>0 ? (icc.size()+ICC_OVERHEAD_SIZE) : 0) +
(xmp.size()>0 ? (xmp.size()+XMP_OVERHEAD_SIZE) : 0);
(xmp.size()>0 ? (xmp.size()+XMP_OVERHEAD_SIZE) : 0) +
(exif.size()>0 ? exif.size() : 0);

// Allocate enough memory for our header and metadata
unsigned long output_size = metadata_size + MX;
Expand Down Expand Up @@ -243,7 +244,8 @@ unsigned int PNGCompressor::Compress( RawTile& rawtile )
// Calculate our metadata storage requirements
unsigned int metadata_size =
(icc.size()>0 ? (icc.size()+ICC_OVERHEAD_SIZE) : 0) +
(xmp.size()>0 ? (xmp.size()+XMP_OVERHEAD_SIZE) : 0);
(xmp.size()>0 ? (xmp.size()+XMP_OVERHEAD_SIZE) : 0) +
(exif.size()>0) ? exif.size() : 0;

// Allocate enough memory for our compressed output data - make sure there is extra buffering
// Note that compressed images at overly high quality factors can be larger than raw data
Expand Down Expand Up @@ -412,6 +414,9 @@ void PNGCompressor::writeMetadata()

// Write XMP chunk
writeXMPMetadata();

// Write EXIF chunk
writeExifMetadata();
}


Expand Down Expand Up @@ -474,3 +479,17 @@ void PNGCompressor::writeXMPMetadata()
png_set_text( dest.png_ptr, dest.info_ptr, &text, 1 );

}



void PNGCompressor::writeExifMetadata()
{
// Skip if EXIF embedding is disabled or no EXIF present
if( !embedEXIF || exif.empty() ) return;

#ifdef PNG_eXIf_SUPPORTED
// Write out EXIF chunk
png_set_eXIf_1( dest.png_ptr, dest.info_ptr, exif.size(), (png_bytep) exif.c_str() );
#endif

}
5 changes: 4 additions & 1 deletion src/PNGCompressor.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,10 @@ class PNGCompressor : public Compressor {
/// Write XMP metadata
void writeXMPMetadata();


/// Write EXIF metadata
void writeExifMetadata();


public:

/// Constructor
Expand Down
29 changes: 24 additions & 5 deletions src/WebPCompressor.cc
Original file line number Diff line number Diff line change
Expand Up @@ -126,12 +126,13 @@ unsigned int WebPCompressor::Compress( RawTile& rawtile ){
WebPData output;

// Use the WebP muxer only if we need to
if( icc.size() > 0 || xmp.size() > 0 ){
if( icc.size() > 0 || xmp.size() > 0 || exif.size() > 0 ){

// Add ICC profile and XMP metadata to our output bitstream
writeICCProfile();
writeXMPMetadata();

writeExifMetadata();

// Add our image data chunk
WebPData chunk;
chunk.bytes = writer.mem;
Expand Down Expand Up @@ -213,15 +214,32 @@ void WebPCompressor::writeXMPMetadata()



/// Write EXIF metadata
void WebPCompressor::writeExifMetadata()
{
// Skip if EXIF embedding disabled or no EXIF chunk exists
if( !embedEXIF || exif.empty() ) return;

WebPData chunk;
chunk.bytes = (const uint8_t*) exif.c_str();
chunk.size = exif.size();

if( WebPMuxSetChunk( mux, "EXIF", &chunk, 0 ) != WEBP_MUX_OK ){
throw string( "WebPCompressor :: Error setting EXIF chunk" );
}
}



void WebPCompressor::injectMetadata( RawTile& rawtile )
{
if( (!embedICC && !embedXMP) || (icc.empty() && xmp.empty()) ) return;
if( (!embedICC && !embedXMP && !embedEXIF) || (icc.empty() && xmp.empty() && exif.empty()) ) return;

WebPData input;
input.bytes = (const uint8_t*) rawtile.data;
input.size = rawtile.dataLength;

// Only add ICC or metadata if we have a raw WebP stream
// Only add ICC or metadata if we have a raw WebP stream:
// Bytes 8-16 should be exactly "WEBPVP8 " (lossy) or "WEBPVP8L" (lossless)
static const unsigned char lossy_header[8] = {0x57,0x45,0x42,0x50,0x56,0x50,0x38,0x20};
static const unsigned char lossless_header[8] = {0x57,0x45,0x42,0x50,0x56,0x50,0x38,0x4c};
Expand All @@ -231,9 +249,10 @@ void WebPCompressor::injectMetadata( RawTile& rawtile )

WebPData output;

// Add ICC profile and XMP metadata to our output bitstream
// Add ICC profile, XMP and EXIF metadata to our output bitstream
writeICCProfile();
writeXMPMetadata();
writeExifMetadata();

// Add our raw image bitstream data
if( WebPMuxSetImage( mux, &input, 0 ) != WEBP_MUX_OK ){
Expand Down
3 changes: 3 additions & 0 deletions src/WebPCompressor.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ class WebPCompressor : public Compressor {
/// Write XMP metadata
void writeXMPMetadata();

/// Write EXIF metadata
void writeExifMetadata();


public:

Expand Down

0 comments on commit d084cf7

Please sign in to comment.