Skip to content

Commit

Permalink
Re-factoring of metadata handling in output compressor classes and ad…
Browse files Browse the repository at this point in the history
…dition of more complete metadata embedding for PNG.
  • Loading branch information
ruven committed Jun 29, 2024
1 parent 70080cc commit 917fe71
Show file tree
Hide file tree
Showing 12 changed files with 221 additions and 132 deletions.
2 changes: 2 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
29/06/2024:
- Updated autoconf tiff m4 to search for tiffio.h rather than just tiff.h when using TIFFOpen().
- Switched metadata assignment within input image classes to use more efficient map insert() syntax.
- Re-factoring of metadata handling in output compressor classes and addition of more complete metadata
embedding for PNG.


29/05/2024:
Expand Down
12 changes: 8 additions & 4 deletions src/CVT.cc
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,10 @@ void CVT::send( Session* session ){
}


// Add metadata
compressor->setMetadata( (*session->image)->metadata );


// Set the physical output resolution for this particular view and zoom level
if( (*session->image)->dpi_x > 0 && (*session->image)->dpi_y > 0 ){
float dpi_x = (*session->image)->dpi_x * (float) im_width / (float) (*session->image)->getImageWidth();
Expand All @@ -513,14 +517,14 @@ void CVT::send( Session* session ){
}
}

