diff --git a/core/libs/dimg/dimg.cpp b/core/libs/dimg/dimg.cpp index bd2705599e..1671f9e986 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 * http://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()); 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()); 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()); 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()); 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()); 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()); 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()); 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()); 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()); return loader.save(filePath, observer); } else { JPEGLoader loader(this); 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()); return loader.save(filePath, observer); } else if (frm == QLatin1String("TIFF") || frm == QLatin1String("TIF")) { TIFFLoader loader(this); 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()); 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()); return loader.save(filePath, observer); } #endif // HAVE_JASPER else if (frm == QLatin1String("PGF")) { PGFLoader loader(this); 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()); 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(); } 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(); } 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")); if (!savedformat.isNull()) { map.insert(QLatin1String("format"), savedformat); } QVariant readonly = attribute(QLatin1String("savedformat-isreadonly")); if (!readonly.isNull()) { 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: rotate(DImg::ROT270); flip(DImg::HORIZONTAL); rotatedOrFlipped = true; break; case DMetadata::ORIENTATION_ROT_90: rotate(DImg::ROT270); rotatedOrFlipped = true; break; case DMetadata::ORIENTATION_ROT_90_VFLIP: rotate(DImg::ROT270); flip(DImg::VERTICAL); 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::uniqueHashStatic(filePath, *this, false); + QByteArray hash = DImgLoader::uniqueHash(filePath, *this, false); // attribute is written by DImgLoader return hash; } QByteArray DImg::getUniqueHash(const QString& filePath) { - return DImgLoader::uniqueHashStatic(filePath, DImg(), true); + 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::uniqueHashV2Static(filePath, this); + return DImgLoader::uniqueHashV2(filePath, this); } QByteArray DImg::getUniqueHashV2(const QString& filePath) { - return DImgLoader::uniqueHashV2Static(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::createHistoryImageIdStatic(filePath, *this, DMetadata(getMetadata())); + 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/loaders/dimgloader.cpp b/core/libs/dimg/loaders/dimgloader.cpp index 1e98bd20d2..b5639cb95c 100644 --- a/core/libs/dimg/loaders/dimgloader.cpp +++ b/core/libs/dimg/loaders/dimgloader.cpp @@ -1,565 +1,441 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2005-06-14 * Description : DImg image loader interface * * 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. * * ============================================================ */ #include "dimgloader.h" // C++ includes #include #include #include // Qt includes #include #include #include // Local includes #include "digikam_debug.h" #include "dimg_p.h" #include "dmetadata.h" #include "dimgloaderobserver.h" #include "kmemoryinfo.h" namespace Digikam { DImgLoader::DImgLoader(DImg* const image) : m_image(image) { m_loadFlags = LoadAll; } DImgLoader::~DImgLoader() { } void DImgLoader::setLoadFlags(LoadFlags flags) { m_loadFlags = flags; } bool DImgLoader::hasLoadedData() const { return (m_loadFlags & LoadImageData) && m_image->m_priv->data; } int DImgLoader::granularity(DImgLoaderObserver* const observer, int total, float progressSlice) { // Splits expect total value into the chunks where checks shall occur // and combines this with a possible correction factor from observer. // Progress slice is the part of 100% concerned with the current granularity // (E.g. in a loop only the values from 10% to 90% are used, then progressSlice is 0.8) // Current default is 1/20, that is progress info every 5% int granularity = 0; if (observer) { granularity = (int)((total / (20 * progressSlice)) / observer->granularity()); } return granularity ? granularity : 1; } unsigned char*& DImgLoader::imageData() { return m_image->m_priv->data; } unsigned int& DImgLoader::imageWidth() { return m_image->m_priv->width; } unsigned int& DImgLoader::imageHeight() { return m_image->m_priv->height; } bool DImgLoader::imageHasAlpha() const { return m_image->hasAlpha(); } bool DImgLoader::imageSixteenBit() const { return m_image->sixteenBit(); } int DImgLoader::imageBitsDepth() const { return m_image->bitsDepth(); } int DImgLoader::imageBytesDepth() const { return m_image->bytesDepth(); } void DImgLoader::imageSetIccProfile(const IccProfile& profile) { m_image->setIccProfile(profile); } QVariant DImgLoader::imageGetAttribute(const QString& key) const { return m_image->attribute(key); } QString DImgLoader::imageGetEmbbededText(const QString& key) const { return m_image->embeddedText(key); } void DImgLoader::imageSetAttribute(const QString& key, const QVariant& value) { m_image->setAttribute(key, value); } QMap& DImgLoader::imageEmbeddedText() const { return m_image->m_priv->embeddedText; } void DImgLoader::imageSetEmbbededText(const QString& key, const QString& text) { m_image->setEmbeddedText(key, text); } void DImgLoader::loadingFailed() { if (m_image->m_priv->data) { delete [] m_image->m_priv->data; } m_image->m_priv->data = 0; m_image->m_priv->width = 0; m_image->m_priv->height = 0; } qint64 DImgLoader::checkAllocation(qint64 fullSize) { if ((quint64)fullSize >= std::numeric_limits::max()) { qCWarning(DIGIKAM_DIMG_LOG) << "Cannot allocate buffer of size" << fullSize; return 0; } // Do extra check if allocating serious amounts of memory. // At the time of writing (2011), I consider 100 MB as "serious". if (fullSize > (qint64)(100 * 1024 * 1024)) { KMemoryInfo memory = KMemoryInfo::currentInfo(); int res = memory.isValid(); if (res == -1) { qCWarning(DIGIKAM_DIMG_LOG) << "Not a recognized platform to get memory information"; return -1; } else if (res == 0) { qCWarning(DIGIKAM_DIMG_LOG) << "Error to get physical memory information form a recognized platform"; return 0; } qint64 available = memory.bytes(KMemoryInfo::AvailableMemory); if (fullSize > available) { qCWarning(DIGIKAM_DIMG_LOG) << "Not enough memory to allocate buffer of size " << fullSize; qCWarning(DIGIKAM_DIMG_LOG) << "Available memory size is " << available; return 0; } } return fullSize; } bool DImgLoader::readMetadata(const QString& filePath, DImg::FORMAT /*ff*/) { if (!((m_loadFlags & LoadMetadata) || (m_loadFlags & LoadUniqueHash) || (m_loadFlags & LoadImageHistory))) { return false; } DMetadata metaDataFromFile; if (!metaDataFromFile.load(filePath)) { m_image->setMetadata(MetaEngineData()); return false; } m_image->setMetadata(metaDataFromFile.data()); if (m_loadFlags & LoadImageHistory) { DImageHistory history = DImageHistory::fromXml(metaDataFromFile.getItemHistory()); HistoryImageId id = createHistoryImageId(filePath, *m_image, metaDataFromFile); id.m_type = HistoryImageId::Current; history << id; m_image->setItemHistory(history); imageSetAttribute(QLatin1String("originalImageHistory"), QVariant::fromValue(history)); } return true; } // copied from imagescanner.cpp static QDateTime creationDateFromFilesystem(const QFileInfo& info) { // creation date is not what it seems on Unix QDateTime ctime = info.created(); QDateTime mtime = info.lastModified(); if (ctime.isNull()) { return mtime; } if (mtime.isNull()) { return ctime; } return qMin(ctime, mtime); } -HistoryImageId DImgLoader::createHistoryImageIdStatic(const QString& filePath, const DImg& image, const DMetadata& metadata) -{ - QFileInfo file(filePath); - - if (!file.exists()) - { - return HistoryImageId(); - } - - HistoryImageId id(metadata.getItemUniqueId()); - - QDateTime dt = metadata.getItemDateTime(); - - if (dt.isNull()) - { - dt = creationDateFromFilesystem(file); - } - - id.setCreationDate(dt); - id.setFileName(file.fileName()); - id.setPath(file.path()); - id.setUniqueHash(QString::fromUtf8(uniqueHashV2Static(filePath, &image)), file.size()); - - return id; -} - HistoryImageId DImgLoader::createHistoryImageId(const QString& filePath, const DImg& image, const DMetadata& metadata) { QFileInfo file(filePath); if (!file.exists()) { return HistoryImageId(); } HistoryImageId id(metadata.getItemUniqueId()); QDateTime dt = metadata.getItemDateTime(); if (dt.isNull()) { dt = creationDateFromFilesystem(file); } id.setCreationDate(dt); id.setFileName(file.fileName()); id.setPath(file.path()); id.setUniqueHash(QString::fromUtf8(uniqueHashV2(filePath, &image)), file.size()); return id; } bool DImgLoader::saveMetadata(const QString& filePath) { DMetadata metaDataToFile(filePath); metaDataToFile.setData(m_image->getMetadata()); return metaDataToFile.applyChanges(true); } bool DImgLoader::checkExifWorkingColorSpace() const { DMetadata metaData(m_image->getMetadata()); IccProfile profile = metaData.getIccProfile(); if (!profile.isNull()) { m_image->setIccProfile(profile); return true; } return false; } void DImgLoader::storeColorProfileInMetadata() { IccProfile profile = m_image->getIccProfile(); if (profile.isNull()) { return; } DMetadata metaData(m_image->getMetadata()); metaData.setIccProfile(profile); m_image->setMetadata(metaData.data()); } void DImgLoader::purgeExifWorkingColorSpace() { DMetadata meta(m_image->getMetadata()); meta.removeExifColorSpace(); m_image->setMetadata(meta.data()); } -QByteArray DImgLoader::uniqueHashV2Static(const QString& filePath, const DImg* const img) -{ - QFile file(filePath); - - if (!file.open(QIODevice::Unbuffered | QIODevice::ReadOnly)) - { - return QByteArray(); - } - - QCryptographicHash md5(QCryptographicHash::Md5); - - // Specified size: 100 kB; but limit to file size - const qint64 specifiedSize = 100 * 1024; // 100 kB - qint64 size = qMin(file.size(), specifiedSize); - - if (size) - { - QScopedArrayPointer databuf(new char[size]); - int read; - - // Read first 100 kB - if ((read = file.read(databuf.data(), size)) > 0) - { - md5.addData(databuf.data(), read); - } - - // Read last 100 kB - file.seek(file.size() - size); - - if ((read = file.read(databuf.data(), size)) > 0) - { - md5.addData(databuf.data(), read); - } - } - - file.close(); - - QByteArray hash = md5.result().toHex(); - - if (img && !hash.isNull()) - { - const_cast(img)->setAttribute(QString::fromUtf8("uniqueHashV2"), hash); - } - - return hash; -} - QByteArray DImgLoader::uniqueHashV2(const QString& filePath, const DImg* const img) { QFile file(filePath); if (!file.open(QIODevice::Unbuffered | QIODevice::ReadOnly)) { return QByteArray(); } QCryptographicHash md5(QCryptographicHash::Md5); // Specified size: 100 kB; but limit to file size const qint64 specifiedSize = 100 * 1024; // 100 kB qint64 size = qMin(file.size(), specifiedSize); if (size) { QScopedArrayPointer databuf(new char[size]); int read; // Read first 100 kB if ((read = file.read(databuf.data(), size)) > 0) { md5.addData(databuf.data(), read); } // Read last 100 kB file.seek(file.size() - size); if ((read = file.read(databuf.data(), size)) > 0) { md5.addData(databuf.data(), read); } } file.close(); QByteArray hash = md5.result().toHex(); if (img && !hash.isNull()) { const_cast(img)->setAttribute(QString::fromUtf8("uniqueHashV2"), hash); } return hash; } -QByteArray DImgLoader::uniqueHashStatic(const QString& filePath, const DImg& img, bool loadMetadata) -{ - QByteArray bv; - - if (loadMetadata) - { - DMetadata metaDataFromFile(filePath); - bv = metaDataFromFile.getExifEncoded(); - } - else - { - DMetadata metaDataFromImage(img.getMetadata()); - bv = metaDataFromImage.getExifEncoded(); - } - - // Create the unique ID - - QCryptographicHash md5(QCryptographicHash::Md5); - - // First, read the Exif data into the hash - md5.addData(bv); - - // Second, read in the first 8KB of the file - QFile qfile(filePath); - - char databuf[8192]; - QByteArray hash; - - if (qfile.open(QIODevice::Unbuffered | QIODevice::ReadOnly)) - { - int readlen = 0; - - if ((readlen = qfile.read(databuf, 8192)) > 0) - { - QByteArray size = 0; - md5.addData(databuf, readlen); - md5.addData(size.setNum(qfile.size())); - hash = md5.result().toHex(); - } - - qfile.close(); - } - - if (!hash.isNull()) - { - const_cast(img).setAttribute(QLatin1String("uniqueHash"), hash); - } - - return hash; -} - QByteArray DImgLoader::uniqueHash(const QString& filePath, const DImg& img, bool loadMetadata) { QByteArray bv; if (loadMetadata) { DMetadata metaDataFromFile(filePath); bv = metaDataFromFile.getExifEncoded(); } else { DMetadata metaDataFromImage(img.getMetadata()); bv = metaDataFromImage.getExifEncoded(); } // Create the unique ID QCryptographicHash md5(QCryptographicHash::Md5); // First, read the Exif data into the hash md5.addData(bv); // Second, read in the first 8KB of the file QFile qfile(filePath); char databuf[8192]; QByteArray hash; if (qfile.open(QIODevice::Unbuffered | QIODevice::ReadOnly)) { int readlen = 0; if ((readlen = qfile.read(databuf, 8192)) > 0) { QByteArray size = 0; md5.addData(databuf, readlen); md5.addData(size.setNum(qfile.size())); hash = md5.result().toHex(); } qfile.close(); } if (!hash.isNull()) { const_cast(img).setAttribute(QLatin1String("uniqueHash"), hash); } return hash; } unsigned char* DImgLoader::new_failureTolerant(size_t unsecureSize) { return new_failureTolerant(unsecureSize); } unsigned char* DImgLoader::new_failureTolerant(quint64 w, quint64 h, uint typesPerPixel) { return new_failureTolerant(w, h, typesPerPixel); } unsigned short* DImgLoader::new_short_failureTolerant(size_t unsecureSize) { return new_failureTolerant(unsecureSize); } unsigned short* DImgLoader::new_short_failureTolerant(quint64 w, quint64 h, uint typesPerPixel) { return new_failureTolerant(w, h, typesPerPixel); } } // namespace Digikam diff --git a/core/libs/dimg/loaders/dimgloader.h b/core/libs/dimg/loaders/dimgloader.h index a87ce64144..f064a966a8 100644 --- a/core/libs/dimg/loaders/dimgloader.h +++ b/core/libs/dimg/loaders/dimgloader.h @@ -1,202 +1,196 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2005-06-14 * Description : DImg image loader interface * * 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_LOADER_H #define DIGIKAM_DIMG_LOADER_H // C++ includes #include // Qt includes #include #include #include #include // Local includes #include "digikam_debug.h" #include "digikam_export.h" #include "dimg.h" namespace Digikam { class DImgLoaderObserver; class DMetadata; class DImgLoader { public: enum LoadFlag { LoadItemInfo = 1, LoadMetadata = 2, LoadICCData = 4, LoadImageData = 8, LoadUniqueHash = 16, LoadImageHistory = 32, LoadAll = LoadItemInfo | LoadMetadata | LoadUniqueHash | LoadICCData | LoadImageData | LoadImageHistory }; Q_DECLARE_FLAGS(LoadFlags, LoadFlag) public: void setLoadFlags(LoadFlags flags); virtual ~DImgLoader(); virtual bool load(const QString& filePath, DImgLoaderObserver* const observer) = 0; virtual bool save(const QString& filePath, DImgLoaderObserver* const observer) = 0; virtual bool hasLoadedData() const; virtual bool hasAlpha() const = 0; virtual bool sixteenBit() const = 0; virtual bool isReadOnly() const = 0; - static QByteArray uniqueHashV2Static(const QString& filePath, const DImg* const img = 0); - static QByteArray uniqueHashStatic(const QString& filePath, const DImg& img, bool loadMetadata); - static HistoryImageId createHistoryImageIdStatic(const QString& filePath, const DImg& img, const DMetadata& metadata); + static QByteArray uniqueHashV2(const QString& filePath, const DImg* const img = 0); + static QByteArray uniqueHash(const QString& filePath, const DImg& img, bool loadMetadata); + static HistoryImageId createHistoryImageId(const QString& filePath, const DImg& img, const DMetadata& metadata); static unsigned char* new_failureTolerant(size_t unsecureSize); static unsigned char* new_failureTolerant(quint64 w, quint64 h, uint typesPerPixel); static unsigned short* new_short_failureTolerant(size_t unsecureSize); static unsigned short* new_short_failureTolerant(quint64 w, quint64 h, uint typesPerPixel); /** Value returned : -1 : unsupported platform * 0 : parse failure from supported platform * 1 : parse done with success from supported platform */ static qint64 checkAllocation(qint64 fullSize); template static Type* new_failureTolerant(size_t unsecureSize); template static Type* new_failureTolerant(quint64 w, quint64 h, uint typesPerPixel); -private: - - QByteArray uniqueHashV2(const QString& filePath, const DImg* const img = 0); - QByteArray uniqueHash(const QString& filePath, const DImg& img, bool loadMetadata); - HistoryImageId createHistoryImageId(const QString& filePath, const DImg& img, const DMetadata& metadata); - protected: explicit DImgLoader(DImg* const image); unsigned char*& imageData(); unsigned int& imageWidth(); unsigned int& imageHeight(); bool imageHasAlpha() const; bool imageSixteenBit() const; int imageBitsDepth() const; int imageBytesDepth() const; void imageSetIccProfile(const IccProfile& profile); QVariant imageGetAttribute(const QString& key) const; void imageSetAttribute(const QString& key, const QVariant& value); QMap& imageEmbeddedText() const; QString imageGetEmbbededText(const QString& key) const; void imageSetEmbbededText(const QString& key, const QString& text); void loadingFailed(); bool checkExifWorkingColorSpace() const; void purgeExifWorkingColorSpace(); void storeColorProfileInMetadata(); virtual bool readMetadata(const QString& filePath, DImg::FORMAT ff); virtual bool saveMetadata(const QString& filePath); virtual int granularity(DImgLoaderObserver* const observer, int total, float progressSlice = 1.0); protected: DImg* m_image; LoadFlags m_loadFlags; private: DImgLoader(); }; // --------------------------------------------------------------------------------------------------- /// Allows safe multiplication of requested pixel number and bytes per pixel, avoiding particularly /// 32bit overflow and exceeding the size_t type template Q_INLINE_TEMPLATE Type* DImgLoader::new_failureTolerant(quint64 w, quint64 h, uint typesPerPixel) { quint64 requested = w * h * quint64(typesPerPixel); quint64 maximum = std::numeric_limits::max(); if (requested > maximum) { qCCritical(DIGIKAM_DIMG_LOG) << "Requested memory of" << requested*quint64(sizeof(Type)) << "is larger than size_t supported by platform."; return 0; } return new_failureTolerant(requested); } template Q_INLINE_TEMPLATE Type* DImgLoader::new_failureTolerant(size_t size) { qint64 res = checkAllocation(size); switch(res) { case 0: // parse failure from supported platform return 0; break; case -1: // unsupported platform // We will try to continue to allocate break; default: // parse done with success from supported platform break; } Type* reserved = 0; try { reserved = new Type[size]; } catch (std::bad_alloc& ex) { qCCritical(DIGIKAM_DIMG_LOG) << "Failed to allocate chunk of memory of size" << size << ex.what(); reserved = 0; } return reserved; } Q_DECLARE_OPERATORS_FOR_FLAGS(DImgLoader::LoadFlags) } // namespace Digikam #endif // DIGIKAM_DIMG_LOADER_H diff --git a/core/libs/threadimageio/fileio/loadsavethread.cpp b/core/libs/threadimageio/fileio/loadsavethread.cpp index 2d95fda249..78bce7ab45 100644 --- a/core/libs/threadimageio/fileio/loadsavethread.cpp +++ b/core/libs/threadimageio/fileio/loadsavethread.cpp @@ -1,372 +1,348 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2005-12-17 * Description : image file IO threaded interface. * * Copyright (C) 2005-2011 by Marcel Wiesweg * 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. * * ============================================================ */ #include "loadsavethread.h" // Local includes #include "metaengine_rotation.h" #include "dmetadata.h" #include "managedloadsavethread.h" #include "sharedloadsavethread.h" #include "loadsavetask.h" namespace Digikam { class Q_DECL_HIDDEN LoadSaveThread::Private { public: explicit Private() { running = true; blockNotification = false; lastTask = 0; } bool running; bool blockNotification; QTime notificationTime; LoadSaveTask* lastTask; static LoadSaveFileInfoProvider* infoProvider; }; LoadSaveFileInfoProvider* LoadSaveThread::Private::infoProvider = 0; //--------------------------------------------------------------------------------------------------- LoadSaveThread::LoadSaveThread(QObject* const parent) : DynamicThread(parent), d(new Private) { m_currentTask = 0; m_notificationPolicy = NotificationPolicyTimeLimited; } LoadSaveThread::~LoadSaveThread() { shutDown(); delete d; } void LoadSaveThread::setInfoProvider(LoadSaveFileInfoProvider* const infoProvider) { Private::infoProvider = infoProvider; } LoadSaveFileInfoProvider* LoadSaveThread::infoProvider() { return Private::infoProvider; } void LoadSaveThread::load(const LoadingDescription& description) { QMutexLocker lock(threadMutex()); m_todo << new LoadingTask(this, description); start(lock); } void LoadSaveThread::save(const DImg& image, const QString& filePath, const QString& format) { QMutexLocker lock(threadMutex()); m_todo << new SavingTask(this, image, filePath, format); start(lock); } void LoadSaveThread::run() { while (runningFlag()) { { QMutexLocker lock(threadMutex()); delete d->lastTask; d->lastTask = 0; delete m_currentTask; m_currentTask = 0; if (!m_todo.isEmpty()) { m_currentTask = m_todo.takeFirst(); if (m_notificationPolicy == NotificationPolicyTimeLimited) { // set timing values so that first event is sent only // after an initial time span. d->notificationTime = QTime::currentTime(); d->blockNotification = true; } } else { stop(lock); } } if (m_currentTask) { m_currentTask->execute(); } } } void LoadSaveThread::taskHasFinished() { // This function is called by the tasks _before_ they send their _final_ message. // This is to guarantee the user of the API that at least the final message // is sent after load() has been called. // We set m_currentTask to 0 here. If a new task is appended, base classes usually check // that m_currentTask is not currently loading the same task. // Now it might happen that m_currentTask has already emitted its final signal, // but the new task is rejected afterwards when m_currentTask is still the task // that has actually already finished (execute() in the loop above is of course not under mutex). // So we set m_currentTask to 0 immediately before the final message is emitted, // so that anyone who finds this task running as m_current task will get a message. QMutexLocker lock(threadMutex()); d->lastTask = m_currentTask; m_currentTask = 0; } void LoadSaveThread::imageStartedLoading(const LoadingDescription& loadingDescription) { notificationReceived(); emit signalImageStartedLoading(loadingDescription); } void LoadSaveThread::loadingProgress(const LoadingDescription& loadingDescription, float progress) { notificationReceived(); emit signalLoadingProgress(loadingDescription, progress); } void LoadSaveThread::imageLoaded(const LoadingDescription& loadingDescription, const DImg& img) { notificationReceived(); emit signalImageLoaded(loadingDescription, img); } void LoadSaveThread::moreCompleteLoadingAvailable(const LoadingDescription& oldLoadingDescription, const LoadingDescription& newLoadingDescription) { notificationReceived(); emit signalMoreCompleteLoadingAvailable(oldLoadingDescription, newLoadingDescription); } void LoadSaveThread::imageStartedSaving(const QString& filePath) { notificationReceived(); emit signalImageStartedSaving(filePath); } void LoadSaveThread::savingProgress(const QString& filePath, float progress) { notificationReceived(); emit signalSavingProgress(filePath, progress); } void LoadSaveThread::imageSaved(const QString& filePath, bool success) { notificationReceived(); emit signalImageSaved(filePath, success); } void LoadSaveThread::thumbnailLoaded(const LoadingDescription& loadingDescription, const QImage& img) { notificationReceived(); emit signalThumbnailLoaded(loadingDescription, img); } void LoadSaveThread::notificationReceived() { switch (m_notificationPolicy) { case NotificationPolicyDirect: d->blockNotification = false; break; case NotificationPolicyTimeLimited: break; } } void LoadSaveThread::setNotificationPolicy(NotificationPolicy notificationPolicy) { m_notificationPolicy = notificationPolicy; d->blockNotification = false; } bool LoadSaveThread::querySendNotifyEvent() const { // This function is called from the thread to ask for permission to send a notify event. switch (m_notificationPolicy) { case NotificationPolicyDirect: // Note that m_blockNotification is not protected by a mutex. However, if there is a // race condition, the worst case is that one event is not sent, which is no problem. if (d->blockNotification) { return false; } else { d->blockNotification = true; return true; } break; case NotificationPolicyTimeLimited: // Current default time value: 100 millisecs. if (d->blockNotification) { d->blockNotification = d->notificationTime.msecsTo(QTime::currentTime()) < 100; } if (d->blockNotification) { return false; } else { d->notificationTime = QTime::currentTime(); d->blockNotification = true; return true; } break; } return false; } int LoadSaveThread::exifOrientation(const DImg& image, const QString& filePath) { QVariant attribute = image.attribute(QLatin1String("fromRawEmbeddedPreview")); return exifOrientation(filePath, DMetadata(image.getMetadata()), image.detectedFormat() == DImg::RAW, (attribute.isValid() && attribute.toBool())); } int LoadSaveThread::exifOrientation(const QString& filePath, const DMetadata& metadata, bool isRaw, bool fromRawEmbeddedPreview) { int dbOrientation = MetaEngine::ORIENTATION_UNSPECIFIED; if (infoProvider()) { dbOrientation = infoProvider()->orientationHint(filePath); } int exifOrientation = metadata.getItemOrientation(); // Raw files are already rotated properly by Raw engine. Only perform auto-rotation with JPEG/PNG/TIFF file. // We don't have a feedback from Raw engine about auto-rotated RAW file during decoding. if (isRaw && !fromRawEmbeddedPreview) { // Did the user apply any additional rotation over the metadata flag? if (dbOrientation == MetaEngine::ORIENTATION_UNSPECIFIED || dbOrientation == exifOrientation) { return MetaEngine::ORIENTATION_NORMAL; } // Assume A is the orientation as from metadata, B is an additional operation applied by the user, // C is the current orientation in the database. // A*B = C and B = A_inv * C QMatrix A = MetaEngineRotation::toMatrix((MetaEngine::ImageOrientation)exifOrientation); QMatrix C = MetaEngineRotation::toMatrix((MetaEngine::ImageOrientation)dbOrientation); QMatrix A_inv = A.inverted(); QMatrix B = A_inv * C; MetaEngineRotation m(B.m11(), B.m12(), B.m21(), B.m22()); return m.exifOrientation(); } if (dbOrientation != MetaEngine::ORIENTATION_UNSPECIFIED) { return dbOrientation; } return exifOrientation; } -bool LoadSaveThread::wasExifRotatedStatic(const DImg& image) -{ - // Keep in sync with the variant in thumbnailcreator.cpp - QVariant attribute(image.attribute(QLatin1String("exifRotated"))); - - return attribute.isValid() && attribute.toBool(); -} - bool LoadSaveThread::wasExifRotated(const DImg& image) { // Keep in sync with the variant in thumbnailcreator.cpp QVariant attribute(image.attribute(QLatin1String("exifRotated"))); return attribute.isValid() && attribute.toBool(); } -bool LoadSaveThread::exifRotateStatic(DImg& image, const QString& filePath) -{ - // Keep in sync with the variant in thumbnailcreator.cpp - if (wasExifRotatedStatic(image)) - { - return false; - } - - // Rotate thumbnail based on metadata orientation information - - bool rotatedOrFlipped = image.rotateAndFlip(exifOrientation(image, filePath)); - image.setAttribute(QLatin1String("exifRotated"), true); - - return rotatedOrFlipped; -} - bool LoadSaveThread::exifRotate(DImg& image, const QString& filePath) { // Keep in sync with the variant in thumbnailcreator.cpp if (wasExifRotated(image)) { return false; } // Rotate thumbnail based on metadata orientation information bool rotatedOrFlipped = image.rotateAndFlip(exifOrientation(image, filePath)); image.setAttribute(QLatin1String("exifRotated"), true); return rotatedOrFlipped; } bool LoadSaveThread::reverseExifRotate(DImg& image, const QString& filePath) { bool rotatedOrFlipped = image.reverseRotateAndFlip(exifOrientation(image, filePath)); return rotatedOrFlipped; } } // namespace Digikam diff --git a/core/libs/threadimageio/fileio/loadsavethread.h b/core/libs/threadimageio/fileio/loadsavethread.h index 176c71a6cf..736bac578a 100644 --- a/core/libs/threadimageio/fileio/loadsavethread.h +++ b/core/libs/threadimageio/fileio/loadsavethread.h @@ -1,236 +1,233 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2005-12-17 * Description : image file IO threaded interface. * * Copyright (C) 2005-2011 by Marcel Wiesweg * 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_LOAD_SAVE_THREAD_H #define DIGIKAM_LOAD_SAVE_THREAD_H // Qt includes #include #include #include #include #include #include // Local includes #include "dimg.h" #include "digikam_export.h" #include "dynamicthread.h" #include "loadingdescription.h" namespace Digikam { class DMetadata; class LoadSaveTask; class DIGIKAM_EXPORT LoadSaveNotifier { public: virtual ~LoadSaveNotifier() {}; virtual void imageStartedLoading(const LoadingDescription& loadingDescription) = 0; virtual void loadingProgress(const LoadingDescription& loadingDescription, float progress) = 0; virtual void imageLoaded(const LoadingDescription& loadingDescription, const DImg& img) = 0; virtual void moreCompleteLoadingAvailable(const LoadingDescription& oldLoadingDescription, const LoadingDescription& newLoadingDescription) = 0; virtual void imageStartedSaving(const QString& filePath) = 0; virtual void savingProgress(const QString& filePath, float progress) = 0; virtual void imageSaved(const QString& filePath, bool success) = 0; virtual void thumbnailLoaded(const LoadingDescription& loadingDescription, const QImage& img) = 0; }; // ------------------------------------------------------------------------------------------------------- class DIGIKAM_EXPORT LoadSaveFileInfoProvider { public: virtual ~LoadSaveFileInfoProvider() {} /** * Gives a hint at the orientation of the image. * This can be used to supersede the Exif information in the file. * Will not be used if DMetadata::ORIENTATION_UNSPECIFIED (default value) */ virtual int orientationHint(const QString& path) = 0; }; // ------------------------------------------------------------------------------------------------------- class DIGIKAM_EXPORT LoadSaveThread : public DynamicThread, public LoadSaveNotifier { Q_OBJECT public: enum NotificationPolicy { /** Always send notification, unless the last event is still in the event queue */ NotificationPolicyDirect, /** * Always wait for a certain amount of time after the last event sent. * In particular, the first event will be sent only after waiting for this time span. * (Or no event will be sent, when the loading has finished before) * This is the default. */ NotificationPolicyTimeLimited }; // used by SharedLoadSaveThread only enum AccessMode { // image will only be used for reading AccessModeRead, // image data will possibly be changed AccessModeReadWrite }; public: explicit LoadSaveThread(QObject* const parent = 0); /** * Destructor: * The thread will execute all pending tasks and wait for this upon destruction */ ~LoadSaveThread(); /** Append a task to load the given file to the task list */ void load(const LoadingDescription& description); /** Append a task to save the image to the task list */ void save(const DImg& image, const QString& filePath, const QString& format); void setNotificationPolicy(NotificationPolicy notificationPolicy); static void setInfoProvider(LoadSaveFileInfoProvider* const infoProvider); static LoadSaveFileInfoProvider* infoProvider(); /** * Utility to make sure that an image is rotated according to Exif tag. * Detects if an image has previously already been rotated: You can * call this method more than one time on the same image. * Returns true if the image has actually been rotated or flipped. * Returns false if a rotation was not needed. */ - static bool exifRotateStatic(DImg& image, const QString& filePath); - static bool wasExifRotatedStatic(const DImg& image); - - bool exifRotate(DImg& image, const QString& filePath); - bool wasExifRotated(const DImg& image); + static bool exifRotate(DImg& image, const QString& filePath); + static bool wasExifRotated(const DImg& image); /** * Reverses the previous function */ static bool reverseExifRotate(DImg& image, const QString& filePath); /** * Retrieves the Exif orientation, either from the info provider if available, * or from the metadata */ static int exifOrientation(const DImg& image, const QString& filePath); static int exifOrientation(const QString& filePath, const DMetadata& metadata, bool isRaw, bool fromRawEmbeddedPreview); Q_SIGNALS: /** All signals are delivered to the thread from where the LoadSaveThread object * has been created. This thread must use its event loop to get the signals. * You must connect to these signals with Qt::AutoConnection (default) or Qt::QueuedConnection. */ /** This signal is emitted when the loading process begins. */ void signalImageStartedLoading(const LoadingDescription& loadingDescription); /** * This signal is emitted whenever new progress info is available * and the notification policy allows emitting the signal. * No progress info will be sent for preloaded images (ManagedLoadSaveThread). */ void signalLoadingProgress(const LoadingDescription& loadingDescription, float progress); /** * This signal is emitted when the loading process has finished. * If the process failed, img is null. */ void signalImageLoaded(const LoadingDescription& loadingDescription, const DImg& img); /** * This signal is emitted if * - you are doing shared loading (SharedLoadSaveThread) * - you started a loading operation with a LoadingDescription for * a reduced version of the image * - another thread started a loading operation for a more complete version * You may want to cancel the current operation and start with the given loadingDescription */ void signalMoreCompleteLoadingAvailable(const LoadingDescription& oldLoadingDescription, const LoadingDescription& newLoadingDescription); void signalImageStartedSaving(const QString& filePath); void signalSavingProgress(const QString& filePath, float progress); void signalImageSaved(const QString& filePath, bool success); void signalThumbnailLoaded(const LoadingDescription& loadingDescription, const QImage& img); public: virtual void imageStartedLoading(const LoadingDescription& loadingDescription); virtual void loadingProgress(const LoadingDescription& loadingDescription, float progress); virtual void imageLoaded(const LoadingDescription& loadingDescription, const DImg& img); virtual void moreCompleteLoadingAvailable(const LoadingDescription& oldLoadingDescription, const LoadingDescription& newLoadingDescription); virtual void imageStartedSaving(const QString& filePath); virtual void savingProgress(const QString& filePath, float progress); virtual void imageSaved(const QString& filePath, bool success); virtual void thumbnailLoaded(const LoadingDescription& loadingDescription, const QImage& img); virtual bool querySendNotifyEvent() const; virtual void taskHasFinished(); protected: virtual void run(); void notificationReceived(); protected: QMutex m_mutex; QList m_todo; LoadSaveTask* m_currentTask; NotificationPolicy m_notificationPolicy; private: class Private; Private* const d; }; } // namespace Digikam #endif // DIGIKAM_LOAD_SAVE_THREAD_H diff --git a/core/libs/threadimageio/preview/previewtask.cpp b/core/libs/threadimageio/preview/previewtask.cpp index 376a7ac967..70378324dc 100644 --- a/core/libs/threadimageio/preview/previewtask.cpp +++ b/core/libs/threadimageio/preview/previewtask.cpp @@ -1,552 +1,552 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2006-12-26 * Description : Multithreaded loader for previews * * Copyright (C) 2006-2011 by Marcel Wiesweg * Copyright (C) 2006-2019 by Gilles Caulier * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * ============================================================ */ #include "previewtask.h" // C ANSI includes #include // Qt includes #include #include #include // Local includes #include "drawdecoder.h" #include "digikam_debug.h" #include "dmetadata.h" #include "jpegutils.h" #include "metaenginesettings.h" #include "previewloadthread.h" namespace Digikam { void PreviewLoadingTask::execute() { if (m_loadingTaskStatus == LoadingTaskStatusStopping) { return; } // Check if preview is in cache first. LoadingCache* const cache = LoadingCache::cache(); { LoadingCache::CacheLock lock(cache); // find possible cached images DImg* cachedImg = 0; QStringList lookupKeys = m_loadingDescription.lookupCacheKeys(); // lookupCacheKeys returns "best first". Prepend the cache key to make the list "fastest first": // Scaling a full version takes longer! lookupKeys.prepend(m_loadingDescription.cacheKey()); foreach (const QString& key, lookupKeys) { if ((cachedImg = cache->retrieveImage(key))) { if (m_loadingDescription.needCheckRawDecoding()) { if (cachedImg->rawDecodingSettings() == m_loadingDescription.rawDecodingSettings) { break; } else { cachedImg = 0; } } else { break; } } } if (cachedImg) { // image is found in image cache, loading is successful m_img = *cachedImg; } else { // find possible running loading process m_usedProcess = 0; for (QStringList::const_iterator it = lookupKeys.constBegin() ; it != lookupKeys.constEnd() ; ++it) { if ((m_usedProcess = cache->retrieveLoadingProcess(*it))) { break; } } if (m_usedProcess) { // Other process is right now loading this image. // Add this task to the list of listeners and // attach this thread to the other thread, wait until loading // has finished. m_usedProcess->addListener(this); // break loop when either the loading has completed, or this task is being stopped while (m_loadingTaskStatus != LoadingTaskStatusStopping && m_usedProcess && !m_usedProcess->completed()) { lock.timedWait(); } // remove listener from process if (m_usedProcess) { m_usedProcess->removeListener(this); } // wake up the process which is waiting until all listeners have removed themselves lock.wakeAll(); // set to 0, as checked in setStatus m_usedProcess = 0; // m_img is now set to the result } else { // Neither in cache, nor currently loading in different thread. // Load it here and now, add this LoadingProcess to cache list. cache->addLoadingProcess(this); // Add this to the list of listeners addListener(this); // for use in setStatus m_usedProcess = this; // Notify other processes that we are now loading this image. // They might be interested - see notifyNewLoadingProcess below cache->notifyNewLoadingProcess(this, m_loadingDescription); } } } if (m_img.isNull()) { // Preview is not in cache, we will load image from file. DImg::FORMAT format = DImg::fileFormat(m_loadingDescription.filePath); m_fromRawEmbeddedPreview = false; if (format == DImg::RAW) { MetaEnginePreviews previews(m_loadingDescription.filePath); // Check original image size using Exiv2. QSize originalSize = previews.originalSize(); // If not valid, get original size from LibRaw if (!originalSize.isValid()) { DRawInfo container; if (DRawDecoder::rawFileIdentify(container, m_loadingDescription.filePath)) { originalSize = container.imageSize; } } switch (m_loadingDescription.previewParameters.previewSettings.quality) { case PreviewSettings::FastPreview: case PreviewSettings::FastButLargePreview: { // Size calculations int sizeLimit = -1; int bestSize = qMax(originalSize.width(), originalSize.height()); // for RAWs, the alternative is the half preview, so best size is already originalSize / 2 bestSize /= 2; if (m_loadingDescription.previewParameters.previewSettings.quality == PreviewSettings::FastButLargePreview) { sizeLimit = qMin(m_loadingDescription.previewParameters.size, bestSize); } if (loadExiv2Preview(previews, sizeLimit)) { break; } if (loadLibRawPreview(sizeLimit)) { break; } loadHalfSizeRaw(); break; } case PreviewSettings::HighQualityPreview: { switch (m_loadingDescription.previewParameters.previewSettings.rawLoading) { case PreviewSettings::RawPreviewAutomatic: { // If we find a preview that is larger than half size (which is what we get from half-size original data), we take it int acceptableSize = qMax(lround(originalSize.width() * 0.48), lround(originalSize.height() * 0.48)); if (loadExiv2Preview(previews, acceptableSize)) { break; } if (loadLibRawPreview(acceptableSize)) { break; } loadHalfSizeRaw(); break; } case PreviewSettings::RawPreviewFromEmbeddedPreview: { if (loadExiv2Preview(previews)) { break; } if (loadLibRawPreview()) { break; } loadHalfSizeRaw(); break; } case PreviewSettings::RawPreviewFromRawHalfSize: { loadHalfSizeRaw(); break; } } } } // So far, everything loaded QImage. Convert to DImg. convertQImageToDImg(); } else // Non-RAW images { bool isFast = (m_loadingDescription.previewParameters.previewSettings.quality == PreviewSettings::FastPreview); switch (m_loadingDescription.previewParameters.previewSettings.quality) { case PreviewSettings::FastPreview: case PreviewSettings::FastButLargePreview: { if (isFast && loadImagePreview(m_loadingDescription.previewParameters.size)) { convertQImageToDImg(); break; } if (continueQuery(&m_img)) { // Set a hint to try to load a JPEG or PGF with the fast scale-before-decoding method if (isFast) { m_img.setAttribute(QLatin1String("scaledLoadingSize"), m_loadingDescription.previewParameters.size); } m_img.load(m_loadingDescription.filePath, this, m_loadingDescription.rawDecodingSettings); } break; } case PreviewSettings::HighQualityPreview: { if (continueQuery(&m_img)) { m_img.load(m_loadingDescription.filePath, this, m_loadingDescription.rawDecodingSettings); } break; } } } LoadingCache::CacheLock lock(cache); // Put valid image into cache of loaded images if (!m_img.isNull()) { cache->putImage(m_loadingDescription.cacheKey(), m_img, m_loadingDescription.filePath); } // remove this from the list of loading processes in cache cache->removeLoadingProcess(this); // indicate that loading has finished so that listeners can stop waiting m_completed = true; // dispatch image to all listeners, including this for (int i = 0 ; i < m_listeners.count() ; ++i) { LoadingProcessListener* const l = m_listeners[i]; LoadSaveNotifier* const notifier = l->loadSaveNotifier(); if (l->accessMode() == LoadSaveThread::AccessModeReadWrite) { // If a listener requested ReadWrite access, it gets a deep copy. // DImg is explicitly shared. l->setResult(m_loadingDescription, m_img.copy()); } else { l->setResult(m_loadingDescription, m_img); } if (notifier) { notifier->imageLoaded(m_loadingDescription, m_img); } } // remove myself from list of listeners removeListener(this); // wake all listeners waiting on cache condVar, so that they remove themselves lock.wakeAll(); // wait until all listeners have removed themselves while (m_listeners.count() != 0) { lock.timedWait(); } // set to 0, as checked in setStatus m_usedProcess = 0; } // following the golden rule to avoid deadlocks, do this when CacheLock is not held if (!m_img.isNull() && continueQuery(&m_img)) { // The image from the cache may or may not be rotated and post processed. // exifRotate() and postProcess() will detect if work is needed. // We check before to find out if we need to provide a deep copy const bool needExifRotate = MetaEngineSettings::instance()->settings().exifRotate && - !m_thread->wasExifRotated(m_img); + !LoadSaveThread::wasExifRotated(m_img); const bool needImageScale = needToScale(); const bool needPostProcess = needsPostProcessing(); const bool needzoomOrgSize = !m_loadingDescription.previewParameters.previewSettings.zoomOrgSize; const bool needConvertToEightBit = m_loadingDescription.previewParameters.previewSettings.convertToEightBit; if (accessMode() == LoadSaveThread::AccessModeReadWrite || needExifRotate || needImageScale || needPostProcess || needzoomOrgSize || needConvertToEightBit) { m_img.detach(); } if (needImageScale) { QSize scaledSize = m_img.size(); scaledSize.scale(m_loadingDescription.previewParameters.size, m_loadingDescription.previewParameters.size, Qt::KeepAspectRatio); m_img = m_img.smoothScale(scaledSize.width(), scaledSize.height()); } // Set originalSize attribute to the m_img size, to disable zoom to the original image size if (needzoomOrgSize) { m_img.setAttribute(QLatin1String("originalSize"), m_img.size()); } if (needConvertToEightBit) { m_img.convertToEightBit(); } if (needExifRotate) { - m_thread->exifRotate(m_img, m_loadingDescription.filePath); + LoadSaveThread::exifRotate(m_img, m_loadingDescription.filePath); } if (needPostProcess) { postProcess(); } } else if (continueQuery(&m_img)) { qCWarning(DIGIKAM_GENERAL_LOG) << "Cannot extract preview for" << m_loadingDescription.filePath; } else { m_img = DImg(); } if (m_thread) { m_thread->taskHasFinished(); m_thread->imageLoaded(m_loadingDescription, m_img); } } bool PreviewLoadingTask::needToScale() { switch (m_loadingDescription.previewParameters.previewSettings.quality) { case PreviewSettings::FastPreview: if (m_loadingDescription.previewParameters.size > 0) { int maxSize = qMax(m_img.width(), m_img.height()); int acceptableUpperSize = lround(1.25 * (double)m_loadingDescription.previewParameters.size); return (maxSize >= acceptableUpperSize); } break; case PreviewSettings::FastButLargePreview: case PreviewSettings::HighQualityPreview: break; } return false; } // -- Exif/IPTC preview extraction using Exiv2 -------------------------------------------------------- bool PreviewLoadingTask::loadExiv2Preview(MetaEnginePreviews& previews, int sizeLimit) { if (previews.isEmpty() || !continueQuery(&m_img)) { return false; } if (sizeLimit == -1 || qMax(previews.width(), previews.height()) >= sizeLimit) { m_qimage = previews.image(); if (!m_qimage.isNull()) { m_fromRawEmbeddedPreview = true; return true; } } return false; } bool PreviewLoadingTask::loadLibRawPreview(int sizeLimit) { if (!continueQuery(&m_img)) { return false; } QImage rawPreview; DRawDecoder::loadEmbeddedPreview(rawPreview, m_loadingDescription.filePath); if (!rawPreview.isNull() && (sizeLimit == -1 || qMax(rawPreview.width(), rawPreview.height()) >= sizeLimit)) { m_qimage = rawPreview; m_fromRawEmbeddedPreview = true; return true; } return false; } bool PreviewLoadingTask::loadHalfSizeRaw() { if (!continueQuery(&m_img)) { return false; } DRawDecoder::loadHalfPreview(m_qimage, m_loadingDescription.filePath); return (!m_qimage.isNull()); } void PreviewLoadingTask::convertQImageToDImg() { if (!continueQuery(&m_img)) { return; } // convert from QImage m_img = DImg(m_qimage); DImg::FORMAT format = DImg::fileFormat(m_loadingDescription.filePath); m_img.setAttribute(QLatin1String("detectedFileFormat"), format); m_img.setAttribute(QLatin1String("originalFilePath"), m_loadingDescription.filePath); DMetadata metadata(m_loadingDescription.filePath); m_img.setAttribute(QLatin1String("originalSize"), metadata.getPixelSize()); m_img.setMetadata(metadata.data()); // mark as embedded preview (for Exif rotation) if (m_fromRawEmbeddedPreview) { m_img.setAttribute(QLatin1String("fromRawEmbeddedPreview"), true); // If we loaded the embedded preview, the Exif of the RAW indicates // the color space of the preview (see bug 195950 for NEF files) m_img.setIccProfile(metadata.getIccProfile()); } // free memory m_qimage = QImage(); } bool PreviewLoadingTask::loadImagePreview(int sizeLimit) { DMetadata metadata(m_loadingDescription.filePath); QImage previewImage; if (metadata.getItemPreview(previewImage)) { if (sizeLimit == -1 || qMax(previewImage.width(), previewImage.height()) > sizeLimit) { m_qimage = previewImage; return true; } } return false; } } // namespace Digikam diff --git a/core/utilities/queuemanager/tools/decorate/watermark.cpp b/core/utilities/queuemanager/tools/decorate/watermark.cpp index 2b62a78b4f..91b058ee35 100644 --- a/core/utilities/queuemanager/tools/decorate/watermark.cpp +++ b/core/utilities/queuemanager/tools/decorate/watermark.cpp @@ -1,894 +1,894 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2009-02-28 * Description : batch tool to add visible watermark. * * Copyright (C) 2009-2019 by Gilles Caulier * Copyright (C) 2010 by Mikkel Baekhoej Christensen * Copyright (C) 2017 by Ahmed Fathi * * 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 "watermark.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include // Local includes #include "dlayoutbox.h" #include "dnuminput.h" #include "dfileselector.h" #include "digikam_debug.h" #include "dimg.h" #include "blurfilter.h" #include "dfontproperties.h" #include "loadsavethread.h" #include "metaengine.h" #include "dcolorselector.h" namespace Digikam { class Q_DECL_HIDDEN WaterMark::Private { public: enum WaterMarkPositon { TopLeft = 0, TopRight, BottomLeft, BottomRight, Center }; enum WaterMarkPlacementType { SpecifiedLocation = 0, SystematicRepetition, RandomRepetition }; public: explicit Private() : textSettingsGroupBox(0), imageSettingsGroupBox(0), useAbsoluteImageSizeGroupBox(0), useImageRadioButton(0), ignoreWatermarkAspectCheckBox(0), useAbsoluteSizeCheckBox(0), useBackgroundCheckBox(0), denseRepetitionCheckBox(0), randomizeRotationCheckBox(0), useTextRadioButton(0), imageFileUrlRequester(0), textEdit(0), placementPositionComboBox(0), placementTypeComboBox(0), rotationComboBox(0), sparsityFactorSpinBox(0), extendedFontChooserWidget(0), fontColorButton(0), backgroundColorButton(0), textOpacity(0), backgroundOpacity(0), xMarginInput(0), yMarginInput(0), waterMarkSizePercent(0), changeSettings(true) { } QGroupBox* textSettingsGroupBox; QGroupBox* imageSettingsGroupBox; QGroupBox* useAbsoluteImageSizeGroupBox ; QRadioButton* useImageRadioButton; QCheckBox* ignoreWatermarkAspectCheckBox; QCheckBox* useAbsoluteSizeCheckBox; QCheckBox* useBackgroundCheckBox; QCheckBox* denseRepetitionCheckBox; QCheckBox* randomizeRotationCheckBox; QRadioButton* useTextRadioButton; DFileSelector* imageFileUrlRequester; QLineEdit* textEdit; QComboBox* placementPositionComboBox; QComboBox* placementTypeComboBox; QComboBox* rotationComboBox; QDoubleSpinBox * sparsityFactorSpinBox; DFontProperties* extendedFontChooserWidget; DColorSelector* fontColorButton; DColorSelector* backgroundColorButton; DIntNumInput* textOpacity; DIntNumInput* backgroundOpacity; DIntNumInput* xMarginInput; DIntNumInput* yMarginInput; DIntNumInput* waterMarkSizePercent; bool changeSettings; }; WaterMark::WaterMark(QObject* const parent) : BatchTool(QLatin1String("WaterMark"), DecorateTool, parent), d(new Private) { setToolTitle(i18n("Add Watermark")); setToolDescription(i18n("Overlay an image or text as a visible watermark")); setToolIconName(QLatin1String("insert-text")); } WaterMark::~WaterMark() { delete d; } void WaterMark::registerSettingsWidget() { const int spacing = QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing); DVBox* const vbox = new DVBox; vbox->setContentsMargins(QMargins()); vbox->setSpacing(spacing); DHBox* const hbox = new DHBox(vbox); hbox->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); hbox->setSpacing(10); d->useAbsoluteImageSizeGroupBox = new QGroupBox(vbox); QVBoxLayout* const useAbsoluteImageSizeGroupBoxLayout = new QVBoxLayout; useAbsoluteImageSizeGroupBoxLayout->setContentsMargins(spacing, spacing, spacing, spacing); useAbsoluteImageSizeGroupBoxLayout->addStretch(1); d->useAbsoluteImageSizeGroupBox->setLayout(useAbsoluteImageSizeGroupBoxLayout); DHBox* const useAbsoluteSizeHBox = new DHBox(); useAbsoluteSizeHBox->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); useAbsoluteSizeHBox->setSpacing(10); d->useAbsoluteSizeCheckBox = new QCheckBox(useAbsoluteSizeHBox); d->useAbsoluteSizeCheckBox->setWhatsThis(i18n("Check this if you want the watermark to use the given size of the font or the image " "without any adjustment to the actual image")); QLabel* const useAbsoluteSizeLabel = new QLabel(useAbsoluteSizeHBox); useAbsoluteSizeLabel->setText(i18n("Use Absolute Size")); d->useAbsoluteSizeCheckBox->setChecked(false); useAbsoluteImageSizeGroupBoxLayout->addWidget(useAbsoluteSizeHBox); QLabel* const watermarkTypeLabel = new QLabel(hbox); watermarkTypeLabel->setText(i18n("Watermark type:")); d->useImageRadioButton = new QRadioButton(hbox); d->useImageRadioButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); QLabel* const useImageLabel = new QLabel(hbox); useImageLabel ->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); d->useTextRadioButton = new QRadioButton(hbox); d->useTextRadioButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); QLabel* const useTextLabel = new QLabel(hbox); useTextLabel ->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); useImageLabel->setText(i18n("Image")); useTextLabel->setText(i18n("Text")); useImageLabel->setAlignment(Qt::AlignLeft); useTextLabel->setAlignment(Qt::AlignLeft); d->useImageRadioButton->setChecked(true); d->imageSettingsGroupBox = new QGroupBox(vbox); d->imageSettingsGroupBox->setTitle(i18n("Image settings")); QVBoxLayout* const imageSettingsGroupBoxLayout = new QVBoxLayout; imageSettingsGroupBoxLayout->setContentsMargins(spacing, spacing, spacing, spacing); imageSettingsGroupBoxLayout->addStretch(1); d->imageSettingsGroupBox->setLayout(imageSettingsGroupBoxLayout); QLabel* const label = new QLabel(); d->imageFileUrlRequester = new DFileSelector(); d->imageFileUrlRequester->lineEdit()->setPlaceholderText(i18n("Click to select watermark image.")); label->setText(i18n("Watermark image:")); imageSettingsGroupBoxLayout->addWidget(label); imageSettingsGroupBoxLayout->addWidget(d->imageFileUrlRequester); DHBox* const ignoreWatermarkAspectRatioHBox = new DHBox(); ignoreWatermarkAspectRatioHBox->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); ignoreWatermarkAspectRatioHBox->setSpacing(5); d->ignoreWatermarkAspectCheckBox = new QCheckBox(ignoreWatermarkAspectRatioHBox); d->ignoreWatermarkAspectCheckBox->setWhatsThis(i18n("Check this if you want the watermark to ignore " "its own aspect ratio and use the image's aspect ratio instead")); QLabel* const ignoreWatermarkAspectRatioLabel = new QLabel(ignoreWatermarkAspectRatioHBox); ignoreWatermarkAspectRatioLabel->setText(i18n("Ignore Watermark aspect Ratio")); d->ignoreWatermarkAspectCheckBox->setChecked(false); imageSettingsGroupBoxLayout->addWidget(ignoreWatermarkAspectRatioHBox); d->textSettingsGroupBox = new QGroupBox(vbox); d->textSettingsGroupBox->setTitle(i18n("Text settings")); QVBoxLayout* const textSettingsGroupBoxLayout = new QVBoxLayout; textSettingsGroupBoxLayout->setContentsMargins(spacing, spacing, spacing, spacing); textSettingsGroupBoxLayout->addStretch(1); d->textSettingsGroupBox->setLayout(textSettingsGroupBoxLayout); QLabel* const textEditLabel = new QLabel(vbox); d->textEdit = new QLineEdit(vbox); d->textEdit->setClearButtonEnabled(true); d->textEdit->setPlaceholderText(i18n("Enter your watermark string here.")); textEditLabel->setText(i18n("Watermark text:")); textSettingsGroupBoxLayout->addWidget(textEditLabel); textSettingsGroupBoxLayout->addWidget(d->textEdit); d->extendedFontChooserWidget = new DFontProperties(0, DFontProperties::NoDisplayFlags); d->extendedFontChooserWidget->setSampleBoxVisible(true); d->extendedFontChooserWidget->enableColumn(0x04,false); d->extendedFontChooserWidget->setWhatsThis(i18n("choose the font type and style. size is auto calculated")); textSettingsGroupBoxLayout->addWidget(d->extendedFontChooserWidget); QLabel* const label3 = new QLabel(); d->fontColorButton = new DColorSelector(); d->fontColorButton->setColor(Qt::black); d->fontColorButton->setWhatsThis(i18n("Set the font color to use here")); label3->setText(i18n("Font color:")); textSettingsGroupBoxLayout->addWidget(label3); textSettingsGroupBoxLayout->addWidget(d->fontColorButton); QLabel* const textOpacityLabel = new QLabel(); textOpacityLabel->setText(i18n("Text opacity:")); d->textOpacity = new DIntNumInput(); d->textOpacity->setRange(0, 100, 1); d->textOpacity->setDefaultValue(100); d->textOpacity->setWhatsThis(i18n("Set the opacity of the watermark text. 100 is fully opaque, 0 is fully transparent.")); textSettingsGroupBoxLayout->addWidget(textOpacityLabel); textSettingsGroupBoxLayout->addWidget(d->textOpacity); DHBox* const useBackgroundHBox = new DHBox(); useBackgroundHBox->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); useBackgroundHBox->setSpacing(5); d->useBackgroundCheckBox = new QCheckBox(useBackgroundHBox); d->useBackgroundCheckBox->setWhatsThis(i18n("Check this if you want a background fill behind the text")); QLabel* const useBackgroundLabel = new QLabel(useBackgroundHBox); useBackgroundLabel->setText(i18n("Use background")); textSettingsGroupBoxLayout->addWidget(useBackgroundHBox); QLabel* const backgroundColorLabel = new QLabel(); d->backgroundColorButton = new DColorSelector(); d->backgroundColorButton->setColor(QColor(0xCC, 0xCC, 0xCC)); d->backgroundColorButton->setWhatsThis(i18n("Choose the color of the watermark background")); backgroundColorLabel ->setText(i18n("Background color:")); textSettingsGroupBoxLayout->addWidget(backgroundColorLabel); textSettingsGroupBoxLayout->addWidget(d->backgroundColorButton); QLabel* const backgroundOpacityLabel = new QLabel(); backgroundOpacityLabel->setText(i18n("Background opacity:")); d->backgroundOpacity = new DIntNumInput(); d->backgroundOpacity->setRange(0, 100, 1); d->backgroundOpacity->setDefaultValue(100); d->backgroundOpacity->setWhatsThis(i18n("Set the opacity of the watermark background. 100 is fully opaque, 0 is fully transparent.")); textSettingsGroupBoxLayout->addWidget(backgroundOpacityLabel); textSettingsGroupBoxLayout->addWidget(d->backgroundOpacity); d->imageSettingsGroupBox->setVisible(true); d->textSettingsGroupBox->setVisible(false); QLabel* const placementTypeLabel = new QLabel(vbox); d->placementTypeComboBox = new QComboBox(vbox); d->placementTypeComboBox->insertItem(Private::SpecifiedLocation, i18n("Specific Location")); d->placementTypeComboBox->insertItem(Private::SystematicRepetition, i18n("Systematic Repetition")); d->placementTypeComboBox->insertItem(Private::RandomRepetition, i18n("Random Repetition")); placementTypeLabel->setText(i18n("Placement Type:")); DHBox* const placementHBox = new DHBox(vbox); placementHBox->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); placementHBox->setSpacing(5); QLabel* const spaceLabel = new QLabel(placementHBox); d->denseRepetitionCheckBox = new QCheckBox(placementHBox); d->denseRepetitionCheckBox->setWhatsThis(i18n("When you choose to have the watermark repeated many times in the placement combo box, you can specify here whether the repetition")); d->denseRepetitionCheckBox->setChecked(false); d->denseRepetitionCheckBox->setEnabled(false); spaceLabel->setText(QLatin1String(" ")); QLabel* const placementDensityLabel = new QLabel(placementHBox); placementDensityLabel->setText(i18n("Density of watermark repetition (Disabled in \"Specific Location\" mode)")); DHBox* const randomizeHBox = new DHBox(vbox); randomizeHBox->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); randomizeHBox->setSpacing(5); QLabel* const spaceLabel2 = new QLabel(randomizeHBox); d->randomizeRotationCheckBox = new QCheckBox(randomizeHBox); d->randomizeRotationCheckBox->setWhatsThis(i18n("When you choose to have the watermark repeated randomly, many times in the placement combo box, you can specify here whether the repetition, " "you can check this to make the watermark rotations random also [0, 90, 180, 270]")); d->randomizeRotationCheckBox->setChecked(true); d->denseRepetitionCheckBox->setEnabled(false); spaceLabel2->setText(QLatin1String(" ")); QLabel* const randomizeRotation = new QLabel(randomizeHBox); randomizeRotation->setText(i18n("Randomize watermark orientation (Enabled in \"Random Repetition\" mode only)")); DHBox* const sparsityHBox = new DHBox(vbox); sparsityHBox->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); sparsityHBox->setSpacing(5); QLabel* const sparsityFactorLabel = new QLabel(sparsityHBox); d->sparsityFactorSpinBox = new QDoubleSpinBox(sparsityHBox); d->sparsityFactorSpinBox->setMinimum(0.0); d->sparsityFactorSpinBox->setValue(1); d->sparsityFactorSpinBox->setSingleStep(0.1); d->sparsityFactorSpinBox->setWhatsThis(i18n("Use this to get more control over the sparsity of watermark repetition." " The higher the value the sparser the watermarks get. Use floating point values," " typically between 1.0 and 3.0. It can also be less than 1.0")); sparsityFactorLabel->setText(i18n("Sparsity Factor:")); QLabel* const label4 = new QLabel(vbox); d->placementPositionComboBox = new QComboBox(vbox); d->placementPositionComboBox->insertItem(Private::TopLeft, i18n("Top left")); d->placementPositionComboBox->insertItem(Private::TopRight, i18n("Top right")); d->placementPositionComboBox->insertItem(Private::BottomLeft, i18n("Bottom left")); d->placementPositionComboBox->insertItem(Private::BottomRight, i18n("Bottom right")); d->placementPositionComboBox->insertItem(Private::Center, i18n("Center")); label4->setText(i18n("Placement Position:")); QLabel* const labelRotation = new QLabel(vbox); d->rotationComboBox = new QComboBox(vbox); d->rotationComboBox->insertItem(0, i18n("0 degrees")); d->rotationComboBox->insertItem(1, i18n("90 degrees CW")); d->rotationComboBox->insertItem(2, i18n("180 degrees")); d->rotationComboBox->insertItem(3, i18n("270 degrees CW")); labelRotation->setText(i18n("Rotation:")); QLabel* const label5 = new QLabel(vbox); d->waterMarkSizePercent = new DIntNumInput(vbox); d->waterMarkSizePercent->setRange(0, 100, 1); d->waterMarkSizePercent->setDefaultValue(30); d->waterMarkSizePercent->setWhatsThis(i18n("Size of watermark, as a percentage of the marked image.")); label5->setText(i18n("Size (%):")); QLabel* const label6 = new QLabel(vbox); d->xMarginInput = new DIntNumInput(vbox); d->xMarginInput->setRange(0, 100, 1); d->xMarginInput->setDefaultValue(2); d->xMarginInput->setWhatsThis(i18n("Margin from edge in X direction, as a percentage of the marked image")); label6->setText(i18n("X margin (%):")); QLabel* const label7 = new QLabel(vbox); d->yMarginInput = new DIntNumInput(vbox); d->yMarginInput->setRange(0, 100, 1); d->yMarginInput->setDefaultValue(2); d->yMarginInput->setWhatsThis(i18n("Margin from edge in Y direction, as a percentage of the marked image")); label7->setText(i18n("Y margin (%):")); QLabel* const space = new QLabel(vbox); vbox->setStretchFactor(space, 10); m_settingsWidget = vbox; // ------------------------------------------------------------------------------------------------------ connect(d->useImageRadioButton, SIGNAL(toggled(bool)), this, SLOT(slotSettingsChanged())); connect(d->useTextRadioButton, SIGNAL(toggled(bool)), this, SLOT(slotSettingsChanged())); connect(d->imageFileUrlRequester->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(slotSettingsChanged())); connect(d->extendedFontChooserWidget, SIGNAL(fontSelected(QFont)), this, SLOT(slotSettingsChanged())); connect(d->fontColorButton, SIGNAL(signalColorSelected(QColor)), this, SLOT(slotSettingsChanged())); connect(d->textEdit, SIGNAL(textChanged(QString)), this, SLOT(slotSettingsChanged())); connect(d->textOpacity, SIGNAL(valueChanged(int)), this, SLOT(slotSettingsChanged())); connect(d->useBackgroundCheckBox, SIGNAL(toggled(bool)), this, SLOT(slotSettingsChanged())); connect(d->ignoreWatermarkAspectCheckBox, SIGNAL(toggled(bool)), this, SLOT(slotSettingsChanged())); connect(d->useAbsoluteSizeCheckBox, SIGNAL(toggled(bool)), this, SLOT(slotSettingsChanged())); connect(d->backgroundColorButton, SIGNAL(signalColorSelected(QColor)), this, SLOT(slotSettingsChanged())); connect(d->backgroundOpacity, SIGNAL(valueChanged(int)), this, SLOT(slotSettingsChanged())); connect(d->placementTypeComboBox, SIGNAL(activated(int)), this, SLOT(slotSettingsChanged())); connect(d->placementPositionComboBox, SIGNAL(activated(int)), this, SLOT(slotSettingsChanged())); connect(d->denseRepetitionCheckBox, SIGNAL(toggled(bool)), this, SLOT(slotSettingsChanged())); connect(d->randomizeRotationCheckBox, SIGNAL(toggled(bool)), this, SLOT(slotSettingsChanged())); connect(d->sparsityFactorSpinBox, SIGNAL(valueChanged(double)), this, SLOT(slotSettingsChanged())); connect(d->rotationComboBox, SIGNAL(activated(int)), this, SLOT(slotSettingsChanged())); connect(d->waterMarkSizePercent, SIGNAL(valueChanged(int)), this, SLOT(slotSettingsChanged())); connect(d->yMarginInput, SIGNAL(valueChanged(int)), this, SLOT(slotSettingsChanged())); connect(d->xMarginInput, SIGNAL(valueChanged(int)), this, SLOT(slotSettingsChanged())); BatchTool::registerSettingsWidget(); } BatchToolSettings WaterMark::defaultSettings() { BatchToolSettings settings; settings.insert(QLatin1String("Use image"), true); settings.insert(QLatin1String("Watermark image"), QString()); settings.insert(QLatin1String("Text"), QString()); settings.insert(QLatin1String("Font"), QFont()); settings.insert(QLatin1String("Color"), QColor(Qt::black)); settings.insert(QLatin1String("Text opacity"), 100); settings.insert(QLatin1String("Use background"), true); settings.insert(QLatin1String("Background color"), QColor(0xCC, 0xCC, 0xCC)); settings.insert(QLatin1String("Background opacity"), 0xCC); settings.insert(QLatin1String("PlacementType"), Private::SpecifiedLocation); // specified location for the watermark settings.insert(QLatin1String("Dense Repetition"), false); settings.insert(QLatin1String("Randomize Rotation"), true); settings.insert(QLatin1String("Sparsity Factor"), 1.0); settings.insert(QLatin1String("Placement"), Private::BottomRight); settings.insert(QLatin1String("Rotation"), 0); settings.insert(QLatin1String("Watermark size"), 25); settings.insert(QLatin1String("X margin"), 2); settings.insert(QLatin1String("Y margin"), 2); return settings; } void WaterMark::slotAssignSettings2Widget() { d->changeSettings = false; d->useImageRadioButton->setChecked(settings()[QLatin1String("Use image")].toBool()); d->useTextRadioButton->setChecked(!settings()[QLatin1String("Use image")].toBool()); d->imageFileUrlRequester->setFileDlgPath(settings()[QLatin1String("Watermark image")].toString()); d->textEdit->setText(settings()[QLatin1String("Text")].toString()); d->fontColorButton->setColor(settings()[QLatin1String("Color")].toString()); d->textOpacity->setValue(settings()[QLatin1String("Text opacity")].toInt()); d->useBackgroundCheckBox->setChecked(settings()[QLatin1String("Use background")].toBool()); d->backgroundColorButton->setColor(settings()[QLatin1String("Background color")].toString()); d->backgroundOpacity->setValue(settings()[QLatin1String("Background opacity")].toInt()); d->placementPositionComboBox->setCurrentIndex(settings()[QLatin1String("PlacementType")].toInt()); d->denseRepetitionCheckBox->setChecked(settings()[QLatin1String("Dense Repetition")].toBool()); d->randomizeRotationCheckBox->setChecked(settings()[QLatin1String("Randomize Rotation")].toBool()); d->sparsityFactorSpinBox->setValue(settings()[QLatin1String("Sparsity Factor")].toDouble()); d->placementPositionComboBox->setCurrentIndex(settings()[QLatin1String("Placement")].toInt()); d->rotationComboBox->setCurrentIndex(settings()[QLatin1String("Rotation")].toInt()); d->waterMarkSizePercent->setValue(settings()[QLatin1String("Watermark size")].toInt()); d->xMarginInput->setValue(settings()[QLatin1String("X margin")].toInt()); d->yMarginInput->setValue(settings()[QLatin1String("Y margin")].toInt()); d->changeSettings = true; } void WaterMark::slotSettingsChanged() { if (d->useImageRadioButton->isChecked()) { d->textSettingsGroupBox->setVisible(false); d->imageSettingsGroupBox->setVisible(true); } else if (d->useTextRadioButton->isChecked()) { d->imageSettingsGroupBox->setVisible(false); d->textSettingsGroupBox->setVisible(true); } d->waterMarkSizePercent->setEnabled(!d->useAbsoluteSizeCheckBox->isChecked()); d->extendedFontChooserWidget->enableColumn(0x04,d->useAbsoluteSizeCheckBox->isChecked()); d->placementPositionComboBox->setEnabled(((int)d->placementTypeComboBox->currentIndex() == Private::SpecifiedLocation)); d->denseRepetitionCheckBox->setEnabled(((int)d->placementTypeComboBox->currentIndex() != Private::SpecifiedLocation)); d->sparsityFactorSpinBox->setEnabled(((int)d->placementTypeComboBox->currentIndex() != Private::SpecifiedLocation)); d->randomizeRotationCheckBox->setEnabled(((int)d->placementTypeComboBox->currentIndex() == Private::RandomRepetition)); d->rotationComboBox->setEnabled(!(d->randomizeRotationCheckBox->isEnabled() && d->randomizeRotationCheckBox->isChecked())); if (d->changeSettings) { BatchToolSettings settings; settings.insert(QLatin1String("Use image"), d->useImageRadioButton->isChecked()); settings.insert(QLatin1String("Watermark image"), d->imageFileUrlRequester->fileDlgPath()); settings.insert(QLatin1String("Text"), d->textEdit->text()); settings.insert(QLatin1String("Font"), d->extendedFontChooserWidget->font()); settings.insert(QLatin1String("Color"), d->fontColorButton->color()); settings.insert(QLatin1String("Text opacity"), d->textOpacity->value()); settings.insert(QLatin1String("Use background"), d->useBackgroundCheckBox->isChecked()); settings.insert(QLatin1String("Ignore Watermark Aspect Ratio"), d->ignoreWatermarkAspectCheckBox->isChecked()); settings.insert(QLatin1String("Use Absolute Size"), d->useAbsoluteSizeCheckBox->isChecked()); settings.insert(QLatin1String("Background color"), d->backgroundColorButton->color()); settings.insert(QLatin1String("Background opacity"), d->backgroundOpacity->value()); settings.insert(QLatin1String("Dense Repetition"), d->denseRepetitionCheckBox->isChecked()); settings.insert(QLatin1String("Randomize Rotation"), d->randomizeRotationCheckBox->isChecked()); settings.insert(QLatin1String("Sparsity Factor"), (double)d->sparsityFactorSpinBox->value()); settings.insert(QLatin1String("PlacementType"), (int)d->placementTypeComboBox->currentIndex()); settings.insert(QLatin1String("Placement"), (int)d->placementPositionComboBox->currentIndex()); settings.insert(QLatin1String("Rotation"), (int)d->rotationComboBox->currentIndex()); settings.insert(QLatin1String("Watermark size"), (int)d->waterMarkSizePercent->value()); settings.insert(QLatin1String("X margin"), (int)d->xMarginInput->value()); settings.insert(QLatin1String("Y margin"), (int)d->yMarginInput->value()); BatchTool::slotSettingsChanged(settings); } } bool WaterMark::toolOperations() { if (!loadToDImg()) { return false; } QString fileName = settings()[QLatin1String("Watermark image")].toString(); int placementPosition = settings()[QLatin1String("Placement")].toInt(); int placementType = settings()[QLatin1String("PlacementType")].toInt(); bool denseRepetition = settings()[QLatin1String("Dense Repetition")].toBool(); bool randomizeRotation = settings()[QLatin1String("Randomize Rotation")].toBool(); double userSparsityFactor = settings()[QLatin1String("Sparsity Factor")].toDouble(); int size = settings()[QLatin1String("Watermark size")].toInt(); int xMargin = settings()[QLatin1String("X margin")].toInt(); int yMargin = settings()[QLatin1String("Y margin")].toInt(); bool useImage = settings()[QLatin1String("Use image")].toBool(); QString text = settings()[QLatin1String("Text")].toString(); QFont font = qvariant_cast(settings()[QLatin1String("Font")]);; QColor fontColor = settings()[QLatin1String("Color")].toString(); int textOpacity = settings()[QLatin1String("Text opacity")].toInt(); bool useBackground = settings()[QLatin1String("Use background")].toBool(); QColor backgroundColor = settings()[QLatin1String("Background color")].toString(); int backgroundOpacity = settings()[QLatin1String("Background opacity")].toInt(); Qt::AspectRatioMode watermarkAspectRatioMode = settings()[QLatin1String("Ignore Watermark Aspect Ratio")].toBool() ? Qt::IgnoreAspectRatio : Qt::KeepAspectRatio; bool useAbsoluteSize = settings()[QLatin1String("Use Absolute Size")].toBool(); DImg watermarkImage; DColorComposer* const composer = DColorComposer::getComposer(DColorComposer::PorterDuffNone); int marginW = lround(image().width() * (xMargin / 100.0)); int marginH = lround(image().height() * (yMargin / 100.0)); int rotationIndex = settings()[QLatin1String("Rotation")].toInt(); DImg::ANGLE rotationAngle = (rotationIndex == 1) ? DImg::ANGLE::ROT90 : (rotationIndex == 2) ? DImg::ANGLE::ROT180 : (rotationIndex == 3) ? DImg::ANGLE::ROT270 : DImg::ANGLE::ROTNONE; // rotate and/or flip the image depending on the exif information to allow for the expected watermark placement. //note that this operation is reversed after proper watermark generation to leave everything as it was. - LoadSaveThread::exifRotateStatic(image(), inputUrl().toLocalFile()); + LoadSaveThread::exifRotate(image(), inputUrl().toLocalFile()); float ratio = (float)image().height()/image().width(); if (rotationAngle == DImg::ANGLE::ROT90 || rotationAngle == DImg::ANGLE::ROT270) { size = size * ratio; } else { // For Images whose height are much larger than their width, this helps keep // the watermark size reasonable if (ratio > 1.5) { int tempSize = size * ratio; if (tempSize < 35) tempSize *= 1.5 ; size = (tempSize < 100) ? tempSize : 100; } } if (useImage) { watermarkImage = DImg(fileName); if (watermarkImage.isNull()) { return false; } if (!useAbsoluteSize) { DImg tempImage = watermarkImage.smoothScale(image().width() * size / 100, image().height() * size / 100, watermarkAspectRatioMode); watermarkImage = tempImage; } } else { int alignMode; const int radius = 10; if (text.isEmpty()) { return false; } int fontSize = queryFontSize(text, font, size); if (fontSize == 0) { return false; } switch (placementPosition) { case Private::TopLeft: alignMode = Qt::AlignLeft; break; case Private::TopRight: alignMode = Qt::AlignRight; break; case Private::BottomLeft: alignMode = Qt::AlignLeft; break; case Private::Center: alignMode = Qt::AlignCenter; break; default : // BottomRight alignMode = Qt::AlignRight; break; } if (!useAbsoluteSize) { font.setPointSizeF(fontSize); } QFontMetrics fontMt(font); QRect fontRect = fontMt.boundingRect(radius, radius, image().width(), image().height(), 0, text); // Add a transparent layer. QRect backgroundRect(fontRect.x() - radius, fontRect.y() - radius, fontRect.width() + 2 * radius, fontRect.height() + 2 * radius); DImg backgroundLayer(backgroundRect.width(), backgroundRect.height(), image().sixteenBit(), true); DColor transparent(QColor(0, 0, 0)); transparent.setAlpha(0); if (image().sixteenBit()) { transparent.convertToSixteenBit(); } backgroundLayer.fill(transparent); DImg grayTransLayer(fontRect.width(), fontRect.height(), image().sixteenBit(), true); if (useBackground) { DColor grayTrans(backgroundColor); grayTrans.setAlpha(backgroundOpacity * 255 / 100); if (image().sixteenBit()) { grayTrans.convertToSixteenBit(); } grayTransLayer.fill(grayTrans); backgroundLayer.bitBlendImage(composer, &grayTransLayer, 0, 0, grayTransLayer.width(), grayTransLayer.height(), radius, radius); } BlurFilter blur(&backgroundLayer, 0L, radius); blur.startFilterDirectly(); backgroundLayer.putImageData(blur.getTargetImage().bits()); // Draw text QImage img = backgroundLayer.copyQImage(fontRect); QPainter p(&img); fontColor.setAlpha(textOpacity * 255 / 100); p.setPen(QPen(fontColor, 1)); p.setFont(font); p.save(); p.drawText(0, 0, fontRect.width(), fontRect.height(), alignMode, text); p.restore(); p.end(); watermarkImage = DImg(img); } watermarkImage.convertToDepthOfImage(&image()); QRect watermarkRect(0, 0, watermarkImage.width(), watermarkImage.height()); int xAdditionalValue = 0; int yAdditionalValue = 0; watermarkImage.rotate(rotationAngle); if (placementType == Private::SpecifiedLocation) { switch (placementPosition) { case Private::TopLeft: watermarkRect.moveTopLeft(QPoint(marginW, marginH)); break; case Private::TopRight: if (rotationAngle == DImg::ANGLE::ROT270 || rotationAngle == DImg::ANGLE::ROT90) { xAdditionalValue += watermarkRect.width() - watermarkRect.height(); } watermarkRect.moveTopRight(QPoint(image().width() + xAdditionalValue -1 - marginW, marginH)); break; case Private::BottomLeft: if (rotationAngle == DImg::ANGLE::ROT90 || rotationAngle == DImg::ANGLE::ROT270) { yAdditionalValue += watermarkRect.height() - watermarkRect.width(); } watermarkRect.moveBottomLeft(QPoint(marginW, image().height() + yAdditionalValue - 1 - marginH)); break; case Private::Center: if (rotationAngle == DImg::ANGLE::ROT90 || rotationAngle == DImg::ANGLE::ROT270) { xAdditionalValue += (watermarkRect.width() - watermarkRect.height())/2; yAdditionalValue += (watermarkRect.height() - watermarkRect.width())/2; } watermarkRect.moveCenter(QPoint((int)(image().width() / 2 + xAdditionalValue), (int)(image().height() / 2 + yAdditionalValue))); break; default : // BottomRight if (rotationAngle == DImg::ANGLE::ROT90 || rotationAngle == DImg::ANGLE::ROT270) { xAdditionalValue += watermarkRect.width() - watermarkRect.height(); yAdditionalValue += watermarkRect.height() - watermarkRect.width(); } watermarkRect.moveBottomRight(QPoint(image().width() + xAdditionalValue - 1 - marginW, image().height() + yAdditionalValue -1 - marginH)); break; } image().bitBlendImage(composer, &watermarkImage, 0, 0, watermarkImage.width(), watermarkImage.height(), watermarkRect.left(), watermarkRect.top()); } else { const float DENSE_SPACING_FACTOR = 1.2; const float SPARSE_SPACING_FACTOR = 1.8; float widthRatio = (float)watermarkRect.width()/image().width(); float heightRatio = (float)watermarkRect.height()/image().height(); float spacingFactor = (denseRepetition) ? DENSE_SPACING_FACTOR : SPARSE_SPACING_FACTOR; spacingFactor *= userSparsityFactor; if (placementType == Private::SystematicRepetition) { if (rotationAngle == DImg::ANGLE::ROT270 || rotationAngle == DImg::ANGLE::ROT90) { widthRatio = (float)watermarkRect.height()/image().width(); heightRatio = (float)watermarkRect.width()/image().height(); } for (uint i = 0 ; i < image().width(); i += spacingFactor * widthRatio * image().width()) { for (uint j = 0 ; j < image().height() ; j += spacingFactor * heightRatio * image().height()) { image().bitBlendImage(composer, &watermarkImage, 0, 0, watermarkImage.width(), watermarkImage.height(), i, j); } } } else if (placementType == Private::RandomRepetition) { widthRatio = (widthRatio > heightRatio) ? widthRatio : heightRatio; heightRatio = widthRatio; qsrand(static_cast(QTime::currentTime().msecsSinceStartOfDay())); for (uint i = 0 ; i < image().width(); i += spacingFactor * widthRatio * image().width()) { for (uint j = 0 ; j < image().height() ; j += spacingFactor * heightRatio * image().height()) { int number = (denseRepetition) ? 2 : 3; if (qrand() % number == 0) { if (randomizeRotation) { int x = qrand() % 4; watermarkImage.rotate((DImg::ANGLE)x); } image().bitBlendImage(composer, &watermarkImage, 0, 0, watermarkImage.width(), watermarkImage.height(), i, j); } } } } } // TODO: Create watermark filter, move code there, implement FilterAction delete composer; LoadSaveThread::reverseExifRotate(image(), inputUrl().toLocalFile()); return (savefromDImg()); } int WaterMark::queryFontSize(const QString& text, const QFont& font, int length) const { // Find font size using relative length compared to image width. QFont fnt = font; QRect fontRect; for (int i = 1 ; i <= 1000 ; ++i) { fnt.setPointSizeF(i); QFontMetrics fontMt(fnt); fontRect = fontMt.boundingRect(0, 0, image().width(), image().height(), 0, text); if (fontRect.width() > lround((image().width() * length) / 100.0)) { return (i - 1); } } return 0; } } // namespace Digikam