diff --git a/core/libs/database/tags/tagregion.cpp b/core/libs/database/tags/tagregion.cpp index a0879a5b16..71ea099670 100644 --- a/core/libs/database/tags/tagregion.cpp +++ b/core/libs/database/tags/tagregion.cpp @@ -1,347 +1,347 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2010-08-26 * Description : Tag region formatting * * Copyright (C) 2010-2011 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 "tagregion.h" // Qt includes #include #include // Local includes #include "dimg.h" #include "metaengine.h" namespace Digikam { TagRegion::TagRegion() : m_type(Invalid) { } TagRegion::TagRegion(const QString& descriptor) : m_value(descriptor), m_type(Invalid) { QString xmlStartDocument = QLatin1String(""); QXmlStreamReader reader(xmlStartDocument + descriptor); if (reader.readNextStartElement()) { if (reader.name() == QLatin1String("rect")) { QRect r(reader.attributes().value(QLatin1String("x")).toString().toInt(), reader.attributes().value(QLatin1String("y")).toString().toInt(), reader.attributes().value(QLatin1String("width")).toString().toInt(), reader.attributes().value(QLatin1String("height")).toString().toInt()); if (r.isValid()) { m_value = r; m_type = Rect; } } } } TagRegion::TagRegion(const QRect& rect) : m_value(rect), m_type(Rect) { } TagRegion::Type TagRegion::type() const { return m_type; } bool TagRegion::isValid() const { return (m_type != Invalid); } bool TagRegion::operator==(const TagRegion& other) const { return ( (m_type == other.m_type) && (m_value == other.m_value) ); } bool TagRegion::operator!=(const TagRegion& other) const { return (!operator==(other)); } QString TagRegion::toXml() const { if (m_type == Invalid) { return QString(); } QString output; QXmlStreamWriter writer(&output); writer.writeStartDocument(); int lengthOfHeader = output.length(); if (m_type == Rect) { QRect rect = m_value.toRect(); writer.writeStartElement(QLatin1String("rect")); writer.writeAttribute(QLatin1String("x"), QString::number(rect.x())); writer.writeAttribute(QLatin1String("y"), QString::number(rect.y())); writer.writeAttribute(QLatin1String("width"), QString::number(rect.width())); writer.writeAttribute(QLatin1String("height"), QString::number(rect.height())); writer.writeEndElement(); } // cut off the tag at start of document return output.mid(lengthOfHeader); } QRect TagRegion::toRect() const { if (m_type == Rect) { return m_value.toRect(); } return QRect(); } QVariant TagRegion::toVariant() const { return m_value; } TagRegion TagRegion::fromVariant(const QVariant& var) { switch (var.type()) { case QVariant::Rect: return TagRegion(var.toRect()); case QVariant::String: return TagRegion(var.toString()); default: return TagRegion(); } } bool TagRegion::intersects(const TagRegion& other, double fraction) { if ((m_type == Invalid) || (other.m_type == Invalid)) { return false; } if (m_type == Rect) { QRect r = toRect(); if (other.m_type == Rect) { QRect r2 = other.toRect(); if (fraction == 0) { return r.intersects(r2); } else if (fraction == 1) { return r.contains(r2); } else { QRect i = r.intersected(r2); return ((double(i.width() * i.height()) / double(r.width() * r.height())) > fraction); } } } return false; } QRect TagRegion::mapToOriginalSize(const QSize& fullImageSize, const QSize& reducedImageSize, const QRect& reducedSizeDetail) { if (fullImageSize == reducedImageSize) { return reducedSizeDetail; } double ratioWidth = double(fullImageSize.width()) / double(reducedImageSize.width()); double ratioHeight = double(fullImageSize.height()) / double(reducedImageSize.height()); return QRectF(reducedSizeDetail.x() * ratioWidth, reducedSizeDetail.y() * ratioHeight, reducedSizeDetail.width() * ratioWidth, reducedSizeDetail.height() * ratioHeight).toRect(); } QRect TagRegion::mapFromOriginalSize(const QSize& fullImageSize, const QSize& reducedImageSize, const QRect& fullSizeDetail) { if (fullImageSize == reducedImageSize) { return fullSizeDetail; } double ratioWidth = double(reducedImageSize.width()) / double(fullImageSize.width()); double ratioHeight = double(reducedImageSize.height()) / double(fullImageSize.height()); return QRectF(fullSizeDetail.x() * ratioWidth, fullSizeDetail.y() * ratioHeight, fullSizeDetail.width() * ratioWidth, fullSizeDetail.height() * ratioHeight).toRect(); } QRect TagRegion::mapToOriginalSize(const DImg& reducedSizeImage, const QRect& reducedSizeDetail) { - return mapToOriginalSize(reducedSizeImage.originalSize(), reducedSizeImage.size(), reducedSizeDetail); + return mapToOriginalSize(reducedSizeImage.originalRatioSize(), reducedSizeImage.size(), reducedSizeDetail); } QRect TagRegion::mapFromOriginalSize(const DImg& reducedSizeImage, const QRect& fullSizeDetail) { - return mapFromOriginalSize(reducedSizeImage.originalSize(), reducedSizeImage.size(), fullSizeDetail); + return mapFromOriginalSize(reducedSizeImage.originalRatioSize(), reducedSizeImage.size(), fullSizeDetail); } QRect TagRegion::relativeToAbsolute(const QRectF& region, const QSize& fullSize) { return QRectF(region.x() * fullSize.width(), region.y() * fullSize.height(), region.width() * fullSize.width(), region.height() * fullSize.height()).toRect(); } QRect TagRegion::relativeToAbsolute(const QRectF& region, const DImg& reducedSizeImage) { - return relativeToAbsolute(region, reducedSizeImage.originalSize()); + return relativeToAbsolute(region, reducedSizeImage.originalRatioSize()); } QRectF TagRegion::absoluteToRelative(const QRect& region, const QSize& fullSize) { return QRectF((qreal)region.x() / (qreal)fullSize.width(), (qreal)region.y() / (qreal)fullSize.height(), (qreal)region.width() / (qreal)fullSize.width(), (qreal)region.height() / (qreal)fullSize.height()); } QSize TagRegion::adjustToOrientation(QRect& region, int rotation, const QSize& fullSize) { QSize size = fullSize; if ((rotation == MetaEngine::ORIENTATION_ROT_90) || (rotation == MetaEngine::ORIENTATION_ROT_90_HFLIP) || (rotation == MetaEngine::ORIENTATION_ROT_90_VFLIP)) { region.moveTo(size.height() - region.y() - region.height(), region.x()); region.setSize(region.size().transposed()); size.transpose(); } else if (rotation == MetaEngine::ORIENTATION_ROT_180) { region.moveTo(size.width() - region.x() - region.width(), size.height() - region.y() - region.height()); } else if (rotation == MetaEngine::ORIENTATION_ROT_270) { region.moveTo(region.y(), size.width() - region.x() - region.width()); region.setSize(region.size().transposed()); size.transpose(); } if ((rotation == MetaEngine::ORIENTATION_HFLIP) || (rotation == MetaEngine::ORIENTATION_ROT_90_HFLIP)) { region.moveTo(size.width() - region.x() - region.width(), region.y()); } else if ((rotation == MetaEngine::ORIENTATION_VFLIP) || (rotation == MetaEngine::ORIENTATION_ROT_90_VFLIP)) { region.moveTo(region.x(), size.height() - region.y() - region.height()); } return size; } void TagRegion::reverseToOrientation(QRect& region, int orientation, const QSize& fullSize) { QSize size = fullSize; switch (orientation) { case MetaEngine::ORIENTATION_ROT_90_HFLIP: TagRegion::adjustToOrientation(region, MetaEngine::ORIENTATION_HFLIP, size); TagRegion::adjustToOrientation(region, MetaEngine::ORIENTATION_ROT_270, size); break; case MetaEngine::ORIENTATION_ROT_90: size.transpose(); TagRegion::adjustToOrientation(region, MetaEngine::ORIENTATION_ROT_270, size); break; case MetaEngine::ORIENTATION_ROT_90_VFLIP: size.transpose(); TagRegion::adjustToOrientation(region, MetaEngine::ORIENTATION_VFLIP, size); TagRegion::adjustToOrientation(region, MetaEngine::ORIENTATION_ROT_270, size); break; case MetaEngine::ORIENTATION_ROT_270: size.transpose(); TagRegion::adjustToOrientation(region, MetaEngine::ORIENTATION_ROT_90, size); break; default: TagRegion::adjustToOrientation(region, orientation, size); break; } } QDebug operator<<(QDebug dbg, const TagRegion& r) { QVariant var = r.toVariant(); switch (var.type()) { case QVariant::Rect: dbg.nospace() << var.toRect(); break; case QVariant::String: dbg.nospace() << var.toString(); break; default: dbg.nospace() << var; break; } return dbg; } } // namespace Digikam diff --git a/core/libs/dimg/dimg.h b/core/libs/dimg/dimg.h index 6014655522..7d72f9b86e 100644 --- a/core/libs/dimg/dimg.h +++ b/core/libs/dimg/dimg.h @@ -1,799 +1,805 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2005-06-14 * Description : digiKam 8/16 bits image management API * * Copyright (C) 2005 by Renchi Raju * Copyright (C) 2005-2020 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. * * ============================================================ */ #ifndef DIGIKAM_DIMG_H #define DIGIKAM_DIMG_H // Qt includes #include #include #include #include #include #include #include // Local includes #include "digikam_export.h" #include "drawdecoding.h" #include "dcolor.h" #include "dcolorcomposer.h" #include "historyimageid.h" #include "iccprofile.h" #include "metaengine_data.h" class QImage; class QPixmap; namespace Digikam { class ExposureSettingsContainer; class DImageHistory; class FilterAction; class IccTransform; class DImgLoaderObserver; class DIGIKAM_EXPORT DImg { public: enum FORMAT { /** * NOTE: Order is important here: * See filesaveoptionbox.cpp which use these values to fill a stack of widgets. */ NONE = 0, JPEG, PNG, TIFF, JP2K, PGF, HEIF, // Others file formats. RAW, QIMAGE ///< QImage or ImageMagick }; enum ANGLE { ROT90 = 0, ROT180, ROT270, ROTNONE }; enum FLIP { HORIZONTAL = 0, VERTICAL }; enum COLORMODEL { COLORMODELUNKNOWN = 0, RGB, GRAYSCALE, MONOCHROME, INDEXED, YCBCR, CMYK, CIELAB, COLORMODELRAW }; public: /** * Identify file format */ static FORMAT fileFormat(const QString& filePath); static QString formatToMimeType(FORMAT frm); public: class Private; public: /** * Create null image */ DImg(); /** * Load image using QByteArray as file path */ explicit DImg(const QByteArray& filePath, DImgLoaderObserver* const observer = nullptr, const DRawDecoding& rawDecodingSettings = DRawDecoding()); /** * Load image using QString as file path */ explicit DImg(const QString& filePath, DImgLoaderObserver* const observer = nullptr, const DRawDecoding& rawDecodingSettings = DRawDecoding()); /** * Copy image: Creates a shallow copy that refers to the same shared data. * The two images will be equal. Call detach() or copy() to create deep copies. */ DImg(const DImg& image); /** * Copy image: Creates a copy of a QImage object. If the QImage is null, a * null DImg will be created. */ explicit DImg(const QImage& image); /** * Create image from data. * If data is 0, a new buffer will be allocated, otherwise the given data will be used: * If copydata is true, the data will be copied to a newly allocated buffer. * If copyData is false, this DImg object will take ownership of the data pointer. * If there is an alpha channel, the data shall be in non-premultiplied form (unassociated alpha). */ DImg(uint width, uint height, bool sixteenBit, bool alpha = false, uchar* const data = nullptr, bool copyData = true); ~DImg(); /** * Equivalent to the copy constructor */ DImg& operator=(const DImg& image); /** Detaches from shared data and makes sure that this image * is the only one referring to the data. * If multiple images share common data, this image makes a copy * of the data and detaches itself from the sharing mechanism. * Nothing is done if there is just a single reference. */ void detach(); /** Returns whether two images are equal. * Two images are equal if and only if they refer to the same shared data. * (Thus, DImg() == DImg() is not true, both instances refer two their * own shared data. image == DImg(image) is true.) * If two or more images refer to the same data, they have the same * image data, bits() returns the same data, they have the same metadata, * and a change to one image also affects the others. * Call detach() to split one image from the group of equal images. */ bool operator==(const DImg& image) const; /** * Replaces image data of this object. Metadata is unchanged. Parameters like constructor above. */ void putImageData(uint width, uint height, bool sixteenBit, bool alpha, uchar* const data, bool copyData = true); /** * Overloaded function, provided for convenience, behaves essentially * like the function above if data is not 0. * Uses current width, height, sixteenBit, and alpha values. * If data is 0, the current data is deleted and the image is set to null * (But metadata unchanged). */ void putImageData(uchar* const data, bool copyData = true); /** * Reset metadata and image data to null image */ void reset(); /** * Reset metadata, but do not change image data */ void resetMetaData(); /** * Returns the data of this image. * Ownership of the buffer is passed to the caller, this image will be null afterwards. */ uchar* stripImageData(); bool load(const QString& filePath, DImgLoaderObserver* const observer = nullptr, const DRawDecoding& rawDecodingSettings = DRawDecoding()); bool load(const QString& filePath, bool loadMetadata, bool loadICCData, bool loadUniqueHash, bool loadHistory, DImgLoaderObserver* const observer = nullptr, const DRawDecoding& rawDecodingSettings = DRawDecoding()); bool load(const QString& filePath, int loadFlags, DImgLoaderObserver* const observer, const DRawDecoding& rawDecodingSettings = DRawDecoding()); bool save(const QString& filePath, FORMAT frm, DImgLoaderObserver* const observer = nullptr); bool save(const QString& filePath, const QString& format, DImgLoaderObserver* const observer = nullptr); /** * It is common that images are not directly saved to the destination path. * For this reason, save() does not call addAsReferredImage(), and the stored * save path may be wrong. * Call this method after save() with the final destination path. * This path will be stored in the image history as well. */ void imageSavedAs(const QString& savePath); /** * Loads most parts of the meta information, but never the image data. * If loadMetadata is true, the metadata will be available with getComments, getExif, getIptc, getXmp . * If loadICCData is true, the ICC profile will be available with getICCProfile. */ bool loadItemInfo(const QString& filePath, bool loadMetadata = true, bool loadICCData = true, bool loadUniqueHash = true, bool loadImageHistory = true); bool isNull() const; uint width() const; uint height() const; QSize size() const; uchar* copyBits() const; uchar* bits() const; uchar* scanLine(uint i) const; bool hasAlpha() const; bool sixteenBit() const; quint64 numBytes() const; quint64 numPixels() const; /** * Return the number of bytes depth of one pixel : 4 (non sixteenBit) or 8 (sixteen) */ int bytesDepth() const; /** * Return the number of bits depth of one color component for one pixel : 8 (non sixteenBit) or 16 (sixteen) */ int bitsDepth() const; /** * Returns the file path from which this DImg was originally loaded. * Returns a null string if the DImg was not loaded from a file. */ QString originalFilePath() const; /** * Returns the file path to which this DImg was saved. * Returns the file path set with imageSavedAs(), if that was not called, * save(), if that was not called, a null string. */ QString lastSavedFilePath() const; /** * Returns the color model in which the image was stored in the file. * The color space of the loaded image data is always RGB. */ COLORMODEL originalColorModel() const; /** * Returns the bit depth (in bits per channel, e.g. 8 or 16) of the original file. */ int originalBitDepth() const; /** * Returns the size of the original file. */ QSize originalSize() const; + /** + * Returns the size of the original file + * in the same aspect ratio as size(). + */ + QSize originalRatioSize() const; + /** * Returns the file format in form of the FORMAT enum that was detected in the load() * method. Other than the format attribute which is written by the DImgLoader, * this can include the QIMAGE or NONE values. * Returns NONE for images that have not been loaded. * For unknown image formats, a value of QIMAGE can be returned to indicate that the * QImage-based loader will have been used. To find out if this has worked, check * the return value you got from load(). */ FORMAT detectedFormat() const; /** * Returns the format string as written by the image loader this image was originally * loaded from. Format strings used include JPEG, PNG, TIFF, PGF, JP2K, RAW, PPM. * For images loaded with the platform QImage loader, the file suffix is used. * Returns null if this DImg was not loaded from a file, but created in memory. */ QString format() const; /** * Returns the format string of the format that this image was last saved to. * An image can be loaded from a file - retrieve that format with fileFormat() * and loadedFormat() - and can the multiple times be saved to different formats. * Format strings used include JPG, PGF, PNG, TIFF and JP2K. * If this file was not save, a null string is returned. */ QString savedFormat() const; /** * Returns the DRawDecoding options that this DImg was loaded with. * If this is not a RAW image or no options were specified, returns DRawDecoding(). */ DRawDecoding rawDecodingSettings() const; /** * Access a single pixel of the image. * These functions add some safety checks and then use the methods from DColor. * In optimized code working directly on the data, * better use the inline methods from DColor. */ DColor getPixelColor(uint x, uint y) const; void setPixelColor(uint x, uint y, const DColor& color); void prepareSubPixelAccess(); DColor getSubPixelColor(float x, float y) const; DColor getSubPixelColorFast(float x, float y) const; /** * If the image has an alpha channel, check if there exist pixels * which actually have non-opaque color, that is alpha < 1.0. * Note that all pixels are scanned to reach a return value of "false". * If hasAlpha() is false, always returns false. */ bool hasTransparentPixels() const; /** * Return true if the original image file format cannot be saved. * This is depending of DImgLoader::save() implementation. For example * RAW file formats are supported by DImg using dcraw than cannot support * writing operations. */ bool isReadOnly() const; /** * Metadata manipulation methods */ MetaEngineData getMetadata() const; IccProfile getIccProfile() const; void setMetadata(const MetaEngineData& data); void setIccProfile(const IccProfile& profile); void setAttribute(const QString& key, const QVariant& value); QVariant attribute(const QString& key) const; bool hasAttribute(const QString& key) const; void removeAttribute(const QString& key); void setEmbeddedText(const QString& key, const QString& text); QString embeddedText(const QString& key) const; const DImageHistory& getItemHistory() const; DImageHistory& getItemHistory(); void setItemHistory(const DImageHistory& history); bool hasImageHistory() const; DImageHistory getOriginalImageHistory() const; void addFilterAction(const FilterAction& action); /** * Sets a step in the history to constitute the beginning of a branch. * Use setHistoryBranch() to take getOriginalImageHistory() and set the first added step as a branch. * Use setHistoryBranchForLastSteps(n) to start the branch before the last n steps in the history. * (Assume the history had 3 steps and you added 2, call setHistoryBranchForLastSteps(2)) * Use setHistoryBranchAfter() if have a copy of the history before branching, * the first added step on top of that history will be made a branch. */ void setHistoryBranchAfter(const DImageHistory& historyBeforeBranch, bool isBranch = true); void setHistoryBranchForLastSteps(int numberOfLastHistorySteps, bool isBranch = true); void setHistoryBranch(bool isBranch = true); /** * When saving, several changes to the image metadata are necessary * before it can safely be written to the new file. * This method updates the stored DMetadata object in preparation to a subsequent * call to save() with the same target file. * 'intendedDestPath' is the finally intended file name. Do not give the temporary * file name if you are going to save() to a temp file. * 'destMimeType' is destination type mime. In some cases, metadata is updated depending on this value. * 'originalFileName' is the original file's name, for simplistic history tracking in metadata. * This is completely independent from the DImageHistory framework. * For the 'flags' see below. * Not all steps are optional and can be controlled with flags. */ enum PrepareMetadataFlag { /** * A small preview can be stored in the metadata. * Remove old preview entries */ RemoveOldMetadataPreviews = 1 << 0, /** * Create a new preview from current image data. */ CreateNewMetadataPreview = 1 << 1, /** * Set the exif orientation tag to "normal" * Applicable if the image data was rotated according to the tag */ ResetExifOrientationTag = 1 << 2, /** * Creates a new UUID for the image history. * Applicable if the file was changed. */ CreateNewImageHistoryUUID = 1 << 3, PrepareMetadataFlagsAll = RemoveOldMetadataPreviews | CreateNewMetadataPreview | ResetExifOrientationTag | CreateNewImageHistoryUUID }; Q_DECLARE_FLAGS(PrepareMetadataFlags, PrepareMetadataFlag) void prepareMetadataToSave(const QString& intendedDestPath, const QString& destMimeType, const QString& originalFileName = QString(), PrepareMetadataFlags flags = PrepareMetadataFlagsAll); /** * For convenience: Including all flags, except for ResetExifOrientationTag which can be selected. * Uses originalFilePath() to fill the original file name. */ void prepareMetadataToSave(const QString& intendedDestPath, const QString& destMimeType, bool resetExifOrientationTag); /** * Create a HistoryImageId for _this_ image _already_ saved at the given file path. */ HistoryImageId createHistoryImageId(const QString& filePath, HistoryImageId::Type type); /** * If you have saved this DImg to filePath, and want to continue using this DImg object * to add further changes to the image history, you can call this method to add to the image history * a reference to the just saved image. * First call updateMetadata(), then call save(), then call addAsReferredImage(). * Do not call this directly after loading, before applying any changes: * The history is correctly initialized when loading. * If you need to insert the referred file to an entry which is not the last entry, * which may happen if the added image was saved after this image's history was created, * you can use insertAsReferredImage. * The added id is returned. */ HistoryImageId addAsReferredImage(const QString& filePath, HistoryImageId::Type type = HistoryImageId::Intermediate); void addAsReferredImage(const HistoryImageId& id); void insertAsReferredImage(int afterHistoryStep, const HistoryImageId& otherImagesId); /** * In the history, adjusts the UUID of the ImageHistoryId of the current file. * Call this if you have associated a UUID with this file which is not written to the metadata. * If there is already a UUID present, read from metadata, it will not be replaced. */ void addCurrentUniqueImageId(const QString& uuid); /** * Retrieves the Exif orientation, either from the LoadSaveThread info provider if available, * or from the metadata */ int exifOrientation(const QString& filePath); /** * When loaded from a file, some attributes like format and isReadOnly still depend on this * originating file. When saving in a different format to a different file, * you may wish to switch these attributes to the new file. * - fileOriginData() returns the current origin data, bundled in the returned QVariant. * - setFileOriginData() takes such a variant and adjusts the properties * - lastSavedFileOriginData() returns the origin data as if the image was loaded from * the last saved image. * - switchOriginToLastSaved is equivalent to setting origin data returned from lastSavedFileOriginData() * * Example: an image loaded from a RAW and saved to PNG will be read-only and format RAW. * After calling switchOriginToLastSaved, it will not be read-only, format will be PNG, * and rawDecodingSettings will be null. detectedFormat() will not change. * In the history, the last referred image that was added (as intermediate) is made * the new Current image. * NOTE: Set the saved image path with imageSavedAs() before! */ QVariant fileOriginData() const; void setFileOriginData(const QVariant& data); QVariant lastSavedFileOriginData() const; void switchOriginToLastSaved(); /** * Return a deep copy of full image */ DImg copy() const; /** * Return a deep copy of the image, but do not include metadata. */ DImg copyImageData() const; /** * Return an image that contains a deep copy of * this image's metadata and the information associated * with the image data (width, height, hasAlpha, sixteenBit), * but no image data, i.e. isNull() is true. */ DImg copyMetaData() const; /** * Return a region of image */ DImg copy(const QRect& rect) const; DImg copy(const QRectF& relativeRect) const; DImg copy(int x, int y, int w, int h) const; /** * Copy a region of pixels from a source image to this image. * Parameters: * sx|sy Coordinates in the source image of the rectangle to be copied * w h Width and height of the rectangle (Default, or when both are -1: whole source image) * dx|dy Coordinates in this image of the rectangle in which the region will be copied * (Default: 0|0) * The bit depth of source and destination must be identical. */ void bitBltImage(const DImg* const src, int dx, int dy); void bitBltImage(const DImg* const src, int sx, int sy, int dx, int dy); void bitBltImage(const DImg* const src, int sx, int sy, int w, int h, int dx, int dy); void bitBltImage(const uchar* const src, int sx, int sy, int w, int h, int dx, int dy, uint swidth, uint sheight, int sdepth); /** * Blend src image on this image (this is dest) with the specified composer * and multiplication flags. See documentation of DColorComposer for more info. * For the other arguments, see documentation of bitBltImage above. */ void bitBlendImage(DColorComposer* const composer, const DImg* const src, int sx, int sy, int w, int h, int dx, int dy, DColorComposer::MultiplicationFlags multiplicationFlags = DColorComposer::NoMultiplication); /** * For the specified region, blend this image on the given color with the specified * composer and multiplication flags. See documentation of DColorComposer for more info. * Note that the result pixel is again written to this image, which is, for the blending, source. */ void bitBlendImageOnColor(DColorComposer* const composer, const DColor& color, int x, int y, int w, int h, DColorComposer::MultiplicationFlags multiplicationFlags = DColorComposer::NoMultiplication); void bitBlendImageOnColor(const DColor& color, int x, int y, int w, int h); void bitBlendImageOnColor(const DColor& color); /** * QImage wrapper methods */ QImage copyQImage() const; QImage copyQImage(const QRect& rect) const; QImage copyQImage(const QRectF& relativeRect) const; QImage copyQImage(int x, int y, int w, int h) const; /** * Crop image to the specified region */ void crop(const QRect& rect); void crop(int x, int y, int w, int h); /** * Set width and height of this image, smoothScale it to the given size */ void resize(int w, int h); /** * If the image has an alpha channel and transparent pixels, * it will be blended on the specified color and the alpha channel will be removed. * This is a no-op if hasTransparentPixels() is false, but this method can be expensive, * therefore it is _not_ checked inside removeAlphaChannel(). * (the trivial hasAlpha() is checked) */ void removeAlphaChannel(const DColor& destColor); void removeAlphaChannel(); /** * Return a version of this image scaled to the specified size with the specified mode. * See QSize documentation for information on available modes */ DImg smoothScale(int width, int height, Qt::AspectRatioMode aspectRatioMode = Qt::IgnoreAspectRatio) const; DImg smoothScale(const QSize& destSize, Qt::AspectRatioMode aspectRatioMode = Qt::IgnoreAspectRatio) const; /** * Executes the same scaling as smoothScale(width, height), but from the result of this call, * returns only the section specified by clipx, clipy, clipwidth, clipheight. * This is thus equivalent to calling * Dimg scaled = smoothScale(width, height); scaled.crop(clipx, clipy, clipwidth, clipheight); * but potentially much faster. * In smoothScaleSection, you specify the source region, here, the result region. * It will often not be possible to find _integer_ source coordinates for a result region! */ DImg smoothScaleClipped(int width, int height, int clipx, int clipy, int clipwidth, int clipheight) const; DImg smoothScaleClipped(const QSize& destSize, const QRect& clip) const; /** * Take the region specified by the rectangle sx|sy, width and height sw * sh, * and scale it to an image with size dw * dh */ DImg smoothScaleSection(int sx, int sy, int sw, int sh, int dw, int dh) const; DImg smoothScaleSection(const QRect& sourceRect, const QSize& destSize) const; void rotate(ANGLE angle); void flip(FLIP direction); /** * Rotates and/or flip the DImg according to the given DMetadata::Orientation, * so that the current state is orientation and the resulting step is normal orientation. * Returns true if the image was actually rotated or flipped (e.g. if ORIENTATION_NORMAL * is given, returns false, because no action is taken). */ bool rotateAndFlip(int orientation); /** * Reverses the previous function. */ bool reverseRotateAndFlip(int orientation); /** * 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. */ bool wasExifRotated(); bool exifRotate(const QString& filePath); /** * Reverses the previous function */ bool reverseExifRotate(const QString& filePath); /** * Returns current DMetadata::Orientation from DImg */ int orientation() const; /** Rotates and/or flip the DImg according to the given transform action, * which is a MetaEngineRotation::TransformAction. * Returns true if the image was actually rotated or flipped. */ bool transform(int transformAction); QPixmap convertToPixmap() const; QPixmap convertToPixmap(IccTransform& monitorICCtrans) const; /** * Return a mask image where pure white and pure black pixels are over-colored. * This way is used to identify over and under exposed pixels. */ QImage pureColorMask(ExposureSettingsContainer* const expoSettings) const; /** * Convert depth of image. Depth is bytesDepth * bitsDepth. * If depth is 32, converts to 8 bits, * if depth is 64, converts to 16 bits. */ void convertDepth(int depth); /** * Wrapper methods for convertDepth */ void convertToSixteenBit(); void convertToEightBit(); void convertToDepthOfImage(const DImg* const otherImage); /** * Fill whole image with specified color. * The bit depth of the color must be identical to the depth of this image. */ void fill(const DColor& color); /** * This methods return a 128-bit MD5 hex digest which is meant to uniquely identify * the file. The hash is calculated on parts of the file and the file metadata. * It cannot be used to find similar images. It is not calculated from the image data. * The hash will be returned as a 32-byte hexadecimal string. * * If you already have a DImg object of the file, use the member method. * The object does not need to have the full image data loaded, but it shall at least * have been loaded with loadItemInfo with loadMetadata = true, or have the metadata * set later with setComments, setExif, setIptc, setXmp. * If the object does not have the metadata loaded, a non-null, but invalid hash will * be returned! In this case, use the static method. * If the image has been loaded with loadUniqueHash = true, the hash can be retrieved * with the member method. * * You do not need a DImg object of the file to retrieve the unique hash; * Use the static method and pass just the file path. */ QByteArray getUniqueHash(); static QByteArray getUniqueHash(const QString& filePath); /** * This methods return a 128-bit MD5 hex digest which is meant to uniquely identify * the file. The hash is calculated on parts of the file. * It cannot be used to find similar images. It is not calculated from the image data. * The hash will be returned as a 32-byte hexadecimal string. * * If you already have a DImg object loaded from the file, use the member method. * If the image has been loaded with loadUniqueHash = true, the hash will already * be available. * * You do not need a DImg object of the file to retrieve the unique hash; * Use the static method and pass just the file path. */ QByteArray getUniqueHashV2(); static QByteArray getUniqueHashV2(const QString& filePath); /** * This method creates a new 256-bit UUID meant to be globally unique. * The UUID will be returned as a 64-byte hexadecimal string. * At least 128bits of the UUID will be created by the platform random number * generator. The rest may be created from a content-based hash similar to the uniqueHash, see above. * This method only generates a new UUID for this image without in any way changing this image object * or saving the UUID anywhere. */ QByteArray createImageUniqueId(); /** * Helper method to translate enum values to user presentable strings */ static QString colorModelToString(COLORMODEL colorModel); /** * Return true if image file is an animation, as GIFa or NMG */ static bool isAnimatedImage(const QString& filePath); private: DImg(const DImg& image, int w, int h); void copyMetaData(const QExplicitlySharedDataPointer& src); void copyImageData(const QExplicitlySharedDataPointer& src); void setImageData(bool null, uint width, uint height, bool sixteenBit, bool alpha); void setImageDimension(uint width, uint height); size_t allocateData() const; bool clipped(int& x, int& y, int& w, int& h, uint width, uint height) const; QDateTime creationDateFromFilesystem(const QFileInfo& fileInfo) const; static QByteArray createUniqueHash(const QString& filePath, const QByteArray& ba); static QByteArray createUniqueHashV2(const QString& filePath); void 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); void 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); void bitBlendOnColor(DColorComposer* const composer, const DColor& color, uchar* data, int x, int y, int w, int h, uint width, uint height, bool sixteenBit, int depth, DColorComposer::MultiplicationFlags multiplicationFlags); bool normalizeRegionArguments(int& sx, int& sy, int& w, int& h, int& dx, int& dy, uint swidth, uint sheight, uint dwidth, uint dheight) const; private: QExplicitlySharedDataPointer m_priv; friend class DImgLoader; }; } // namespace Digikam Q_DECLARE_OPERATORS_FOR_FLAGS(Digikam::DImg::PrepareMetadataFlags) #endif // DIGIKAM_DIMG_H diff --git a/core/libs/dimg/dimg_props.cpp b/core/libs/dimg/dimg_props.cpp index adc9fb3bcb..dddf08cec1 100644 --- a/core/libs/dimg/dimg_props.cpp +++ b/core/libs/dimg/dimg_props.cpp @@ -1,710 +1,723 @@ /* ============================================================ * * This file is a part of digiKam project * https://www.digikam.org * * Date : 2005-06-14 * Description : digiKam 8/16 bits image management API. * Properties accessors. * * Copyright (C) 2005-2020 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_p.h" namespace Digikam { 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 nullptr; } 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 (hasAttribute(QLatin1String("originalColorModel"))) { return (COLORMODEL)attribute(QLatin1String("originalColorModel")).toInt(); } else { return COLORMODELUNKNOWN; } } int DImg::originalBitDepth() const { return attribute(QLatin1String("originalBitDepth")).toInt(); } QSize DImg::originalSize() const { if (hasAttribute(QLatin1String("originalSize"))) { QSize size = attribute(QLatin1String("originalSize")).toSize(); if (size.isValid() && !size.isNull()) { return size; } } return size(); } +QSize DImg::originalRatioSize() const +{ + QSize size = originalSize(); + + if (((width() < height()) && (size.width() > size.height())) || + ((width() > height()) && (size.width() < size.height()))) + { + size.transpose(); + } + + return size; +} + DImg::FORMAT DImg::detectedFormat() const { if (hasAttribute(QLatin1String("detectedFileFormat"))) { return (FORMAT)attribute(QLatin1String("detectedFileFormat")).toInt(); } else { return NONE; } } QString DImg::format() const { return attribute(QLatin1String("format")).toString(); } QString DImg::savedFormat() const { return attribute(QLatin1String("savedFormat")).toString(); } DRawDecoding DImg::rawDecodingSettings() const { if (hasAttribute(QLatin1String("rawDecodingSettings"))) { return attribute(QLatin1String("rawDecodingSettings")).value(); } else { return DRawDecoding(); } } IccProfile DImg::getIccProfile() const { QMutexLocker lock(&m_priv->mutex); return m_priv->iccProfile; } void DImg::setIccProfile(const IccProfile& profile) { QMutexLocker lock(&m_priv->mutex); m_priv->iccProfile = profile; } MetaEngineData DImg::getMetadata() const { QMutexLocker lock(&m_priv->mutex); return m_priv->metaData; } void DImg::setMetadata(const MetaEngineData& data) { QMutexLocker lock(&m_priv->mutex); m_priv->metaData = data; } quint64 DImg::numBytes() const { return ((quint64)width() * (quint64)height() * (quint64)bytesDepth()); } quint64 DImg::numPixels() const { return ((quint64)width() * (quint64)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) { QMutexLocker lock(&m_priv->mutex); m_priv->attributes.insert(key, value); } QVariant DImg::attribute(const QString& key) const { QMutexLocker lock(&m_priv->mutex); if (m_priv->attributes.contains(key)) { return m_priv->attributes[key]; } return QVariant(); } bool DImg::hasAttribute(const QString& key) const { QMutexLocker lock(&m_priv->mutex); return m_priv->attributes.contains(key); } void DImg::removeAttribute(const QString& key) { QMutexLocker lock(&m_priv->mutex); m_priv->attributes.remove(key); } void DImg::setEmbeddedText(const QString& key, const QString& text) { QMutexLocker lock(&m_priv->mutex); m_priv->embeddedText.insert(key, text); } QString DImg::embeddedText(const QString& key) const { QMutexLocker lock(&m_priv->mutex); 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 == nullptr) { 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; int 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 = nullptr; (void)data; // To prevent cppcheck warnings. 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; } } // namespace Digikam