// Set ICC profile if of a reasonable size
// Embed ICC profile if of a reasonable size
if( session->view->embedICC() && ((*session->image)->getMetadata("icc").size()>0) ){
if( (*session->image)->getMetadata("icc").size() < 65536 ){
if( session->loglevel >= 3 ){
*(session->logfile) << "CVT :: Embedding ICC profile with size "
<< (*session->image)->getMetadata("icc").size() << " bytes" << endl;
}
compressor->setICCProfile( (*session->image)->getMetadata("icc") );
compressor->embedICCProfile( true );
}
else{
if( session->loglevel >= 3 ){
Expand All @@ -530,13 +534,13 @@ void CVT::send( Session* session ){
}
}

// Add XMP metadata if this exists
// Always embed XMP metadata in CVT function
if( (*session->image)->getMetadata("xmp").size() > 0 ){
if( session->loglevel >= 3 ){
*(session->logfile) << "CVT :: Embedding XMP metadata with size "
<< (*session->image)->getMetadata("xmp").size() << " bytes" << endl;
}
compressor->setXMPMetadata( (*session->image)->getMetadata("xmp") );
compressor->embedXMPMetadata( true );
}


Expand Down
73 changes: 52 additions & 21 deletions src/Compressor.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* Generic compressor class - extended by JPEG and PNG Compressor classes
Copyright (C) 2017-2023 Ruven Pillay
Copyright (C) 2017-2024 Ruven Pillay
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
Expand All @@ -23,9 +23,8 @@



#include <string>
#include "RawTile.h"

#include <map>


/// Base class for IIP output images
Expand All @@ -52,12 +51,23 @@ class Compressor {
/** Units can be 0 for unknown, 1 for dots/inch or 2 for dots/cm */
int dpi_units;

/// Metadata
std::map <const std::string, const std::string> metadata;

/// ICC Profile
bool embedICC;
std::string icc;

/// XMP metadata
bool embedXMP;
std::string xmp;

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

/// Write DPI
virtual void writeResolution() {};

/// Write ICC profile
virtual void writeICCProfile() {};

Expand All @@ -76,12 +86,24 @@ class Compressor {
header_size( 0 ),
dpi_x( 0 ),
dpi_y( 0 ),
dpi_units( 0 ) {};
dpi_units( 0 ),
embedICC( false ),
embedXMP( false ) {};


virtual ~Compressor() {};


/// Return the image header size
/** @return header size in bytes */
unsigned int getHeaderSize() const { return header_size; };


/// Return a pointer to the image header itself
/** @return binary header blob */
unsigned char* getHeader() { return header; };


/// Get the current quality level
inline int getQuality() const { return Q; }

Expand All @@ -94,24 +116,38 @@ class Compressor {
inline void setResolution( float x, float y, int units ){ dpi_x = x; dpi_y = y; dpi_units = units; };


/// Set the ICC profile
/** @param profile ICC profile string */
inline void setICCProfile( const std::string& profile ){ icc = profile; }
/// Embed ICC profile
/** @param embed Whether ICC profile should be embedded */
inline void embedICCProfile( const bool embed ){ this->embedICC = embed; }


/// Set XMP metadata
/** @param x XMP metadata string */
inline void setXMPMetadata( const std::string& x ){ xmp = x; }
/// Embed XMP metadata
/** @param embed Whether XMP metadata should be embedded */
inline void embedXMPMetadata( const bool embed ){ this->embedXMP = embed; }


/// Return the image header size
/** @return header size in bytes */
virtual unsigned int getHeaderSize() const { return 0; };
/// Set general metadata
/** @param metadata Metadata list */
inline void setMetadata( const std::map <const std::string, const std::string>& metadata )
{
this->metadata = std::map <const std::string, const std::string>( metadata );

// Extract ICC profile if it exists and remove from list
std::map<const std::string, const std::string> :: const_iterator it;
it = this->metadata.find("icc");
if( it != this->metadata.end() ){
icc = it->second;
this->metadata.erase( it );
}

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

/// Return a pointer to the image header itself
/** @return binary header blob */
virtual unsigned char* getHeader() { return NULL; };


/// Initialise strip based compression
Expand Down Expand Up @@ -147,11 +183,6 @@ class Compressor {
virtual unsigned int Compress( RawTile& t ) { return 0; };


/// Add metadata to the image header
/** @param m metadata */
virtual void addXMPMetadata( const std::string& m ) {};


/// Get mime type
/** @return IANA mime type as const char* */
virtual const char* getMimeType() const { return "image/example"; };
Expand Down
2 changes: 1 addition & 1 deletion src/IIPImage.h
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ class IIPImage {
std::vector<unsigned int> histogram;

/// STL map to hold string metadata
std::map <const std::string, std::string> metadata;
std::map <const std::string, const std::string> metadata;

/// Image modification timestamp
time_t timestamp;
Expand Down
34 changes: 20 additions & 14 deletions src/JPEGCompressor.cc
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* JPEG class wrapper to ijg jpeg library
Copyright (C) 2000-2023 Ruven Pillay.
Copyright (C) 2000-2024 Ruven Pillay
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
Expand Down Expand Up @@ -228,10 +228,8 @@ void JPEGCompressor::InitCompression( const RawTile& rawtile, unsigned int strip
cinfo.in_color_space = ( channels == 3 ? JCS_RGB : JCS_GRAYSCALE );
jpeg_set_defaults( &cinfo );

// Set our physical output resolution (JPEG only supports integers)
cinfo.X_density = round( dpi_x );
cinfo.Y_density = round( dpi_y );
cinfo.density_unit = dpi_units;
// Add DPI
writeResolution();

// Set compression point quality (highest, but possibly slower depending
// on hardware) - must do this after we've set the defaults!
Expand Down Expand Up @@ -402,10 +400,8 @@ unsigned int JPEGCompressor::Compress( RawTile& rawtile )
cinfo.in_color_space = ( channels == 3 ? JCS_RGB : JCS_GRAYSCALE );
jpeg_set_defaults( &cinfo );

// Set our physical output resolution (JPEG only supports integers)
if( dpi_x ) cinfo.X_density = round( dpi_x );
if( dpi_y ) cinfo.Y_density = round( dpi_y );
if( dpi_x || dpi_y ) cinfo.density_unit = dpi_units;
// Add DPI
writeResolution();

// Set compression quality (fastest, but possibly slower depending
// on hardware) - must do this after we've set the defaults!
Expand Down Expand Up @@ -468,6 +464,17 @@ unsigned int JPEGCompressor::Compress( RawTile& rawtile )



// Write DPI information
void JPEGCompressor::writeResolution()
{
// Set our physical output resolution (JPEG only supports integers)
if( dpi_x ) cinfo.X_density = round( dpi_x );
if( dpi_y ) cinfo.Y_density = round( dpi_y );
if( dpi_x || dpi_y ) cinfo.density_unit = dpi_units;
}



// Write ICC profile into JPEG header if profile has been set
// Function *must* be called AFTER calling jpeg_start_compress() and BEFORE
// the first call to jpeg_write_scanlines().
Expand All @@ -478,14 +485,13 @@ unsigned int JPEGCompressor::Compress( RawTile& rawtile )
// See the copyright notice in COPYING.ijg for details
void JPEGCompressor::writeICCProfile()
{
// Skip if profile embedding disabled
if( !embedICC || icc.empty() ) return;

unsigned int num_markers; // total number of markers we'll write
int cur_marker = 1; // per spec, counting starts at 1
unsigned int length; // number of bytes to write in this marker

// Skip if our profile has zero size or is too big
// if( icc.size() == 0 || icc.size() > MAX_DATA_BYTES_IN_MARKER ) return;
if( icc.empty() ) return;

unsigned int icc_data_len = icc.size();
const char* icc_data_ptr = icc.c_str();

Expand Down Expand Up @@ -542,7 +548,7 @@ void JPEGCompressor::writeICCProfile()
void JPEGCompressor::writeXMPMetadata()
{
// Make sure our XMP data has a valid size (namespace prefix is 29 bytes)
if( xmp.empty() || xmp.size()>(65536-XMP_PREFIX_SIZE) ) return;
if( !embedXMP || xmp.empty() || xmp.size()>(65536-XMP_PREFIX_SIZE) ) return;

// The XMP data in a JPEG stream needs to be prefixed with a zero-terminated ID string
// ref http://www.adobe.com/content/dam/Adobe/en/devnet/xmp/pdfs/cs6/XMPSpecificationPart3.pdf (pp13-14)
Expand Down
11 changes: 4 additions & 7 deletions src/JPEGCompressor.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* JPEG class wrapper to ijg libjpeg library
Copyright (C) 2000-2023 Ruven Pillay.
Copyright (C) 2000-2024 Ruven Pillay.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
Expand Down Expand Up @@ -73,6 +73,9 @@ class JPEGCompressor: public Compressor{
iip_destination_mgr dest_mgr;
iip_dest_ptr dest;

/// Write DPI
void writeResolution();

/// Write ICC profile
void writeICCProfile();

Expand Down Expand Up @@ -128,12 +131,6 @@ class JPEGCompressor: public Compressor{
/** @param t tile of image data */
unsigned int Compress( RawTile& t );

/// Return the JPEG header size
inline unsigned int getHeaderSize() const { return header_size; }

/// Return a pointer to the header itself
inline unsigned char* getHeader() { return header; }

/// Return the JPEG mime type
inline const char* getMimeType() const { return "image/jpeg"; }

Expand Down
9 changes: 7 additions & 2 deletions src/JTL.cc
Original file line number Diff line number Diff line change
Expand Up @@ -141,16 +141,21 @@ void JTL::send( Session* session, int resolution, int tile ){
}


// Add metadata
compressor->setMetadata( (*session->image)->metadata );


// Embed ICC profile
if( session->view->embedICC() && ((*session->image)->getMetadata("icc").size()>0) ){
if( session->view->embedICC() && (*session->image)->metadata["icc"].size() > 0 ){
if( session->loglevel >= 3 ){
*(session->logfile) << "JTL :: Embedding ICC profile with size "
<< (*session->image)->getMetadata("icc").size() << " bytes" << endl;
}
compressor->setICCProfile( (*session->image)->getMetadata("icc") );
compressor->embedICCProfile( true );
}



RawTile rawtile = tilemanager.getTile( resolution, tile, session->view->xangle,
session->view->yangle, session->view->getLayers(), ct );

Expand Down
5 changes: 2 additions & 3 deletions src/OBJ.cc
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,8 @@ void OBJ::run( Session* s, const std::string& a )
stringstream json;
json << "{ ";

map <const string, string> metadata = (*session->image)->metadata;
map<const string,string> :: const_iterator i;
for( i = metadata.begin(); i != metadata.end(); i++ ){
map <const string, const string> :: const_iterator i;
for( i = (*session->image)->metadata.begin(); i != (*session->image)->metadata.end(); i++ ){
if( i->first == "icc" || i->first == "xmp" ) continue;
if( (i->second).length() ) json << endl << "\t\"" << i->first << "\": \"" << i->second << "\",";
}
Expand Down
Loading

0 comments on commit 917fe71

Please sign in to comment.