diff --git a/core/libs/dimg/dimg.cpp b/core/libs/dimg/dimg.cpp index ed1edd766e..914cabb276 100644 --- a/core/libs/dimg/dimg.cpp +++ b/core/libs/dimg/dimg.cpp @@ -1,3277 +1,3277 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2005-06-14 * Description : digiKam 8/16 bits image management API * * Copyright (C) 2005 by Renchi Raju * Copyright (C) 2005-2019 by Gilles Caulier * Copyright (C) 2006-2013 by Marcel Wiesweg * * 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 "dimg.h" #include "dimg_p.h" // C ANSI includes #ifndef Q_OS_WIN extern "C" { #endif #include #ifndef Q_OS_WIN #include } #endif // C++ includes #include // Qt includes #include #include #include #include #include #include #include #include #include // Local includes #include "metaengine_rotation.h" #include "drawdecoder.h" #include "digikam_config.h" #include "dimagehistory.h" #include "pngloader.h" #include "tiffloader.h" #include "ppmloader.h" #include "rawloader.h" #include "pgfloader.h" #include "qimageloader.h" #include "jpegloader.h" #include "filereadwritelock.h" #include "iccmanager.h" #include "icctransform.h" #include "exposurecontainer.h" #include "dmetadata.h" #include "dimgloaderobserver.h" #include "randomnumbergenerator.h" #include "digikam_debug.h" #ifdef HAVE_JASPER # include "jp2kloader.h" #endif // HAVE_JASPER typedef uint64_t ullong; // krazy:exclude=typedefs typedef int64_t llong; // krazy:exclude=typedefs namespace Digikam { DImg::DImg() : m_priv(new Private) { } DImg::DImg(const QByteArray& filePath, DImgLoaderObserver* const observer, const DRawDecoding& rawDecodingSettings) : m_priv(new Private) { load(QString::fromUtf8(filePath), observer, rawDecodingSettings); } DImg::DImg(const QString& filePath, DImgLoaderObserver* const observer, const DRawDecoding& rawDecodingSettings) : m_priv(new Private) { load(filePath, observer, rawDecodingSettings); } DImg::DImg(const DImg& image) : m_priv(image.m_priv) { } DImg::DImg(uint width, uint height, bool sixteenBit, bool alpha, uchar* const data, bool copyData) : m_priv(new Private) { putImageData(width, height, sixteenBit, alpha, data, copyData); } DImg::DImg(const DImg& image, int w, int h) : m_priv(new Private) { // This private constructor creates a copy of everything except the data. // The image size is set to the given values and a buffer corresponding to these values is allocated. // This is used by copy and scale. copyImageData(image.m_priv); copyMetaData(image.m_priv); setImageDimension(w, h); allocateData(); } DImg::DImg(const QImage& image) : m_priv(new Private) { if (!image.isNull()) { QImage target; if (image.format() == QImage::Format_RGB32 || image.format() == QImage::Format_ARGB32) { target = image; } else { target = image.convertToFormat(QImage::Format_ARGB32); } setImageData(true, image.width(), image.height(), false, image.hasAlphaChannel()); if (allocateData()) { uint* sptr = reinterpret_cast(target.bits()); uchar* dptr = m_priv->data; const uint pixels = numPixels(); for (uint i = 0 ; i < pixels ; ++i) { dptr[0] = qBlue(*sptr); dptr[1] = qGreen(*sptr); dptr[2] = qRed(*sptr); dptr[3] = qAlpha(*sptr); dptr += 4; ++sptr; } } } } DImg::~DImg() { } //--------------------------------------------------------------------------------------------------- // data management DImg& DImg::operator=(const DImg& image) { //qCDebug(DIGIKAM_DIMG_LOG) << "Original image: " << m_priv->imageHistory.entries().count() << " | " << &m_priv; //qCDebug(DIGIKAM_DIMG_LOG) << "New image: " << image.m_priv->imageHistory.entries().count() << " | " << &(image.m_priv); m_priv = image.m_priv; //qCDebug(DIGIKAM_DIMG_LOG) << "Original new image: " << m_priv->imageHistory.entries().count() << " | " << &m_priv; return *this; } bool DImg::operator==(const DImg& image) const { return m_priv == image.m_priv; } void DImg::reset() { m_priv = new Private; } void DImg::detach() { // are we being shared? if (!m_priv->hasMoreReferences()) { return; } DSharedDataPointer old = m_priv; m_priv = new Private; copyImageData(old); copyMetaData(old); if (old->data) { size_t size = allocateData(); memcpy(m_priv->data, old->data, size); } } void DImg::putImageData(uint width, uint height, bool sixteenBit, bool alpha, uchar* const data, bool copyData) { // set image data, metadata is untouched bool null = (width == 0) || (height == 0); // allocateData, or code below will set null to false setImageData(true, width, height, sixteenBit, alpha); // replace data delete [] m_priv->data; if (null) { // image is null - no data m_priv->data = 0; } else if (copyData) { size_t size = allocateData(); if (data) { memcpy(m_priv->data, data, size); } } else { if (data) { m_priv->data = data; m_priv->null = false; } else { allocateData(); } } } void DImg::putImageData(uchar* const data, bool copyData) { if (!data) { delete [] m_priv->data; m_priv->data = 0; m_priv->null = true; } else if (copyData) { memcpy(m_priv->data, data, numBytes()); } else { m_priv->data = data; } } void DImg::resetMetaData() { m_priv->attributes.clear(); m_priv->embeddedText.clear(); m_priv->metaData = MetaEngineData(); } uchar* DImg::stripImageData() { uchar* const data = m_priv->data; m_priv->data = 0; m_priv->null = true; return data; } void DImg::copyMetaData(const Private* const src) { m_priv->metaData = src->metaData; m_priv->attributes = src->attributes; m_priv->embeddedText = src->embeddedText; m_priv->iccProfile = src->iccProfile; m_priv->imageHistory = src->imageHistory; //FIXME: what about sharing and deleting lanczos_func? } void DImg::copyImageData(const Private* const src) { setImageData(src->null, src->width, src->height, src->sixteenBit, src->alpha); } size_t DImg::allocateData() { size_t size = m_priv->width * m_priv->height * (m_priv->sixteenBit ? 8 : 4); m_priv->data = DImgLoader::new_failureTolerant(size); if (!m_priv->data) { m_priv->null = true; m_priv->width = 0; m_priv->height = 0; return 0; } m_priv->null = false; return size; } void DImg::setImageDimension(uint width, uint height) { m_priv->width = width; m_priv->height = height; } void DImg::setImageData(bool null, uint width, uint height, bool sixteenBit, bool alpha) { m_priv->null = null; m_priv->width = width; m_priv->height = height; m_priv->alpha = alpha; m_priv->sixteenBit = sixteenBit; } //--------------------------------------------------------------------------------------------------- // load and save bool DImg::loadItemInfo(const QString& filePath, bool loadMetadata, bool loadICCData, bool loadUniqueHash, bool loadImageHistory) { DImgLoader::LoadFlags loadFlags = DImgLoader::LoadItemInfo; if (loadMetadata) { loadFlags |= DImgLoader::LoadMetadata; } if (loadICCData) { loadFlags |= DImgLoader::LoadICCData; } if (loadUniqueHash) { loadFlags |= DImgLoader::LoadUniqueHash; } if (loadImageHistory) { loadFlags |= DImgLoader::LoadImageHistory; } return load(filePath, loadFlags, 0, DRawDecoding()); } bool DImg::load(const QString& filePath, DImgLoaderObserver* const observer, const DRawDecoding& rawDecodingSettings) { return load(filePath, DImgLoader::LoadAll, observer, rawDecodingSettings); } bool DImg::load(const QString& filePath, bool loadMetadata, bool loadICCData, bool loadUniqueHash, bool loadImageHistory, DImgLoaderObserver* const observer, const DRawDecoding& rawDecodingSettings) { DImgLoader::LoadFlags loadFlags = DImgLoader::LoadItemInfo | DImgLoader::LoadImageData; if (loadMetadata) { loadFlags |= DImgLoader::LoadMetadata; } if (loadICCData) { loadFlags |= DImgLoader::LoadICCData; } if (loadUniqueHash) { loadFlags |= DImgLoader::LoadUniqueHash; } if (loadImageHistory) { loadFlags |= DImgLoader::LoadImageHistory; } return load(filePath, loadFlags, observer, rawDecodingSettings); } bool DImg::load(const QString& filePath, int loadFlagsInt, DImgLoaderObserver* const observer, const DRawDecoding& rawDecodingSettings) { FORMAT format = fileFormat(filePath); DImgLoader::LoadFlags loadFlags = (DImgLoader::LoadFlags)loadFlagsInt; setAttribute(QLatin1String("detectedFileFormat"), format); setAttribute(QLatin1String("originalFilePath"), filePath); FileReadLocker lock(filePath); switch (format) { case (NONE): { qCDebug(DIGIKAM_DIMG_LOG) << filePath << " : Unknown image format !!!"; return false; } case (JPEG): { qCDebug(DIGIKAM_DIMG_LOG) << filePath << " : JPEG file identified"; JPEGLoader loader(this); loader.setLoadFlags(loadFlags); if (loader.load(filePath, observer)) { m_priv->null = !loader.hasLoadedData(); m_priv->alpha = loader.hasAlpha(); m_priv->sixteenBit = loader.sixteenBit(); - setAttribute(QLatin1String("isreadonly"), loader.isReadOnly()); + setAttribute(QLatin1String("isReadOnly"), loader.isReadOnly()); return true; } break; } case (TIFF): { qCDebug(DIGIKAM_DIMG_LOG) << filePath << " : TIFF file identified"; TIFFLoader loader(this); loader.setLoadFlags(loadFlags); if (loader.load(filePath, observer)) { m_priv->null = !loader.hasLoadedData(); m_priv->alpha = loader.hasAlpha(); m_priv->sixteenBit = loader.sixteenBit(); - setAttribute(QLatin1String("isreadonly"), loader.isReadOnly()); + setAttribute(QLatin1String("isReadOnly"), loader.isReadOnly()); return true; } break; } case (PNG): { qCDebug(DIGIKAM_DIMG_LOG) << filePath << " : PNG file identified"; PNGLoader loader(this); loader.setLoadFlags(loadFlags); if (loader.load(filePath, observer)) { m_priv->null = !loader.hasLoadedData(); m_priv->alpha = loader.hasAlpha(); m_priv->sixteenBit = loader.sixteenBit(); - setAttribute(QLatin1String("isreadonly"), loader.isReadOnly()); + setAttribute(QLatin1String("isReadOnly"), loader.isReadOnly()); return true; } break; } case (PPM): { qCDebug(DIGIKAM_DIMG_LOG) << filePath << " : PPM file identified"; PPMLoader loader(this); loader.setLoadFlags(loadFlags); if (loader.load(filePath, observer)) { m_priv->null = !loader.hasLoadedData(); m_priv->alpha = loader.hasAlpha(); m_priv->sixteenBit = loader.sixteenBit(); - setAttribute(QLatin1String("isreadonly"), loader.isReadOnly()); + setAttribute(QLatin1String("isReadOnly"), loader.isReadOnly()); return true; } break; } case (RAW): { qCDebug(DIGIKAM_DIMG_LOG) << filePath << " : RAW file identified"; RAWLoader loader(this, rawDecodingSettings); loader.setLoadFlags(loadFlags); if (loader.load(filePath, observer)) { m_priv->null = !loader.hasLoadedData(); m_priv->alpha = loader.hasAlpha(); m_priv->sixteenBit = loader.sixteenBit(); - setAttribute(QLatin1String("isreadonly"), loader.isReadOnly()); + setAttribute(QLatin1String("isReadOnly"), loader.isReadOnly()); loader.postProcess(observer); return true; } break; } #ifdef HAVE_JASPER case (JP2K): { qCDebug(DIGIKAM_DIMG_LOG) << filePath << " : JPEG2000 file identified"; JP2KLoader loader(this); loader.setLoadFlags(loadFlags); if (loader.load(filePath, observer)) { m_priv->null = !loader.hasLoadedData(); m_priv->alpha = loader.hasAlpha(); m_priv->sixteenBit = loader.sixteenBit(); - setAttribute(QLatin1String("isreadonly"), loader.isReadOnly()); + setAttribute(QLatin1String("isReadOnly"), loader.isReadOnly()); return true; } break; } #endif // HAVE_JASPER case (PGF): { qCDebug(DIGIKAM_DIMG_LOG) << filePath << " : PGF file identified"; PGFLoader loader(this); loader.setLoadFlags(loadFlags); if (loader.load(filePath, observer)) { m_priv->null = !loader.hasLoadedData(); m_priv->alpha = loader.hasAlpha(); m_priv->sixteenBit = loader.sixteenBit(); - setAttribute(QLatin1String("isreadonly"), loader.isReadOnly()); + setAttribute(QLatin1String("isReadOnly"), loader.isReadOnly()); return true; } break; } default: break; } if (observer && !observer->continueQuery(0)) { return false; } qCDebug(DIGIKAM_DIMG_LOG) << filePath << " : QIMAGE file identified"; QImageLoader loader(this); loader.setLoadFlags(loadFlags); if (loader.load(filePath, observer)) { m_priv->null = !loader.hasLoadedData(); m_priv->alpha = loader.hasAlpha(); m_priv->sixteenBit = loader.sixteenBit(); - setAttribute(QLatin1String("isreadonly"), loader.isReadOnly()); + setAttribute(QLatin1String("isReadOnly"), loader.isReadOnly()); return true; } return false; } QString DImg::formatToMimeType(FORMAT frm) { QString format; switch (frm) { case (NONE): { return format; } case (JPEG): { format = QLatin1String("JPG"); break; } case (TIFF): { format = QLatin1String("TIF"); break; } case (PNG): { format = QLatin1String("PNG"); break; } case (PPM): { format = QLatin1String("PPM"); break; } case (JP2K): { format = QLatin1String("JP2"); break; } case (PGF): { format = QLatin1String("PGF"); break; } default: { // For QImage based. break; } } return format; } bool DImg::save(const QString& filePath, FORMAT frm, DImgLoaderObserver* const observer) { if (isNull()) { return false; } return(save(filePath, formatToMimeType(frm), observer)); } bool DImg::save(const QString& filePath, const QString& format, DImgLoaderObserver* const observer) { qCDebug(DIGIKAM_DIMG_LOG) << "Saving to " << filePath << " with format: " << format; if (isNull()) { return false; } if (format.isEmpty()) { return false; } QString frm = format.toUpper(); setAttribute(QLatin1String("savedFilePath"), filePath); FileWriteLocker lock(filePath); if (frm == QLatin1String("JPEG") || frm == QLatin1String("JPG") || frm == QLatin1String("JPE")) { // JPEG does not support transparency, so we shall provide an image without alpha channel. // This is only necessary if the image has an alpha channel, and there are actually transparent pixels if (hasTransparentPixels()) { DImg alphaRemoved = copy(); alphaRemoved.removeAlphaChannel(); JPEGLoader loader(&alphaRemoved); - setAttribute(QLatin1String("savedformat-isreadonly"), loader.isReadOnly()); + setAttribute(QLatin1String("savedFormat-isReadOnly"), loader.isReadOnly()); return loader.save(filePath, observer); } else { JPEGLoader loader(this); - setAttribute(QLatin1String("savedformat-isreadonly"), loader.isReadOnly()); + setAttribute(QLatin1String("savedFormat-isReadOnly"), loader.isReadOnly()); return loader.save(filePath, observer); } } else if (frm == QLatin1String("PNG")) { PNGLoader loader(this); - setAttribute(QLatin1String("savedformat-isreadonly"), loader.isReadOnly()); + setAttribute(QLatin1String("savedFormat-isReadOnly"), loader.isReadOnly()); return loader.save(filePath, observer); } else if (frm == QLatin1String("TIFF") || frm == QLatin1String("TIF")) { TIFFLoader loader(this); - setAttribute(QLatin1String("savedformat-isreadonly"), loader.isReadOnly()); + setAttribute(QLatin1String("savedFormat-isReadOnly"), loader.isReadOnly()); return loader.save(filePath, observer); } else if (frm == QLatin1String("PPM")) { PPMLoader loader(this); - setAttribute(QLatin1String("savedformat-isreadonly"), loader.isReadOnly()); + setAttribute(QLatin1String("savedFormat-isReadOnly"), loader.isReadOnly()); return loader.save(filePath, observer); } #ifdef HAVE_JASPER else if (frm == QLatin1String("JP2") || frm == QLatin1String("J2K") || frm == QLatin1String("JPX") || frm == QLatin1String("JPC") || frm == QLatin1String("PGX")) { JP2KLoader loader(this); - setAttribute(QLatin1String("savedformat-isreadonly"), loader.isReadOnly()); + setAttribute(QLatin1String("savedFormat-isReadOnly"), loader.isReadOnly()); return loader.save(filePath, observer); } #endif // HAVE_JASPER else if (frm == QLatin1String("PGF")) { PGFLoader loader(this); - setAttribute(QLatin1String("savedformat-isreadonly"), loader.isReadOnly()); + setAttribute(QLatin1String("savedFormat-isReadOnly"), loader.isReadOnly()); return loader.save(filePath, observer); } else { setAttribute(QLatin1String("format"), format); QImageLoader loader(this); - setAttribute(QLatin1String("savedformat-isreadonly"), loader.isReadOnly()); + setAttribute(QLatin1String("savedFormat-isReadOnly"), loader.isReadOnly()); return loader.save(filePath, observer); } return false; } DImg::FORMAT DImg::fileFormat(const QString& filePath) { if (filePath.isNull()) { return NONE; } // In first we trying to check the file extension. This is mandatory because // some tiff files are detected like RAW files by identify method. QFileInfo fileInfo(filePath); if (!fileInfo.exists()) { qCDebug(DIGIKAM_DIMG_LOG) << "File " << filePath << " does not exist"; return NONE; } QString rawFilesExt = QLatin1String(DRawDecoder::rawFiles()); QString ext = fileInfo.suffix().toUpper(); if (!ext.isEmpty()) { if (ext == QLatin1String("JPEG") || ext == QLatin1String("JPG") || ext == QLatin1String("JPE")) { return JPEG; } else if (ext == QLatin1String("PNG")) { return PNG; } else if (ext == QLatin1String("TIFF") || ext == QLatin1String("TIF")) { return TIFF; } else if (rawFilesExt.toUpper().contains(ext)) { return RAW; } else if (ext == QLatin1String("JP2") || ext == QLatin1String("JPX") || // JPEG2000 file format ext == QLatin1String("JPC") || ext == QLatin1String("J2K") || // JPEG2000 code stream ext == QLatin1String("PGX")) // JPEG2000 WM format { return JP2K; } else if (ext == QLatin1String("PGF")) { return PGF; } } // In second, we trying to parse file header. FILE* const f = fopen(QFile::encodeName(filePath).constData(), "rb"); if (!f) { qCDebug(DIGIKAM_DIMG_LOG) << "Failed to open file " << filePath; return NONE; } const int headerLen = 9; unsigned char header[headerLen]; if (fread(&header, headerLen, 1, f) != 1) { qCDebug(DIGIKAM_DIMG_LOG) << "Failed to read header of file " << filePath; fclose(f); return NONE; } fclose(f); DRawInfo dcrawIdentify; uchar jpegID[2] = { 0xFF, 0xD8 }; uchar tiffBigID[2] = { 0x4D, 0x4D }; uchar tiffLilID[2] = { 0x49, 0x49 }; uchar pngID[8] = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A }; uchar jp2ID[5] = { 0x6A, 0x50, 0x20, 0x20, 0x0D, }; uchar jpcID[2] = { 0xFF, 0x4F }; uchar pgfID[3] = { 0x50, 0x47, 0x46 }; if (memcmp(&header, &jpegID, 2) == 0) // JPEG file ? { return JPEG; } else if (memcmp(&header, &pngID, 8) == 0) // PNG file ? { return PNG; } else if (memcmp(&header[0], "P", 1) == 0 && memcmp(&header[2], "\n", 1) == 0) // PPM 16 bits file ? { int width, height, rgbmax; char nl; FILE* const file = fopen(QFile::encodeName(filePath).constData(), "rb"); // FIXME: scanf without field width limits can crash with huge input data if (file && fscanf(file, "P6 %d %d %d%c", &width, &height, &rgbmax, &nl) == 4) { if (rgbmax > 255) { fclose(file); return PPM; } } if (file) fclose(file); } else if (DRawDecoder::rawFileIdentify(dcrawIdentify, filePath) && dcrawIdentify.isDecodable) { // RAW File test using identify method. // Need to test it before TIFF because any RAW file // formats using TIFF header. return RAW; } else if (memcmp(&header, &tiffBigID, 2) == 0 || // TIFF file ? memcmp(&header, &tiffLilID, 2) == 0) { return TIFF; } else if (memcmp(&header[4], &jp2ID, 5) == 0 || // JPEG2000 file ? memcmp(&header, &jpcID, 2) == 0) { return JP2K; } else if (memcmp(&header, &pgfID, 3) == 0) // PGF file ? { return PNG; } // In others cases, QImage will be used to try to open file. return QIMAGE; } //--------------------------------------------------------------------------------------------------- // accessing properties bool DImg::isNull() const { return m_priv->null; } uint DImg::width() const { return m_priv->width; } uint DImg::height() const { return m_priv->height; } QSize DImg::size() const { return QSize(m_priv->width, m_priv->height); } uchar* DImg::bits() const { return m_priv->data; } uchar* DImg::copyBits() const { uchar* const data = new uchar[numBytes()]; memcpy(data, bits(), numBytes()); return data; } uchar* DImg::scanLine(uint i) const { if (i >= height()) { return 0; } uchar* const data = bits() + (width() * bytesDepth() * i); return data; } bool DImg::hasAlpha() const { return m_priv->alpha; } bool DImg::sixteenBit() const { return m_priv->sixteenBit; } bool DImg::isReadOnly() const { - return attribute(QLatin1String("isreadonly")).toBool(); + return attribute(QLatin1String("isReadOnly")).toBool(); } DImg::COLORMODEL DImg::originalColorModel() const { if (m_priv->attributes.contains(QLatin1String("originalColorModel"))) { return (COLORMODEL)m_priv->attributes.value(QLatin1String("originalColorModel")).toInt(); } else { return COLORMODELUNKNOWN; } } int DImg::originalBitDepth() const { return m_priv->attributes.value(QLatin1String("originalBitDepth")).toInt(); } QSize DImg::originalSize() const { if (m_priv->attributes.contains(QLatin1String("originalSize"))) { QSize size = m_priv->attributes.value(QLatin1String("originalSize")).toSize(); if (size.isValid() && !size.isNull()) { return size; } } return size(); } DImg::FORMAT DImg::detectedFormat() const { if (m_priv->attributes.contains(QLatin1String("detectedFileFormat"))) { return (FORMAT)m_priv->attributes.value(QLatin1String("detectedFileFormat")).toInt(); } else { return NONE; } } QString DImg::format() const { return m_priv->attributes.value(QLatin1String("format")).toString(); } QString DImg::savedFormat() const { - return m_priv->attributes.value(QLatin1String("savedformat")).toString(); + return m_priv->attributes.value(QLatin1String("savedFormat")).toString(); } DRawDecoding DImg::rawDecodingSettings() const { if (m_priv->attributes.contains(QLatin1String("rawDecodingSettings"))) { return m_priv->attributes.value(QLatin1String("rawDecodingSettings")).value(); } else { return DRawDecoding(); } } IccProfile DImg::getIccProfile() const { return m_priv->iccProfile; } void DImg::setIccProfile(const IccProfile& profile) { m_priv->iccProfile = profile; } MetaEngineData DImg::getMetadata() const { return m_priv->metaData; } void DImg::setMetadata(const MetaEngineData& data) { m_priv->metaData = data; } uint DImg::numBytes() const { return (width() * height() * bytesDepth()); } uint DImg::numPixels() const { return (width() * height()); } int DImg::bytesDepth() const { if (m_priv->sixteenBit) { return 8; } return 4; } int DImg::bitsDepth() const { if (m_priv->sixteenBit) { return 16; } return 8; } void DImg::setAttribute(const QString& key, const QVariant& value) { m_priv->attributes.insert(key, value); } QVariant DImg::attribute(const QString& key) const { if (m_priv->attributes.contains(key)) { return m_priv->attributes[key]; } return QVariant(); } bool DImg::hasAttribute(const QString& key) const { return m_priv->attributes.contains(key); } void DImg::removeAttribute(const QString& key) { m_priv->attributes.remove(key); } void DImg::setEmbeddedText(const QString& key, const QString& text) { m_priv->embeddedText.insert(key, text); } QString DImg::embeddedText(const QString& key) const { if (m_priv->embeddedText.contains(key)) { return m_priv->embeddedText[key]; } return QString(); } void DImg::imageSavedAs(const QString& savePath) { setAttribute(QLatin1String("savedFilePath"), savePath); addAsReferredImage(savePath); } QString DImg::originalFilePath() const { return attribute(QLatin1String("originalFilePath")).toString(); } QString DImg::lastSavedFilePath() const { return attribute(QLatin1String("savedFilePath")).toString(); } QVariant DImg::fileOriginData() const { QVariantMap map; foreach (const QString& key, m_priv->fileOriginAttributes()) { QVariant attr = attribute(key); if (!attr.isNull()) { map.insert(key, attr); } } return map; } QVariant DImg::lastSavedFileOriginData() const { QVariantMap map; - QVariant savedformat = attribute(QLatin1String("savedformat")); + QVariant savedformat = attribute(QLatin1String("savedFormat")); if (!savedformat.isNull()) { map.insert(QLatin1String("format"), savedformat); } - QVariant readonly = attribute(QLatin1String("savedformat-isreadonly")); + QVariant readonly = attribute(QLatin1String("savedFormat-isReadOnly")); if (!readonly.isNull()) { - map.insert(QLatin1String("isreadonly"), readonly); + map.insert(QLatin1String("isReadOnly"), readonly); } QVariant filePath = attribute(QLatin1String("savedFilePath")); if (!filePath.isNull()) { map.insert(QLatin1String("originalFilePath"), filePath); } DImageHistory history = m_priv->imageHistory; if (!history.isEmpty()) { history.adjustReferredImages(); if (!history.entries().last().referredImages.isEmpty()) { history.entries().last().referredImages.last().setType(HistoryImageId::Current); } map.insert(QLatin1String("originalImageHistory"), QVariant::fromValue(history)); } return map; } void DImg::setFileOriginData(const QVariant& data) { QVariantMap map = data.toMap(); foreach (const QString& key, m_priv->fileOriginAttributes()) { removeAttribute(key); QVariant attr = map.value(key); if (!attr.isNull()) { setAttribute(key, attr); } } } void DImg::switchOriginToLastSaved() { setFileOriginData(lastSavedFileOriginData()); } DColor DImg::getPixelColor(uint x, uint y) const { if (m_priv->null || x >= m_priv->width || y >= m_priv->height) { return DColor(); } int depth = bytesDepth(); uchar* const data = m_priv->data + x * depth + (m_priv->width * y * depth); return (DColor(data, m_priv->sixteenBit)); } void DImg::prepareSubPixelAccess() { if (m_priv->lanczos_func) { return; } /* Precompute the Lanczos kernel */ LANCZOS_DATA_TYPE* lanczos_func = new LANCZOS_DATA_TYPE[LANCZOS_SUPPORT * LANCZOS_SUPPORT * LANCZOS_TABLE_RES]; for (int i = 0; i < LANCZOS_SUPPORT * LANCZOS_SUPPORT * LANCZOS_TABLE_RES; ++i) { if (i == 0) { lanczos_func [i] = LANCZOS_DATA_ONE; } else { float d = sqrt(((float)i) / LANCZOS_TABLE_RES); lanczos_func [i] = (LANCZOS_DATA_TYPE)((LANCZOS_DATA_ONE * LANCZOS_SUPPORT * sin(M_PI * d) * sin((M_PI / LANCZOS_SUPPORT) * d)) / (M_PI * M_PI * d * d)); } } m_priv->lanczos_func = lanczos_func; } #ifdef LANCZOS_DATA_FLOAT static inline int normalizeAndClamp(float norm, int sum, int max) { int r = 0; if (norm != 0.0) { r = sum / norm; } if (r < 0) { r = 0; } else if (r > max) { r = max; } return r; } #else /* LANCZOS_DATA_FLOAT */ static inline int normalizeAndClamp(int norm, int sum, int max) { int r = 0; if (norm != 0) { r = sum / norm; } if (r < 0) { r = 0; } else if (r > max) { r = max; } return r; } #endif /* LANCZOS_DATA_FLOAT */ DColor DImg::getSubPixelColor(float x, float y) const { if (isNull()) { return DColor(); } const LANCZOS_DATA_TYPE* lanczos_func = m_priv->lanczos_func; if (lanczos_func == 0) { return DColor(); } x = qBound(0.0f, x, (float)width() - 1); y = qBound(0.0f, y, (float)height() - 1); Digikam::DColor col(0, 0, 0, 0xFFFF, sixteenBit()); #ifdef LANCZOS_DATA_FLOAT float xs = ::ceilf(x) - LANCZOS_SUPPORT; float xe = ::floorf(x) + LANCZOS_SUPPORT; float ys = ::ceilf(y) - LANCZOS_SUPPORT; float ye = ::floorf(y) + LANCZOS_SUPPORT; if (xs >= 0 && ys >= 0 && xe < width() && ye < height()) { float norm = 0.0; float sumR = 0.0; float sumG = 0.0; float sumB = 0.0; float _dx = x - xs; float dy = y - ys; for (; ys <= ye; ys += 1.0, dy -= 1.0) { float xc, dx = _dx; for (xc = xs; xc <= xe; xc += 1.0, dx -= 1.0) { uchar* const data = bits() + (int)(xs * bytesDepth()) + (int)(width() * ys * bytesDepth()); DColor src = DColor(data, sixteenBit()); float d = dx * dx + dy * dy; if (d >= LANCZOS_SUPPORT * LANCZOS_SUPPORT) { continue; } d = lanczos_func [(int)(d * LANCZOS_TABLE_RES)]; norm += d; sumR += d * src.red(); sumG += d * src.green(); sumB += d * src.blue(); } } int max = sixteenBit() ? 65535 : 255; col.setRed(normalizeAndClamp(norm, sumR, max)); col.setGreen(normalizeAndClamp(norm, sumG, max)); col.setBlue(normalizeAndClamp(norm, sumB, max)); } #else /* LANCZOS_DATA_FLOAT */ /* Do it in integer arithmetic, it's faster */ int xx = (int)x; int yy = (int)y; int xs = xx + 1 - LANCZOS_SUPPORT; int xe = xx + LANCZOS_SUPPORT; int ys = yy + 1 - LANCZOS_SUPPORT; int ye = yy + LANCZOS_SUPPORT; int norm = 0; int sumR = 0; int sumG = 0; int sumB = 0; int _dx = (int)(x * 4096.0) - (xs << 12); int dy = (int)(y * 4096.0) - (ys << 12); for (; ys <= ye; ++ys, dy -= 4096) { int xc, dx = _dx; for (xc = xs; xc <= xe; ++xc, dx -= 4096) { DColor src(0, 0, 0, 0xFFFF, sixteenBit()); if (xc >= 0 && ys >= 0 && xc < (int)width() && ys < (int)height()) { uchar* const data = bits() + xc * bytesDepth() + width() * ys * bytesDepth(); src.setColor(data, sixteenBit()); } int d = (dx * dx + dy * dy) >> 12; if (d >= 4096 * LANCZOS_SUPPORT * LANCZOS_SUPPORT) { continue; } d = lanczos_func [(d * LANCZOS_TABLE_RES) >> 12]; norm += d; sumR += d * src.red(); sumG += d * src.green(); sumB += d * src.blue(); } } int max = sixteenBit() ? 65535 : 255; col.setRed(normalizeAndClamp(norm, sumR, max)); col.setGreen(normalizeAndClamp(norm, sumG, max)); col.setBlue(normalizeAndClamp(norm, sumB, max)); #endif /* LANCZOS_DATA_FLOAT */ return col; } DColor DImg::getSubPixelColorFast(float x, float y) const { if (isNull()) { return DColor(); } x = qBound(0.0f, x, (float)width() - 1); y = qBound(0.0f, y, (float)height() - 1); int xx = (int)x; int yy = (int)y; float d_x = x - (int)x; float d_y = y - (int)y; uchar* data = 0; DColor d00, d01, d10, d11; DColor col; data = bits() + xx * bytesDepth() + yy * width() * bytesDepth(); d00.setColor(data, sixteenBit()); if ((xx + 1) < (int)width()) { data = bits() + (xx + 1) * bytesDepth() + yy * width() * bytesDepth(); d10.setColor(data, sixteenBit()); } if ((yy + 1) < (int)height()) { data = bits() + xx * bytesDepth() + (yy + 1) * width() * bytesDepth(); d01.setColor(data, sixteenBit()); } if ((xx + 1) < (int)width() && (yy + 1) < (int)height()) { data = bits() + (xx + 1) * bytesDepth() + (yy + 1) * width() * bytesDepth(); d11.setColor(data, sixteenBit()); } d00.multiply(1.0 - d_x); d00.multiply(1.0 - d_y); d10.multiply(d_x); d10.multiply(1.0 - d_y); d01.multiply(1.0 - d_x); d01.multiply(d_y); d11.multiply(d_x); d11.multiply(d_y); col.blendAdd(d00); col.blendAdd(d10); col.blendAdd(d01); col.blendAdd(d11); if (sixteenBit()) { col.blendClamp16(); } else { col.blendClamp8(); } return col; } void DImg::setPixelColor(uint x, uint y, const DColor& color) { if (m_priv->null || x >= m_priv->width || y >= m_priv->height) { return; } if (color.sixteenBit() != m_priv->sixteenBit) { return; } int depth = bytesDepth(); uchar* const data = m_priv->data + x * depth + (m_priv->width * y * depth); color.setPixel(data); } bool DImg::hasTransparentPixels() const { if (m_priv->null || !m_priv->alpha) { return false; } const uint w = m_priv->width; const uint h = m_priv->height; if (!m_priv->sixteenBit) // 8 bits image. { uchar* srcPtr = m_priv->data; for (uint j = 0; j < h; ++j) { for (uint i = 0; i < w; ++i) { if (srcPtr[3] != 0xFF) { return true; } srcPtr += 4; } } } else { unsigned short* srcPtr = reinterpret_cast(m_priv->data); for (uint j = 0; j < h; ++j) { for (uint i = 0; i < w; ++i) { if (srcPtr[3] != 0xFFFF) { return true; } srcPtr += 4; } } } return false; } //--------------------------------------------------------------------------------------------------- // copying operations DImg DImg::copy() const { DImg img(*this); img.detach(); return img; } DImg DImg::copyImageData() const { DImg img(width(), height(), sixteenBit(), hasAlpha(), bits(), true); return img; } DImg DImg::copyMetaData() const { DImg img; // copy width, height, alpha, sixteenBit, null img.copyImageData(m_priv); // deeply copy metadata img.copyMetaData(m_priv); // set image to null img.m_priv->null = true; return img; } DImg DImg::copy(const QRect& rect) const { return copy(rect.x(), rect.y(), rect.width(), rect.height()); } DImg DImg::copy(const QRectF& rel) const { if (isNull() || !rel.isValid()) { return DImg(); } return copy(QRectF(rel.x() * m_priv->width, rel.y() * m_priv->height, rel.width() * m_priv->width, rel.height() * m_priv->height) .toRect()); } DImg DImg::copy(int x, int y, int w, int h) const { if (isNull() || w <= 0 || h <= 0) { qCDebug(DIGIKAM_DIMG_LOG) << " : return null image! (" << isNull() << ", " << w << ", " << h << ")"; return DImg(); } if (!Private::clipped(x, y, w, h, m_priv->width, m_priv->height)) { return DImg(); } DImg image(*this, w, h); image.bitBltImage(this, x, y, w, h, 0, 0); return image; } //--------------------------------------------------------------------------------------------------- // bitwise operations void DImg::bitBltImage(const DImg* const src, int dx, int dy) { bitBltImage(src, 0, 0, src->width(), src->height(), dx, dy); } void DImg::bitBltImage(const DImg* const src, int sx, int sy, int dx, int dy) { bitBltImage(src, sx, sy, src->width() - sx, src->height() - sy, dx, dy); } void DImg::bitBltImage(const DImg* const src, int sx, int sy, int w, int h, int dx, int dy) { if (isNull()) { return; } if (src->sixteenBit() != sixteenBit()) { qCWarning(DIGIKAM_DIMG_LOG) << "Blitting from 8-bit to 16-bit or vice versa is not supported"; return; } if (w == -1 && h == -1) { w = src->width(); h = src->height(); } bitBlt(src->bits(), bits(), sx, sy, w, h, dx, dy, src->width(), src->height(), width(), height(), sixteenBit(), src->bytesDepth(), bytesDepth()); } void DImg::bitBltImage(const uchar* const src, int sx, int sy, int w, int h, int dx, int dy, uint swidth, uint sheight, int sdepth) { if (isNull()) { return; } if (bytesDepth() != sdepth) { qCWarning(DIGIKAM_DIMG_LOG) << "Blitting from 8-bit to 16-bit or vice versa is not supported"; return; } if (w == -1 && h == -1) { w = swidth; h = sheight; } bitBlt(src, bits(), sx, sy, w, h, dx, dy, swidth, sheight, width(), height(), sixteenBit(), sdepth, bytesDepth()); } bool DImg::normalizeRegionArguments(int& sx, int& sy, int& w, int& h, int& dx, int& dy, uint swidth, uint sheight, uint dwidth, uint dheight) { if (sx < 0) { // sx is negative, so + is - and - is + dx -= sx; w += sx; sx = 0; } if (sy < 0) { dy -= sy; h += sy; sy = 0; } if (dx < 0) { sx -= dx; w += dx; dx = 0; } if (dy < 0) { sy -= dy; h += dy; dy = 0; } if (sx + w > (int)swidth) { w = swidth - sx; } if (sy + h > (int)sheight) { h = sheight - sy; } if (dx + w > (int)dwidth) { w = dwidth - dx; } if (dy + h > (int)dheight) { h = dheight - dy; } // Nothing left to copy if (w <= 0 || h <= 0) { return false; } return true; } void DImg::bitBlt(const uchar* const src, uchar* const dest, int sx, int sy, int w, int h, int dx, int dy, uint swidth, uint sheight, uint dwidth, uint dheight, bool /*sixteenBit*/, int sdepth, int ddepth) { // Normalize if (!normalizeRegionArguments(sx, sy, w, h, dx, dy, swidth, sheight, dwidth, dheight)) { return; } // Same pixels if (src == dest && dx == sx && dy == sy) { return; } const uchar* sptr = 0; uchar* dptr = 0; uint slinelength = swidth * sdepth; uint dlinelength = dwidth * ddepth; int scurY = sy; int dcurY = dy; int sdepthlength = w * sdepth; for (int j = 0 ; j < h ; ++j, ++scurY, ++dcurY) { sptr = &src [ scurY * slinelength ] + sx * sdepth; dptr = &dest[ dcurY * dlinelength ] + dx * ddepth; // plain and simple bitBlt for (int i = 0; i < sdepthlength ; ++i, ++sptr, ++dptr) { *dptr = *sptr; } } } void DImg::bitBlendImage(DColorComposer* const composer, const DImg* const src, int sx, int sy, int w, int h, int dx, int dy, DColorComposer::MultiplicationFlags multiplicationFlags) { if (isNull()) { return; } if (src->sixteenBit() != sixteenBit()) { qCWarning(DIGIKAM_DIMG_LOG) << "Blending from 8-bit to 16-bit or vice versa is not supported"; return; } bitBlend(composer, src->bits(), bits(), sx, sy, w, h, dx, dy, src->width(), src->height(), width(), height(), sixteenBit(), src->bytesDepth(), bytesDepth(), multiplicationFlags); } void DImg::bitBlend(DColorComposer* const composer, uchar* const src, uchar* const dest, int sx, int sy, int w, int h, int dx, int dy, uint swidth, uint sheight, uint dwidth, uint dheight, bool sixteenBit, int sdepth, int ddepth, DColorComposer::MultiplicationFlags multiplicationFlags) { // Normalize if (!normalizeRegionArguments(sx, sy, w, h, dx, dy, swidth, sheight, dwidth, dheight)) { return; } uchar* sptr = 0; uchar* dptr = 0; uint slinelength = swidth * sdepth; uint dlinelength = dwidth * ddepth; int scurY = sy; int dcurY = dy; for (int j = 0 ; j < h ; ++j, ++scurY, ++dcurY) { sptr = &src [ scurY * slinelength ] + sx * sdepth; dptr = &dest[ dcurY * dlinelength ] + dx * ddepth; // blend src and destination for (int i = 0 ; i < w ; ++i, sptr += sdepth, dptr += ddepth) { DColor src(sptr, sixteenBit); DColor dst(dptr, sixteenBit); // blend colors composer->compose(dst, src, multiplicationFlags); dst.setPixel(dptr); } } } void DImg::bitBlendImageOnColor(const DColor& color) { bitBlendImageOnColor(color, 0, 0, width(), height()); } void DImg::bitBlendImageOnColor(const DColor& color, int x, int y, int w, int h) { // get composer for compositing rule DColorComposer* const composer = DColorComposer::getComposer(DColorComposer::PorterDuffNone); // flags would be MultiplicationFlagsDImg for anything but PorterDuffNone bitBlendImageOnColor(composer, color, x, y, w, h, DColorComposer::NoMultiplication); delete composer; } void DImg::bitBlendImageOnColor(DColorComposer* const composer, const DColor& color, int x, int y, int w, int h, DColorComposer::MultiplicationFlags multiplicationFlags) { if (isNull()) { return; } DColor c = color; if (sixteenBit()) { c.convertToSixteenBit(); } else { c.convertToEightBit(); } bitBlendOnColor(composer, c, bits(), x, y, w, h, width(), height(), sixteenBit(), bytesDepth(), multiplicationFlags); } void DImg::bitBlendOnColor(DColorComposer* const composer, const DColor& color, uchar* const data, int x, int y, int w, int h, uint width, uint height, bool sixteenBit, int depth, DColorComposer::MultiplicationFlags multiplicationFlags) { // Normalize if (!normalizeRegionArguments(x, y, w, h, x, y, width, height, width, height)) { return; } uchar* ptr = 0; uint linelength = width * depth; int curY = y; for (int j = 0 ; j < h ; ++j, ++curY) { ptr = &data[ curY * linelength ] + x * depth; // blend src and destination for (int i = 0 ; i < w ; ++i, ptr += depth) { DColor src(ptr, sixteenBit); DColor dst(color); // blend colors composer->compose(dst, src, multiplicationFlags); dst.setPixel(ptr); } } } //--------------------------------------------------------------------------------------------------- // QImage / QPixmap access QImage DImg::copyQImage() const { if (isNull()) { return QImage(); } if (sixteenBit()) { DImg img(*this); img.detach(); img.convertDepth(32); return img.copyQImage(); } QImage img(width(), height(), QImage::Format_ARGB32); if (img.isNull()) { qCDebug(DIGIKAM_DIMG_LOG) << "Failed to allocate memory to copy DImg of size" << size() << "to QImage"; return QImage(); } uchar* sptr = bits(); uint* dptr = reinterpret_cast(img.bits()); for (uint i = 0; i < width()*height(); ++i) { *dptr++ = qRgba(sptr[2], sptr[1], sptr[0], sptr[3]); sptr += 4; } // NOTE: Qt4 do not provide anymore QImage::setAlphaChannel() because // alpha channel is auto-detected during QImage->QPixmap conversion return img; } QImage DImg::copyQImage(const QRect& rect) const { return (copyQImage(rect.x(), rect.y(), rect.width(), rect.height())); } QImage DImg::copyQImage(const QRectF& rel) const { if (isNull() || !rel.isValid()) { return QImage(); } return copyQImage(QRectF(rel.x() * m_priv->width, rel.y() * m_priv->height, rel.width() * m_priv->width, rel.height() * m_priv->height) .toRect()); } QImage DImg::copyQImage(int x, int y, int w, int h) const { if (isNull()) { return QImage(); } DImg img = copy(x, y, w, h); if (img.sixteenBit()) { img.convertDepth(32); } return img.copyQImage(); } // -------------------------------------------------------------------------------------- class Q_DECL_HIDDEN PixmapPaintEngineDetector { public: PixmapPaintEngineDetector() : m_isRaster(detectRasterFromPixmap()) { } bool isRaster() const { return m_isRaster; } private: static bool detectRasterFromPixmap() { QPixmap pix(1, 1); QPainter p(&pix); return (p.paintEngine() && p.paintEngine()->type() == QPaintEngine::Raster); } const bool m_isRaster; }; Q_GLOBAL_STATIC(PixmapPaintEngineDetector, pixmapPaintEngineDetector) // -------------------------------------------------------------------------------------- QPixmap DImg::convertToPixmap() const { if (isNull()) { return QPixmap(); } if (sixteenBit()) { // make fastaaaa... return QPixmap::fromImage(copyQImage(0, 0, width(), height())); } if (QSysInfo::ByteOrder == QSysInfo::BigEndian) { QImage img(width(), height(), hasAlpha() ? QImage::Format_ARGB32 : QImage::Format_RGB32); uchar* sptr = bits(); uint* dptr = reinterpret_cast(img.bits()); uint dim = width() * height(); for (uint i = 0; i < dim; ++i) { *dptr++ = qRgba(sptr[2], sptr[1], sptr[0], sptr[3]); sptr += 4; } // alpha channel is auto-detected during QImage->QPixmap conversion return QPixmap::fromImage(img); } else { // This is a temporary image operating on the DImg buffer QImage img(bits(), width(), height(), hasAlpha() ? QImage::Format_ARGB32 : QImage::Format_RGB32); // For paint engines which base the QPixmap internally on a QImage, we must use a persistent QImage if (pixmapPaintEngineDetector->isRaster()) { img = img.copy(); } // alpha channel is auto-detected during QImage->QPixmap conversion return QPixmap::fromImage(img); } } QPixmap DImg::convertToPixmap(IccTransform& monitorICCtrans) const { if (isNull()) { return QPixmap(); } if (monitorICCtrans.outputProfile().isNull()) { return convertToPixmap(); } DImg img = copy(); monitorICCtrans.apply(img); return (img.convertToPixmap()); } QImage DImg::pureColorMask(ExposureSettingsContainer* const expoSettings) const { if (isNull() || (!expoSettings->underExposureIndicator && !expoSettings->overExposureIndicator)) { return QImage(); } QImage img(size(), QImage::Format_ARGB32); img.fill(0x00000000); // Full transparent. // NOTE: Qt4 do not provide anymore QImage::setAlphaChannel() because // alpha channel is auto-detected during QImage->QPixmap conversion uchar* bits = img.bits(); // NOTE: Using DImgScale before to compute Mask clamp to 65534 | 254. Why ? int max = lround(sixteenBit() ? 65535.0 - (65535.0 * expoSettings->overExposurePercent / 100.0) : 255.0 - (255.0 * expoSettings->overExposurePercent / 100.0)); int min = lround(sixteenBit() ? 0.0 + (65535.0 * expoSettings->underExposurePercent / 100.0) : 0.0 + (255.0 * expoSettings->underExposurePercent / 100.0)); // -------------------------------------------------------- // caching int u_red = expoSettings->underExposureColor.red(); int u_green = expoSettings->underExposureColor.green(); int u_blue = expoSettings->underExposureColor.blue(); int o_red = expoSettings->overExposureColor.red(); int o_green = expoSettings->overExposureColor.green(); int o_blue = expoSettings->overExposureColor.blue(); bool under = expoSettings->underExposureIndicator; bool over = expoSettings->overExposureIndicator; bool pure = expoSettings->exposureIndicatorMode; // -------------------------------------------------------- uint dim = m_priv->width * m_priv->height; uchar* dptr = bits; int s_blue, s_green, s_red; bool match = false; if (sixteenBit()) { unsigned short* sptr = reinterpret_cast(m_priv->data); for (uint i = 0; i < dim; ++i) { s_blue = *sptr++; s_green = *sptr++; s_red = *sptr++; sptr++; match = pure ? (s_red <= min) && (s_green <= min) && (s_blue <= min) : (s_red <= min) || (s_green <= min) || (s_blue <= min); if (under && match) { if (QSysInfo::ByteOrder == QSysInfo::BigEndian) { dptr[0] = 0xFF; dptr[1] = u_red; dptr[2] = u_green; dptr[3] = u_blue; } else { dptr[0] = u_blue; dptr[1] = u_green; dptr[2] = u_red; dptr[3] = 0xFF; } } match = pure ? (s_red >= max) && (s_green >= max) && (s_blue >= max) : (s_red >= max) || (s_green >= max) || (s_blue >= max); if (over && match) { if (QSysInfo::ByteOrder == QSysInfo::BigEndian) { dptr[0] = 0xFF; dptr[1] = o_red; dptr[2] = o_green; dptr[3] = o_blue; } else { dptr[0] = o_blue; dptr[1] = o_green; dptr[2] = o_red; dptr[3] = 0xFF; } } dptr += 4; } } else { uchar* sptr = m_priv->data; for (uint i = 0; i < dim; ++i) { s_blue = *sptr++; s_green = *sptr++; s_red = *sptr++; sptr++; match = pure ? (s_red <= min) && (s_green <= min) && (s_blue <= min) : (s_red <= min) || (s_green <= min) || (s_blue <= min); if (under && match) { if (QSysInfo::ByteOrder == QSysInfo::BigEndian) { dptr[0] = 0xFF; dptr[1] = u_red; dptr[2] = u_green; dptr[3] = u_blue; } else { dptr[0] = u_blue; dptr[1] = u_green; dptr[2] = u_red; dptr[3] = 0xFF; } } match = pure ? (s_red >= max) && (s_green >= max) && (s_blue >= max) : (s_red >= max) || (s_green >= max) || (s_blue >= max); if (over && match) { if (QSysInfo::ByteOrder == QSysInfo::BigEndian) { dptr[0] = 0xFF; dptr[1] = o_red; dptr[2] = o_green; dptr[3] = o_blue; } else { dptr[0] = o_blue; dptr[1] = o_green; dptr[2] = o_red; dptr[3] = 0xFF; } } dptr += 4; } } return img; } //--------------------------------------------------------------------------------------------------- // basic imaging operations void DImg::crop(const QRect& rect) { crop(rect.x(), rect.y(), rect.width(), rect.height()); } void DImg::crop(int x, int y, int w, int h) { if (isNull() || w <= 0 || h <= 0) { return; } uint oldw = width(); uint oldh = height(); QScopedArrayPointer old(stripImageData()); // set new image data, bits(), width(), height() change setImageDimension(w, h); allocateData(); // copy image region (x|y), wxh, from old data to point (0|0) of new data bitBlt(old.data(), bits(), x, y, w, h, 0, 0, oldw, oldh, width(), height(), sixteenBit(), bytesDepth(), bytesDepth()); } void DImg::resize(int w, int h) { if (isNull() || w <= 0 || h <= 0) { return; } DImg image = smoothScale(w, h); delete [] m_priv->data; m_priv->data = image.stripImageData(); setImageDimension(w, h); } void DImg::removeAlphaChannel() { removeAlphaChannel(DColor(0xFF, 0xFF, 0xFF, 0xFF, false)); } void DImg::removeAlphaChannel(const DColor& destColor) { if (isNull() || !hasAlpha()) { return; } bitBlendImageOnColor(destColor); // unsure if alpha value is always 0xFF now m_priv->alpha = false; } void DImg::rotate(ANGLE angle) { if (isNull()) { return; } bool switchDims = false; switch (angle) { case (ROT90): { uint w = height(); uint h = width(); if (sixteenBit()) { ullong* newData = DImgLoader::new_failureTolerant(w * h); ullong* from = reinterpret_cast(m_priv->data); ullong* to = 0; for (int y = w - 1; y >= 0; --y) { to = newData + y; for (uint x = 0; x < h; ++x) { *to = *from++; to += w; } } switchDims = true; delete [] m_priv->data; m_priv->data = (uchar*)newData; } else { uint* newData = DImgLoader::new_failureTolerant(w * h); uint* from = reinterpret_cast(m_priv->data); uint* to = 0; for (int y = w - 1; y >= 0; --y) { to = newData + y; for (uint x = 0; x < h; ++x) { *to = *from++; to += w; } } switchDims = true; delete [] m_priv->data; m_priv->data = (uchar*)newData; } break; } case (ROT180): { uint w = width(); uint h = height(); int middle_line = -1; if (h % 2) { middle_line = h / 2; } if (sixteenBit()) { ullong* line1 = 0; ullong* line2 = 0; ullong* data = reinterpret_cast(bits()); ullong tmp; // can be done inplace uint ymax = (h + 1) / 2; for (uint y = 0; y < ymax; ++y) { line1 = data + y * w; line2 = data + (h - y) * w - 1; for (uint x = 0; x < w; ++x) { tmp = *line1; *line1 = *line2; *line2 = tmp; ++line1; --line2; if ((int)y == middle_line && x * 2 >= w) { break; } } } } else { uint* line1 = 0; uint* line2 = 0; uint* data = reinterpret_cast(bits()); uint tmp; // can be done inplace uint ymax = (h + 1) / 2; for (uint y = 0; y < ymax; ++y) { line1 = data + y * w; line2 = data + (h - y) * w - 1; for (uint x = 0; x < w; ++x) { tmp = *line1; *line1 = *line2; *line2 = tmp; ++line1; --line2; if ((int)y == middle_line && x * 2 >= w) { break; } } } } break; } case (ROT270): { uint w = height(); uint h = width(); if (sixteenBit()) { ullong* newData = DImgLoader::new_failureTolerant(w * h); ullong* from = reinterpret_cast(m_priv->data); ullong* to = 0; for (uint y = 0; y < w; ++y) { to = newData + y + w * (h - 1); for (uint x = 0; x < h; ++x) { *to = *from++; to -= w; } } switchDims = true; delete [] m_priv->data; m_priv->data = (uchar*)newData; } else { uint* newData = DImgLoader::new_failureTolerant(w * h); uint* from = reinterpret_cast(m_priv->data); uint* to = 0; for (uint y = 0; y < w; ++y) { to = newData + y + w * (h - 1); for (uint x = 0; x < h; ++x) { *to = *from++; to -= w; } } switchDims = true; delete [] m_priv->data; m_priv->data = (uchar*)newData; } break; } default: break; } if (switchDims) { setImageDimension(height(), width()); QMap::iterator it = m_priv->attributes.find(QLatin1String("originalSize")); if (it != m_priv->attributes.end()) { QSize size = it.value().toSize(); it.value() = QSize(size.height(), size.width()); } } } // 15-11-2005: This method have been tested indeep with valgrind by Gilles. void DImg::flip(FLIP direction) { if (isNull()) { return; } switch (direction) { case (HORIZONTAL): { uint w = width(); uint h = height(); if (sixteenBit()) { unsigned short tmp[4]; unsigned short* beg = 0; unsigned short* end = 0; unsigned short* data = reinterpret_cast(bits()); // can be done inplace uint wHalf = (w / 2); for (uint y = 0 ; y < h ; ++y) { beg = data + y * w * 4; end = beg + (w - 1) * 4; for (uint x = 0 ; x < wHalf ; ++x) { memcpy(&tmp, beg, 8); memcpy(beg, end, 8); memcpy(end, &tmp, 8); beg += 4; end -= 4; } } } else { uchar tmp[4]; uchar* beg = 0; uchar* end = 0; uchar* data = bits(); // can be done inplace uint wHalf = (w / 2); for (uint y = 0 ; y < h ; ++y) { beg = data + y * w * 4; end = beg + (w - 1) * 4; for (uint x = 0 ; x < wHalf ; ++x) { memcpy(&tmp, beg, 4); memcpy(beg, end, 4); memcpy(end, &tmp, 4); beg += 4; end -= 4; } } } break; } case (VERTICAL): { uint w = width(); uint h = height(); if (sixteenBit()) { unsigned short tmp[4]; unsigned short* line1 = 0; unsigned short* line2 = 0; unsigned short* data = reinterpret_cast(bits()); // can be done inplace uint hHalf = (h / 2); for (uint y = 0 ; y < hHalf ; ++y) { line1 = data + y * w * 4; line2 = data + (h - y - 1) * w * 4; for (uint x = 0 ; x < w ; ++x) { memcpy(&tmp, line1, 8); memcpy(line1, line2, 8); memcpy(line2, &tmp, 8); line1 += 4; line2 += 4; } } } else { uchar tmp[4]; uchar* line1 = 0; uchar* line2 = 0; uchar* data = bits(); // can be done inplace uint hHalf = (h / 2); for (uint y = 0 ; y < hHalf ; ++y) { line1 = data + y * w * 4; line2 = data + (h - y - 1) * w * 4; for (uint x = 0 ; x < w ; ++x) { memcpy(&tmp, line1, 4); memcpy(line1, line2, 4); memcpy(line2, &tmp, 4); line1 += 4; line2 += 4; } } } break; } default: break; } } bool DImg::rotateAndFlip(int orientation) { bool rotatedOrFlipped = false; switch (orientation) { case DMetadata::ORIENTATION_NORMAL: case DMetadata::ORIENTATION_UNSPECIFIED: return false; case DMetadata::ORIENTATION_HFLIP: flip(DImg::HORIZONTAL); rotatedOrFlipped = true; break; case DMetadata::ORIENTATION_ROT_180: rotate(DImg::ROT180); rotatedOrFlipped = true; break; case DMetadata::ORIENTATION_VFLIP: flip(DImg::VERTICAL); rotatedOrFlipped = true; break; case DMetadata::ORIENTATION_ROT_90_HFLIP: rotate(DImg::ROT90); flip(DImg::HORIZONTAL); rotatedOrFlipped = true; break; case DMetadata::ORIENTATION_ROT_90: rotate(DImg::ROT90); rotatedOrFlipped = true; break; case DMetadata::ORIENTATION_ROT_90_VFLIP: rotate(DImg::ROT90); flip(DImg::VERTICAL); rotatedOrFlipped = true; break; case DMetadata::ORIENTATION_ROT_270: rotate(DImg::ROT270); rotatedOrFlipped = true; break; } return rotatedOrFlipped; } bool DImg::reverseRotateAndFlip(int orientation) { bool rotatedOrFlipped = false; switch (orientation) { case DMetadata::ORIENTATION_NORMAL: case DMetadata::ORIENTATION_UNSPECIFIED: return false; case DMetadata::ORIENTATION_HFLIP: flip(DImg::HORIZONTAL); rotatedOrFlipped = true; break; case DMetadata::ORIENTATION_ROT_180: rotate(DImg::ROT180); rotatedOrFlipped = true; break; case DMetadata::ORIENTATION_VFLIP: flip(DImg::VERTICAL); rotatedOrFlipped = true; break; case DMetadata::ORIENTATION_ROT_90_HFLIP: flip(DImg::HORIZONTAL); rotate(DImg::ROT270); rotatedOrFlipped = true; break; case DMetadata::ORIENTATION_ROT_90: rotate(DImg::ROT270); rotatedOrFlipped = true; break; case DMetadata::ORIENTATION_ROT_90_VFLIP: flip(DImg::VERTICAL); rotate(DImg::ROT270); rotatedOrFlipped = true; break; case DMetadata::ORIENTATION_ROT_270: rotate(DImg::ROT90); rotatedOrFlipped = true; break; } return rotatedOrFlipped; } bool DImg::transform(int transformAction) { switch (transformAction) { case MetaEngineRotation::NoTransformation: default: return false; break; case MetaEngineRotation::FlipHorizontal: flip(DImg::HORIZONTAL); break; case MetaEngineRotation::FlipVertical: flip(DImg::VERTICAL); break; case MetaEngineRotation::Rotate90: rotate(DImg::ROT90); break; case MetaEngineRotation::Rotate180: rotate(DImg::ROT180); break; case MetaEngineRotation::Rotate270: rotate(DImg::ROT270); break; } return true; } void DImg::convertToSixteenBit() { convertDepth(64); } void DImg::convertToEightBit() { convertDepth(32); } void DImg::convertToDepthOfImage(const DImg* const otherImage) { if (otherImage->sixteenBit()) { convertToSixteenBit(); } else { convertToEightBit(); } } void DImg::convertDepth(int depth) { if (isNull()) { return; } if (depth != 32 && depth != 64) { qCDebug(DIGIKAM_DIMG_LOG) << " : wrong color depth!"; return; } if (((depth == 32) && !sixteenBit()) || ((depth == 64) && sixteenBit())) { return; } if (depth == 32) { // downgrading from 16 bit to 8 bit uchar* data = new uchar[width()*height() * 4]; uchar* dptr = data; ushort* sptr = reinterpret_cast(bits()); uint dim = width() * height() * 4; for (uint i = 0; i < dim; ++i) { *dptr++ = (*sptr++ * 256UL) / 65536UL; } delete [] m_priv->data; m_priv->data = data; m_priv->sixteenBit = false; } else if (depth == 64) { // upgrading from 8 bit to 16 bit uchar* data = new uchar[width()*height() * 8]; ushort* dptr = reinterpret_cast(data); uchar* sptr = bits(); // use default seed of the generator RandomNumberGenerator generator; ushort noise = 0; uint dim = width() * height() * 4; for (uint i = 0; i < dim; ++i) { if (i % 4 < 3) { noise = generator.number(0, 255); } else { noise = 0; } *dptr++ = (*sptr++ * 65536ULL) / 256ULL + noise; } delete [] m_priv->data; m_priv->data = data; m_priv->sixteenBit = true; } } void DImg::fill(const DColor& color) { if (isNull()) { return; } // caching uint dim = width() * height() * 4; if (sixteenBit()) { unsigned short* imgData16 = reinterpret_cast(m_priv->data); unsigned short red = (unsigned short)color.red(); unsigned short green = (unsigned short)color.green(); unsigned short blue = (unsigned short)color.blue(); unsigned short alpha = (unsigned short)color.alpha(); for (uint i = 0 ; i < dim ; i += 4) { imgData16[i ] = blue; imgData16[i + 1] = green; imgData16[i + 2] = red; imgData16[i + 3] = alpha; } } else { uchar* imgData = m_priv->data; uchar red = (uchar)color.red(); uchar green = (uchar)color.green(); uchar blue = (uchar)color.blue(); uchar alpha = (uchar)color.alpha(); for (uint i = 0 ; i < dim ; i += 4) { imgData[i ] = blue; imgData[i + 1] = green; imgData[i + 2] = red; imgData[i + 3] = alpha; } } } QByteArray DImg::getUniqueHash() const { if (m_priv->attributes.contains(QLatin1String("uniqueHash"))) { return m_priv->attributes[QLatin1String("uniqueHash")].toByteArray(); } if (!m_priv->attributes.contains(QLatin1String("originalFilePath"))) { qCWarning(DIGIKAM_DIMG_LOG) << "DImg::getUniqueHash called without originalFilePath property set!"; return QByteArray(); } QString filePath = m_priv->attributes.value(QLatin1String("originalFilePath")).toString(); if (filePath.isEmpty()) { return QByteArray(); } FileReadLocker lock(filePath); QByteArray hash = DImgLoader::uniqueHash(filePath, *this, false); // attribute is written by DImgLoader return hash; } QByteArray DImg::getUniqueHash(const QString& filePath) { return DImgLoader::uniqueHash(filePath, DImg(), true); } QByteArray DImg::getUniqueHashV2() const { if (m_priv->attributes.contains(QLatin1String("uniqueHashV2"))) { return m_priv->attributes[QLatin1String("uniqueHashV2")].toByteArray(); } if (!m_priv->attributes.contains(QLatin1String("originalFilePath"))) { qCWarning(DIGIKAM_DIMG_LOG) << "DImg::getUniqueHash called without originalFilePath property set!"; return QByteArray(); } QString filePath = m_priv->attributes.value(QLatin1String("originalFilePath")).toString(); if (filePath.isEmpty()) { return QByteArray(); } FileReadLocker lock(filePath); return DImgLoader::uniqueHashV2(filePath, this); } QByteArray DImg::getUniqueHashV2(const QString& filePath) { return DImgLoader::uniqueHashV2(filePath); } QByteArray DImg::createImageUniqueId() const { NonDeterministicRandomData randomData(16); QByteArray imageUUID = randomData.toHex(); imageUUID += getUniqueHashV2(); return imageUUID; } void DImg::prepareMetadataToSave(const QString& intendedDestPath, const QString& destMimeType, bool resetExifOrientationTag) { PrepareMetadataFlags flags = PrepareMetadataFlagsAll; if (!resetExifOrientationTag) { flags &= ~ResetExifOrientationTag; } QUrl url = QUrl::fromLocalFile(originalFilePath()); prepareMetadataToSave(intendedDestPath, destMimeType, url.fileName(), flags); } void DImg::prepareMetadataToSave(const QString& intendedDestPath, const QString& destMimeType, const QString& originalFileName, PrepareMetadataFlags flags) { if (isNull()) { return; } // Get image Exif/IPTC data. DMetadata meta(getMetadata()); if (flags & RemoveOldMetadataPreviews || flags & CreateNewMetadataPreview) { // Clear IPTC preview meta.removeIptcTag("Iptc.Application2.Preview"); meta.removeIptcTag("Iptc.Application2.PreviewFormat"); meta.removeIptcTag("Iptc.Application2.PreviewVersion"); // Clear Exif thumbnail meta.removeExifThumbnail(); // Clear Tiff thumbnail MetaEngine::MetaDataMap tiffThumbTags = meta.getExifTagsDataList(QStringList() << QLatin1String("SubImage1")); for (MetaEngine::MetaDataMap::iterator it = tiffThumbTags.begin(); it != tiffThumbTags.end(); ++it) { meta.removeExifTag(it.key().toLatin1().constData()); } } bool createNewPreview = false; QSize previewSize; // Refuse preview creation for images with transparency // as long as we have no format to support this. See bug 286127 bool skipPreviewCreation = hasTransparentPixels(); if (flags & CreateNewMetadataPreview && !skipPreviewCreation) { const QSize standardPreviewSize(1280, 1280); previewSize = size(); // Scale to standard preview size. Only scale down, not up if (width() > (uint)standardPreviewSize.width() && height() > (uint)standardPreviewSize.height()) { previewSize.scale(standardPreviewSize, Qt::KeepAspectRatio); } // Only store a new preview if it is worth it - the original should be significantly larger than the preview createNewPreview = (2 * (uint)previewSize.width() <= width()); } if (createNewPreview) { // Create the preview QImage QImage preview; { if (!IccManager::isSRGB(*this)) { DImg previewDImg; if (previewSize.width() >= (int)width()) { previewDImg = copy(); } else { previewDImg = smoothScale(previewSize.width(), previewSize.height(), Qt::IgnoreAspectRatio); } IccManager manager(previewDImg); manager.transformToSRGB(); preview = previewDImg.copyQImage(); } else { // Ensure that preview is not upscaled if (previewSize.width() >= (int)width()) { preview = copyQImage(); } else { preview = smoothScale(previewSize.width(), previewSize.height(), Qt::IgnoreAspectRatio).copyQImage(); } } } // Update IPTC preview. // see bug #130525. a JPEG segment is limited to 64K. If the IPTC byte array is // bigger than 64K during of image preview tag size, the target JPEG image will be // broken. Note that IPTC image preview tag is limited to 256K!!! // There is no limitation with TIFF and PNG about IPTC byte array size. // So for a JPEG file, we don't store the IPTC preview. if ((destMimeType.toUpper() != QLatin1String("JPG") && destMimeType.toUpper() != QLatin1String("JPEG") && destMimeType.toUpper() != QLatin1String("JPE")) ) { // Non JPEG file, we update IPTC preview meta.setItemPreview(preview); } if (destMimeType.toUpper() == QLatin1String("TIFF") || destMimeType.toUpper() == QLatin1String("TIF")) { // With TIFF file, we don't store JPEG thumbnail, we even need to erase it and store // a thumbnail at a special location. See bug #211758 QImage thumb = preview.scaled(160, 120, Qt::KeepAspectRatio, Qt::SmoothTransformation); meta.setTiffThumbnail(thumb); } else { // Update Exif thumbnail. QImage thumb = preview.scaled(160, 120, Qt::KeepAspectRatio, Qt::SmoothTransformation); meta.setExifThumbnail(thumb); } } // Update Exif Image dimensions. meta.setItemDimensions(size()); // Update Exif Document Name tag with the original file name. if (!originalFileName.isEmpty()) { meta.setExifTagString("Exif.Image.DocumentName", originalFileName); } // Update Exif Orientation tag if necessary. if (flags & ResetExifOrientationTag) { meta.setItemOrientation(DMetadata::ORIENTATION_NORMAL); } if (!m_priv->imageHistory.isEmpty()) { DImageHistory forSaving(m_priv->imageHistory); forSaving.adjustReferredImages(); QUrl url = QUrl::fromLocalFile(intendedDestPath); QString filePath = url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).toLocalFile() + QLatin1Char('/'); QString fileName = url.fileName(); if (!filePath.isEmpty() && !fileName.isEmpty()) { forSaving.purgePathFromReferredImages(filePath, fileName); } QString imageHistoryXml = forSaving.toXml(); meta.setItemHistory(imageHistoryXml); } if (flags & CreateNewImageHistoryUUID) { meta.setItemUniqueId(QString::fromUtf8(createImageUniqueId())); } // Store new Exif/IPTC/XMP data into image. setMetadata(meta.data()); } HistoryImageId DImg::createHistoryImageId(const QString& filePath, HistoryImageId::Type type) const { HistoryImageId id = DImgLoader::createHistoryImageId(filePath, *this, DMetadata(getMetadata())); id.setType(type); return id; } HistoryImageId DImg::addAsReferredImage(const QString& filePath, HistoryImageId::Type type) { HistoryImageId id = createHistoryImageId(filePath, type); m_priv->imageHistory.purgePathFromReferredImages(id.path(), id.fileName()); addAsReferredImage(id); return id; } void DImg::addAsReferredImage(const HistoryImageId& id) { m_priv->imageHistory << id; } void DImg::insertAsReferredImage(int afterHistoryStep, const HistoryImageId& id) { m_priv->imageHistory.insertReferredImage(afterHistoryStep, id); } void DImg::addCurrentUniqueImageId(const QString& uuid) { m_priv->imageHistory.adjustCurrentUuid(uuid); } void DImg::addFilterAction(const Digikam::FilterAction& action) { m_priv->imageHistory << action; } const DImageHistory& DImg::getItemHistory() const { return m_priv->imageHistory; } DImageHistory& DImg::getItemHistory() { return m_priv->imageHistory; } void DImg::setItemHistory(const DImageHistory& history) { m_priv->imageHistory = history; } bool DImg::hasImageHistory() const { if (m_priv->imageHistory.isEmpty()) { return false; } else { return true; } } DImageHistory DImg::getOriginalImageHistory() const { return attribute(QLatin1String("originalImageHistory")).value(); } void DImg::setHistoryBranch(bool isBranch) { setHistoryBranchAfter(getOriginalImageHistory(), isBranch); } void DImg::setHistoryBranchAfter(const DImageHistory& historyBeforeBranch, bool isBranch) { int addedSteps = m_priv->imageHistory.size() - historyBeforeBranch.size(); setHistoryBranchForLastSteps(addedSteps, isBranch); } void DImg::setHistoryBranchForLastSteps(int numberOfLastHistorySteps, bool isBranch) { int firstStep = m_priv->imageHistory.size() - numberOfLastHistorySteps; if (firstStep < m_priv->imageHistory.size()) { if (isBranch) { m_priv->imageHistory[firstStep].action.addFlag(FilterAction::ExplicitBranch); } else { m_priv->imageHistory[firstStep].action.removeFlag(FilterAction::ExplicitBranch); } } } QString DImg::colorModelToString(COLORMODEL colorModel) { switch (colorModel) { case RGB: return i18nc("Color Model: RGB", "RGB"); case GRAYSCALE: return i18nc("Color Model: Grayscale", "Grayscale"); case MONOCHROME: return i18nc("Color Model: Monochrome", "Monochrome"); case INDEXED: return i18nc("Color Model: Indexed", "Indexed"); case YCBCR: return i18nc("Color Model: YCbCr", "YCbCr"); case CMYK: return i18nc("Color Model: CMYK", "CMYK"); case CIELAB: return i18nc("Color Model: CIE L*a*b*", "CIE L*a*b*"); case COLORMODELRAW: return i18nc("Color Model: Uncalibrated (RAW)", "Uncalibrated (RAW)"); case COLORMODELUNKNOWN: default: return i18nc("Color Model: Unknown", "Unknown"); } } bool DImg::isAnimatedImage(const QString& filePath) { QImageReader reader(filePath); reader.setDecideFormatFromContent(true); if (reader.supportsAnimation() && (reader.imageCount() > 1)) { qDebug(DIGIKAM_DIMG_LOG_QIMAGE) << "File \"" << filePath << "\" is an animated image "; return true; } return false; } } // namespace Digikam diff --git a/core/libs/dimg/dimg_p.h b/core/libs/dimg/dimg_p.h index 14f973b5c9..71c8cbbee5 100644 --- a/core/libs/dimg/dimg_p.h +++ b/core/libs/dimg/dimg_p.h @@ -1,151 +1,151 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2005-06-15 * Description : DImg private data members * * Copyright (C) 2005 by Renchi Raju * Copyright (C) 2005-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. * * ============================================================ */ #ifndef DIGIKAM_DIMG_PRIVATE_H #define DIGIKAM_DIMG_PRIVATE_H // Qt includes #include #include #include #include // Local includes #include "digikam_export.h" #include "dmetadata.h" #include "dshareddata.h" #include "dimagehistory.h" #include "iccprofile.h" /** Lanczos kernel is precomputed in a table with this resolution The value below seems to be enough for HQ upscaling up to eight times */ #define LANCZOS_TABLE_RES 256 /** A support of 3 gives an overall sharper looking image, but it is a) slower b) gives more sharpening artifacts */ #define LANCZOS_SUPPORT 2 /** Define this to use a floating-point implementation of Lanczos interpolation. The integer implementation is a little bit less accurate, but MUCH faster (even on machines with FPU - ~2.5 times faster on Core2); besides, it will run a hell lot faster on computers without a FPU (e.g. PDAs). */ //#define LANCZOS_DATA_FLOAT #ifdef LANCZOS_DATA_FLOAT #define LANCZOS_DATA_TYPE float #define LANCZOS_DATA_ONE 1.0 #else #define LANCZOS_DATA_TYPE int #define LANCZOS_DATA_ONE 4096 #endif namespace Digikam { class DIGIKAM_EXPORT DImg::Private : public DSharedData { public: explicit Private() { null = true; width = 0; height = 0; data = 0; lanczos_func = 0; alpha = false; sixteenBit = false; } ~Private() { delete [] data; delete [] lanczos_func; } static QStringList fileOriginAttributes() { QStringList list; list << QLatin1String("format") - << QLatin1String("isreadonly") + << QLatin1String("isReadOnly") << QLatin1String("originalFilePath") << QLatin1String("originalSize") << QLatin1String("originalImageHistory") << QLatin1String("rawDecodingSettings") << QLatin1String("rawDecodingFilterAction") << QLatin1String("uniqueHash") << QLatin1String("uniqueHashV2"); return list; } /** * x,y, w x h is a section of the image. The image size is width x height. * Clips the section to the bounds of the image. * Returns if the (clipped) section is a valid rectangle. */ static bool clipped(int& x, int& y, int& w, int& h, uint width, uint height) { QRect inner(x, y, w, h); QRect outer(0, 0, width, height); if (!outer.contains(inner)) { QRect clipped = inner.intersected(outer); x = clipped.x(); y = clipped.y(); w = clipped.width(); h = clipped.height(); return clipped.isValid(); } return inner.isValid(); } public: bool null; bool alpha; bool sixteenBit; unsigned int width; unsigned int height; unsigned char* data; LANCZOS_DATA_TYPE* lanczos_func; MetaEngineData metaData; QMap attributes; QMap embeddedText; IccProfile iccProfile; DImageHistory imageHistory; }; } // namespace Digikam #endif // DIGIKAM_DIMG_PRIVATE_H diff --git a/core/libs/dimg/loaders/jp2kloader.cpp b/core/libs/dimg/loaders/jp2kloader.cpp index 9ee9263cfe..9cc15f1381 100644 --- a/core/libs/dimg/loaders/jp2kloader.cpp +++ b/core/libs/dimg/loaders/jp2kloader.cpp @@ -1,890 +1,890 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2006-06-14 * Description : A JPEG2000 IO file for DImg framework * * Copyright (C) 2006-2019 by Gilles Caulier * * This implementation use Jasper API * library : http://www.ece.uvic.ca/~mdadams/jasper * Other JPEG2000 encoder-decoder : http://www.openjpeg.org * * Others Linux JPEG2000 Loader implementation using Jasper: * http://cvs.graphicsmagick.org/cgi-bin/cvsweb.cgi/GraphicsMagick/coders/jp2.c * https://subversion.imagemagick.org/subversion/ImageMagick/trunk/coders/jp2.c * http://svn.ghostscript.com:8080/jasper/trunk/src/appl/jasper.c * http://websvn.kde.org/trunk/KDE/kdelibs/kimgio/jp2.cpp * * 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 "jp2kloader.h" // Qt includes #include #include #include // Local includes #include "digikam_config.h" #include "dimg.h" #include "digikam_debug.h" #include "dimgloaderobserver.h" #include "dmetadata.h" // Jasper includes #ifndef Q_CC_MSVC extern "C" { #endif #if defined(Q_OS_DARWIN) && defined(Q_CC_CLANG) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wshift-negative-value" #endif #include #if defined(Q_OS_DARWIN) && defined(Q_CC_CLANG) #pragma clang diagnostic pop #endif #ifndef Q_CC_MSVC } #endif namespace Digikam { JP2KLoader::JP2KLoader(DImg* const image) : DImgLoader(image) { m_hasAlpha = false; m_sixteenBit = false; } bool JP2KLoader::load(const QString& filePath, DImgLoaderObserver* const observer) { readMetadata(filePath, DImg::JPEG); FILE* const file = fopen(QFile::encodeName(filePath).constData(), "rb"); if (!file) { loadingFailed(); return false; } unsigned char header[9]; if (fread(&header, 9, 1, file) != 1) { fclose(file); loadingFailed(); return false; } fclose(file); unsigned char jp2ID[5] = { 0x6A, 0x50, 0x20, 0x20, 0x0D, }; unsigned char jpcID[2] = { 0xFF, 0x4F }; if (memcmp(&header[4], &jp2ID, 5) != 0 && memcmp(&header, &jpcID, 2) != 0) { // not a jpeg2000 file loadingFailed(); return false; } imageSetAttribute(QLatin1String("format"), QLatin1String("JP2")); if (!(m_loadFlags & LoadImageData) && !(m_loadFlags & LoadICCData)) { // libjasper will load the full image in memory already when calling jas_image_decode. // This is bad when scanning. See bugs 215458 and 195583. // FIXME: Use Exiv2 to extract this info DMetadata metadata(filePath); QSize size = metadata.getItemDimensions(); if (size.isValid()) { imageWidth() = size.width(); imageHeight() = size.height(); } return true; } // ------------------------------------------------------------------- // Initialize JPEG 2000 API. long i, x, y; int components[4]; unsigned int maximum_component_depth, scale[4], x_step[4], y_step[4]; unsigned long number_components; jas_image_t* jp2_image = 0; jas_stream_t* jp2_stream = 0; jas_matrix_t* pixels[4]; int init = jas_init(); if (init != 0) { qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Unable to init JPEG2000 decoder"; loadingFailed(); return false; } jp2_stream = jas_stream_fopen(QFile::encodeName(filePath).constData(), "rb"); if (jp2_stream == 0) { qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Unable to open JPEG2000 stream"; loadingFailed(); return false; } int fmt = jas_image_strtofmt(QByteArray("jp2").data()); jp2_image = jas_image_decode(jp2_stream, fmt, 0); if (jp2_image == 0) { jas_stream_close(jp2_stream); qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Unable to decode JPEG2000 image"; loadingFailed(); return false; } jas_stream_close(jp2_stream); // some pseudo-progress if (observer) { observer->progressInfo(m_image, 0.1F); } // ------------------------------------------------------------------- // Check color space. int colorModel; switch (jas_clrspc_fam(jas_image_clrspc(jp2_image))) { case JAS_CLRSPC_FAM_RGB: { components[0] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_RGB_R); components[1] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_RGB_G); components[2] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_RGB_B); if ((components[0] < 0) || (components[1] < 0) || (components[2] < 0)) { jas_image_destroy(jp2_image); qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Error parsing JPEG2000 image : Missing Image Channel"; loadingFailed(); return false; } number_components = 3; components[3] = jas_image_getcmptbytype(jp2_image, 3); if (components[3] > 0) { m_hasAlpha = true; ++number_components; } colorModel = DImg::RGB; break; } case JAS_CLRSPC_FAM_GRAY: { components[0] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_GRAY_Y); if (components[0] < 0) { jas_image_destroy(jp2_image); qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Error parsing JP2000 image : Missing Image Channel"; loadingFailed(); return false; } number_components = 1; colorModel = DImg::GRAYSCALE; break; } case JAS_CLRSPC_FAM_YCBCR: { components[0] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_YCBCR_Y); components[1] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_YCBCR_CB); components[2] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_YCBCR_CR); if ((components[0] < 0) || (components[1] < 0) || (components[2] < 0)) { jas_image_destroy(jp2_image); qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Error parsing JP2000 image : Missing Image Channel"; loadingFailed(); return false; } number_components = 3; components[3] = jas_image_getcmptbytype(jp2_image, JAS_IMAGE_CT_UNKNOWN); if (components[3] > 0) { m_hasAlpha = true; ++number_components; } // FIXME : image->colorspace=YCbCrColorspace; colorModel = DImg::YCBCR; break; } default: { jas_image_destroy(jp2_image); qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Error parsing JP2000 image : Colorspace Model Is Not Supported"; loadingFailed(); return false; } } // ------------------------------------------------------------------- // Check image geometry. imageWidth() = jas_image_width(jp2_image); imageHeight() = jas_image_height(jp2_image); for (i = 0; i < (long)number_components; ++i) { if ((((jas_image_cmptwidth(jp2_image, components[i])* jas_image_cmpthstep(jp2_image, components[i])) != (long)imageWidth())) || (((jas_image_cmptheight(jp2_image, components[i])* jas_image_cmptvstep(jp2_image, components[i])) != (long)imageHeight())) || (jas_image_cmpttlx(jp2_image, components[i]) != 0) || (jas_image_cmpttly(jp2_image, components[i]) != 0) || (jas_image_cmptsgnd(jp2_image, components[i]) != false)) { jas_image_destroy(jp2_image); qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Error parsing JPEG2000 image : Irregular Channel Geometry Not Supported"; loadingFailed(); return false; } x_step[i] = jas_image_cmpthstep(jp2_image, components[i]); y_step[i] = jas_image_cmptvstep(jp2_image, components[i]); } // ------------------------------------------------------------------- // Get image format. maximum_component_depth = 0; for (i = 0; i < (long)number_components; ++i) { maximum_component_depth = qMax((long)jas_image_cmptprec(jp2_image, components[i]), (long)maximum_component_depth); pixels[i] = jas_matrix_create(1, ((unsigned int)imageWidth()) / x_step[i]); if (!pixels[i]) { jas_image_destroy(jp2_image); qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Error decoding JPEG2000 image data : Memory Allocation Failed"; loadingFailed(); return false; } } if (maximum_component_depth > 8) { m_sixteenBit = true; } for (i = 0 ; i < (long)number_components ; ++i) { scale[i] = 1; int prec = jas_image_cmptprec(jp2_image, components[i]); if (m_sixteenBit && prec < 16) { scale[i] = (1 << (16 - jas_image_cmptprec(jp2_image, components[i]))); } } // ------------------------------------------------------------------- // Get image data. QScopedArrayPointer data; if (m_loadFlags & LoadImageData) { if (m_sixteenBit) // 16 bits image. { data.reset(new_failureTolerant(imageWidth(), imageHeight(), 8)); } else { data.reset(new_failureTolerant(imageWidth(), imageHeight(), 4)); } if (!data) { qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Error decoding JPEG2000 image data : Memory Allocation Failed"; jas_image_destroy(jp2_image); for (i = 0 ; i < (long)number_components ; ++i) { jas_matrix_destroy(pixels[i]); } jas_cleanup(); loadingFailed(); return false; } uint checkPoint = 0; uchar* dst = data.data(); unsigned short* dst16 = reinterpret_cast(data.data()); for (y = 0 ; y < (long)imageHeight() ; ++y) { for (i = 0 ; i < (long)number_components; ++i) { int ret = jas_image_readcmpt(jp2_image, (short)components[i], 0, ((unsigned int) y) / y_step[i], ((unsigned int) imageWidth()) / x_step[i], 1, pixels[i]); if (ret != 0) { qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Error decoding JPEG2000 image data"; jas_image_destroy(jp2_image); for (i = 0 ; i < (long)number_components ; ++i) { jas_matrix_destroy(pixels[i]); } jas_cleanup(); loadingFailed(); return false; } } switch (number_components) { case 1: // Grayscale. { if (!m_sixteenBit) // 8 bits image. { for (x = 0 ; x < (long)imageWidth() ; ++x) { dst[0] = (uchar)(scale[0] * jas_matrix_getv(pixels[0], x / x_step[0])); dst[1] = dst[0]; dst[2] = dst[0]; dst[3] = 0xFF; dst += 4; } } else // 16 bits image. { for (x = 0 ; x < (long)imageWidth() ; ++x) { dst16[0] = (unsigned short)(scale[0] * jas_matrix_getv(pixels[0], x / x_step[0])); dst16[1] = dst16[0]; dst16[2] = dst16[0]; dst16[3] = 0xFFFF; dst16 += 4; } } break; } case 3: // RGB. { if (!m_sixteenBit) // 8 bits image. { for (x = 0 ; x < (long)imageWidth() ; ++x) { // Blue dst[0] = (uchar)(scale[2] * jas_matrix_getv(pixels[2], x / x_step[2])); // Green dst[1] = (uchar)(scale[1] * jas_matrix_getv(pixels[1], x / x_step[1])); // Red dst[2] = (uchar)(scale[0] * jas_matrix_getv(pixels[0], x / x_step[0])); // Alpha dst[3] = 0xFF; dst += 4; } } else // 16 bits image. { for (x = 0 ; x < (long)imageWidth() ; ++x) { // Blue dst16[0] = (unsigned short)(scale[2] * jas_matrix_getv(pixels[2], x / x_step[2])); // Green dst16[1] = (unsigned short)(scale[1] * jas_matrix_getv(pixels[1], x / x_step[1])); // Red dst16[2] = (unsigned short)(scale[0] * jas_matrix_getv(pixels[0], x / x_step[0])); // Alpha dst16[3] = 0xFFFF; dst16 += 4; } } break; } case 4: // RGBA. { if (!m_sixteenBit) // 8 bits image. { for (x = 0 ; x < (long)imageWidth() ; ++x) { // Blue dst[0] = (uchar)(scale[2] * jas_matrix_getv(pixels[2], x / x_step[2])); // Green dst[1] = (uchar)(scale[1] * jas_matrix_getv(pixels[1], x / x_step[1])); // Red dst[2] = (uchar)(scale[0] * jas_matrix_getv(pixels[0], x / x_step[0])); // Alpha dst[3] = (uchar)(scale[3] * jas_matrix_getv(pixels[3], x / x_step[3])); dst += 4; } } else // 16 bits image. { for (x = 0 ; x < (long)imageWidth() ; ++x) { // Blue dst16[0] = (unsigned short)(scale[2] * jas_matrix_getv(pixels[2], x / x_step[2])); // Green dst16[1] = (unsigned short)(scale[1] * jas_matrix_getv(pixels[1], x / x_step[1])); // Red dst16[2] = (unsigned short)(scale[0] * jas_matrix_getv(pixels[0], x / x_step[0])); // Alpha dst16[3] = (unsigned short)(scale[3] * jas_matrix_getv(pixels[3], x / x_step[3])); dst16 += 4; } } break; } } // use 0-10% and 90-100% for pseudo-progress if (observer && y >= (long)checkPoint) { checkPoint += granularity(observer, y, 0.8F); if (!observer->continueQuery(m_image)) { jas_image_destroy(jp2_image); for (i = 0 ; i < (long)number_components ; ++i) { jas_matrix_destroy(pixels[i]); } jas_cleanup(); loadingFailed(); return false; } observer->progressInfo(m_image, 0.1 + (0.8 * (((float)y) / ((float)imageHeight())))); } } } // ------------------------------------------------------------------- // Get ICC color profile. if (m_loadFlags & LoadICCData) { jas_iccprof_t* icc_profile = 0; jas_stream_t* icc_stream = 0; jas_cmprof_t* cm_profile = 0; cm_profile = jas_image_cmprof(jp2_image); if (cm_profile != 0) { icc_profile = jas_iccprof_createfromcmprof(cm_profile); } if (icc_profile != 0) { icc_stream = jas_stream_memopen(NULL, 0); if (icc_stream != 0) { if (jas_iccprof_save(icc_profile, icc_stream) == 0) { if (jas_stream_flush(icc_stream) == 0) { jas_stream_memobj_t* blob = (jas_stream_memobj_t*) icc_stream->obj_; QByteArray profile_rawdata; profile_rawdata.resize(blob->len_); memcpy(profile_rawdata.data(), blob->buf_, blob->len_); imageSetIccProfile(IccProfile(profile_rawdata)); jas_stream_close(icc_stream); } } } } else { // If ICC profile is null, check Exif metadata. checkExifWorkingColorSpace(); } } if (observer) { observer->progressInfo(m_image, 1.0); } imageData() = data.take(); imageSetAttribute(QLatin1String("format"), QLatin1String("JP2")); imageSetAttribute(QLatin1String("originalColorModel"), colorModel); imageSetAttribute(QLatin1String("originalBitDepth"), maximum_component_depth); imageSetAttribute(QLatin1String("originalSize"), QSize(imageWidth(), imageHeight())); jas_image_destroy(jp2_image); for (i = 0 ; i < (long)number_components ; ++i) { jas_matrix_destroy(pixels[i]); } jas_cleanup(); return true; } bool JP2KLoader::save(const QString& filePath, DImgLoaderObserver* const observer) { FILE* const file = fopen(QFile::encodeName(filePath).constData(), "wb"); if (!file) { return false; } fclose(file); // ------------------------------------------------------------------- // Initialize JPEG 2000 API. long i, x, y; unsigned long number_components; jas_image_t* jp2_image = 0; jas_stream_t* jp2_stream = 0; jas_matrix_t* pixels[4]; jas_image_cmptparm_t component_info[4]; int init = jas_init(); if (init != 0) { qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Unable to init JPEG2000 decoder"; return false; } jp2_stream = jas_stream_fopen(QFile::encodeName(filePath).constData(), "wb"); if (jp2_stream == 0) { qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Unable to open JPEG2000 stream"; return false; } number_components = imageHasAlpha() ? 4 : 3; for (i = 0 ; i < (long)number_components ; ++i) { component_info[i].tlx = 0; component_info[i].tly = 0; component_info[i].hstep = 1; component_info[i].vstep = 1; component_info[i].width = imageWidth(); component_info[i].height = imageHeight(); component_info[i].prec = imageBitsDepth(); component_info[i].sgnd = false; } jp2_image = jas_image_create(number_components, component_info, JAS_CLRSPC_UNKNOWN); if (jp2_image == 0) { jas_stream_close(jp2_stream); qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Unable to create JPEG2000 image"; return false; } if (observer) { observer->progressInfo(m_image, 0.1F); } // ------------------------------------------------------------------- // Check color space. if (number_components >= 3) // RGB & RGBA { // Alpha Channel if (number_components == 4) { jas_image_setcmpttype(jp2_image, 3, JAS_IMAGE_CT_OPACITY); } jas_image_setclrspc(jp2_image, JAS_CLRSPC_SRGB); jas_image_setcmpttype(jp2_image, 0, JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_RGB_R)); jas_image_setcmpttype(jp2_image, 1, JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_RGB_G)); jas_image_setcmpttype(jp2_image, 2, JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_RGB_B)); } // ------------------------------------------------------------------- // Set ICC color profile. // FIXME : doesn't work yet! jas_cmprof_t* cm_profile = 0; jas_iccprof_t* icc_profile = 0; QByteArray profile_rawdata = m_image->getIccProfile().data(); icc_profile = jas_iccprof_createfrombuf((uchar*)profile_rawdata.data(), profile_rawdata.size()); if (icc_profile != 0) { cm_profile = jas_cmprof_createfromiccprof(icc_profile); if (cm_profile != 0) { jas_image_setcmprof(jp2_image, cm_profile); //enable when it works: purgeExifWorkingColorSpace(); } } // workaround: storeColorProfileInMetadata(); // ------------------------------------------------------------------- // Convert to JPEG 2000 pixels. for (i = 0 ; i < (long)number_components ; ++i) { pixels[i] = jas_matrix_create(1, (unsigned int)imageWidth()); if (pixels[i] == 0) { for (x = 0 ; x < i ; ++x) { jas_matrix_destroy(pixels[x]); } jas_image_destroy(jp2_image); qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Error encoding JPEG2000 image data : Memory Allocation Failed"; return false; } } unsigned char* data = imageData(); unsigned char* pixel; unsigned short r, g, b, a = 0; uint checkpoint = 0; for (y = 0 ; y < (long)imageHeight() ; ++y) { if (observer && y == (long)checkpoint) { checkpoint += granularity(observer, imageHeight(), 0.8F); if (!observer->continueQuery(m_image)) { jas_image_destroy(jp2_image); for (i = 0 ; i < (long)number_components ; ++i) { jas_matrix_destroy(pixels[i]); } jas_cleanup(); return false; } observer->progressInfo(m_image, 0.1 + (0.8 * (((float)y) / ((float)imageHeight())))); } for (x = 0 ; x < (long)imageWidth() ; ++x) { pixel = &data[((y * imageWidth()) + x) * imageBytesDepth()]; if (imageSixteenBit()) // 16 bits image. { b = (unsigned short)(pixel[0] + 256 * pixel[1]); g = (unsigned short)(pixel[2] + 256 * pixel[3]); r = (unsigned short)(pixel[4] + 256 * pixel[5]); if (imageHasAlpha()) { a = (unsigned short)(pixel[6] + 256 * pixel[7]); } } else // 8 bits image. { b = (unsigned short)pixel[0]; g = (unsigned short)pixel[1]; r = (unsigned short)pixel[2]; if (imageHasAlpha()) { a = (unsigned short)(pixel[3]); } } jas_matrix_setv(pixels[0], x, r); jas_matrix_setv(pixels[1], x, g); jas_matrix_setv(pixels[2], x, b); if (number_components > 3) { jas_matrix_setv(pixels[3], x, a); } } for (i = 0 ; i < (long)number_components ; ++i) { int ret = jas_image_writecmpt(jp2_image, (short) i, 0, (unsigned int)y, (unsigned int)imageWidth(), 1, pixels[i]); if (ret != 0) { qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Error encoding JPEG2000 image data"; jas_image_destroy(jp2_image); for (i = 0 ; i < (long)number_components ; ++i) { jas_matrix_destroy(pixels[i]); } jas_cleanup(); return false; } } } QVariant qualityAttr = imageGetAttribute(QLatin1String("quality")); int quality = qualityAttr.isValid() ? qualityAttr.toInt() : 90; if (quality < 0) { quality = 90; } if (quality > 100) { quality = 100; } // optstr: // - rate=#B => the resulting file size is about # bytes // - rate=0.0 .. 1.0 => the resulting file size is about the factor times // the uncompressed size // use sprintf for locale-aware string char rateBuffer[16]; sprintf(rateBuffer, "rate=%.2g", (quality / 100.0)); qCDebug(DIGIKAM_DIMG_LOG_JP2K) << "JPEG2000 quality: " << quality; qCDebug(DIGIKAM_DIMG_LOG_JP2K) << "JPEG2000 " << rateBuffer; int fmt = jas_image_strtofmt(QByteArray("jp2").data()); int ret = jas_image_encode(jp2_image, jp2_stream, fmt, rateBuffer); if (ret != 0) { qCWarning(DIGIKAM_DIMG_LOG_JP2K) << "Unable to encode JPEG2000 image"; jas_image_destroy(jp2_image); jas_stream_close(jp2_stream); for (i = 0 ; i < (long)number_components ; ++i) { jas_matrix_destroy(pixels[i]); } jas_cleanup(); return false; } if (observer) { observer->progressInfo(m_image, 1.0); } jas_image_destroy(jp2_image); jas_stream_close(jp2_stream); for (i = 0 ; i < (long)number_components ; ++i) { jas_matrix_destroy(pixels[i]); } jas_cleanup(); - imageSetAttribute(QLatin1String("savedformat"), QLatin1String("JP2")); + imageSetAttribute(QLatin1String("savedFormat"), QLatin1String("JP2")); saveMetadata(filePath); return true; } bool JP2KLoader::hasAlpha() const { return m_hasAlpha; } bool JP2KLoader::sixteenBit() const { return m_sixteenBit; } bool JP2KLoader::isReadOnly() const { return false; } } // namespace Digikam diff --git a/core/libs/dimg/loaders/jpegloader.cpp b/core/libs/dimg/loaders/jpegloader.cpp index 1c337c919a..d0ca9731e5 100644 --- a/core/libs/dimg/loaders/jpegloader.cpp +++ b/core/libs/dimg/loaders/jpegloader.cpp @@ -1,977 +1,977 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2005-06-14 * Description : A JPEG IO file for DImg framework * * Copyright (C) 2005 by Renchi Raju * Copyright (C) 2005-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. * * ============================================================ */ #define XMD_H #include "jpegloader.h" // C ANSI includes extern "C" { #include "iccjpeg.h" } // Qt includes #include #include // Local includes #include "digikam_config.h" #include "dimg.h" #include "digikam_debug.h" #include "dimgloaderobserver.h" #ifdef Q_OS_WIN #include "jpegwin.h" #endif namespace Digikam { // To manage Errors/Warnings handling provide by libjpeg void JPEGLoader::dimg_jpeg_error_exit(j_common_ptr cinfo) { dimg_jpeg_error_mgr* const myerr = static_cast(cinfo->err); char buffer[JMSG_LENGTH_MAX]; (*cinfo->err->format_message)(cinfo, buffer); qCWarning(DIGIKAM_DIMG_LOG_JPEG) << buffer; longjmp(myerr->setjmp_buffer, 1); } void JPEGLoader::dimg_jpeg_emit_message(j_common_ptr cinfo, int msg_level) { char buffer[JMSG_LENGTH_MAX]; (*cinfo->err->format_message)(cinfo, buffer); qCDebug(DIGIKAM_DIMG_LOG_JPEG) << buffer << " (" << msg_level << ")"; } void JPEGLoader::dimg_jpeg_output_message(j_common_ptr cinfo) { char buffer[JMSG_LENGTH_MAX]; (*cinfo->err->format_message)(cinfo, buffer); qCDebug(DIGIKAM_DIMG_LOG_JPEG) << buffer; } JPEGLoader::JPEGLoader(DImg* const image) : DImgLoader(image) { } bool JPEGLoader::load(const QString& filePath, DImgLoaderObserver* const observer) { readMetadata(filePath, DImg::JPEG); FILE* const file = fopen(QFile::encodeName(filePath).constData(), "rb"); if (!file) { loadingFailed(); return false; } unsigned char header[2]; if (fread(&header, 2, 1, file) != 1) { fclose(file); loadingFailed(); return false; } unsigned char jpegID[] = { 0xFF, 0xD8 }; if (memcmp(header, jpegID, 2) != 0) { // not a jpeg file fclose(file); loadingFailed(); return false; } rewind(file); struct jpeg_decompress_struct cinfo; struct dimg_jpeg_error_mgr jerr; // ------------------------------------------------------------------- // JPEG error handling. cinfo.err = jpeg_std_error(&jerr); cinfo.err->error_exit = dimg_jpeg_error_exit; cinfo.err->emit_message = dimg_jpeg_emit_message; cinfo.err->output_message = dimg_jpeg_output_message; // setjmp-save cleanup class Q_DECL_HIDDEN CleanupData { public: CleanupData() { data = 0; dest = 0; file = 0; cmod = 0; } ~CleanupData() { delete [] data; delete [] dest; if (file) { fclose(file); } } void setData(uchar* const d) { data = d; } void setDest(uchar* const d) { dest = d; } void setFile(FILE* const f) { file = f; } void setSize(const QSize& s) { size = s; } void setColorModel(int c) { cmod = c; } void deleteData() { delete [] data; data = 0; } void takeDest() { dest = 0; } public: uchar* data; uchar* dest; FILE* file; QSize size; int cmod; }; CleanupData* const cleanupData = new CleanupData; cleanupData->setFile(file); // If an error occurs during reading, libjpeg will jump here if (setjmp(jerr.setjmp_buffer)) { jpeg_destroy_decompress(&cinfo); if (!cleanupData->dest || !cleanupData->size.isValid()) { delete cleanupData; loadingFailed(); return false; } // We check only Exif metadata for ICC profile to prevent endless loop if (m_loadFlags & LoadICCData) { checkExifWorkingColorSpace(); } if (observer) { observer->progressInfo(m_image, 1.0); } imageWidth() = cleanupData->size.width(); imageHeight() = cleanupData->size.height(); imageData() = cleanupData->dest; imageSetAttribute(QLatin1String("format"), QLatin1String("JPG")); imageSetAttribute(QLatin1String("originalColorModel"), cleanupData->cmod); imageSetAttribute(QLatin1String("originalBitDepth"), 8); imageSetAttribute(QLatin1String("originalSize"), cleanupData->size); cleanupData->takeDest(); delete cleanupData; return true; } // ------------------------------------------------------------------- // Find out if we do the fast-track loading with reduced size. Jpeg specific. int scaledLoadingSize = 0; QVariant attribute = imageGetAttribute(QLatin1String("scaledLoadingSize")); if (attribute.isValid()) { scaledLoadingSize = attribute.toInt(); } // ------------------------------------------------------------------- // Set JPEG decompressor instance jpeg_create_decompress(&cinfo); bool startedDecompress = false; #ifdef Q_OS_WIN QFile inFile(filePath); QByteArray buffer; if (inFile.open(QIODevice::ReadOnly)) { buffer = inFile.readAll(); inFile.close(); } JPEGUtils::jpeg_memory_src(&cinfo, (JOCTET*)buffer.data(), buffer.size()); #else jpeg_stdio_src(&cinfo, file); #endif // Recording ICC profile marker (from iccjpeg.c) if (m_loadFlags & LoadICCData) { setup_read_icc_profile(&cinfo); } // read image information jpeg_read_header(&cinfo, boolean(true)); // read dimension (nominal values from header) int w = cinfo.image_width; int h = cinfo.image_height; QSize originalSize(w, h); // Libjpeg handles the following conversions: // YCbCr => GRAYSCALE, YCbCr => RGB, GRAYSCALE => RGB, YCCK => CMYK // So we cannot get RGB from CMYK or YCCK, CMYK conversion is handled below int colorModel = DImg::COLORMODELUNKNOWN; switch (cinfo.jpeg_color_space) { case JCS_UNKNOWN: // perhaps jpeg_read_header did some guessing, leave value unchanged colorModel = DImg::COLORMODELUNKNOWN; break; case JCS_GRAYSCALE: cinfo.out_color_space = JCS_RGB; colorModel = DImg::GRAYSCALE; break; case JCS_RGB: cinfo.out_color_space = JCS_RGB; colorModel = DImg::RGB; break; case JCS_YCbCr: cinfo.out_color_space = JCS_RGB; colorModel = DImg::YCBCR; break; case JCS_CMYK: case JCS_YCCK: cinfo.out_color_space = JCS_CMYK; colorModel = DImg::CMYK; break; default: break; } cleanupData->setColorModel(colorModel); // ------------------------------------------------------------------- // Load image data. uchar* dest = 0; if (m_loadFlags & LoadImageData) { // set decompression parameters cinfo.do_fancy_upsampling = boolean(true); cinfo.do_block_smoothing = boolean(false); // handle scaled loading if (scaledLoadingSize) { int imgSize = qMax(cinfo.image_width, cinfo.image_height); // libjpeg supports 1/1, 1/2, 1/4, 1/8 int scale = 1; while (scaledLoadingSize* scale * 2 <= imgSize) { scale *= 2; } if (scale > 8) { scale = 8; } //cinfo.scale_num = 1; //cinfo.scale_denom = scale; cinfo.scale_denom *= scale; } // initialize decompression if (!startedDecompress) { jpeg_start_decompress(&cinfo); startedDecompress = true; } // some pseudo-progress if (observer) { observer->progressInfo(m_image, 0.1F); } // re-read dimension (scaling included) w = cinfo.output_width; h = cinfo.output_height; // ------------------------------------------------------------------- // Get scanlines uchar* ptr = 0, *data = 0, *line[16]; uchar* ptr2 = 0; int x, y, l, i, scans; // int count; // int prevy; if (cinfo.rec_outbuf_height > 16) { jpeg_destroy_decompress(&cinfo); qCWarning(DIGIKAM_DIMG_LOG_JPEG) << "Height of JPEG scanline buffer out of range!"; delete cleanupData; loadingFailed(); return false; } // We only take RGB with 1 or 3 components, or CMYK with 4 components if (!( (cinfo.out_color_space == JCS_RGB && (cinfo.output_components == 3 || cinfo.output_components == 1)) || (cinfo.out_color_space == JCS_CMYK && cinfo.output_components == 4) )) { jpeg_destroy_decompress(&cinfo); qCWarning(DIGIKAM_DIMG_LOG_JPEG) << "JPEG colorspace (" << cinfo.out_color_space << ") or Number of JPEG color components (" << cinfo.output_components << ") unsupported!"; delete cleanupData; loadingFailed(); return false; } data = new_failureTolerant(w * 16 * cinfo.output_components); cleanupData->setData(data); if (!data) { jpeg_destroy_decompress(&cinfo); qCWarning(DIGIKAM_DIMG_LOG_JPEG) << "Cannot allocate memory!"; delete cleanupData; loadingFailed(); return false; } dest = new_failureTolerant(w, h, 4); cleanupData->setSize(QSize(w, h)); cleanupData->setDest(dest); if (!dest) { jpeg_destroy_decompress(&cinfo); qCWarning(DIGIKAM_DIMG_LOG_JPEG) << "Cannot allocate memory!"; delete cleanupData; loadingFailed(); return false; } ptr2 = dest; // count = 0; // prevy = 0; if (cinfo.output_components == 3) { for (i = 0; i < cinfo.rec_outbuf_height; ++i) { line[i] = data + (i * w * 3); } int checkPoint = 0; for (l = 0; l < h; l += cinfo.rec_outbuf_height) { // use 0-10% and 90-100% for pseudo-progress if (observer && l >= checkPoint) { checkPoint += granularity(observer, h, 0.8F); if (!observer->continueQuery(m_image)) { jpeg_destroy_decompress(&cinfo); delete cleanupData; loadingFailed(); return false; } observer->progressInfo(m_image, 0.1 + (0.8 * (((float)l) / ((float)h)))); } jpeg_read_scanlines(&cinfo, &line[0], cinfo.rec_outbuf_height); scans = cinfo.rec_outbuf_height; if ((h - l) < scans) { scans = h - l; } ptr = data; for (y = 0; y < scans; ++y) { for (x = 0; x < w; ++x) { ptr2[3] = 0xFF; ptr2[2] = ptr[0]; ptr2[1] = ptr[1]; ptr2[0] = ptr[2]; ptr += 3; ptr2 += 4; } } } } else if (cinfo.output_components == 1) { for (i = 0; i < cinfo.rec_outbuf_height; ++i) { line[i] = data + (i * w); } int checkPoint = 0; for (l = 0; l < h; l += cinfo.rec_outbuf_height) { if (observer && l >= checkPoint) { checkPoint += granularity(observer, h, 0.8F); if (!observer->continueQuery(m_image)) { jpeg_destroy_decompress(&cinfo); delete cleanupData; loadingFailed(); return false; } observer->progressInfo(m_image, 0.1 + (0.8 * (((float)l) / ((float)h)))); } jpeg_read_scanlines(&cinfo, line, cinfo.rec_outbuf_height); scans = cinfo.rec_outbuf_height; if ((h - l) < scans) { scans = h - l; } ptr = data; for (y = 0; y < scans; ++y) { for (x = 0; x < w; ++x) { ptr2[3] = 0xFF; ptr2[2] = ptr[0]; ptr2[1] = ptr[0]; ptr2[0] = ptr[0]; ptr ++; ptr2 += 4; } } } } else // CMYK { for (i = 0; i < cinfo.rec_outbuf_height; ++i) { line[i] = data + (i * w * 4); } int checkPoint = 0; for (l = 0; l < h; l += cinfo.rec_outbuf_height) { // use 0-10% and 90-100% for pseudo-progress if (observer && l >= checkPoint) { checkPoint += granularity(observer, h, 0.8F); if (!observer->continueQuery(m_image)) { jpeg_destroy_decompress(&cinfo); delete cleanupData; loadingFailed(); return false; } observer->progressInfo(m_image, 0.1 + (0.8 * (((float)l) / ((float)h)))); } jpeg_read_scanlines(&cinfo, &line[0], cinfo.rec_outbuf_height); scans = cinfo.rec_outbuf_height; if ((h - l) < scans) { scans = h - l; } ptr = data; for (y = 0; y < scans; ++y) { for (x = 0; x < w; ++x) { // Inspired by Qt's JPEG loader int k = ptr[3]; ptr2[3] = 0xFF; ptr2[2] = k * ptr[0] / 255; ptr2[1] = k * ptr[1] / 255; ptr2[0] = k * ptr[2] / 255; ptr += 4; ptr2 += 4; } } } } // clean up cleanupData->deleteData(); } // ------------------------------------------------------------------- // Read image ICC profile if (m_loadFlags & LoadICCData) { if (!startedDecompress) { jpeg_start_decompress(&cinfo); startedDecompress = true; } JOCTET* profile_data = NULL; uint profile_size = 0; read_icc_profile(&cinfo, &profile_data, &profile_size); if (profile_data != NULL) { QByteArray profile_rawdata; profile_rawdata.resize(profile_size); memcpy(profile_rawdata.data(), profile_data, profile_size); imageSetIccProfile(IccProfile(profile_rawdata)); free(profile_data); } else { // If ICC profile is null, check Exif metadata. checkExifWorkingColorSpace(); } } // ------------------------------------------------------------------- if (startedDecompress) { jpeg_finish_decompress(&cinfo); } jpeg_destroy_decompress(&cinfo); // ------------------------------------------------------------------- cleanupData->takeDest(); delete cleanupData; if (observer) { observer->progressInfo(m_image, 1.0); } imageWidth() = w; imageHeight() = h; imageData() = dest; imageSetAttribute(QLatin1String("format"), QLatin1String("JPG")); imageSetAttribute(QLatin1String("originalColorModel"), colorModel); imageSetAttribute(QLatin1String("originalBitDepth"), 8); imageSetAttribute(QLatin1String("originalSize"), originalSize); return true; } bool JPEGLoader::save(const QString& filePath, DImgLoaderObserver* const observer) { FILE* const file = fopen(QFile::encodeName(filePath).constData(), "wb"); if (!file) { return false; } struct jpeg_compress_struct cinfo; struct dimg_jpeg_error_mgr jerr; // ------------------------------------------------------------------- // JPEG error handling. cinfo.err = jpeg_std_error(&jerr); cinfo.err->error_exit = dimg_jpeg_error_exit; cinfo.err->emit_message = dimg_jpeg_emit_message; cinfo.err->output_message = dimg_jpeg_output_message; // setjmp-save cleanup class Q_DECL_HIDDEN CleanupData { public: CleanupData() : line(0), f(0) { } ~CleanupData() { deleteLine(); if (f) { fclose(f); } } void setLine(uchar* const l) { line = l; } void setFile(FILE* const file) { f = file; } void deleteLine() { delete [] line; line = 0; } public: uchar* line; FILE* f; }; CleanupData* const cleanupData = new CleanupData; cleanupData->setFile(file); // If an error occurs during writing, libjpeg will jump here if (setjmp(jerr.setjmp_buffer)) { jpeg_destroy_compress(&cinfo); delete cleanupData; return false; } // ------------------------------------------------------------------- // Set JPEG compressor instance jpeg_create_compress(&cinfo); jpeg_stdio_dest(&cinfo, file); uint& w = imageWidth(); uint& h = imageHeight(); unsigned char*& data = imageData(); // Size of image. cinfo.image_width = w; cinfo.image_height = h; // Color components of image in RGB. cinfo.input_components = 3; cinfo.in_color_space = JCS_RGB; QVariant qualityAttr = imageGetAttribute(QLatin1String("quality")); int quality = qualityAttr.isValid() ? qualityAttr.toInt() : 90; if (quality < 0) { quality = 90; } if (quality > 100) { quality = 100; } QVariant subSamplingAttr = imageGetAttribute(QLatin1String("subsampling")); int subsampling = subSamplingAttr.isValid() ? subSamplingAttr.toInt() : 1; // Medium jpeg_set_defaults(&cinfo); // bug #149578: set horizontal and vertical chroma subsampling factor to encoder. // See this page for details: http://en.wikipedia.org/wiki/Chroma_subsampling switch (subsampling) { case 1: // 2x1, 1x1, 1x1 (4:2:2) { qCDebug(DIGIKAM_DIMG_LOG_JPEG) << "Using LibJPEG chroma-subsampling 4:2:2"; cinfo.comp_info[0].h_samp_factor = 2; cinfo.comp_info[0].v_samp_factor = 1; cinfo.comp_info[1].h_samp_factor = 1; cinfo.comp_info[1].v_samp_factor = 1; cinfo.comp_info[2].h_samp_factor = 1; cinfo.comp_info[2].v_samp_factor = 1; break; } case 2: // 2x2, 1x1, 1x1 (4:2:0) { qCDebug(DIGIKAM_DIMG_LOG_JPEG) << "Using LibJPEG chroma-subsampling 4:2:0"; cinfo.comp_info[0].h_samp_factor = 2; cinfo.comp_info[0].v_samp_factor = 2; cinfo.comp_info[1].h_samp_factor = 1; cinfo.comp_info[1].v_samp_factor = 1; cinfo.comp_info[2].h_samp_factor = 1; cinfo.comp_info[2].v_samp_factor = 1; break; } case 3: // 4x1, 1x1, 1x1 (4:1:1) { qCDebug(DIGIKAM_DIMG_LOG_JPEG) << "Using LibJPEG chroma-subsampling 4:1:1"; cinfo.comp_info[0].h_samp_factor = 4; cinfo.comp_info[0].v_samp_factor = 1; cinfo.comp_info[1].h_samp_factor = 1; cinfo.comp_info[1].v_samp_factor = 1; cinfo.comp_info[2].h_samp_factor = 1; cinfo.comp_info[2].v_samp_factor = 1; break; } default: // 1x1, 1x1, 1x1 (4:4:4) { qCDebug(DIGIKAM_DIMG_LOG_JPEG) << "Using LibJPEG chroma-subsampling 4:4:4"; cinfo.comp_info[0].h_samp_factor = 1; cinfo.comp_info[0].v_samp_factor = 1; cinfo.comp_info[1].h_samp_factor = 1; cinfo.comp_info[1].v_samp_factor = 1; cinfo.comp_info[2].h_samp_factor = 1; cinfo.comp_info[2].v_samp_factor = 1; break; } } jpeg_set_quality(&cinfo, quality, boolean(true)); jpeg_start_compress(&cinfo, boolean(true)); qCDebug(DIGIKAM_DIMG_LOG_JPEG) << "Using LibJPEG quality compression value: " << quality; if (observer) { observer->progressInfo(m_image, 0.1F); } // ------------------------------------------------------------------- // Write ICC profile. QByteArray profile_rawdata = m_image->getIccProfile().data(); if (!profile_rawdata.isEmpty()) { purgeExifWorkingColorSpace(); write_icc_profile(&cinfo, (JOCTET*)profile_rawdata.data(), profile_rawdata.size()); } if (observer) { observer->progressInfo(m_image, 0.2F); } // ------------------------------------------------------------------- // Write Image data. uchar* line = new uchar[w * 3]; uchar* dstPtr = 0; uint checkPoint = 0; cleanupData->setLine(line); if (!imageSixteenBit()) // 8 bits image. { uchar* srcPtr = data; for (uint j = 0; j < h; ++j) { if (observer && j == checkPoint) { checkPoint += granularity(observer, h, 0.8F); if (!observer->continueQuery(m_image)) { jpeg_destroy_compress(&cinfo); delete cleanupData; return false; } // use 0-20% for pseudo-progress, now fill 20-100% observer->progressInfo(m_image, 0.2 + (0.8 * (((float)j) / ((float)h)))); } dstPtr = line; for (uint i = 0; i < w; ++i) { dstPtr[2] = srcPtr[0]; // Blue dstPtr[1] = srcPtr[1]; // Green dstPtr[0] = srcPtr[2]; // Red srcPtr += 4; dstPtr += 3; } jpeg_write_scanlines(&cinfo, &line, 1); } } else { unsigned short* srcPtr = reinterpret_cast(data); for (uint j = 0; j < h; ++j) { if (observer && j == checkPoint) { checkPoint += granularity(observer, h, 0.8F); if (!observer->continueQuery(m_image)) { jpeg_destroy_compress(&cinfo); delete cleanupData; return false; } // use 0-20% for pseudo-progress, now fill 20-100% observer->progressInfo(m_image, 0.2 + (0.8 * (((float)j) / ((float)h)))); } dstPtr = line; for (uint i = 0; i < w; ++i) { dstPtr[2] = (srcPtr[0] * 255UL) / 65535UL; // Blue dstPtr[1] = (srcPtr[1] * 255UL) / 65535UL; // Green dstPtr[0] = (srcPtr[2] * 255UL) / 65535UL; // Red srcPtr += 4; dstPtr += 3; } jpeg_write_scanlines(&cinfo, &line, 1); } } cleanupData->deleteLine(); // ------------------------------------------------------------------- jpeg_finish_compress(&cinfo); jpeg_destroy_compress(&cinfo); delete cleanupData; - imageSetAttribute(QLatin1String("savedformat"), QLatin1String("JPG")); + imageSetAttribute(QLatin1String("savedFormat"), QLatin1String("JPG")); saveMetadata(filePath); return true; } bool JPEGLoader::hasAlpha() const { return false; } bool JPEGLoader::sixteenBit() const { return false; } bool JPEGLoader::isReadOnly() const { return false; } } // namespace Digikam diff --git a/core/libs/dimg/loaders/pgfloader.cpp b/core/libs/dimg/loaders/pgfloader.cpp index 06fee18738..c152aa08a1 100644 --- a/core/libs/dimg/loaders/pgfloader.cpp +++ b/core/libs/dimg/loaders/pgfloader.cpp @@ -1,553 +1,553 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2009-06-03 * Description : A PGF IO file for DImg framework * * Copyright (C) 2009-2019 by Gilles Caulier * * This implementation use LibPGF API * * 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 "digikam_config.h" #include "pgfloader.h" // krazy:exclude=includes // C Ansi includes extern "C" { #include #include #include #include } // C++ includes #include #include #include // Qt includes #include #include #include #include #include #include // Windows includes #ifdef Q_OS_WIN32 #include #endif // Libpgf includes #include "PGFimage.h" // Local includes #include "digikam_debug.h" #include "dimg.h" #include "dimgloaderobserver.h" #include "pgfutils.h" #include "metaengine.h" namespace Digikam { static bool CallbackForLibPGF(double percent, bool escapeAllowed, void* data) { if (data) { PGFLoader* const d = static_cast(data); if (d) { return d->progressCallback(percent, escapeAllowed); } } return false; } PGFLoader::PGFLoader(DImg* const image) : DImgLoader(image) { m_hasAlpha = false; m_sixteenBit = false; m_observer = 0; } bool PGFLoader::load(const QString& filePath, DImgLoaderObserver* const observer) { m_observer = observer; readMetadata(filePath, DImg::PGF); FILE* const file = fopen(QFile::encodeName(filePath).constData(), "rb"); if (!file) { qCWarning(DIGIKAM_DIMG_LOG_PGF) << "Error: Could not open source file."; loadingFailed(); return false; } unsigned char header[3]; if (fread(&header, 3, 1, file) != 1) { fclose(file); loadingFailed(); return false; } unsigned char pgfID[3] = { 0x50, 0x47, 0x46 }; if (memcmp(&header[0], &pgfID, 3) != 0) { // not a PGF file fclose(file); loadingFailed(); return false; } fclose(file); // ------------------------------------------------------------------- // Initialize PGF API. #ifdef Q_OS_WIN32 #ifdef UNICODE HANDLE fd = CreateFileW((LPCWSTR)filePath.utf16(), GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, 0); #else HANDLE fd = CreateFile(QFile::encodeName(filePath).constData(), GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, 0); #endif if (fd == INVALID_HANDLE_VALUE) { qCWarning(DIGIKAM_DIMG_LOG_PGF) << "Error: Could not open source file."; qCWarning(DIGIKAM_DIMG_LOG_PGF) << "Last error code:" << GetLastError(); loadingFailed(); return false; } #else int fd = QT_OPEN(QFile::encodeName(filePath).constData(), O_RDONLY); if (fd == -1) { loadingFailed(); return false; } #endif CPGFFileStream stream(fd); CPGFImage pgf; int colorModel = DImg::COLORMODELUNKNOWN; try { // open pgf image pgf.Open(&stream); switch (pgf.Mode()) { case ImageModeRGBColor: case ImageModeRGB48: m_hasAlpha = false; colorModel = DImg::RGB; break; case ImageModeRGBA: m_hasAlpha = true; colorModel = DImg::RGB; break; default: qCWarning(DIGIKAM_DIMG_LOG_PGF) << "Cannot load PGF image: color mode not supported (" << pgf.Mode() << ")"; loadingFailed(); return false; break; } switch (pgf.Channels()) { case 3: case 4: break; default: qCWarning(DIGIKAM_DIMG_LOG_PGF) << "Cannot load PGF image: color channels number not supported (" << pgf.Channels() << ")"; loadingFailed(); return false; break; } int bitDepth = pgf.BPP(); switch (bitDepth) { case 24: // RGB 8 bits. case 32: // RGBA 8 bits. m_sixteenBit = false; break; case 48: // RGB 16 bits. case 64: // RGBA 16 bits. m_sixteenBit = true; break; default: qCWarning(DIGIKAM_DIMG_LOG_PGF) << "Cannot load PGF image: color bits depth not supported (" << bitDepth << ")"; loadingFailed(); return false; break; } if (DIGIKAM_DIMG_LOG_PGF().isDebugEnabled()) { const PGFHeader* header = pgf.GetHeader(); qCDebug(DIGIKAM_DIMG_LOG_PGF) << "PGF width = " << header->width; qCDebug(DIGIKAM_DIMG_LOG_PGF) << "PGF height = " << header->height; qCDebug(DIGIKAM_DIMG_LOG_PGF) << "PGF bbp = " << header->bpp; qCDebug(DIGIKAM_DIMG_LOG_PGF) << "PGF channels = " << header->channels; qCDebug(DIGIKAM_DIMG_LOG_PGF) << "PGF quality = " << header->quality; qCDebug(DIGIKAM_DIMG_LOG_PGF) << "PGF mode = " << header->mode; qCDebug(DIGIKAM_DIMG_LOG_PGF) << "Has Alpha = " << m_hasAlpha; qCDebug(DIGIKAM_DIMG_LOG_PGF) << "Is 16 bits = " << m_sixteenBit; } // NOTE: see bug #273765 : Loading PGF thumbs with OpenMP support through a separated thread do not work properly with libppgf 6.11.24 pgf.ConfigureDecoder(false); int width = pgf.Width(); int height = pgf.Height(); uchar* data = 0; QSize originalSize(width, height); if (m_loadFlags & LoadImageData) { // ------------------------------------------------------------------- // Find out if we do the fast-track loading with reduced size. PGF specific. int level = 0; QVariant attribute = imageGetAttribute(QLatin1String("scaledLoadingSize")); if (attribute.isValid() && pgf.Levels() > 0) { int scaledLoadingSize = attribute.toInt(); int i, w, h; for (i = pgf.Levels() - 1 ; i >= 0 ; --i) { w = pgf.Width(i); h = pgf.Height(i); if (qMin(w, h) >= scaledLoadingSize) { break; } } if (i >= 0) { width = w; height = h; level = i; qCDebug(DIGIKAM_DIMG_LOG_PGF) << "Loading PGF scaled version at level " << i << " (" << w << " x " << h << ") for size " << scaledLoadingSize; } } if (m_sixteenBit) { data = new_failureTolerant(width, height, 8); // 16 bits/color/pixel } else { data = new_failureTolerant(width, height, 4); // 8 bits/color/pixel } // Fill all with 255 including alpha channel. memset(data, 0xFF, width * height * (m_sixteenBit ? 8 : 4)); pgf.Read(level, CallbackForLibPGF, this); pgf.GetBitmap(m_sixteenBit ? width * 8 : width * 4, (UINT8*)data, m_sixteenBit ? 64 : 32, NULL, CallbackForLibPGF, this); if (observer) { observer->progressInfo(m_image, 1.0); } } // ------------------------------------------------------------------- // Get ICC color profile. if (m_loadFlags & LoadICCData) { // TODO: Implement proper storage in PGF for color profiles checkExifWorkingColorSpace(); } imageWidth() = width; imageHeight() = height; imageData() = data; imageSetAttribute(QLatin1String("format"), QLatin1String("PGF")); imageSetAttribute(QLatin1String("originalColorModel"), colorModel); imageSetAttribute(QLatin1String("originalBitDepth"), bitDepth); imageSetAttribute(QLatin1String("originalSize"), originalSize); #ifdef Q_OS_WIN32 CloseHandle(fd); #else close(fd); #endif return true; } catch (IOException& e) { int err = e.error; if (err >= AppError) { err -= AppError; } qCWarning(DIGIKAM_DIMG_LOG_PGF) << "Error: Opening and reading PGF image failed (" << err << ")!"; #ifdef Q_OS_WIN32 CloseHandle(fd); #else close(fd); #endif loadingFailed(); return false; } catch (std::bad_alloc& e) { qCWarning(DIGIKAM_DIMG_LOG_PGF) << "Failed to allocate memory for loading" << filePath << e.what(); #ifdef Q_OS_WIN32 CloseHandle(fd); #else close(fd); #endif loadingFailed(); return false; } return true; } bool PGFLoader::save(const QString& filePath, DImgLoaderObserver* const observer) { m_observer = observer; #ifdef Q_OS_WIN32 #ifdef UNICODE HANDLE fd = CreateFileW((LPCWSTR)filePath.utf16(), GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); #else HANDLE fd = CreateFile(QFile::encodeName(filePath).constData(), GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); #endif if (fd == INVALID_HANDLE_VALUE) { qCWarning(DIGIKAM_DIMG_LOG_PGF) << "Error: Could not open destination file."; qCWarning(DIGIKAM_DIMG_LOG_PGF) << "Last error code:" << GetLastError(); 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) { qCWarning(DIGIKAM_DIMG_LOG_PGF) << "Error: Could not open destination file."; return false; } #endif try { QVariant qualityAttr = imageGetAttribute(QLatin1String("quality")); int quality = qualityAttr.isValid() ? qualityAttr.toInt() : 3; qCDebug(DIGIKAM_DIMG_LOG_PGF) << "PGF quality: " << quality; CPGFFileStream stream(fd); CPGFImage pgf; PGFHeader header; header.width = imageWidth(); header.height = imageHeight(); header.quality = quality; if (imageHasAlpha()) { if (imageSixteenBit()) { // NOTE : there is no PGF color mode in 16 bits with alpha. header.channels = 3; header.bpp = 48; header.mode = ImageModeRGB48; } else { header.channels = 4; header.bpp = 32; header.mode = ImageModeRGBA; } } else { if (imageSixteenBit()) { header.channels = 3; header.bpp = 48; header.mode = ImageModeRGB48; } else { header.channels = 3; header.bpp = 24; header.mode = ImageModeRGBColor; } } #ifdef PGFCodecVersionID # if PGFCodecVersionID < 0x061142 header.background.rgbtBlue = 0; header.background.rgbtGreen = 0; header.background.rgbtRed = 0; # endif #endif pgf.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 pgf.ConfigureEncoder(false); pgf.ImportBitmap(4 * imageWidth() * (imageSixteenBit() ? 2 : 1), (UINT8*)imageData(), imageBitsDepth() * 4, NULL, CallbackForLibPGF, this); UINT32 nWrittenBytes = 0; #ifdef PGFCodecVersionID # if PGFCodecVersionID >= 0x061124 pgf.Write(&stream, &nWrittenBytes, CallbackForLibPGF, this); # endif #else pgf.Write(&stream, 0, CallbackForLibPGF, &nWrittenBytes, this); #endif qCDebug(DIGIKAM_DIMG_LOG_PGF) << "PGF width = " << header.width; qCDebug(DIGIKAM_DIMG_LOG_PGF) << "PGF height = " << header.height; qCDebug(DIGIKAM_DIMG_LOG_PGF) << "PGF bbp = " << header.bpp; qCDebug(DIGIKAM_DIMG_LOG_PGF) << "PGF channels = " << header.channels; qCDebug(DIGIKAM_DIMG_LOG_PGF) << "PGF quality = " << header.quality; qCDebug(DIGIKAM_DIMG_LOG_PGF) << "PGF mode = " << header.mode; qCDebug(DIGIKAM_DIMG_LOG_PGF) << "Bytes Written = " << nWrittenBytes; #ifdef Q_OS_WIN32 CloseHandle(fd); #else close(fd); #endif // TODO: Store ICC profile in an appropriate place in the image storeColorProfileInMetadata(); if (observer) { observer->progressInfo(m_image, 1.0); } - imageSetAttribute(QLatin1String("savedformat"), QLatin1String("PGF")); + imageSetAttribute(QLatin1String("savedFormat"), QLatin1String("PGF")); saveMetadata(filePath); return true; } catch (IOException& e) { int err = e.error; if (err >= AppError) { err -= AppError; } qCWarning(DIGIKAM_DIMG_LOG_PGF) << "Error: Opening and saving PGF image failed (" << err << ")!"; #ifdef Q_OS_WIN32 CloseHandle(fd); #else close(fd); #endif return false; } return true; } bool PGFLoader::hasAlpha() const { return m_hasAlpha; } bool PGFLoader::sixteenBit() const { return m_sixteenBit; } bool PGFLoader::progressCallback(double percent, bool escapeAllowed) { if (m_observer) { m_observer->progressInfo(m_image, percent); if (escapeAllowed) { return (!m_observer->continueQuery(m_image)); } } return false; } bool PGFLoader::isReadOnly() const { return false; } } // namespace Digikam diff --git a/core/libs/dimg/loaders/pngloader.cpp b/core/libs/dimg/loaders/pngloader.cpp index 31fd1573b9..3ceb71f3c3 100644 --- a/core/libs/dimg/loaders/pngloader.cpp +++ b/core/libs/dimg/loaders/pngloader.cpp @@ -1,969 +1,969 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2005-11-01 * Description : a PNG image loader for DImg framework. * * Copyright (C) 2005-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. * * ============================================================ */ #define PNG_BYTES_TO_CHECK 4 #include "pngloader.h" // C ANSI includes extern "C" { #include } // C++ includes #include #include // Qt includes #include #include #include // Local includes #include "metaengine.h" #include "digikam_debug.h" #include "digikam_config.h" #include "digikam_version.h" #include "dimg.h" #include "dimgloaderobserver.h" // libPNG includes extern "C" { #include } #ifdef Q_OS_WIN void _ReadProc(struct png_struct_def* png_ptr, png_bytep data, png_size_t size) { FILE* const file_handle = (FILE*)png_get_io_ptr(png_ptr); fread(data, size, 1, file_handle); } #endif namespace Digikam { #if PNG_LIBPNG_VER_MAJOR >= 1 && PNG_LIBPNG_VER_MINOR >= 5 typedef png_bytep iCCP_data; #else typedef png_charp iCCP_data; #endif PNGLoader::PNGLoader(DImg* const image) : DImgLoader(image) { m_hasAlpha = false; m_sixteenBit = false; } bool PNGLoader::load(const QString& filePath, DImgLoaderObserver* const observer) { png_uint_32 w32, h32; int width, height; FILE* f = 0; int bit_depth, color_type, interlace_type; png_structp png_ptr = NULL; png_infop info_ptr = NULL; readMetadata(filePath, DImg::PNG); // ------------------------------------------------------------------- // Open the file qCDebug(DIGIKAM_DIMG_LOG_PNG) << "Opening file" << filePath; f = fopen(QFile::encodeName(filePath).constData(), "rb"); if (!f) { qCWarning(DIGIKAM_DIMG_LOG_PNG) << "Cannot open image file."; loadingFailed(); return false; } unsigned char buf[PNG_BYTES_TO_CHECK]; size_t membersRead = fread(buf, 1, PNG_BYTES_TO_CHECK, f); #if PNG_LIBPNG_VER >= 10400 if ((membersRead != PNG_BYTES_TO_CHECK) || png_sig_cmp(buf, 0, PNG_BYTES_TO_CHECK)) #else if ((membersRead != PNG_BYTES_TO_CHECK) || !png_check_sig(buf, PNG_BYTES_TO_CHECK)) #endif { qCWarning(DIGIKAM_DIMG_LOG_PNG) << "Not a PNG image file."; fclose(f); loadingFailed(); return false; } rewind(f); // ------------------------------------------------------------------- // Initialize the internal structures png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (!png_ptr) { qCWarning(DIGIKAM_DIMG_LOG_PNG) << "Invalid PNG image file structure."; fclose(f); loadingFailed(); return false; } info_ptr = png_create_info_struct(png_ptr); if (!info_ptr) { qCWarning(DIGIKAM_DIMG_LOG_PNG) << "Cannot reading PNG image file structure."; png_destroy_read_struct(&png_ptr, NULL, NULL); fclose(f); loadingFailed(); return false; } // ------------------------------------------------------------------- // PNG error handling. If an error occurs during reading, libpng // will jump here // setjmp-save cleanup class Q_DECL_HIDDEN CleanupData { public: CleanupData() : data(0), lines(0), f(0) { } ~CleanupData() { delete [] data; freeLines(); if (f) { fclose(f); } } void setData(uchar* const d) { data = d; } void setLines(uchar** const l) { lines = l; } void setFile(FILE* const file) { f = file; } void takeData() { data = 0; } void freeLines() { if (lines) { free(lines); } lines = 0; } uchar* data; uchar** lines; FILE* f; }; CleanupData* const cleanupData = new CleanupData; cleanupData->setFile(f); #if PNG_LIBPNG_VER >= 10400 if (setjmp(png_jmpbuf(png_ptr))) #else if (setjmp(png_ptr->jmpbuf)) #endif { qCWarning(DIGIKAM_DIMG_LOG_PNG) << "Internal libPNG error during reading file. Process aborted!"; png_destroy_read_struct(&png_ptr, &info_ptr, NULL); delete cleanupData; loadingFailed(); return false; } #ifdef Q_OS_WIN png_set_read_fn(png_ptr, f, _ReadProc); #else png_init_io(png_ptr, f); #endif // ------------------------------------------------------------------- // Read all PNG info up to image data png_read_info(png_ptr, info_ptr); png_get_IHDR(png_ptr, info_ptr, (png_uint_32*)(&w32), (png_uint_32*)(&h32), &bit_depth, &color_type, &interlace_type, NULL, NULL); width = (int)w32; height = (int)h32; int colorModel = DImg::COLORMODELUNKNOWN; m_sixteenBit = (bit_depth == 16); switch (color_type) { case PNG_COLOR_TYPE_RGB: // RGB m_hasAlpha = false; colorModel = DImg::RGB; break; case PNG_COLOR_TYPE_RGB_ALPHA: // RGBA m_hasAlpha = true; colorModel = DImg::RGB; break; case PNG_COLOR_TYPE_GRAY: // Grayscale m_hasAlpha = false; colorModel = DImg::GRAYSCALE; break; case PNG_COLOR_TYPE_GRAY_ALPHA: // Grayscale + Alpha m_hasAlpha = true; colorModel = DImg::GRAYSCALE; break; case PNG_COLOR_TYPE_PALETTE: // Indexed m_hasAlpha = false; colorModel = DImg::INDEXED; break; } uchar* data = 0; if (m_loadFlags & LoadImageData) { // TODO: Endianness: // You may notice that the code for little and big endian // below is now identical. This was found to work by PPC users. // If this proves right, all the conditional clauses can be removed. if (bit_depth == 16) { qCDebug(DIGIKAM_DIMG_LOG_PNG) << "PNG in 16 bits/color/pixel."; switch (color_type) { case PNG_COLOR_TYPE_RGB : // RGB qCDebug(DIGIKAM_DIMG_LOG_PNG) << "PNG in PNG_COLOR_TYPE_RGB"; png_set_add_alpha(png_ptr, 0xFFFF, PNG_FILLER_AFTER); break; case PNG_COLOR_TYPE_RGB_ALPHA : // RGBA qCDebug(DIGIKAM_DIMG_LOG_PNG) << "PNG in PNG_COLOR_TYPE_RGB_ALPHA"; break; case PNG_COLOR_TYPE_GRAY : // Grayscale qCDebug(DIGIKAM_DIMG_LOG_PNG) << "PNG in PNG_COLOR_TYPE_GRAY"; png_set_gray_to_rgb(png_ptr); png_set_add_alpha(png_ptr, 0xFFFF, PNG_FILLER_AFTER); break; case PNG_COLOR_TYPE_GRAY_ALPHA : // Grayscale + Alpha qCDebug(DIGIKAM_DIMG_LOG_PNG) << "PNG in PNG_COLOR_TYPE_GRAY_ALPHA"; png_set_gray_to_rgb(png_ptr); break; case PNG_COLOR_TYPE_PALETTE : // Indexed qCDebug(DIGIKAM_DIMG_LOG_PNG) << "PNG in PNG_COLOR_TYPE_PALETTE"; png_set_palette_to_rgb(png_ptr); png_set_add_alpha(png_ptr, 0xFFFF, PNG_FILLER_AFTER); break; default: qCWarning(DIGIKAM_DIMG_LOG_PNG) << "PNG color type unknown."; delete cleanupData; loadingFailed(); return false; } } else { qCDebug(DIGIKAM_DIMG_LOG_PNG) << "PNG in >=8 bits/color/pixel."; png_set_packing(png_ptr); switch (color_type) { case PNG_COLOR_TYPE_RGB : // RGB qCDebug(DIGIKAM_DIMG_LOG_PNG) << "PNG in PNG_COLOR_TYPE_RGB"; png_set_add_alpha(png_ptr, 0xFF, PNG_FILLER_AFTER); break; case PNG_COLOR_TYPE_RGB_ALPHA : // RGBA qCDebug(DIGIKAM_DIMG_LOG_PNG) << "PNG in PNG_COLOR_TYPE_RGB_ALPHA"; break; case PNG_COLOR_TYPE_GRAY : // Grayscale qCDebug(DIGIKAM_DIMG_LOG_PNG) << "PNG in PNG_COLOR_TYPE_GRAY"; #if PNG_LIBPNG_VER >= 10400 png_set_expand_gray_1_2_4_to_8(png_ptr); #else png_set_gray_1_2_4_to_8(png_ptr); #endif png_set_gray_to_rgb(png_ptr); png_set_add_alpha(png_ptr, 0xFF, PNG_FILLER_AFTER); break; case PNG_COLOR_TYPE_GRAY_ALPHA : // Grayscale + alpha qCDebug(DIGIKAM_DIMG_LOG_PNG) << "PNG in PNG_COLOR_TYPE_GRAY_ALPHA"; png_set_gray_to_rgb(png_ptr); break; case PNG_COLOR_TYPE_PALETTE : // Indexed qCDebug(DIGIKAM_DIMG_LOG_PNG) << "PNG in PNG_COLOR_TYPE_PALETTE"; png_set_packing(png_ptr); png_set_palette_to_rgb(png_ptr); png_set_add_alpha(png_ptr, 0xFF, PNG_FILLER_AFTER); break; default: qCWarning(DIGIKAM_DIMG_LOG_PNG) << "PNG color type unknown." << color_type; delete cleanupData; loadingFailed(); return false; } } if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { png_set_tRNS_to_alpha(png_ptr); } png_set_bgr(png_ptr); //png_set_swap_alpha(png_ptr); if (observer) { observer->progressInfo(m_image, 0.1F); } // ------------------------------------------------------------------- // Get image data. // Call before png_read_update_info and png_start_read_image() // for non-interlaced images number_passes will be 1 int number_passes = png_set_interlace_handling(png_ptr); png_read_update_info(png_ptr, info_ptr); if (m_sixteenBit) { data = new_failureTolerant(width, height, 8); // 16 bits/color/pixel } else { data = new_failureTolerant(width, height, 4); // 8 bits/color/pixel } cleanupData->setData(data); uchar** lines = 0; lines = (uchar**)malloc(height * sizeof(uchar*)); cleanupData->setLines(lines); if (!data || !lines) { qCDebug(DIGIKAM_DIMG_LOG_PNG) << "Cannot allocate memory to load PNG image data."; png_read_end(png_ptr, info_ptr); png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp) NULL); delete cleanupData; loadingFailed(); return false; } for (int i = 0; i < height; ++i) { if (m_sixteenBit) { lines[i] = data + (i * width * 8); } else { lines[i] = data + (i * width * 4); } } // The easy way to read the whole image // png_read_image(png_ptr, lines); // The other way to read images is row by row. Necessary for observer. // Now we need to deal with interlacing. for (int pass = 0; pass < number_passes; ++pass) { int y; int checkPoint = 0; for (y = 0; y < height; ++y) { if (observer && y == checkPoint) { checkPoint += granularity(observer, height, 0.7F); if (!observer->continueQuery(m_image)) { png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp) NULL); delete cleanupData; loadingFailed(); return false; } // use 10% - 80% for progress while reading rows observer->progressInfo(m_image, 0.1 + (0.7 * (((float)y) / ((float)height)))); } png_read_rows(png_ptr, lines + y, NULL, 1); } } cleanupData->freeLines(); if (QSysInfo::ByteOrder == QSysInfo::LittleEndian) { // Swap bytes in 16 bits/color/pixel for DImg if (m_sixteenBit) { uchar ptr[8]; // One pixel to swap for (int p = 0; p < width * height * 8; p += 8) { memcpy(&ptr[0], &data[p], 8); // Current pixel data[ p ] = ptr[1]; // Blue data[p + 1] = ptr[0]; data[p + 2] = ptr[3]; // Green data[p + 3] = ptr[2]; data[p + 4] = ptr[5]; // Red data[p + 5] = ptr[4]; data[p + 6] = ptr[7]; // Alpha data[p + 7] = ptr[6]; } } } } if (observer) { observer->progressInfo(m_image, 0.9F); } // ------------------------------------------------------------------- // Read image ICC profile if (m_loadFlags & LoadICCData) { png_charp profile_name; iCCP_data profile_data = NULL; png_uint_32 profile_size; int compression_type; png_get_iCCP(png_ptr, info_ptr, &profile_name, &compression_type, &profile_data, &profile_size); if (profile_data != NULL) { QByteArray profile_rawdata; profile_rawdata.resize(profile_size); memcpy(profile_rawdata.data(), profile_data, profile_size); imageSetIccProfile(IccProfile(profile_rawdata)); } else { // If ICC profile is null, check Exif metadata. checkExifWorkingColorSpace(); } } // ------------------------------------------------------------------- // Get embedded text data. png_text* text_ptr = 0; int num_comments = png_get_text(png_ptr, info_ptr, &text_ptr, NULL); /* Standard Embedded text includes in PNG : Title Short (one line) title or caption for image Author Name of image's creator Description Description of image (possibly long) Copyright Copyright notice Creation Time Time of original image creation Software Software used to create the image Disclaimer Legal disclaimer Warning Warning of nature of content Source Device used to create the image Comment Miscellaneous comment; conversion from GIF comment Extra Raw profiles tag are used by ImageMagick and defines at this URL : http://search.cpan.org/src/EXIFTOOL/Image-ExifTool-5.87/html/TagNames/PNG.html#TextualData */ if (m_loadFlags & LoadICCData) { for (int i = 0; i < num_comments; ++i) { // Check if we have a Raw profile embedded using ImageMagick technique. if (memcmp(text_ptr[i].key, "Raw profile type exif", 21) != 0 || memcmp(text_ptr[i].key, "Raw profile type APP1", 21) != 0 || memcmp(text_ptr[i].key, "Raw profile type iptc", 21) != 0) { imageSetEmbbededText(QLatin1String(text_ptr[i].key), QLatin1String(text_ptr[i].text)); qCDebug(DIGIKAM_DIMG_LOG_PNG) << "Reading PNG Embedded text: key=" << text_ptr[i].key << " text=" << text_ptr[i].text; } } } // ------------------------------------------------------------------- if (m_loadFlags & LoadImageData) { png_read_end(png_ptr, info_ptr); } png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp) NULL); cleanupData->takeData(); delete cleanupData; if (observer) { observer->progressInfo(m_image, 1.0); } imageWidth() = width; imageHeight() = height; imageData() = data; imageSetAttribute(QLatin1String("format"), QLatin1String("PNG")); imageSetAttribute(QLatin1String("originalColorModel"), colorModel); imageSetAttribute(QLatin1String("originalBitDepth"), bit_depth); imageSetAttribute(QLatin1String("originalSize"), QSize(width, height)); return true; } bool PNGLoader::save(const QString& filePath, DImgLoaderObserver* const observer) { FILE* f = 0; png_structp png_ptr; png_infop info_ptr; uchar* ptr = 0; uchar* data = 0; uint x, y, j; png_bytep row_ptr; png_color_8 sig_bit; int quality = 75; int compression = 3; // ------------------------------------------------------------------- // Open the file f = fopen(QFile::encodeName(filePath).constData(), "wb"); if (!f) { qCWarning(DIGIKAM_DIMG_LOG_PNG) << "Cannot open target image file."; return false; } // ------------------------------------------------------------------- // Initialize the internal structures png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (!png_ptr) { qCWarning(DIGIKAM_DIMG_LOG_PNG) << "Invalid target PNG image file structure."; fclose(f); return false; } info_ptr = png_create_info_struct(png_ptr); if (info_ptr == NULL) { qCWarning(DIGIKAM_DIMG_LOG_PNG) << "Cannot create PNG image file structure."; png_destroy_write_struct(&png_ptr, (png_infopp) NULL); fclose(f); return false; } // ------------------------------------------------------------------- // PNG error handling. If an error occurs during writing, libpng // will jump here // setjmp-save cleanup class Q_DECL_HIDDEN CleanupData { public: CleanupData() : data(0), f(0) { } ~CleanupData() { delete [] data; if (f) { fclose(f); } } void setData(uchar* const d) { data = d; } void setFile(FILE* const file) { f = file; } uchar* data; FILE* f; }; CleanupData* const cleanupData = new CleanupData; cleanupData->setFile(f); #if PNG_LIBPNG_VER >= 10400 if (setjmp(png_jmpbuf(png_ptr))) #else if (setjmp(png_ptr->jmpbuf)) #endif { qCWarning(DIGIKAM_DIMG_LOG_PNG) << "Internal libPNG error during writing file. Process aborted!"; png_destroy_write_struct(&png_ptr, (png_infopp) & info_ptr); png_destroy_info_struct(png_ptr, (png_infopp) & info_ptr); delete cleanupData; return false; } png_init_io(png_ptr, f); png_set_bgr(png_ptr); //png_set_swap_alpha(png_ptr); if (imageHasAlpha()) { png_set_IHDR(png_ptr, info_ptr, imageWidth(), imageHeight(), imageBitsDepth(), PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); if (imageSixteenBit()) { data = new uchar[imageWidth() * 8 * sizeof(uchar)]; } else { data = new uchar[imageWidth() * 4 * sizeof(uchar)]; } } else { png_set_IHDR(png_ptr, info_ptr, imageWidth(), imageHeight(), imageBitsDepth(), PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); if (imageSixteenBit()) { data = new uchar[imageWidth() * 6 * sizeof(uchar)]; } else { data = new uchar[imageWidth() * 3 * sizeof(uchar)]; } } cleanupData->setData(data); sig_bit.red = imageBitsDepth(); sig_bit.green = imageBitsDepth(); sig_bit.blue = imageBitsDepth(); sig_bit.alpha = imageBitsDepth(); png_set_sBIT(png_ptr, info_ptr, &sig_bit); // ------------------------------------------------------------------- // Quality to convert to compression QVariant qualityAttr = imageGetAttribute(QLatin1String("quality")); quality = qualityAttr.isValid() ? qualityAttr.toInt() : 90; qCDebug(DIGIKAM_DIMG_LOG_PNG) << "DImg quality level: " << quality; if (quality < 1) { quality = 1; } if (quality > 99) { quality = 99; } quality = quality / 10; compression = 9 - quality; if (compression < 0) { compression = 0; } if (compression > 9) { compression = 9; } qCDebug(DIGIKAM_DIMG_LOG_PNG) << "PNG compression level: " << compression; png_set_compression_level(png_ptr, compression); // ------------------------------------------------------------------- // Write ICC profile. QByteArray profile_rawdata = m_image->getIccProfile().data(); if (!profile_rawdata.isEmpty()) { purgeExifWorkingColorSpace(); png_set_iCCP(png_ptr, info_ptr, (png_charp)("icc"), PNG_COMPRESSION_TYPE_BASE, (iCCP_data)profile_rawdata.data(), profile_rawdata.size()); } // ------------------------------------------------------------------- // Write embedded Text typedef QMap EmbeddedTextMap; EmbeddedTextMap map = imageEmbeddedText(); for (EmbeddedTextMap::const_iterator it = map.constBegin(); it != map.constEnd(); ++it) { if (it.key() != QLatin1String("Software") && it.key() != QLatin1String("Comment")) { QByteArray key = it.key().toLatin1(); QByteArray value = it.value().toLatin1(); png_text text; text.key = key.data(); text.text = value.data(); qCDebug(DIGIKAM_DIMG_LOG_PNG) << "Writing PNG Embedded text: key=" << text.key << " text=" << text.text; text.compression = PNG_TEXT_COMPRESSION_zTXt; png_set_text(png_ptr, info_ptr, &(text), 1); } } // Update 'Software' text tag. QString software = QLatin1String("digiKam "); software.append(digiKamVersion()); QString libpngver = QLatin1String(PNG_HEADER_VERSION_STRING); libpngver.replace(QLatin1Char('\n'), QLatin1Char(' ')); software.append(QString::fromLatin1(" (%1)").arg(libpngver)); QByteArray softwareAsAscii = software.toLatin1(); png_text text; text.key = (png_charp)("Software"); text.text = softwareAsAscii.data(); qCDebug(DIGIKAM_DIMG_LOG_PNG) << "Writing PNG Embedded text: key=" << text.key << " text=" << text.text; text.compression = PNG_TEXT_COMPRESSION_zTXt; png_set_text(png_ptr, info_ptr, &(text), 1); if (observer) { observer->progressInfo(m_image, 0.2F); } // ------------------------------------------------------------------- // Write image data png_write_info(png_ptr, info_ptr); png_set_shift(png_ptr, &sig_bit); png_set_packing(png_ptr); ptr = imageData(); uint checkPoint = 0; for (y = 0; y < imageHeight(); ++y) { if (observer && y == checkPoint) { checkPoint += granularity(observer, imageHeight(), 0.8F); if (!observer->continueQuery(m_image)) { png_destroy_write_struct(&png_ptr, (png_infopp) & info_ptr); png_destroy_info_struct(png_ptr, (png_infopp) & info_ptr); delete cleanupData; return false; } observer->progressInfo(m_image, 0.2 + (0.8 * (((float)y) / ((float)imageHeight())))); } j = 0; if (QSysInfo::ByteOrder == QSysInfo::LittleEndian) { for (x = 0; x < imageWidth()*imageBytesDepth(); x += imageBytesDepth()) { if (imageSixteenBit()) { if (imageHasAlpha()) { data[j++] = ptr[x + 1]; // Blue data[j++] = ptr[ x ]; data[j++] = ptr[x + 3]; // Green data[j++] = ptr[x + 2]; data[j++] = ptr[x + 5]; // Red data[j++] = ptr[x + 4]; data[j++] = ptr[x + 7]; // Alpha data[j++] = ptr[x + 6]; } else { data[j++] = ptr[x + 1]; // Blue data[j++] = ptr[ x ]; data[j++] = ptr[x + 3]; // Green data[j++] = ptr[x + 2]; data[j++] = ptr[x + 5]; // Red data[j++] = ptr[x + 4]; } } else { if (imageHasAlpha()) { data[j++] = ptr[ x ]; // Blue data[j++] = ptr[x + 1]; // Green data[j++] = ptr[x + 2]; // Red data[j++] = ptr[x + 3]; // Alpha } else { data[j++] = ptr[ x ]; // Blue data[j++] = ptr[x + 1]; // Green data[j++] = ptr[x + 2]; // Red } } } } else { int bytes = (imageSixteenBit() ? 2 : 1) * (imageHasAlpha() ? 4 : 3); for (x = 0; x < imageWidth()*imageBytesDepth(); x += imageBytesDepth()) { memcpy(data + j, ptr + x, bytes); j += bytes; } } row_ptr = (png_bytep) data; png_write_rows(png_ptr, &row_ptr, 1); ptr += (imageWidth() * imageBytesDepth()); } // ------------------------------------------------------------------- png_write_end(png_ptr, info_ptr); png_destroy_write_struct(&png_ptr, (png_infopp) & info_ptr); png_destroy_info_struct(png_ptr, (png_infopp) & info_ptr); delete cleanupData; - imageSetAttribute(QLatin1String("savedformat"), QLatin1String("PNG")); + imageSetAttribute(QLatin1String("savedFormat"), QLatin1String("PNG")); saveMetadata(filePath); return true; } bool PNGLoader::hasAlpha() const { return m_hasAlpha; } bool PNGLoader::sixteenBit() const { return m_sixteenBit; } bool PNGLoader::isReadOnly() const { return false; } } // namespace Digikam diff --git a/core/libs/dimg/loaders/tiffloader.cpp b/core/libs/dimg/loaders/tiffloader.cpp index 4f5d12ad0a..b126538c8c 100644 --- a/core/libs/dimg/loaders/tiffloader.cpp +++ b/core/libs/dimg/loaders/tiffloader.cpp @@ -1,1130 +1,1130 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2005-06-17 * Description : A TIFF IO file for DImg framework * * Copyright (C) 2005 by Renchi Raju * Copyright (C) 2006-2019 by Gilles Caulier * * Specifications & references: * - TIFF 6.0 : http://partners.adobe.com/public/developer/en/tiff/TIFF6.pdf * - TIFF/EP : http://www.map.tu.chiba-u.ac.jp/IEC/100/TA2/recdoc/N4378.pdf * - TIFF/Tags : http://www.awaresystems.be/imaging/tiff/tifftags.html * - DNG : http://www.adobe.com/products/dng/pdfs/dng_spec.pdf * * Others Linux Tiff Loader implementation using libtiff: * - http://websvn.kde.org/trunk/koffice/filters/krita/tiff/kis_tiff_converter.cc * - http://artis.inrialpes.fr/Software/TiffIO/ * - http://cvs.graphicsmagick.org/cgi-bin/cvsweb.cgi/GraphicsMagick/coders/tiff.c * - http://freeimage.cvs.sourceforge.net/freeimage/FreeImage/Source/FreeImage/PluginTIFF.cpp * - http://freeimage.cvs.sourceforge.net/freeimage/FreeImage/Source/Metadata/XTIFF.cpp * - https://subversion.imagemagick.org/subversion/ImageMagick/trunk/coders/tiff.c * * Test images repository: * - http://www.remotesensing.org/libtiff/images.html * * 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. * * ============================================================ */ // C ANSI includes extern "C" { #include } // C++ includes #include // Qt includes #include #include // Local includes #include "digikam_config.h" #include "dimg.h" #include "digikam_debug.h" #include "dimgloaderobserver.h" #include "dmetadata.h" #include "tiffloader.h" //krazy:exclude=includes namespace Digikam { // To manage Errors/Warnings handling provide by libtiff void TIFFLoader::dimg_tiff_warning(const char* module, const char* format, va_list warnings) { if (DIGIKAM_DIMG_LOG_TIFF().isDebugEnabled()) { char message[4096]; vsnprintf(message, 4096, format, warnings); qCDebug(DIGIKAM_DIMG_LOG_TIFF) << module << "::" << message; } } void TIFFLoader::dimg_tiff_error(const char* module, const char* format, va_list errors) { if (DIGIKAM_DIMG_LOG_TIFF().isDebugEnabled()) { char message[4096]; vsnprintf(message, 4096, format, errors); qCDebug(DIGIKAM_DIMG_LOG_TIFF) << module << "::" << message; } } TIFFLoader::TIFFLoader(DImg* const image) : DImgLoader(image) { m_hasAlpha = false; m_sixteenBit = false; } bool TIFFLoader::load(const QString& filePath, DImgLoaderObserver* const observer) { readMetadata(filePath, DImg::TIFF); // ------------------------------------------------------------------- // TIFF error handling. If an errors/warnings occurs during reading, // libtiff will call these methods #ifdef Q_OS_WIN TIFFSetWarningHandler(NULL); #else TIFFSetWarningHandler(dimg_tiff_warning); #endif TIFFSetErrorHandler(dimg_tiff_error); // ------------------------------------------------------------------- // Open the file TIFF* const tif = TIFFOpen(QFile::encodeName(filePath).constData(), "r"); if (!tif) { qCWarning(DIGIKAM_DIMG_LOG_TIFF) << "Cannot open image file."; loadingFailed(); return false; } if (DIGIKAM_DIMG_LOG_TIFF().isDebugEnabled()) { TIFFPrintDirectory(tif, stdout, 0); } // ------------------------------------------------------------------- // Get image information. uint32 w, h; uint16 bits_per_sample; uint16 samples_per_pixel; uint16 photometric; uint16 planar_config; uint32 rows_per_strip; tsize_t strip_size; tstrip_t num_of_strips; TIFFGetFieldDefaulted(tif, TIFFTAG_IMAGEWIDTH, &w); TIFFGetFieldDefaulted(tif, TIFFTAG_IMAGELENGTH, &h); TIFFGetFieldDefaulted(tif, TIFFTAG_BITSPERSAMPLE, &bits_per_sample); TIFFGetFieldDefaulted(tif, TIFFTAG_SAMPLESPERPIXEL, &samples_per_pixel); TIFFGetFieldDefaulted(tif, TIFFTAG_PLANARCONFIG, &planar_config); if (TIFFGetFieldDefaulted(tif, TIFFTAG_ROWSPERSTRIP, &rows_per_strip) == 0 || rows_per_strip == 0) { qCWarning(DIGIKAM_DIMG_LOG_TIFF) << "TIFF loader: Cannot handle non-stripped images. Loading file " << filePath; TIFFClose(tif); loadingFailed(); return false; } if (rows_per_strip > h) { rows_per_strip = h; } if (bits_per_sample == 0 || samples_per_pixel == 0 || rows_per_strip == 0 // || rows_per_strip > h ) { qCWarning(DIGIKAM_DIMG_LOG_TIFF) << "TIFF loader: Encountered invalid value in image." << endl << " bits_per_sample : " << bits_per_sample << endl << " samples_per_pixel : " << samples_per_pixel << endl << " rows_per_strip : " << rows_per_strip << endl << " h : " << h << endl << " Loading file : " << filePath; TIFFClose(tif); loadingFailed(); return false; } // TODO: check others TIFF color-spaces here. Actually, only RGB, PALETTE and MINISBLACK // have been tested. // Complete description of TIFFTAG_PHOTOMETRIC tag can be found at this url: // http://www.awaresystems.be/imaging/tiff/tifftags/photometricinterpretation.html TIFFGetFieldDefaulted(tif, TIFFTAG_PHOTOMETRIC, &photometric); if (photometric != PHOTOMETRIC_RGB && photometric != PHOTOMETRIC_PALETTE && photometric != PHOTOMETRIC_MINISWHITE && photometric != PHOTOMETRIC_MINISBLACK && ((photometric != PHOTOMETRIC_YCBCR) | (bits_per_sample != 8)) && ((photometric != PHOTOMETRIC_SEPARATED) | (bits_per_sample != 8)) && (m_loadFlags & LoadImageData)) { qCWarning(DIGIKAM_DIMG_LOG_TIFF) << "Can not handle image without RGB color-space: " << photometric; TIFFClose(tif); loadingFailed(); return false; } int colorModel = DImg::COLORMODELUNKNOWN; switch (photometric) { case PHOTOMETRIC_MINISWHITE: case PHOTOMETRIC_MINISBLACK: colorModel = DImg::GRAYSCALE; break; case PHOTOMETRIC_RGB: colorModel = DImg::RGB; break; case PHOTOMETRIC_PALETTE: colorModel = DImg::INDEXED; break; case PHOTOMETRIC_MASK: colorModel = DImg::MONOCHROME; break; case PHOTOMETRIC_SEPARATED: colorModel = DImg::CMYK; break; case PHOTOMETRIC_YCBCR: colorModel = DImg::YCBCR; break; case PHOTOMETRIC_CIELAB: case PHOTOMETRIC_ICCLAB: case PHOTOMETRIC_ITULAB: colorModel = DImg::CIELAB; break; case PHOTOMETRIC_LOGL: case PHOTOMETRIC_LOGLUV: colorModel = DImg::COLORMODELRAW; break; } if (samples_per_pixel == 4) { m_hasAlpha = true; } else { m_hasAlpha = false; } if (bits_per_sample == 16 || bits_per_sample == 32) { m_sixteenBit = true; } else { m_sixteenBit = false; } // ------------------------------------------------------------------- // Read image ICC profile if (m_loadFlags & LoadICCData) { uchar* profile_data = 0; uint32 profile_size; if (TIFFGetField(tif, TIFFTAG_ICCPROFILE, &profile_size, &profile_data)) { QByteArray profile_rawdata; profile_rawdata.resize(profile_size); memcpy(profile_rawdata.data(), profile_data, profile_size); imageSetIccProfile(IccProfile(profile_rawdata)); } else { // If ICC profile is null, check Exif metadata. checkExifWorkingColorSpace(); } } // ------------------------------------------------------------------- // Get image data. QScopedArrayPointer data; if (m_loadFlags & LoadImageData) { if (observer) { observer->progressInfo(m_image, 0.1F); } strip_size = TIFFStripSize(tif); num_of_strips = TIFFNumberOfStrips(tif); if (bits_per_sample == 16) // 16 bits image. { data.reset(new_failureTolerant(w, h, 8)); QScopedArrayPointer strip(new_failureTolerant(strip_size)); if (!data || strip.isNull()) { qCWarning(DIGIKAM_DIMG_LOG_TIFF) << "Failed to allocate memory for TIFF image" << filePath; TIFFClose(tif); loadingFailed(); return false; } long offset = 0; long bytesRead = 0; uint checkpoint = 0; for (tstrip_t st = 0 ; st < num_of_strips ; ++st) { if (observer && st == checkpoint) { checkpoint += granularity(observer, num_of_strips, 0.8F); if (!observer->continueQuery(m_image)) { TIFFClose(tif); loadingFailed(); return false; } observer->progressInfo(m_image, 0.1 + (0.8 * (((float)st) / ((float)num_of_strips)))); } bytesRead = TIFFReadEncodedStrip(tif, st, strip.data(), strip_size); if (bytesRead == -1) { qCWarning(DIGIKAM_DIMG_LOG_TIFF) << "Failed to read strip"; TIFFClose(tif); loadingFailed(); return false; } if ((planar_config == PLANARCONFIG_SEPARATE) && (st % (num_of_strips / samples_per_pixel)) == 0) { offset = 0; } ushort* stripPtr = reinterpret_cast(strip.data()); ushort* dataPtr = reinterpret_cast(data.data() + offset); ushort* p; // tiff data is read as BGR or ABGR or Greyscale if (samples_per_pixel == 1) // See bug #148400: Greyscale pictures only have _one_ sample per pixel { for (int i = 0 ; i < bytesRead / 2 ; ++i) { // We have to read two bytes for one pixel p = dataPtr; p[0] = *stripPtr; // RGB have to be set to the _same_ value p[1] = *stripPtr; p[2] = *stripPtr++; p[3] = 0xFFFF; // set alpha to 100% dataPtr += 4; } offset += bytesRead * 4; // The _byte_offset in the data array is, of course, four times bytesRead } else if ((samples_per_pixel == 3) && (planar_config == PLANARCONFIG_CONTIG)) { for (int i = 0 ; i < bytesRead / 6 ; ++i) { p = dataPtr; p[2] = *stripPtr++; p[1] = *stripPtr++; p[0] = *stripPtr++; p[3] = 0xFFFF; dataPtr += 4; } offset += bytesRead / 6 * 8; } else if ((samples_per_pixel == 3) && (planar_config == PLANARCONFIG_SEPARATE)) { for (int i = 0 ; i < bytesRead / 2 ; ++i) { p = dataPtr; switch ((st / (num_of_strips / samples_per_pixel))) { case 0: p[2] = *stripPtr++; p[3] = 0xFFFF; break; case 1: p[1] = *stripPtr++; break; case 2: p[0] = *stripPtr++; break; } dataPtr += 4; } offset += bytesRead / 2 * 8; } else if ((samples_per_pixel == 4) && (planar_config == PLANARCONFIG_CONTIG)) { for (int i = 0 ; i < bytesRead / 8 ; ++i) { p = dataPtr; p[2] = *stripPtr++; p[1] = *stripPtr++; p[0] = *stripPtr++; p[3] = *stripPtr++; dataPtr += 4; } offset += bytesRead; } else if ((samples_per_pixel == 4) && (planar_config == PLANARCONFIG_SEPARATE)) { for (int i = 0 ; i < bytesRead / 2 ; ++i) { p = dataPtr; switch ((st / (num_of_strips / samples_per_pixel))) { case 0: p[2] = *stripPtr++; break; case 1: p[1] = *stripPtr++; break; case 2: p[0] = *stripPtr++; break; case 3: p[3] = *stripPtr++; break; } dataPtr += 4; } offset += bytesRead / 2 * 8; } } } else if (bits_per_sample == 32) // 32 bits image. { data.reset(new_failureTolerant(w, h, 8)); QScopedArrayPointer strip(new_failureTolerant(strip_size)); if (!data || strip.isNull()) { qCWarning(DIGIKAM_DIMG_LOG_TIFF) << "Failed to allocate memory for TIFF image" << filePath; TIFFClose(tif); loadingFailed(); return false; } long offset = 0; long bytesRead = 0; uint checkpoint = 0; float maxValue = 0.0; for (tstrip_t st = 0 ; st < num_of_strips ; ++st) { if (observer && !observer->continueQuery(m_image)) { TIFFClose(tif); loadingFailed(); return false; } bytesRead = TIFFReadEncodedStrip(tif, st, strip.data(), strip_size); if (bytesRead == -1) { qCWarning(DIGIKAM_DIMG_LOG_TIFF) << "Failed to read strip"; TIFFClose(tif); loadingFailed(); return false; } float* stripPtr = reinterpret_cast(strip.data()); for (int i = 0 ; i < bytesRead / 4 ; ++i) { maxValue = qMax(maxValue, *stripPtr++); } } double factor = (maxValue > 10.0) ? log10(maxValue) * 1.5 : 1.0; double scale = (factor > 1.0) ? 0.75 : 1.0; if (factor > 1.0) { qCWarning(DIGIKAM_DIMG_LOG_TIFF) << "TIFF image cannot be converted lossless from 32 to 16 bits" << filePath; } for (tstrip_t st = 0 ; st < num_of_strips ; ++st) { if (observer && st == checkpoint) { checkpoint += granularity(observer, num_of_strips, 0.8F); if (!observer->continueQuery(m_image)) { TIFFClose(tif); loadingFailed(); return false; } observer->progressInfo(m_image, 0.1 + (0.8 * (((float)st) / ((float)num_of_strips)))); } bytesRead = TIFFReadEncodedStrip(tif, st, strip.data(), strip_size); if (bytesRead == -1) { qCWarning(DIGIKAM_DIMG_LOG_TIFF) << "Failed to read strip"; TIFFClose(tif); loadingFailed(); return false; } if ((planar_config == PLANARCONFIG_SEPARATE) && (st % (num_of_strips / samples_per_pixel)) == 0) { offset = 0; } float* stripPtr = reinterpret_cast(strip.data()); ushort* dataPtr = reinterpret_cast(data.data() + offset); ushort* p; if ((samples_per_pixel == 3) && (planar_config == PLANARCONFIG_CONTIG)) { for (int i = 0 ; i < bytesRead / 12 ; ++i) { p = dataPtr; p[2] = (ushort)qBound(0.0, pow((double)*stripPtr++ / factor, scale) * 65535.0, 65535.0); p[1] = (ushort)qBound(0.0, pow((double)*stripPtr++ / factor, scale) * 65535.0, 65535.0); p[0] = (ushort)qBound(0.0, pow((double)*stripPtr++ / factor, scale) * 65535.0, 65535.0); p[3] = 0xFFFF; dataPtr += 4; } offset += bytesRead / 12 * 8; } else if ((samples_per_pixel == 3) && (planar_config == PLANARCONFIG_SEPARATE)) { for (int i = 0 ; i < bytesRead / 4 ; ++i) { p = dataPtr; switch ((st / (num_of_strips / samples_per_pixel))) { case 0: p[2] = (ushort)qBound(0.0, pow((double)*stripPtr++ / factor, scale) * 65535.0, 65535.0); p[3] = 0xFFFF; break; case 1: p[1] = (ushort)qBound(0.0, pow((double)*stripPtr++ / factor, scale) * 65535.0, 65535.0); break; case 2: p[0] = (ushort)qBound(0.0, pow((double)*stripPtr++ / factor, scale) * 65535.0, 65535.0); break; } dataPtr += 4; } offset += bytesRead / 4 * 8; } else if ((samples_per_pixel == 4) && (planar_config == PLANARCONFIG_CONTIG)) { for (int i = 0 ; i < bytesRead / 16 ; ++i) { p = dataPtr; p[2] = (ushort)qBound(0.0, pow((double)*stripPtr++ / factor, scale) * 65535.0, 65535.0); p[1] = (ushort)qBound(0.0, pow((double)*stripPtr++ / factor, scale) * 65535.0, 65535.0); p[0] = (ushort)qBound(0.0, pow((double)*stripPtr++ / factor, scale) * 65535.0, 65535.0); p[3] = (ushort)qBound(0.0, (double)*stripPtr++ * 65535.0, 65535.0); dataPtr += 4; } offset += bytesRead / 16 * 8; } else if ((samples_per_pixel == 4) && (planar_config == PLANARCONFIG_SEPARATE)) { for (int i = 0 ; i < bytesRead / 4 ; ++i) { p = dataPtr; switch ((st / (num_of_strips / samples_per_pixel))) { case 0: p[2] = (ushort)qBound(0.0, pow((double)*stripPtr++ / factor, scale) * 65535.0, 65535.0); break; case 1: p[1] = (ushort)qBound(0.0, pow((double)*stripPtr++ / factor, scale) * 65535.0, 65535.0); break; case 2: p[0] = (ushort)qBound(0.0, pow((double)*stripPtr++ / factor, scale) * 65535.0, 65535.0); break; case 3: p[3] = (ushort)qBound(0.0, (double)*stripPtr++ * 65535.0, 65535.0); break; } dataPtr += 4; } offset += bytesRead / 4 * 8; } } } else // Non 16 or 32 bits images ==> get it on BGRA 8 bits. { data.reset(new_failureTolerant(w, h, 4)); QScopedArrayPointer strip(new_failureTolerant(w, rows_per_strip, 4)); if (!data || strip.isNull()) { qCWarning(DIGIKAM_DIMG_LOG_TIFF) << "Failed to allocate memory for TIFF image" << filePath; TIFFClose(tif); loadingFailed(); return false; } long offset = 0; long pixelsRead = 0; // this is inspired by TIFFReadRGBAStrip, tif_getimage.c char emsg[1024] = ""; TIFFRGBAImage img; uint32 rows_to_read; uint checkpoint = 0; // test whether libtiff can read format and initiate reading if (!TIFFRGBAImageOK(tif, emsg) || !TIFFRGBAImageBegin(&img, tif, 0, emsg)) { qCWarning(DIGIKAM_DIMG_LOG_TIFF) << "Failed to set up RGBA reading of image, filename " << TIFFFileName(tif) << " error message from Libtiff: " << emsg; TIFFClose(tif); loadingFailed(); return false; } // libtiff cannot handle all possible orientations, it give weird results. // We rotate ourselves. (Bug 274865) img.req_orientation = img.orientation; // read strips from image: read rows_per_strip, so always start at beginning of a strip for (uint row = 0 ; row < h ; row += rows_per_strip) { if (observer && row >= checkpoint) { checkpoint += granularity(observer, h, 0.8F); if (!observer->continueQuery(m_image)) { TIFFClose(tif); loadingFailed(); return false; } observer->progressInfo(m_image, 0.1 + (0.8 * (((float)row) / ((float)h)))); } img.row_offset = row; img.col_offset = 0; if (row + rows_per_strip > img.height) { rows_to_read = img.height - row; } else { rows_to_read = rows_per_strip; } // Read data if (TIFFRGBAImageGet(&img, reinterpret_cast(strip.data()), img.width, rows_to_read) == -1) { qCWarning(DIGIKAM_DIMG_LOG_TIFF) << "Failed to read image data"; TIFFClose(tif); loadingFailed(); return false; } pixelsRead = rows_to_read * img.width; uchar* stripPtr = (uchar*)(strip.data()); uchar* dataPtr = (uchar*)(data.data() + offset); uchar* p; // Reverse red and blue for (int i = 0 ; i < pixelsRead ; ++i) { p = dataPtr; p[2] = *stripPtr++; p[1] = *stripPtr++; p[0] = *stripPtr++; p[3] = *stripPtr++; dataPtr += 4; } offset += pixelsRead * 4; } TIFFRGBAImageEnd(&img); } } // ------------------------------------------------------------------- TIFFClose(tif); if (observer) { observer->progressInfo(m_image, 1.0); } imageWidth() = w; imageHeight() = h; imageData() = data.take(); imageSetAttribute(QLatin1String("format"), QLatin1String("TIFF")); imageSetAttribute(QLatin1String("originalColorModel"), colorModel); imageSetAttribute(QLatin1String("originalBitDepth"), bits_per_sample); imageSetAttribute(QLatin1String("originalSize"), QSize(w, h)); return true; } bool TIFFLoader::save(const QString& filePath, DImgLoaderObserver* const observer) { uint32 w = imageWidth(); uint32 h = imageHeight(); uchar* data = imageData(); // ------------------------------------------------------------------- // TIFF error handling. If an errors/warnings occurs during reading, // libtiff will call these methods TIFFSetWarningHandler(dimg_tiff_warning); TIFFSetErrorHandler(dimg_tiff_error); // ------------------------------------------------------------------- // Open the file TIFF* const tif = TIFFOpen(QFile::encodeName(filePath).constData(), "w"); if (!tif) { qCWarning(DIGIKAM_DIMG_LOG_TIFF) << "Cannot open target image file."; return false; } // ------------------------------------------------------------------- // Set image properties TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, w); TIFFSetField(tif, TIFFTAG_IMAGELENGTH, h); TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB); TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); TIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT); TIFFSetField(tif, TIFFTAG_RESOLUTIONUNIT, RESUNIT_NONE); // Image must be compressed using deflate algorithm ? QVariant compressAttr = imageGetAttribute(QLatin1String("compress")); bool compress = compressAttr.isValid() ? compressAttr.toBool() : false; if (compress) { TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_ADOBE_DEFLATE); TIFFSetField(tif, TIFFTAG_ZIPQUALITY, 9); // NOTE : this tag values aren't defined in libtiff 3.6.1. '2' is PREDICTOR_HORIZONTAL. // Use horizontal differencing for images which are // likely to be continuous tone. The TIFF spec says that this // usually leads to better compression. // See this url for more details: // http://www.awaresystems.be/imaging/tiff/tifftags/predictor.html TIFFSetField(tif, TIFFTAG_PREDICTOR, 2); } else { TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_NONE); } uint16 sampleinfo[1]; if (imageHasAlpha()) { sampleinfo[0] = EXTRASAMPLE_ASSOCALPHA; TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 4); TIFFSetField(tif, TIFFTAG_EXTRASAMPLES, 1, sampleinfo); } else { TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 3); } TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, (uint16)imageBitsDepth()); TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, TIFFDefaultStripSize(tif, 0)); // ------------------------------------------------------------------- // Write meta-data Tags contents. DMetadata metaData(m_image->getMetadata()); // Standard IPTC tag (available with libtiff 3.6.1) QByteArray ba = metaData.getIptc(true); if (!ba.isEmpty()) { #if defined(TIFFTAG_PHOTOSHOP) TIFFSetField(tif, TIFFTAG_PHOTOSHOP, (uint32)ba.size(), (uchar*)ba.data()); #endif } // Standard XMP tag (available with libtiff 3.6.1) if (metaData.hasXmp()) { #if defined(TIFFTAG_XMLPACKET) tiffSetExifDataTag(tif, TIFFTAG_XMLPACKET, metaData, "Exif.Image.XMLPacket"); #endif } // Standard Exif ASCII tags (available with libtiff 3.6.1) tiffSetExifAsciiTag(tif, TIFFTAG_DOCUMENTNAME, metaData, "Exif.Image.DocumentName"); tiffSetExifAsciiTag(tif, TIFFTAG_IMAGEDESCRIPTION, metaData, "Exif.Image.ImageDescription"); tiffSetExifAsciiTag(tif, TIFFTAG_MAKE, metaData, "Exif.Image.Make"); tiffSetExifAsciiTag(tif, TIFFTAG_MODEL, metaData, "Exif.Image.Model"); tiffSetExifAsciiTag(tif, TIFFTAG_DATETIME, metaData, "Exif.Image.DateTime"); tiffSetExifAsciiTag(tif, TIFFTAG_ARTIST, metaData, "Exif.Image.Artist"); tiffSetExifAsciiTag(tif, TIFFTAG_COPYRIGHT, metaData, "Exif.Image.Copyright"); QString soft = metaData.getExifTagString("Exif.Image.Software"); QString libtiffver = QLatin1String(TIFFLIB_VERSION_STR); libtiffver.replace(QLatin1Char('\n'), QLatin1Char(' ')); soft.append(QString::fromLatin1(" ( %1 )").arg(libtiffver)); TIFFSetField(tif, TIFFTAG_SOFTWARE, (const char*)soft.toLatin1().constData()); // NOTE: All others Exif tags will be written by Exiv2 (<= 0.18) // ------------------------------------------------------------------- // Write ICC profile. QByteArray profile_rawdata = m_image->getIccProfile().data(); if (!profile_rawdata.isEmpty()) { #if defined(TIFFTAG_ICCPROFILE) purgeExifWorkingColorSpace(); TIFFSetField(tif, TIFFTAG_ICCPROFILE, (uint32)profile_rawdata.size(), (uchar*)profile_rawdata.data()); #endif } // ------------------------------------------------------------------- // Write full image data in tiff directory IFD0 if (observer) { observer->progressInfo(m_image, 0.1F); } uchar* pixel = 0; uint16* pixel16 = 0; double alpha_factor = 0; uint32 x = 0, y = 0; uint8 r8 = 0, g8 = 0, b8 = 0, a8 = 0; uint16 r16 = 0, g16 = 0, b16 = 0, a16 = 0; int i = 0; uint8* buf = (uint8*)_TIFFmalloc(TIFFScanlineSize(tif)); uint16* buf16; if (!buf) { qCWarning(DIGIKAM_DIMG_LOG_TIFF) << "Cannot allocate memory buffer for main image."; TIFFClose(tif); return false; } uint checkpoint = 0; for (y = 0 ; y < h ; ++y) { if (observer && y == checkpoint) { checkpoint += granularity(observer, h, 0.8F); if (!observer->continueQuery(m_image)) { _TIFFfree(buf); TIFFClose(tif); return false; } observer->progressInfo(m_image, 0.1 + (0.8 * (((float)y) / ((float)h)))); } i = 0; for (x = 0 ; x < w ; ++x) { pixel = &data[((y * w) + x) * imageBytesDepth()]; if (imageSixteenBit()) // 16 bits image. { pixel16 = reinterpret_cast(pixel); b16 = pixel16[0]; g16 = pixel16[1]; r16 = pixel16[2]; if (imageHasAlpha()) { // TIFF makes you pre-multiply the RGB components by alpha a16 = pixel16[3]; alpha_factor = ((double)a16 / 65535.0); r16 = (uint16)(r16 * alpha_factor); g16 = (uint16)(g16 * alpha_factor); b16 = (uint16)(b16 * alpha_factor); } // This might be endian dependent buf16 = reinterpret_cast(buf+i); *buf16++ = r16; *buf16++ = g16; *buf16++ = b16; i += 6; if (imageHasAlpha()) { *buf16++ = a16; i += 2; } } else // 8 bits image. { b8 = (uint8)pixel[0]; g8 = (uint8)pixel[1]; r8 = (uint8)pixel[2]; if (imageHasAlpha()) { // TIFF makes you pre-multiply the RGB components by alpha a8 = (uint8)(pixel[3]); alpha_factor = ((double)a8 / 255.0); r8 = (uint8)(r8 * alpha_factor); g8 = (uint8)(g8 * alpha_factor); b8 = (uint8)(b8 * alpha_factor); } // This might be endian dependent buf[i++] = r8; buf[i++] = g8; buf[i++] = b8; if (imageHasAlpha()) { buf[i++] = a8; } } } if (!TIFFWriteScanline(tif, buf, y, 0)) { qCWarning(DIGIKAM_DIMG_LOG_TIFF) << "Cannot write main image to target file."; _TIFFfree(buf); TIFFClose(tif); return false; } } _TIFFfree(buf); TIFFWriteDirectory(tif); // ------------------------------------------------------------------- // Write thumbnail in tiff directory IFD1 QImage thumb = m_image->smoothScale(160, 120, Qt::KeepAspectRatio).copyQImage(); TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, (uint32)thumb.width()); TIFFSetField(tif, TIFFTAG_IMAGELENGTH, (uint32)thumb.height()); TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB); TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); TIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT); TIFFSetField(tif, TIFFTAG_RESOLUTIONUNIT, RESUNIT_NONE); TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_NONE); TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 3); TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 8); TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, TIFFDefaultStripSize(tif, 0)); uchar* pixelThumb; uchar* dataThumb = thumb.bits(); uint8* bufThumb = (uint8*) _TIFFmalloc(TIFFScanlineSize(tif)); if (!bufThumb) { qCWarning(DIGIKAM_DIMG_LOG_TIFF) << "Cannot allocate memory buffer for thumbnail."; TIFFClose(tif); return false; } for (y = 0 ; y < uint32(thumb.height()) ; ++y) { i = 0; for (x = 0 ; x < uint32(thumb.width()) ; ++x) { pixelThumb = &dataThumb[((y * thumb.width()) + x) * 4]; // This might be endian dependent bufThumb[i++] = (uint8)pixelThumb[2]; bufThumb[i++] = (uint8)pixelThumb[1]; bufThumb[i++] = (uint8)pixelThumb[0]; } if (!TIFFWriteScanline(tif, bufThumb, y, 0)) { qCWarning(DIGIKAM_DIMG_LOG_TIFF) << "Cannot write thumbnail to target file."; _TIFFfree(bufThumb); TIFFClose(tif); return false; } } _TIFFfree(bufThumb); TIFFClose(tif); // ------------------------------------------------------------------- if (observer) { observer->progressInfo(m_image, 1.0); } - imageSetAttribute(QLatin1String("savedformat"), QLatin1String("TIFF")); + imageSetAttribute(QLatin1String("savedFormat"), QLatin1String("TIFF")); // Save metadata DMetadata metaDataToFile(filePath); metaDataToFile.setData(m_image->getMetadata()); // see bug #211758 for these special steps needed when writing a TIFF metaDataToFile.removeExifThumbnail(); metaDataToFile.removeExifTag("Exif.Image.ProcessingSoftware"); metaDataToFile.applyChanges(true); return true; } bool TIFFLoader::hasAlpha() const { return m_hasAlpha; } bool TIFFLoader::sixteenBit() const { return m_sixteenBit; } void TIFFLoader::tiffSetExifAsciiTag(TIFF* const tif, ttag_t tiffTag, const DMetadata& metaData, const char* const exifTagName) { QByteArray tag = metaData.getExifTagData(exifTagName); if (!tag.isEmpty()) { QByteArray str(tag.data(), tag.size()); TIFFSetField(tif, tiffTag, (const char*)str.constData()); } } void TIFFLoader::tiffSetExifDataTag(TIFF* const tif, ttag_t tiffTag, const DMetadata& metaData, const char* const exifTagName) { QByteArray tag = metaData.getExifTagData(exifTagName); if (!tag.isEmpty()) { TIFFSetField(tif, tiffTag, (uint32)tag.size(), (char*)tag.data()); } } bool TIFFLoader::isReadOnly() const { return false; } } // namespace Digikam