// ================================================================================================= // Copyright Adobe // Copyright 2020 Adobe // All Rights Reserved // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms // of the Adobe license agreement accompanying it. // ================================================================================================= #include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header. #include "public/include/XMP_Const.h" #include "XMPFiles/source/FileHandlers/PNG_Handler.hpp" #include "XMPFiles/source/FormatSupport/PNG_Support.hpp" #include "source/XIO.hpp" using namespace std; // ================================================================================================= /// \file PNG_Handler.hpp /// \brief File format handler for PNG. /// /// This handler ... /// // ================================================================================================= // ================================================================================================= // PNG_MetaHandlerCTor // ==================== XMPFileHandler * PNG_MetaHandlerCTor ( XMPFiles * parent ) { return new PNG_MetaHandler ( parent ); } // PNG_MetaHandlerCTor // ================================================================================================= // PNG_CheckFormat // =============== bool PNG_CheckFormat ( XMP_FileFormat format, XMP_StringPtr filePath, XMP_IO* fileRef, XMPFiles * parent ) { IgnoreParam(format); IgnoreParam(fileRef); IgnoreParam(parent); XMP_Assert ( format == kXMP_PNGFile ); if ( fileRef->Length() < PNG_SIGNATURE_LEN ) return false; XMP_Uns8 buffer [PNG_SIGNATURE_LEN]; fileRef->Rewind(); fileRef->Read ( buffer, PNG_SIGNATURE_LEN ); if ( ! CheckBytes ( buffer, PNG_SIGNATURE_DATA, PNG_SIGNATURE_LEN ) ) return false; return true; } // PNG_CheckFormat // ================================================================================================= // PNG_MetaHandler::PNG_MetaHandler // ================================== PNG_MetaHandler::PNG_MetaHandler ( XMPFiles * _parent ) { this->parent = _parent; this->handlerFlags = kPNG_HandlerFlags; this->stdCharForm = kXMP_Char8Bit; } // ================================================================================================= // PNG_MetaHandler::~PNG_MetaHandler // =================================== PNG_MetaHandler::~PNG_MetaHandler() { } // ================================================================================================= // PNG_MetaHandler::CacheFileData // =============================== void PNG_MetaHandler::CacheFileData() { this->containsXMP = false; XMP_IO* fileRef ( this->parent->ioRef ); if ( fileRef == 0) return; /* CacheFileData is not using OpenPNG API to find the XMP packet because OpenPNG required multiple file read calls which is very expensive in case of file present on n/w. see bug [CTECHXMP-4169872]. if PNG is opened with kXMPFiles_OpenForRead, it will not read chunks after IEND chunk encounters but if it opened with kXMPFiles_OpenForUpdate, then it will throw exception if garbage data present after IEND chunk. see bug [CTAIDS-4119487] */ if(PNG_Support::FindAndReadXMPChunk ( fileRef, this->xmpPacket, this->packetInfo.offset, XMP_OptionIsSet(this->parent->openFlags, kXMPFiles_OpenForRead))) { this->packetInfo.length = static_cast(this->xmpPacket.size()); this->containsXMP = true; } else { // no XMP } } // PNG_MetaHandler::CacheFileData // ================================================================================================= // PNG_MetaHandler::ProcessXMP // ============================ // // Process the raw XMP and legacy metadata that was previously cached. void PNG_MetaHandler::ProcessXMP() { this->processedXMP = true; // Make sure we only come through here once. // Process the XMP packet. if ( ! this->xmpPacket.empty() ) { XMP_Assert ( this->containsXMP ); XMP_StringPtr packetStr = this->xmpPacket.c_str(); XMP_StringLen packetLen = (XMP_StringLen)this->xmpPacket.size(); this->xmpObj.ParseFromBuffer ( packetStr, packetLen ); this->containsXMP = true; } } // PNG_MetaHandler::ProcessXMP // ================================================================================================= // PNG_MetaHandler::UpdateFile // ============================ void PNG_MetaHandler::UpdateFile ( bool doSafeUpdate ) { bool updated = false; if ( ! this->needsUpdate ) return; if ( doSafeUpdate ) XMP_Throw ( "PNG_MetaHandler::UpdateFile: Safe update not supported", kXMPErr_Unavailable ); XMP_StringPtr packetStr = xmpPacket.c_str(); XMP_StringLen packetLen = (XMP_StringLen)xmpPacket.size(); if ( packetLen == 0 ) return; XMP_IO* fileRef(this->parent->ioRef); if ( fileRef == 0 ) return; PNG_Support::ChunkState chunkState; long numChunks = PNG_Support::OpenPNG ( fileRef, chunkState ); if ( numChunks == 0 ) return; // write/update chunk if (chunkState.xmpLen == 0) { // no current chunk -> inject updated = SafeWriteFile(); } else if (chunkState.xmpLen >= packetLen ) { // current chunk size is sufficient -> write and update CRC (in place update) updated = PNG_Support::WriteBuffer(fileRef, chunkState.xmpPos, packetLen, packetStr ); PNG_Support::UpdateChunkCRC(fileRef, chunkState.xmpChunk ); } else if (chunkState.xmpLen < packetLen) { // XMP is too large for current chunk -> expand updated = SafeWriteFile(); } if ( ! updated )return; // If there's an error writing the chunk, bail. this->needsUpdate = false; } // PNG_MetaHandler::UpdateFile // ================================================================================================= // PNG_MetaHandler::WriteTempFile // ============================== void PNG_MetaHandler::WriteTempFile ( XMP_IO* tempRef ) { XMP_IO* originalRef = this->parent->ioRef; PNG_Support::ChunkState chunkState; long numChunks = PNG_Support::OpenPNG ( originalRef, chunkState ); if ( numChunks == 0 ) return; tempRef->Truncate ( 0 ); tempRef->Write ( PNG_SIGNATURE_DATA, PNG_SIGNATURE_LEN ); PNG_Support::ChunkIterator curPos = chunkState.chunks.begin(); PNG_Support::ChunkIterator endPos = chunkState.chunks.end(); for (; (curPos != endPos); ++curPos) { PNG_Support::ChunkData chunk = *curPos; // discard existing XMP chunk if (chunk.xmp) continue; // copy any other chunk bool ret = PNG_Support::CopyChunk(originalRef, tempRef, chunk); if(!ret) XMP_Throw("PNG chunk copy failed.", kXMPErr_InternalFailure); // place XMP chunk immediately after IHDR-chunk if (PNG_Support::CheckIHDRChunkHeader(chunk)) { XMP_StringPtr packetStr = xmpPacket.c_str(); XMP_StringLen packetLen = (XMP_StringLen)xmpPacket.size(); PNG_Support::WriteXMPChunk(tempRef, packetLen, packetStr ); } } } // PNG_MetaHandler::WriteTempFile // ================================================================================================= // PNG_MetaHandler::SafeWriteFile // ============================== bool PNG_MetaHandler::SafeWriteFile() { XMP_IO* originalFile = this->parent->ioRef; XMP_IO* tempFile = originalFile->DeriveTemp(); if ( tempFile == 0 ) XMP_Throw ( "Failure creating PNG temp file", kXMPErr_InternalFailure ); this->WriteTempFile ( tempFile ); originalFile->AbsorbTemp(); return true; } // PNG_MetaHandler::SafeWriteFile // =================================================================================================