diff --git a/autotests/read/xcf/fruktpilot_icc.png b/autotests/read/xcf/fruktpilot_icc.png new file mode 100644 index 0000000..89150ed Binary files /dev/null and b/autotests/read/xcf/fruktpilot_icc.png differ diff --git a/autotests/read/xcf/fruktpilot_icc.xcf b/autotests/read/xcf/fruktpilot_icc.xcf new file mode 100644 index 0000000..c57c654 Binary files /dev/null and b/autotests/read/xcf/fruktpilot_icc.xcf differ diff --git a/src/imageformats/xcf.cpp b/src/imageformats/xcf.cpp index dcba6ad..bd27634 100644 --- a/src/imageformats/xcf.cpp +++ b/src/imageformats/xcf.cpp @@ -16,8 +16,9 @@ #include #include #include -#include +#include +#include #include #include "gimp_p.h" @@ -351,6 +352,8 @@ private: bool initialized; //!< Is the QImage initialized? QImage image; //!< final QImage + QHash parasites; //!< parasites data + XCFImage(void) : initialized(false) { @@ -405,6 +408,7 @@ private: bool composeTiles(XCFImage &xcf_image); void setGrayPalette(QImage &image); void setPalette(XCFImage &xcf_image, QImage &image); + void setImageParasites(const XCFImage &xcf_image, QImage &image); static void assignImageBytes(Layer &layer, uint i, uint j); bool loadHierarchy(QDataStream &xcf_io, Layer &layer); bool loadLevel(QDataStream &xcf_io, Layer &layer, qint32 bpp); @@ -665,6 +669,9 @@ bool XCFImageFormat::readXCF(QIODevice *device, QImage *outImage) return false; } + // The image was created: now I can set metadata and ICC color profile inside it. + setImageParasites(xcf_image, xcf_image.image); + *outImage = xcf_image.image; return true; } @@ -715,15 +722,15 @@ bool XCFImageFormat::loadImageProperties(QDataStream &xcf_io, XCFImage &xcf_imag property.readBytes(tag, size); quint32 flags; - char *data = nullptr; + QByteArray data; property >> flags >> data; - if (tag && strncmp(tag, "gimp-comment", strlen("gimp-comment")) == 0) { - xcf_image.image.setText(QStringLiteral("Comment"), QString::fromUtf8(data)); - } + // WARNING: you cannot add metadata to QImage here because it can be null. + // Adding a metadata to a QImage when it is null, does nothing (metas are lost). + if(tag) // store metadata for future use + xcf_image.parasites.insert(QString::fromUtf8(tag), data); delete[] tag; - delete[] data; } break; @@ -1234,6 +1241,77 @@ void XCFImageFormat::setPalette(XCFImage &xcf_image, QImage &image) image.setColorTable(xcf_image.palette); } +/*! + * Copy the parasites info to QImage. + * \param xcf_image XCF image containing the parasites read from the data stream. + * \param image image to apply the parasites data. + * \note Some comment taken from https://gitlab.gnome.org/GNOME/gimp/-/blob/master/devel-docs/parasites.txt + */ +void XCFImageFormat::setImageParasites(const XCFImage &xcf_image, QImage &image) +{ + auto&& p = xcf_image.parasites; + auto keys = p.keys(); + for (auto&& key : qAsConst(keys)) { + auto value = p.value(key); + if(value.isEmpty()) + continue; + + // "icc-profile" (IMAGE, PERSISTENT | UNDOABLE) + // This contains an ICC profile describing the color space the + // image was produced in. TIFF images stored in PhotoShop do + // oftentimes contain embedded profiles. An experimental color + // manager exists to use this parasite, and it will be used + // for interchange between TIFF and PNG (identical profiles) + if (key == QStringLiteral("icc-profile")) { + auto cs = QColorSpace::fromIccProfile(value); + if(cs.isValid()) + image.setColorSpace(cs); + continue; + } + + // "gimp-comment" (IMAGE, PERSISTENT) + // Standard GIF-style image comments. This parasite should be + // human-readable text in UTF-8 encoding. A trailing \0 might + // be included and is not part of the comment. Note that image + // comments may also be present in the "gimp-metadata" parasite. + if (key == QStringLiteral("gimp-comment")) { + value.replace('\0', QByteArray()); + image.setText(QStringLiteral("Comment"), QString::fromUtf8(value)); + continue; + } + + // "gimp-image-metadata" + // Saved by GIMP 2.10.30 but it is not mentioned in the specification. + // It is an XML block with the properties set using GIMP. + if (key == QStringLiteral("gimp-image-metadata")) { + // NOTE: I arbitrary defined the metadata "XML:org.gimp.xml" because it seems + // a GIMP proprietary XML format (no xmlns defined) + value.replace('\0', QByteArray()); + image.setText(QStringLiteral("XML:org.gimp.xml"), QString::fromUtf8(value)); + continue; + } + +#if 0 // Unable to generate it using latest GIMP version + // "gimp-metadata" (IMAGE, PERSISTENT) + // The metadata associated with the image, serialized as one XMP + // packet. This metadata includes the contents of any XMP, EXIF + // and IPTC blocks from the original image, as well as + // user-specified values such as image comment, copyright, + // license, etc. + if (key == QStringLiteral("gimp-metadata")) { + // NOTE: "XML:com.adobe.xmp" is the meta set by Qt reader when an + // XMP packet is found (e.g. when reading a PNG saved by Photoshop). + // I reused the same key because some programs could search for it. + value.replace('\0', QByteArray()); + image.setText(QStringLiteral("XML:com.adobe.xmp"), QString::fromUtf8(value)); + continue; + } +#endif + + } +} + + /*! * Copy the bytes from the tile buffer into the image tile QImage, taking into * account all the myriad different modes.