// SPDX-License-Identifier: GPL-2.0-or-later // included header files #include "pgfimage.hpp" #include "basicio.hpp" #include "config.h" #include "enforce.hpp" #include "error.hpp" #include "futils.hpp" #include "image.hpp" #include // Signature from front of PGF file const unsigned char pgfSignature[3] = {0x50, 0x47, 0x46}; const unsigned char pgfBlank[] = { 0x50, 0x47, 0x46, 0x36, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x03, 0x03, 0x00, 0x00, 0x00, 0x14, 0x00, 0x67, 0x08, 0x20, 0x00, 0xc0, 0x01, 0x00, 0x00, 0x37, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00}; // ***************************************************************************** // class member definitions namespace Exiv2 { static uint32_t byteSwap_(uint32_t value, bool bSwap) { uint32_t result = 0; result |= (value & 0x000000FF) << 24; result |= (value & 0x0000FF00) << 8; result |= (value & 0x00FF0000) >> 8; result |= (value & 0xFF000000) >> 24; return bSwap ? result : value; } static uint32_t byteSwap_(Exiv2::DataBuf& buf, size_t offset, bool bSwap) { uint32_t v = 0; auto p = reinterpret_cast(&v); int i; for (i = 0; i < 4; i++) p[i] = buf.read_uint8(offset + i); uint32_t result = byteSwap_(v, bSwap); p = reinterpret_cast(&result); for (i = 0; i < 4; i++) buf.write_uint8(offset + i, p[i]); return result; } PgfImage::PgfImage(BasicIo::UniquePtr io, bool create) : Image(ImageType::pgf, mdExif | mdIptc | mdXmp | mdComment, std::move(io)), bSwap_(isBigEndianPlatform()) { if (create && io_->open() == 0) { #ifdef EXIV2_DEBUG_MESSAGES std::cerr << "Exiv2::PgfImage:: Creating PGF image to memory\n"; #endif IoCloser closer(*io_); if (io_->write(pgfBlank, sizeof(pgfBlank)) != sizeof(pgfBlank)) { #ifdef EXIV2_DEBUG_MESSAGES std::cerr << "Exiv2::PgfImage:: Failed to create PGF image on memory\n"; #endif } } } // PgfImage::PgfImage void PgfImage::readMetadata() { #ifdef EXIV2_DEBUG_MESSAGES std::cerr << "Exiv2::PgfImage::readMetadata: Reading PGF file " << io_->path() << "\n"; #endif if (io_->open() != 0) { throw Error(ErrorCode::kerDataSourceOpenFailed, io_->path(), strError()); } IoCloser closer(*io_); // Ensure that this is the correct image type if (!isPgfType(*io_, true)) { if (io_->error() || io_->eof()) throw Error(ErrorCode::kerFailedToReadImageData); throw Error(ErrorCode::kerNotAnImage, "PGF"); } clearMetadata(); readPgfMagicNumber(*io_); size_t headerSize = readPgfHeaderSize(*io_); readPgfHeaderStructure(*io_, pixelWidth_, pixelHeight_); // And now, the most interesting, the user data byte array where metadata are stored as small image. enforce(headerSize <= std::numeric_limits::max() - 8, ErrorCode::kerCorruptedMetadata); size_t size = headerSize + 8 - io_->tell(); #ifdef EXIV2_DEBUG_MESSAGES std::cout << "Exiv2::PgfImage::readMetadata: Found Image data (" << size << " bytes)\n"; #endif if (size > io_->size()) throw Error(ErrorCode::kerInputDataReadFailed); if (size == 0) return; DataBuf imgData(size); const size_t bufRead = io_->read(imgData.data(), imgData.size()); if (io_->error()) throw Error(ErrorCode::kerFailedToReadImageData); if (bufRead != imgData.size()) throw Error(ErrorCode::kerInputDataReadFailed); auto image = Exiv2::ImageFactory::open(imgData.c_data(), imgData.size()); image->readMetadata(); exifData() = image->exifData(); iptcData() = image->iptcData(); xmpData() = image->xmpData(); } void PgfImage::writeMetadata() { if (io_->open() != 0) { throw Error(ErrorCode::kerDataSourceOpenFailed, io_->path(), strError()); } IoCloser closer(*io_); MemIo tempIo; doWriteMetadata(tempIo); // may throw io_->close(); io_->transfer(tempIo); // may throw } // PgfImage::writeMetadata void PgfImage::doWriteMetadata(BasicIo& outIo) { if (!io_->isopen()) throw Error(ErrorCode::kerInputDataReadFailed); if (!outIo.isopen()) throw Error(ErrorCode::kerImageWriteFailed); #ifdef EXIV2_DEBUG_MESSAGES std::cout << "Exiv2::PgfImage::doWriteMetadata: Writing PGF file " << io_->path() << "\n"; std::cout << "Exiv2::PgfImage::doWriteMetadata: tmp file created " << outIo.path() << "\n"; #endif // Ensure that this is the correct image type if (!isPgfType(*io_, true)) { if (io_->error() || io_->eof()) throw Error(ErrorCode::kerInputDataReadFailed); throw Error(ErrorCode::kerNoImageInInputData); } // Ensure PGF version. byte mnb = readPgfMagicNumber(*io_); readPgfHeaderSize(*io_); uint32_t w = 0, h = 0; DataBuf header = readPgfHeaderStructure(*io_, w, h); auto img = ImageFactory::create(ImageType::png); img->setExifData(exifData_); img->setIptcData(iptcData_); img->setXmpData(xmpData_); img->writeMetadata(); size_t imgSize = img->io().size(); DataBuf imgBuf = img->io().read(imgSize); #ifdef EXIV2_DEBUG_MESSAGES std::cout << "Exiv2::PgfImage::doWriteMetadata: Creating image to host metadata (" << imgSize << " bytes)\n"; #endif //--------------------------------------------------------------- // Write PGF Signature. if (outIo.write(pgfSignature, 3) != 3) throw Error(ErrorCode::kerImageWriteFailed); // Write Magic number. if (outIo.putb(mnb) == EOF) throw Error(ErrorCode::kerImageWriteFailed); // Write new Header size. auto newHeaderSize = static_cast(header.size() + imgSize); DataBuf buffer(4); std::copy_n(&newHeaderSize, 4, buffer.data()); byteSwap_(buffer, 0, bSwap_); if (outIo.write(buffer.c_data(), 4) != 4) throw Error(ErrorCode::kerImageWriteFailed); #ifdef EXIV2_DEBUG_MESSAGES std::cout << "Exiv2::PgfImage: new PGF header size : " << newHeaderSize << " bytes\n"; printf("%x\n", buffer.read_uint8(0)); printf("%x\n", buffer.read_uint8(1)); printf("%x\n", buffer.read_uint8(2)); printf("%x\n", buffer.read_uint8(3)); #endif // Write Header data. if (outIo.write(header.c_data(), header.size()) != header.size()) throw Error(ErrorCode::kerImageWriteFailed); // Write new metadata byte array. if (outIo.write(imgBuf.c_data(), imgBuf.size()) != imgBuf.size()) throw Error(ErrorCode::kerImageWriteFailed); // Copy the rest of PGF image data. DataBuf buf(4096); size_t readSize = 0; while ((readSize = io_->read(buf.data(), buf.size()))) { if (outIo.write(buf.c_data(), readSize) != readSize) throw Error(ErrorCode::kerImageWriteFailed); } if (outIo.error()) throw Error(ErrorCode::kerImageWriteFailed); } // PgfImage::doWriteMetadata byte PgfImage::readPgfMagicNumber(BasicIo& iIo) { byte b = iIo.getb(); if (iIo.error()) throw Error(ErrorCode::kerFailedToReadImageData); if (b < 0x36) // 0x36 = '6'. { // Not right Magick version. #ifdef EXIV2_DEBUG_MESSAGES std::cout << "Exiv2::PgfImage::readMetadata: wrong Magick number\n"; #endif } return b; } // PgfImage::readPgfMagicNumber size_t PgfImage::readPgfHeaderSize(BasicIo& iIo) const { DataBuf buffer(4); const size_t bufRead = iIo.read(buffer.data(), buffer.size()); if (iIo.error()) throw Error(ErrorCode::kerFailedToReadImageData); if (bufRead != buffer.size()) throw Error(ErrorCode::kerInputDataReadFailed); auto headerSize = static_cast(byteSwap_(buffer, 0, bSwap_)); if (headerSize == 0) throw Error(ErrorCode::kerNoImageInInputData); #ifdef EXIV2_DEBUG_MESSAGES std::cout << "Exiv2::PgfImage: PGF header size : " << headerSize << " bytes\n"; #endif return headerSize; } DataBuf PgfImage::readPgfHeaderStructure(BasicIo& iIo, uint32_t& width, uint32_t& height) const { DataBuf header(16); size_t bufRead = iIo.read(header.data(), header.size()); if (iIo.error()) throw Error(ErrorCode::kerFailedToReadImageData); if (bufRead != header.size()) throw Error(ErrorCode::kerInputDataReadFailed); DataBuf work(8); // don't disturb the binary data - doWriteMetadata reuses it std::copy_n(header.c_data(), 8, work.begin()); width = byteSwap_(work, 0, bSwap_); height = byteSwap_(work, 4, bSwap_); /* NOTE: properties not yet used byte nLevels = buffer.pData_[8]; byte quality = buffer.pData_[9]; byte bpp = buffer.pData_[10]; byte channels = buffer.pData_[11]; */ byte mode = header.read_uint8(12); if (mode == 2) // Indexed color image. We pass color table (256 * 3 bytes). { header.alloc(16 + 256 * 3); bufRead = iIo.read(header.data(16), 256 * 3); if (iIo.error()) throw Error(ErrorCode::kerFailedToReadImageData); if (bufRead != 256 * 3) throw Error(ErrorCode::kerInputDataReadFailed); } return header; } // PgfImage::readPgfHeaderStructure // ************************************************************************* // free functions Image::UniquePtr newPgfInstance(BasicIo::UniquePtr io, bool create) { auto image = std::make_unique(std::move(io), create); if (!image->good()) { return nullptr; } return image; } bool isPgfType(BasicIo& iIo, bool advance) { const int32_t len = 3; byte buf[len]; iIo.read(buf, len); if (iIo.error() || iIo.eof()) { return false; } int rc = memcmp(buf, pgfSignature, 3); if (!advance || rc != 0) { iIo.seek(-len, BasicIo::cur); } return rc == 0; } } // namespace Exiv2