You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
471 lines
12 KiB
471 lines
12 KiB
/* ============================================================ |
|
* |
|
* 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 <caulier dot gilles at gmail dot com> |
|
* |
|
* 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 <unistd.h> |
|
#include <sys/types.h> |
|
#include <sys/stat.h> |
|
#include <fcntl.h> |
|
} |
|
|
|
#include <iostream> |
|
|
|
// Qt includes |
|
|
|
#include <QImage> |
|
#include <QByteArray> |
|
#include <QFile> |
|
#include <qplatformdefs.h> |
|
|
|
// Windows includes |
|
|
|
#ifdef Q_OS_WIN32 |
|
# include <windows.h> |
|
#endif |
|
|
|
// LibPGF includes |
|
|
|
#if defined(Q_CC_CLANG) |
|
# pragma clang diagnostic push |
|
# pragma clang diagnostic ignored "-Wkeyword-macro" |
|
#endif |
|
|
|
#include <PGFimage.h> |
|
|
|
#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)); |
|
}
|
|
|