/* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2009-05-29 * Description : static helper methods for PGF image format. * * Copyright (C) 2009-2019 by Gilles Caulier * * 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 the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "pgfutils.h" // C Ansi includes extern "C" { #include #include #include #include } #include // Qt includes #include #include #include #include // Windows includes #ifdef Q_OS_WIN32 # include #endif // LibPGF includes #if defined(Q_CC_CLANG) # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wkeyword-macro" #endif #include #if defined(Q_CC_CLANG) # pragma clang diagnostic pop #endif // Private method bool writePGFImageDataToStream(const QImage& image, CPGFStream& stream, int quality, UINT32& nWrittenBytes, bool verbose); bool readPGFImageData(const QByteArray& data, QImage& img, bool verbose) { try { if (data.isEmpty()) { std::cout << "PGFUtils: PGF image data to decode : size is null"; return false; } CPGFMemoryStream stream((UINT8*)data.data(), (size_t)data.size()); if (verbose) std::cout << "PGFUtils: image data stream size is : " << stream.GetSize(); CPGFImage pgfImg; // NOTE: see bug #273765 : Loading PGF thumbs with OpenMP support through a separated thread do not work properly with libppgf 6.11.24 pgfImg.ConfigureDecoder(false); pgfImg.Open(&stream); if (verbose) std::cout << "PGFUtils: PGF image is open"; if (pgfImg.Channels() != 4) { std::cout << "PGFUtils: PGF channels not supported"; return false; } img = QImage(pgfImg.Width(), pgfImg.Height(), QImage::Format_ARGB32); pgfImg.Read(); if (verbose) std::cout << "PGFUtils: PGF image is read"; if (QSysInfo::ByteOrder == QSysInfo::BigEndian) { int map[] = {3, 2, 1, 0}; pgfImg.GetBitmap(img.bytesPerLine(), (UINT8*)img.bits(), img.depth(), map); } else { int map[] = {0, 1, 2, 3}; pgfImg.GetBitmap(img.bytesPerLine(), (UINT8*)img.bits(), img.depth(), map); } if (verbose) std::cout << "PGFUtils: PGF image is decoded"; } catch (IOException& e) { int err = e.error; if (err >= AppError) { err -= AppError; } std::cout << "PGFUtils: Error running libpgf (" << err << ")!"; return false; } return true; } bool writePGFImageFile(const QImage& image, const QString& filePath, int quality, bool verbose) { #ifdef Q_OS_WIN32 #ifdef UNICODE HANDLE fd = CreateFile((LPCWSTR)(QFile::encodeName(filePath).constData()), GENERIC_READ, 0, 0, OPEN_EXISTING, 0, 0); #else HANDLE fd = CreateFile(QFile::encodeName(filePath).constData(), GENERIC_READ, 0, 0, OPEN_EXISTING, 0, 0); #endif if (fd == INVALID_HANDLE_VALUE) { qCDebug(DIGIKAM_GENERAL_LOG) << "PGFUtils: Error: Could not open destination file."; return false; } #elif defined(__POSIX__) int fd = QT_OPEN(QFile::encodeName(filePath).constData(), O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); if (fd == -1) { std::cout << "PGFUtils: Error: Could not open destination file."; return false; } #endif CPGFFileStream stream(fd); UINT32 nWrittenBytes = 0; bool ret = writePGFImageDataToStream(image, stream, quality, nWrittenBytes, verbose); if (!nWrittenBytes) { std::cout << "PGFUtils: Written PGF file : data size is null"; ret = false; } else { if (verbose) std::cout << "PGFUtils: file size written : " << nWrittenBytes; } #ifdef Q_OS_WIN32 CloseHandle(fd); #else close(fd); #endif return ret; } bool writePGFImageData(const QImage& image, QByteArray& data, int quality, bool verbose) { try { // We will use uncompressed image bytes size to allocate PGF stream in memory. In all case, due to PGF compression ratio, // PGF data will be so far lesser than image raw size. #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) int rawSize = image.sizeInBytes(); #else int rawSize = image.byteCount(); #endif CPGFMemoryStream stream(rawSize); if (verbose) std::cout << "PGFUtils: PGF stream memory allocation in bytes: " << rawSize; UINT32 nWrittenBytes = 0; bool ret = writePGFImageDataToStream(image, stream, quality, nWrittenBytes, verbose); int pgfsize = #ifdef PGFCodecVersionID # if PGFCodecVersionID == 0x061224 // Wrap around libpgf 6.12.24 about CPGFMemoryStream bytes size generated to make PGF file data. // It miss 16 bytes at end. This solution fix the problem. Problem have been fixed in 6.12.27. nWrittenBytes + 16; # else nWrittenBytes; # endif #else nWrittenBytes; #endif data = QByteArray((const char*)stream.GetBuffer(), pgfsize); if (!pgfsize) { std::cout << "PGFUtils: Encoded PGF image : data size is null"; ret = false; } else { if (verbose) std::cout << "PGFUtils: data size written : " << pgfsize; } return ret; } catch (IOException& e) { int err = e.error; if (err >= AppError) { err -= AppError; } std::cout << "PGFUtils: Error running libpgf (" << err << ")!"; return false; } } bool writePGFImageDataToStream(const QImage& image, CPGFStream& stream, int quality, UINT32& nWrittenBytes, bool verbose) { try { if (image.isNull()) { std::cout << "PGFUtils: Thumb image is null"; return false; } QImage img; // Convert image with Alpha channel. if (image.format() != QImage::Format_ARGB32) { img = image.convertToFormat(QImage::Format_ARGB32); if (verbose) std::cout << "PGFUtils: RGB => ARGB"; } else { img = image; } CPGFImage pgfImg; PGFHeader header; header.width = img.width(); header.height = img.height(); header.nLevels = 0; // Auto. header.quality = quality; header.bpp = img.depth(); header.channels = 4; header.mode = ImageModeRGBA; header.usedBitsPerChannel = 0; // Auto #ifdef PGFCodecVersionID # if PGFCodecVersionID < 0x061142 header.background.rgbtBlue = 0; header.background.rgbtGreen = 0; header.background.rgbtRed = 0; # endif #endif pgfImg.SetHeader(header); // NOTE: see bug #273765 : Loading PGF thumbs with OpenMP support through a separated thread do not work properly with libppgf 6.11.24 pgfImg.ConfigureEncoder(false); if (verbose) { std::cout << "PGFUtils: PGF image settings:"; std::cout << "PGFUtils: width: " << header.width; std::cout << "PGFUtils: height: " << header.height; std::cout << "PGFUtils: nLevels: " << header.nLevels; std::cout << "PGFUtils: quality: " << header.quality; std::cout << "PGFUtils: bpp: " << header.bpp; std::cout << "PGFUtils: channels: " << header.channels; std::cout << "PGFUtils: mode: " << header.mode; std::cout << "PGFUtils: usedBitsPerChannel: " << header.usedBitsPerChannel; } if (QSysInfo::ByteOrder == QSysInfo::BigEndian) { int map[] = {3, 2, 1, 0}; pgfImg.ImportBitmap(img.bytesPerLine(), (UINT8*)img.bits(), img.depth(), map); } else { int map[] = {0, 1, 2, 3}; pgfImg.ImportBitmap(img.bytesPerLine(), (UINT8*)img.bits(), img.depth(), map); } nWrittenBytes = 0; #ifdef PGFCodecVersionID # if PGFCodecVersionID >= 0x061124 pgfImg.Write(&stream, &nWrittenBytes); # else pgfImg.Write(&stream, 0, 0, &nWrittenBytes); # endif #else pgfImg.Write(&stream, 0, 0, &nWrittenBytes); #endif } catch (IOException& e) { int err = e.error; if (err >= AppError) { err -= AppError; } std::cout << "PGFUtils: Error running libpgf (" << err << ")!"; return false; } return true; } bool loadPGFScaled(QImage& img, const QString& path, int maximumSize) { FILE* const file = fopen(QFile::encodeName(path).constData(), "rb"); if (!file) { std::cout << "PGFUtils: Error: Could not open source file."; return false; } unsigned char header[3]; if (fread(&header, 3, 1, file) != 1) { fclose(file); return false; } unsigned char pgfID[3] = { 0x50, 0x47, 0x46 }; if (memcmp(&header[0], &pgfID, 3) != 0) { // not a PGF file fclose(file); return false; } fclose(file); // ------------------------------------------------------------------- // Initialize PGF API. #ifdef Q_OS_WIN32 #ifdef UNICODE HANDLE fd = CreateFile((LPCWSTR)(QFile::encodeName(path).constData()), GENERIC_READ, 0, 0, OPEN_EXISTING, 0, 0); #else HANDLE fd = CreateFile(QFile::encodeName(path).constData(), GENERIC_READ, 0, 0, OPEN_EXISTING, 0, 0); #endif if (fd == INVALID_HANDLE_VALUE) { return false; } #else int fd = QT_OPEN(QFile::encodeName(path).constData(), O_RDONLY); if (fd == -1) { return false; } #endif try { CPGFFileStream stream(fd); CPGFImage pgf; pgf.Open(&stream); // Try to find the right PGF level to get reduced image accordingly // with preview size wanted. int i = 0; if (pgf.Levels() > 0) { for (i = pgf.Levels()-1 ; i >= 0 ; --i) { if (qMin((int)pgf.Width(i), (int)pgf.Height(i)) >= maximumSize) { break; } } } if (i < 0) { i = 0; } pgf.Read(i); // Read PGF image at reduced level i. img = QImage(pgf.Width(i), pgf.Height(i), QImage::Format_RGB32); if (QSysInfo::ByteOrder == QSysInfo::BigEndian) { int map[] = {3, 2, 1, 0}; pgf.GetBitmap(img.bytesPerLine(), (UINT8*)img.bits(), img.depth(), map); } else { int map[] = {0, 1, 2, 3}; pgf.GetBitmap(img.bytesPerLine(), (UINT8*)img.bits(), img.depth(), map); } } catch (IOException& e) { int err = e.error; if (err >= AppError) { err -= AppError; } std::cerr << "PGFUtils: Error running libpgf (" << err << ")!"; return false; } return true; } QString libPGFVersion() { return (QLatin1String(PGFCodecVersion)); }