diff --git a/libs/ui/KisImportExportErrorCode.cpp b/libs/ui/KisImportExportErrorCode.cpp
index 02a3ce4bda..5b8b080b75 100644
--- a/libs/ui/KisImportExportErrorCode.cpp
+++ b/libs/ui/KisImportExportErrorCode.cpp
@@ -1,191 +1,219 @@
/*
* Copyright (c) 2019 Agata Cacko
*
* 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 of the License, 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "KisImportExportErrorCode.h"
#include
#include
KisImportExportComplexError::KisImportExportComplexError(QFileDevice::FileError error) : m_error(error) { }
QString KisImportExportComplexError::qtErrorMessage() const
{
// Error descriptions in most cases taken from https://doc.qt.io/qt-5/qfiledevice.html
QString unspecifiedError = i18n("An unspecified error occurred.");
switch (m_error) {
case QFileDevice::FileError::NoError :
// Returning this file error may mean that something is wrong in our code.
// Successful operation should return ImportExportCodes::OK instead.
return i18n("The action has been completed successfully.");
case QFileDevice::FileError::ReadError :
return i18n("An error occurred when reading from the file.");
case QFileDevice::FileError::WriteError :
return i18n("An error occurred when writing to the file.");
case QFileDevice::FileError::FatalError :
return i18n("A fatal error occurred.");
case QFileDevice::FileError::ResourceError :
return i18n("Out of resources (e.g. out of memory).");
case QFileDevice::FileError::OpenError :
return i18n("The file could not be opened.");
case QFileDevice::FileError::AbortError :
return i18n("The operation was aborted.");
case QFileDevice::FileError::TimeOutError :
return i18n("A timeout occurred.");
case QFileDevice::FileError::UnspecifiedError :
return unspecifiedError;
case QFileDevice::FileError::RemoveError :
return i18n("The file could not be removed.");
case QFileDevice::FileError::RenameError :
return i18n("The file could not be renamed.");
case QFileDevice::FileError::PositionError :
return i18n("The position in the file could not be changed.");
case QFileDevice::FileError::ResizeError :
return i18n("The file could not be resized.");
case QFileDevice::FileError::PermissionsError :
return i18n("Permission denied. Krita is not allowed to read or write to the file.");
case QFileDevice::FileError::CopyError :
return i18n("The file could not be copied.");
}
return unspecifiedError;
}
KisImportExportErrorCannotRead::KisImportExportErrorCannotRead() : KisImportExportComplexError(QFileDevice::FileError()) { }
KisImportExportErrorCannotRead::KisImportExportErrorCannotRead(QFileDevice::FileError error) : KisImportExportComplexError(error) {
KIS_ASSERT_RECOVER_NOOP(error != QFileDevice::NoError);
}
QString KisImportExportErrorCannotRead::errorMessage() const
{
return i18n("Cannot open file for reading. Reason: %1", qtErrorMessage());
}
+bool KisImportExportErrorCannotRead::operator==(KisImportExportErrorCannotRead other)
+{
+ return other.m_error == m_error;
+}
+
+
+
KisImportExportErrorCannotWrite::KisImportExportErrorCannotWrite() : KisImportExportComplexError(QFileDevice::FileError()) { }
KisImportExportErrorCannotWrite::KisImportExportErrorCannotWrite(QFileDevice::FileError error) : KisImportExportComplexError(error) {
KIS_ASSERT_RECOVER_NOOP(error != QFileDevice::NoError);
}
QString KisImportExportErrorCannotWrite::errorMessage() const
{
return i18n("Cannot open file for writing. Reason: %1", qtErrorMessage());
}
+bool KisImportExportErrorCannotWrite::operator==(KisImportExportErrorCannotWrite other)
+{
+ return other.m_error == m_error;
+}
+
+
+
+
+
KisImportExportErrorCode::KisImportExportErrorCode() : errorFieldUsed(None), cannotRead(), cannotWrite() { }
KisImportExportErrorCode::KisImportExportErrorCode(ImportExportCodes::ErrorCodeID id) : errorFieldUsed(CodeId), codeId(id), cannotRead(), cannotWrite() { }
KisImportExportErrorCode::KisImportExportErrorCode(KisImportExportErrorCannotRead error) : errorFieldUsed(CannotRead), cannotRead(error), cannotWrite() { }
KisImportExportErrorCode::KisImportExportErrorCode(KisImportExportErrorCannotWrite error) : errorFieldUsed(CannotWrite), cannotRead(), cannotWrite(error) { }
-
-
-
bool KisImportExportErrorCode::isOk() const
{
// if cannotRead or cannotWrite is "NoError", it means that something is wrong in our code
return errorFieldUsed == CodeId && codeId == ImportExportCodes::OK;
}
bool KisImportExportErrorCode::isCancelled() const
{
return errorFieldUsed == CodeId && codeId == ImportExportCodes::Cancelled;
}
bool KisImportExportErrorCode::isInternalError() const
{
return errorFieldUsed == CodeId && codeId == ImportExportCodes::InternalError;
}
QString KisImportExportErrorCode::errorMessage() const
{
QString internal = i18n("Unexpected error. Please contact developers.");
if (errorFieldUsed == CannotRead) {
return cannotRead.errorMessage();
} else if (errorFieldUsed == CannotWrite) {
return cannotWrite.errorMessage();
} else if (errorFieldUsed == CodeId) {
switch (codeId) {
// Reading
case ImportExportCodes::FileNotExist:
return i18n("The file doesn't exists.");
case ImportExportCodes::NoAccessToRead:
return i18n("Permission denied: Krita is not allowed to read the file.");
case ImportExportCodes::FileFormatIncorrect:
return i18n("The file format cannot be parsed.");
case ImportExportCodes::FormatFeaturesUnsupported:
return i18n("The file format contains unsupported features.");
case ImportExportCodes::FormatColorSpaceUnsupported:
return i18n("The file format contains unsupported color space.");
// Writing
case ImportExportCodes::CannotCreateFile:
return i18n("The file cannot be created.");
case ImportExportCodes::NoAccessToWrite:
return i18n("Permission denied: Krita is not allowed to write to the file.");
case ImportExportCodes::InsufficientMemory:
return i18n("There is not enough memory left to save the file.");
// Both
case ImportExportCodes::Cancelled:
return i18n("The action was cancelled by the user.");
// Other
case ImportExportCodes::Failure:
return i18n("Unknown error.");
case ImportExportCodes::InternalError:
return internal;
// OK
case ImportExportCodes::OK:
return i18n("The action has been completed successfully.");
default:
return internal;
}
}
return internal; // errorFieldUsed = None
}
+bool KisImportExportErrorCode::operator==(KisImportExportErrorCode errorCode)
+{
+ if (errorFieldUsed != errorCode.errorFieldUsed) {
+ return false;
+ }
+ if (errorFieldUsed == CodeId) {
+ return codeId == errorCode.codeId;
+ }
+ if (errorFieldUsed == CannotRead) {
+ return cannotRead == errorCode.cannotRead;
+ }
+ return cannotWrite == errorCode.cannotWrite;
+}
+
+
QDebug operator<<(QDebug d, const KisImportExportErrorCode& errorCode)
{
switch(errorCode.errorFieldUsed) {
case KisImportExportErrorCode::None:
d << "None of the error fields is in use.";
break;
case KisImportExportErrorCode::CannotRead:
d << "Cannot read: " << errorCode.cannotRead.m_error;
break;
case KisImportExportErrorCode::CannotWrite:
d << "Cannot read: " << errorCode.cannotRead.m_error;
break;
case KisImportExportErrorCode::CodeId:
d << "Error code = " << errorCode.codeId;
}
d << " " << errorCode.errorMessage();
return d;
}
diff --git a/libs/ui/KisImportExportErrorCode.h b/libs/ui/KisImportExportErrorCode.h
index 2b57046592..7f9f5d0e02 100644
--- a/libs/ui/KisImportExportErrorCode.h
+++ b/libs/ui/KisImportExportErrorCode.h
@@ -1,155 +1,163 @@
/*
* Copyright (c) 2019 Agata Cacko
*
* 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 of the License, 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef KIS_IMPORT_EXPORT_ERROR_CODES_H
#define KIS_IMPORT_EXPORT_ERROR_CODES_H
#include
#include
#include
#include
#include
namespace ImportExportCodes
{
enum KRITAUI_EXPORT ErrorCodeID
{
InternalError, // error that shouldn't happen; only inside ASSERTS
// Reading
FileNotExist, // there is no file with that name in that location,
NoAccessToRead, // Krita has no reading access to the file,
ErrorWhileReading, // there was an error that occured during reading,
FileFormatIncorrect, // file format cannot be parsed,
FormatFeaturesUnsupported, // file format can be parsed, but some features are unsupported,
FormatColorSpaceUnsupported, // file format can be parsed, but color space of the image is unsupported
// Writing
CannotCreateFile, // file cannot be created
NoAccessToWrite, // Krita has no writing access to the file
ErrorWhileWriting, // there was an error that occured during writing (can be insufficient memory, too, just we don't know)
InsufficientMemory, // there is not enough memory left
// Both
Cancelled, // cancelled by a user
// Other
Failure, // unspecified error
// OK
OK, // everything went ok
};
};
struct KisImportExportErrorCode;
struct KRITAUI_EXPORT KisImportExportComplexError
{
virtual QString errorMessage() const = 0;
KisImportExportComplexError(QFileDevice::FileError error);
friend QDebug operator<<(QDebug d, const KisImportExportErrorCode& errorCode);
+
+
protected:
QString qtErrorMessage() const;
QFileDevice::FileError m_error;
virtual ~KisImportExportComplexError() {}
};
struct KRITAUI_EXPORT KisImportExportErrorCannotWrite : KisImportExportComplexError
{
KisImportExportErrorCannotWrite(QFileDevice::FileError error);
QString errorMessage() const override;
~KisImportExportErrorCannotWrite() { }
+ bool operator==(KisImportExportErrorCannotWrite other);
+
private:
KisImportExportErrorCannotWrite();
//friend KisImportExportErrorCode::KisImportExportErrorCode(KisImportExportErrorCannotWrite code);
friend class KisImportExportErrorCode;
};
struct KRITAUI_EXPORT KisImportExportErrorCannotRead : KisImportExportComplexError
{
KisImportExportErrorCannotRead(QFileDevice::FileError error);
QString errorMessage() const override;
~KisImportExportErrorCannotRead() { }
+ bool operator==(KisImportExportErrorCannotRead other);
+
private:
KisImportExportErrorCannotRead();
//friend KisImportExportErrorCode::KisImportExportErrorCode(KisImportExportErrorCannotRead code);
friend class KisImportExportErrorCode;
};
struct KRITAUI_EXPORT KisImportExportErrorCode
{
public:
// required by kis_async_action_feedback
KisImportExportErrorCode();
KisImportExportErrorCode(ImportExportCodes::ErrorCodeID code);
KisImportExportErrorCode(KisImportExportErrorCannotRead code);
KisImportExportErrorCode(KisImportExportErrorCannotWrite code);
QString errorMessage() const;
bool isOk() const;
bool isCancelled() const;
bool isInternalError() const;
friend QDebug operator<<(QDebug d, const KisImportExportErrorCode& errorCode);
+ bool operator==(KisImportExportErrorCode errorCode);
+
private:
enum ErrorFieldUsed
{
None,
CodeId,
CannotRead,
CannotWrite
};
ErrorFieldUsed errorFieldUsed;
ImportExportCodes::ErrorCodeID codeId;
KisImportExportErrorCannotRead cannotRead;
KisImportExportErrorCannotWrite cannotWrite;
};
KRITAUI_EXPORT QDebug operator<<(QDebug d, const KisImportExportErrorCode& errorCode);
#endif // KIS_IMPORT_EXPORT_ERROR_CODES_H
diff --git a/plugins/impex/exr/exr_converter.cc b/plugins/impex/exr/exr_converter.cc
index 653b3842f8..86664b3c45 100644
--- a/plugins/impex/exr/exr_converter.cc
+++ b/plugins/impex/exr/exr_converter.cc
@@ -1,1417 +1,1407 @@
/*
* Copyright (c) 2005 Adrian Page
* Copyright (c) 2010 Cyrille Berger
*
* 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 of the License, 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "exr_converter.h"
#include
#include
#include
#include
#include
#include
#include "exr_extra_tags.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "kis_iterator_ng.h"
#include
#include
#include
#include
#include
#include
#include "kis_kra_savexml_visitor.h"
#include
// Do not translate!
#define HDR_LAYER "HDR Layer"
template
struct Rgba {
_T_ r;
_T_ g;
_T_ b;
_T_ a;
};
struct ExrGroupLayerInfo;
struct ExrLayerInfoBase {
ExrLayerInfoBase() : colorSpace(0), parent(0) {
}
const KoColorSpace* colorSpace;
QString name;
const ExrGroupLayerInfo* parent;
};
struct ExrGroupLayerInfo : public ExrLayerInfoBase {
ExrGroupLayerInfo() : groupLayer(0) {}
KisGroupLayerSP groupLayer;
};
enum ImageType {
IT_UNKNOWN,
IT_FLOAT16,
IT_FLOAT32,
IT_UNSUPPORTED
};
struct ExrPaintLayerInfo : public ExrLayerInfoBase {
ExrPaintLayerInfo()
: imageType(IT_UNKNOWN)
{
}
ImageType imageType;
QMap< QString, QString> channelMap; ///< first is either R, G, B or A second is the EXR channel name
struct Remap {
Remap(const QString& _original, const QString& _current) : original(_original), current(_current) {
}
QString original;
QString current;
};
QList< Remap > remappedChannels; ///< this is used to store in the metadata the mapping between exr channel name, and channels used in Krita
void updateImageType(ImageType channelType);
};
void ExrPaintLayerInfo::updateImageType(ImageType channelType)
{
if (imageType == IT_UNKNOWN) {
imageType = channelType;
}
else if (imageType != channelType) {
imageType = IT_UNSUPPORTED;
}
}
struct ExrPaintLayerSaveInfo {
QString name; ///< name of the layer with a "." at the end (ie "group1.group2.layer1.")
KisPaintDeviceSP layerDevice;
KisPaintLayerSP layer;
QList channels;
Imf::PixelType pixelType;
};
struct EXRConverter::Private {
Private()
: doc(0)
, alphaWasModified(false)
, showNotifications(false)
{}
KisImageSP image;
KisDocument *doc;
bool alphaWasModified;
bool showNotifications;
QString errorMessage;
template
void unmultiplyAlpha(typename WrapperType::pixel_type *pixel);
template
void decodeData4(Imf::InputFile& file, ExrPaintLayerInfo& info, KisPaintLayerSP layer, int width, int xstart, int ystart, int height, Imf::PixelType ptype);
template
void decodeData1(Imf::InputFile& file, ExrPaintLayerInfo& info, KisPaintLayerSP layer, int width, int xstart, int ystart, int height, Imf::PixelType ptype);
QDomDocument loadExtraLayersInfo(const Imf::Header &header);
bool checkExtraLayersInfoConsistent(const QDomDocument &doc, std::set exrLayerNames);
void makeLayerNamesUnique(QList& informationObjects);
void recBuildPaintLayerSaveInfo(QList& informationObjects, const QString& name, KisGroupLayerSP parent);
void reportLayersNotSaved(const QSet &layersNotSaved);
QString fetchExtraLayersInfo(QList& informationObjects);
};
EXRConverter::EXRConverter(KisDocument *doc, bool showNotifications)
: d(new Private)
{
d->doc = doc;
d->showNotifications = showNotifications;
// Set thread count for IlmImf library
Imf::setGlobalThreadCount(QThread::idealThreadCount());
dbgFile << "EXR Threadcount was set to: " << QThread::idealThreadCount();
}
EXRConverter::~EXRConverter()
{
}
ImageType imfTypeToKisType(Imf::PixelType type)
{
switch (type) {
case Imf::UINT:
case Imf::NUM_PIXELTYPES:
return IT_UNSUPPORTED;
case Imf::HALF:
return IT_FLOAT16;
case Imf::FLOAT:
return IT_FLOAT32;
default:
qFatal("Out of bound enum");
return IT_UNKNOWN;
}
}
const KoColorSpace* kisTypeToColorSpace(QString model, ImageType imageType)
{
const QString profileName = KisConfig(false).readEntry("ExrDefaultColorProfile", KoColorSpaceRegistry::instance()->defaultProfileForColorSpace(model));
switch (imageType) {
case IT_FLOAT16:
return KoColorSpaceRegistry::instance()->colorSpace(model, Float16BitsColorDepthID.id(), profileName);
case IT_FLOAT32:
return KoColorSpaceRegistry::instance()->colorSpace(model, Float32BitsColorDepthID.id(), profileName);
case IT_UNKNOWN:
case IT_UNSUPPORTED:
return 0;
default:
qFatal("Out of bound enum");
return 0;
}
}
template
static inline T alphaEpsilon()
{
return static_cast(HALF_EPSILON);
}
template
static inline T alphaNoiseThreshold()
{
return static_cast(0.01); // 1%
}
static inline bool qFuzzyCompare(half p1, half p2)
{
return std::abs(p1 - p2) < float(HALF_EPSILON);
}
static inline bool qFuzzyIsNull(half h)
{
return std::abs(h) < float(HALF_EPSILON);
}
template
struct RgbPixelWrapper
{
typedef T channel_type;
typedef Rgba pixel_type;
RgbPixelWrapper(Rgba &_pixel) : pixel(_pixel) {}
inline T alpha() const {
return pixel.a;
}
inline bool checkMultipliedColorsConsistent() const {
return !(std::abs(pixel.a) < alphaEpsilon() &&
(!qFuzzyIsNull(pixel.r) ||
!qFuzzyIsNull(pixel.g) ||
!qFuzzyIsNull(pixel.b)));
}
inline bool checkUnmultipliedColorsConsistent(const Rgba &mult) const {
const T alpha = std::abs(pixel.a);
return alpha >= alphaNoiseThreshold() ||
(qFuzzyCompare(T(pixel.r * alpha), mult.r) &&
qFuzzyCompare(T(pixel.g * alpha), mult.g) &&
qFuzzyCompare(T(pixel.b * alpha), mult.b));
}
inline void setUnmultiplied(const Rgba &mult, T newAlpha) {
const T absoluteAlpha = std::abs(newAlpha);
pixel.r = mult.r / absoluteAlpha;
pixel.g = mult.g / absoluteAlpha;
pixel.b = mult.b / absoluteAlpha;
pixel.a = newAlpha;
}
Rgba &pixel;
};
template
struct GrayPixelWrapper
{
typedef T channel_type;
typedef typename KoGrayTraits::Pixel pixel_type;
GrayPixelWrapper(pixel_type &_pixel) : pixel(_pixel) {}
inline T alpha() const {
return pixel.alpha;
}
inline bool checkMultipliedColorsConsistent() const {
return !(std::abs(pixel.alpha) < alphaEpsilon() &&
!qFuzzyIsNull(pixel.gray));
}
inline bool checkUnmultipliedColorsConsistent(const pixel_type &mult) const {
const T alpha = std::abs(pixel.alpha);
return alpha >= alphaNoiseThreshold() ||
qFuzzyCompare(T(pixel.gray * alpha), mult.gray);
}
inline void setUnmultiplied(const pixel_type &mult, T newAlpha) {
const T absoluteAlpha = std::abs(newAlpha);
pixel.gray = mult.gray / absoluteAlpha;
pixel.alpha = newAlpha;
}
pixel_type &pixel;
};
template
void EXRConverter::Private::unmultiplyAlpha(typename WrapperType::pixel_type *pixel)
{
typedef typename WrapperType::pixel_type pixel_type;
typedef typename WrapperType::channel_type channel_type;
WrapperType srcPixel(*pixel);
if (!srcPixel.checkMultipliedColorsConsistent()) {
channel_type newAlpha = srcPixel.alpha();
pixel_type __dstPixelData;
WrapperType dstPixel(__dstPixelData);
/**
* Division by a tiny alpha may result in an overflow of half
* value. That is why we use safe iterational approach.
*/
while (1) {
dstPixel.setUnmultiplied(srcPixel.pixel, newAlpha);
if (dstPixel.checkUnmultipliedColorsConsistent(srcPixel.pixel)) {
break;
}
newAlpha += alphaEpsilon();
alphaWasModified = true;
}
*pixel = dstPixel.pixel;
} else if (srcPixel.alpha() > 0.0) {
srcPixel.setUnmultiplied(srcPixel.pixel, srcPixel.alpha());
}
}
template
void multiplyAlpha(Pixel *pixel)
{
if (alphaPos >= 0) {
T alpha = pixel->data[alphaPos];
if (alpha > 0.0) {
for (int i = 0; i < size; ++i) {
if (i != alphaPos) {
pixel->data[i] *= alpha;
}
}
pixel->data[alphaPos] = alpha;
}
}
}
template
void EXRConverter::Private::decodeData4(Imf::InputFile& file, ExrPaintLayerInfo& info, KisPaintLayerSP layer, int width, int xstart, int ystart, int height, Imf::PixelType ptype)
{
typedef Rgba<_T_> Rgba;
QVector pixels(width * height);
bool hasAlpha = info.channelMap.contains("A");
Imf::FrameBuffer frameBuffer;
Rgba* frameBufferData = (pixels.data()) - xstart - ystart * width;
frameBuffer.insert(info.channelMap["R"].toLatin1().constData(),
Imf::Slice(ptype, (char *) &frameBufferData->r,
sizeof(Rgba) * 1,
sizeof(Rgba) * width));
frameBuffer.insert(info.channelMap["G"].toLatin1().constData(),
Imf::Slice(ptype, (char *) &frameBufferData->g,
sizeof(Rgba) * 1,
sizeof(Rgba) * width));
frameBuffer.insert(info.channelMap["B"].toLatin1().constData(),
Imf::Slice(ptype, (char *) &frameBufferData->b,
sizeof(Rgba) * 1,
sizeof(Rgba) * width));
if (hasAlpha) {
frameBuffer.insert(info.channelMap["A"].toLatin1().constData(),
Imf::Slice(ptype, (char *) &frameBufferData->a,
sizeof(Rgba) * 1,
sizeof(Rgba) * width));
}
file.setFrameBuffer(frameBuffer);
file.readPixels(ystart, height + ystart - 1);
Rgba *rgba = pixels.data();
QRect paintRegion(xstart, ystart, width, height);
KisSequentialIterator it(layer->paintDevice(), paintRegion);
while (it.nextPixel()) {
if (hasAlpha) {
unmultiplyAlpha >(rgba);
}
typename KoRgbTraits<_T_>::Pixel* dst = reinterpret_cast::Pixel*>(it.rawData());
dst->red = rgba->r;
dst->green = rgba->g;
dst->blue = rgba->b;
if (hasAlpha) {
dst->alpha = rgba->a;
} else {
dst->alpha = 1.0;
}
++rgba;
}
}
template
void EXRConverter::Private::decodeData1(Imf::InputFile& file, ExrPaintLayerInfo& info, KisPaintLayerSP layer, int width, int xstart, int ystart, int height, Imf::PixelType ptype)
{
typedef typename GrayPixelWrapper<_T_>::channel_type channel_type;
typedef typename GrayPixelWrapper<_T_>::pixel_type pixel_type;
KIS_ASSERT_RECOVER_RETURN(
layer->paintDevice()->colorSpace()->colorModelId() == GrayAColorModelID);
QVector pixels(width * height);
Q_ASSERT(info.channelMap.contains("G"));
dbgFile << "G -> " << info.channelMap["G"];
bool hasAlpha = info.channelMap.contains("A");
dbgFile << "Has Alpha:" << hasAlpha;
Imf::FrameBuffer frameBuffer;
pixel_type* frameBufferData = (pixels.data()) - xstart - ystart * width;
frameBuffer.insert(info.channelMap["G"].toLatin1().constData(),
Imf::Slice(ptype, (char *) &frameBufferData->gray,
sizeof(pixel_type) * 1,
sizeof(pixel_type) * width));
if (hasAlpha) {
frameBuffer.insert(info.channelMap["A"].toLatin1().constData(),
Imf::Slice(ptype, (char *) &frameBufferData->alpha,
sizeof(pixel_type) * 1,
sizeof(pixel_type) * width));
}
file.setFrameBuffer(frameBuffer);
file.readPixels(ystart, height + ystart - 1);
pixel_type *srcPtr = pixels.data();
QRect paintRegion(xstart, ystart, width, height);
KisSequentialIterator it(layer->paintDevice(), paintRegion);
do {
if (hasAlpha) {
unmultiplyAlpha >(srcPtr);
}
pixel_type* dstPtr = reinterpret_cast(it.rawData());
dstPtr->gray = srcPtr->gray;
dstPtr->alpha = hasAlpha ? srcPtr->alpha : channel_type(1.0);
++srcPtr;
} while (it.nextPixel());
}
bool recCheckGroup(const ExrGroupLayerInfo& group, QStringList list, int idx1, int idx2)
{
if (idx1 > idx2) return true;
if (group.name == list[idx2]) {
return recCheckGroup(*group.parent, list, idx1, idx2 - 1);
}
return false;
}
ExrGroupLayerInfo* searchGroup(QList* groups, QStringList list, int idx1, int idx2)
{
if (idx1 > idx2) {
return 0;
}
// Look for the group
for (int i = 0; i < groups->size(); ++i) {
if (recCheckGroup(groups->at(i), list, idx1, idx2)) {
return &(*groups)[i];
}
}
// Create the group
ExrGroupLayerInfo info;
info.name = list.at(idx2);
info.parent = searchGroup(groups, list, idx1, idx2 - 1);
groups->append(info);
return &groups->last();
}
QDomDocument EXRConverter::Private::loadExtraLayersInfo(const Imf::Header &header)
{
const Imf::StringAttribute *layersInfoAttribute =
header.findTypedAttribute(EXR_KRITA_LAYERS);
if (!layersInfoAttribute) return QDomDocument();
QString layersInfoString = QString::fromUtf8(layersInfoAttribute->value().c_str());
QDomDocument doc;
doc.setContent(layersInfoString);
return doc;
}
bool EXRConverter::Private::checkExtraLayersInfoConsistent(const QDomDocument &doc, std::set exrLayerNames)
{
std::set extraInfoLayers;
QDomElement root = doc.documentElement();
KIS_ASSERT_RECOVER(!root.isNull() && root.hasChildNodes()) { return false; };
QDomElement el = root.firstChildElement();
while(!el.isNull()) {
KIS_ASSERT_RECOVER(el.hasAttribute(EXR_NAME)) { return false; };
QString layerName = el.attribute(EXR_NAME).toUtf8();
if (layerName != QString(HDR_LAYER)) {
extraInfoLayers.insert(el.attribute(EXR_NAME).toUtf8().constData());
}
el = el.nextSiblingElement();
}
bool result = (extraInfoLayers == exrLayerNames);
if (!result) {
dbgKrita << "WARINING: Krita EXR extra layers info is inconsistent!";
dbgKrita << ppVar(extraInfoLayers.size()) << ppVar(exrLayerNames.size());
std::set::const_iterator it1 = extraInfoLayers.begin();
std::set::const_iterator it2 = exrLayerNames.begin();
std::set::const_iterator end1 = extraInfoLayers.end();
for (; it1 != end1; ++it1, ++it2) {
dbgKrita << it1->c_str() << it2->c_str();
}
}
return result;
}
KisImportExportErrorCode EXRConverter::decode(const QString &filename)
{
try {
Imf::InputFile file(QFile::encodeName(filename));
Imath::Box2i dw = file.header().dataWindow();
Imath::Box2i displayWindow = file.header().displayWindow();
int width = dw.max.x - dw.min.x + 1;
int height = dw.max.y - dw.min.y + 1;
int dx = dw.min.x;
int dy = dw.min.y;
// Display the attributes of a file
for (Imf::Header::ConstIterator it = file.header().begin();
it != file.header().end(); ++it) {
dbgFile << "Attribute: " << it.name() << " type: " << it.attribute().typeName();
}
// fetch Krita's extra layer info, which might have been stored previously
QDomDocument extraLayersInfo = d->loadExtraLayersInfo(file.header());
// Construct the list of LayerInfo
QList informationObjects;
QList groups;
ImageType imageType = IT_UNKNOWN;
const Imf::ChannelList &channels = file.header().channels();
std::set layerNames;
channels.layers(layerNames);
if (!extraLayersInfo.isNull() &&
!d->checkExtraLayersInfoConsistent(extraLayersInfo, layerNames)) {
// it is inconsistent anyway
extraLayersInfo = QDomDocument();
}
// Check if there are A, R, G, B channels
dbgFile << "Checking for ARGB channels, they can occur in single-layer _or_ multi-layer images:";
ExrPaintLayerInfo info;
bool topLevelRGBFound = false;
info.name = HDR_LAYER;
QStringList topLevelChannelNames = QStringList() << "A" << "R" << "G" << "B"
<< ".A" << ".R" << ".G" << ".B"
<< "A." << "R." << "G." << "B."
<< "A." << "R." << "G." << "B."
<< ".alpha" << ".red" << ".green" << ".blue";
for (Imf::ChannelList::ConstIterator i = channels.begin(); i != channels.end(); ++i) {
const Imf::Channel &channel = i.channel();
dbgFile << "Channel name = " << i.name() << " type = " << channel.type;
QString qname = i.name();
if (topLevelChannelNames.contains(qname)) {
topLevelRGBFound = true;
dbgFile << "Found top-level channel" << qname;
info.channelMap[qname] = qname;
info.updateImageType(imfTypeToKisType(channel.type));
}
// Channel names that don't contain a "." or that contain a
// "." only at the beginning or at the end are not considered
// to be part of any layer.
else if (!qname.contains('.')
|| !qname.mid(1).contains('.')
|| !qname.left(qname.size() - 1).contains('.')) {
warnFile << "Found a top-level channel that is not part of the rendered image" << qname << ". Krita will not load this channel.";
}
}
if (topLevelRGBFound) {
dbgFile << "Toplevel layer" << info.name << ":Image type:" << imageType << "Layer type" << info.imageType;
informationObjects.push_back(info);
imageType = info.imageType;
}
dbgFile << "Extra layers:" << layerNames.size();
for (std::set::const_iterator i = layerNames.begin();i != layerNames.end(); ++i) {
info = ExrPaintLayerInfo();
dbgFile << "layer name = " << i->c_str();
info.name = i->c_str();
Imf::ChannelList::ConstIterator layerBegin, layerEnd;
channels.channelsInLayer(*i, layerBegin, layerEnd);
for (Imf::ChannelList::ConstIterator j = layerBegin;
j != layerEnd; ++j) {
const Imf::Channel &channel = j.channel();
info.updateImageType(imfTypeToKisType(channel.type));
QString qname = j.name();
QStringList list = qname.split('.');
QString layersuffix = list.last();
dbgFile << "\tchannel " << j.name() << "suffix" << layersuffix << " type = " << channel.type;
// Nuke writes the channels for sublayers as .red instead of .R, so convert those.
// See https://bugs.kde.org/show_bug.cgi?id=393771
if (topLevelChannelNames.contains("." + layersuffix)) {
layersuffix = layersuffix.at(0).toUpper();
}
dbgFile << "\t\tsuffix" << layersuffix;
if (list.size() > 1) {
info.name = list[list.size()-2];
info.parent = searchGroup(&groups, list, 0, list.size() - 3);
}
info.channelMap[layersuffix] = qname;
}
if (info.imageType != IT_UNKNOWN && info.imageType != IT_UNSUPPORTED) {
informationObjects.push_back(info);
if (imageType < info.imageType) {
imageType = info.imageType;
}
}
}
dbgFile << "File has" << informationObjects.size() << "layer(s)";
// Set the colorspaces
for (int i = 0; i < informationObjects.size(); ++i) {
ExrPaintLayerInfo& info = informationObjects[i];
QString modelId;
if (info.channelMap.size() == 1) {
modelId = GrayAColorModelID.id();
QString key = info.channelMap.begin().key();
if (key != "G") {
info.remappedChannels.push_back(ExrPaintLayerInfo::Remap(key, "G"));
QString channel = info.channelMap.begin().value();
info.channelMap.clear();
info.channelMap["G"] = channel;
}
}
else if (info.channelMap.size() == 2) {
modelId = GrayAColorModelID.id();
QMap::const_iterator it = info.channelMap.constBegin();
QMap::const_iterator end = info.channelMap.constEnd();
QString failingChannelKey;
for (; it != end; ++it) {
if (it.key() != "G" && it.key() != "A") {
failingChannelKey = it.key();
break;
}
}
info.remappedChannels.push_back(
ExrPaintLayerInfo::Remap(failingChannelKey, "G"));
QString failingChannelValue = info.channelMap[failingChannelKey];
info.channelMap.remove(failingChannelKey);
info.channelMap["G"] = failingChannelValue;
}
else if (info.channelMap.size() == 3 || info.channelMap.size() == 4) {
if (info.channelMap.contains("R") && info.channelMap.contains("G") && info.channelMap.contains("B")) {
modelId = RGBAColorModelID.id();
}
else if (info.channelMap.contains("X") && info.channelMap.contains("Y") && info.channelMap.contains("Z")) {
modelId = XYZAColorModelID.id();
QMap newChannelMap;
if (info.channelMap.contains("W")) {
newChannelMap["A"] = info.channelMap["W"];
info.remappedChannels.push_back(ExrPaintLayerInfo::Remap("W", "A"));
info.remappedChannels.push_back(ExrPaintLayerInfo::Remap("X", "X"));
info.remappedChannels.push_back(ExrPaintLayerInfo::Remap("Y", "Y"));
info.remappedChannels.push_back(ExrPaintLayerInfo::Remap("Z", "Z"));
} else if (info.channelMap.contains("A")) {
newChannelMap["A"] = info.channelMap["A"];
}
// The decode function expect R, G, B in the channel map
newChannelMap["B"] = info.channelMap["X"];
newChannelMap["G"] = info.channelMap["Y"];
newChannelMap["R"] = info.channelMap["Z"];
info.channelMap = newChannelMap;
}
else {
modelId = RGBAColorModelID.id();
QMap newChannelMap;
QMap::iterator it = info.channelMap.begin();
newChannelMap["R"] = it.value();
info.remappedChannels.push_back(ExrPaintLayerInfo::Remap(it.key(), "R"));
++it;
newChannelMap["G"] = it.value();
info.remappedChannels.push_back(ExrPaintLayerInfo::Remap(it.key(), "G"));
++it;
newChannelMap["B"] = it.value();
info.remappedChannels.push_back(ExrPaintLayerInfo::Remap(it.key(), "B"));
if (info.channelMap.size() == 4) {
++it;
newChannelMap["A"] = it.value();
info.remappedChannels.push_back(ExrPaintLayerInfo::Remap(it.key(), "A"));
}
info.channelMap = newChannelMap;
}
}
else {
dbgFile << info.name << "has" << info.channelMap.size() << "channels, and we don't know what to do.";
}
if (!modelId.isEmpty()) {
info.colorSpace = kisTypeToColorSpace(modelId, info.imageType);
}
}
// Get colorspace
dbgFile << "Image type = " << imageType;
const KoColorSpace* colorSpace = kisTypeToColorSpace(RGBAColorModelID.id(), imageType);
if (!colorSpace) return ImportExportCodes::FormatColorSpaceUnsupported;
dbgFile << "Colorspace: " << colorSpace->name();
// Set the colorspace on all groups
for (int i = 0; i < groups.size(); ++i) {
ExrGroupLayerInfo& info = groups[i];
info.colorSpace = colorSpace;
}
// Create the image
// Make sure the created image is the same size as the displayWindow since
// the dataWindow can be cropped in some cases.
int displayWidth = displayWindow.max.x - displayWindow.min.x + 1;
int displayHeight = displayWindow.max.y - displayWindow.min.y + 1;
d->image = new KisImage(d->doc->createUndoStore(), displayWidth, displayHeight, colorSpace, "");
if (!d->image) {
return ImportExportCodes::Failure;
}
/**
* EXR semi-transparent images are expected to be rendered on
* black to ensure correctness of the light model
*/
d->image->setDefaultProjectionColor(KoColor(Qt::black, colorSpace));
// Create group layers
for (int i = 0; i < groups.size(); ++i) {
ExrGroupLayerInfo& info = groups[i];
Q_ASSERT(info.parent == 0 || info.parent->groupLayer);
KisGroupLayerSP groupLayerParent = (info.parent) ? info.parent->groupLayer : d->image->rootLayer();
info.groupLayer = new KisGroupLayer(d->image, info.name, OPACITY_OPAQUE_U8);
d->image->addNode(info.groupLayer, groupLayerParent);
}
// Load the layers
for (int i = informationObjects.size() - 1; i >= 0; --i) {
ExrPaintLayerInfo& info = informationObjects[i];
if (info.colorSpace) {
dbgFile << "Decoding " << info.name << " with " << info.channelMap.size() << " channels, and color space " << info.colorSpace->id();
KisPaintLayerSP layer = new KisPaintLayer(d->image, info.name, OPACITY_OPAQUE_U8, info.colorSpace);
layer->setCompositeOpId(COMPOSITE_OVER);
if (!layer) {
return ImportExportCodes::Failure;
}
switch (info.channelMap.size()) {
case 1:
case 2:
// Decode the data
switch (info.imageType) {
case IT_FLOAT16:
d->decodeData1(file, info, layer, width, dx, dy, height, Imf::HALF);
break;
case IT_FLOAT32:
d->decodeData1(file, info, layer, width, dx, dy, height, Imf::FLOAT);
break;
case IT_UNKNOWN:
case IT_UNSUPPORTED:
qFatal("Impossible error");
}
break;
case 3:
case 4:
// Decode the data
switch (info.imageType) {
case IT_FLOAT16:
d->decodeData4(file, info, layer, width, dx, dy, height, Imf::HALF);
break;
case IT_FLOAT32:
d->decodeData4(file, info, layer, width, dx, dy, height, Imf::FLOAT);
break;
case IT_UNKNOWN:
case IT_UNSUPPORTED:
qFatal("Impossible error");
}
break;
default:
qFatal("Invalid number of channels: %i", info.channelMap.size());
}
// Check if should set the channels
if (!info.remappedChannels.isEmpty()) {
QList values;
Q_FOREACH (const ExrPaintLayerInfo::Remap& remap, info.remappedChannels) {
QMap map;
map["original"] = KisMetaData::Value(remap.original);
map["current"] = KisMetaData::Value(remap.current);
values.append(map);
}
layer->metaData()->addEntry(KisMetaData::Entry(KisMetaData::SchemaRegistry::instance()->create("http://krita.org/exrchannels/1.0/" , "exrchannels"), "channelsmap", values));
}
// Add the layer
KisGroupLayerSP groupLayerParent = (info.parent) ? info.parent->groupLayer : d->image->rootLayer();
d->image->addNode(layer, groupLayerParent);
} else {
dbgFile << "No decoding " << info.name << " with " << info.channelMap.size() << " channels, and lack of a color space";
}
}
// Set projectionColor to opaque
d->image->setDefaultProjectionColor(KoColor(Qt::transparent, colorSpace));
// After reading the image, notify the user about changed alpha.
if (d->alphaWasModified) {
QString msg =
i18nc("@info",
"The image contains pixels with zero alpha channel and non-zero "
"color channels. Krita has modified those pixels to have "
"at least some alpha. The initial values will not "
"be reverted on saving the image back."
"
"
"This will hardly make any visual difference just keep it in mind.");
if (d->showNotifications) {
QMessageBox::information(0, i18nc("@title:window", "EXR image has been modified"), msg);
} else {
warnKrita << "WARNING:" << msg;
}
}
if (!extraLayersInfo.isNull()) {
KisExrLayersSorter sorter(extraLayersInfo, d->image);
}
return ImportExportCodes::OK;
} catch (std::exception &e) {
dbgFile << "Error while reading from the exr file: " << e.what();
KisImportExportAdditionalChecks checks;
if (!checks.doesFileExist(filename)) {
return ImportExportCodes::FileNotExist;
} else if(!checks.isFileReadable(filename)) {
return ImportExportCodes::NoAccessToRead;
} else {
return ImportExportCodes::Failure;
}
}
}
KisImportExportErrorCode EXRConverter::buildImage(const QString &filename)
{
return decode(filename);
}
KisImageSP EXRConverter::image()
{
return d->image;
}
QString EXRConverter::errorMessage() const
{
return d->errorMessage;
}
template
struct ExrPixel_ {
_T_ data[size];
};
class Encoder
{
public:
virtual ~Encoder() {}
virtual void prepareFrameBuffer(Imf::FrameBuffer*, int line) = 0;
virtual void encodeData(int line) = 0;
};
template
class EncoderImpl : public Encoder
{
public:
EncoderImpl(Imf::OutputFile* _file, const ExrPaintLayerSaveInfo* _info, int width) : file(_file), info(_info), pixels(width), m_width(width) {}
~EncoderImpl() override {}
void prepareFrameBuffer(Imf::FrameBuffer*, int line) override;
void encodeData(int line) override;
private:
typedef ExrPixel_<_T_, size> ExrPixel;
Imf::OutputFile* file;
const ExrPaintLayerSaveInfo* info;
QVector pixels;
int m_width;
};
template
void EncoderImpl<_T_, size, alphaPos>::prepareFrameBuffer(Imf::FrameBuffer* frameBuffer, int line)
{
int xstart = 0;
int ystart = 0;
ExrPixel* frameBufferData = (pixels.data()) - xstart - (ystart + line) * m_width;
for (int k = 0; k < size; ++k) {
frameBuffer->insert(info->channels[k].toUtf8(),
Imf::Slice(info->pixelType, (char *) &frameBufferData->data[k],
sizeof(ExrPixel) * 1,
sizeof(ExrPixel) * m_width));
}
}
template
void EncoderImpl<_T_, size, alphaPos>::encodeData(int line)
{
ExrPixel *rgba = pixels.data();
KisHLineConstIteratorSP it = info->layerDevice->createHLineConstIteratorNG(0, line, m_width);
do {
const _T_* dst = reinterpret_cast < const _T_* >(it->oldRawData());
for (int i = 0; i < size; ++i) {
rgba->data[i] = dst[i];
}
if (alphaPos != -1) {
multiplyAlpha<_T_, ExrPixel, size, alphaPos>(rgba);
}
++rgba;
} while (it->nextPixel());
}
Encoder* encoder(Imf::OutputFile& file, const ExrPaintLayerSaveInfo& info, int width)
{
dbgFile << "Create encoder for" << info.name << info.channels << info.layerDevice->colorSpace()->channelCount();
switch (info.layerDevice->colorSpace()->channelCount()) {
case 1: {
if (info.layerDevice->colorSpace()->colorDepthId() == Float16BitsColorDepthID) {
Q_ASSERT(info.pixelType == Imf::HALF);
return new EncoderImpl < half, 1, -1 > (&file, &info, width);
} else if (info.layerDevice->colorSpace()->colorDepthId() == Float32BitsColorDepthID) {
Q_ASSERT(info.pixelType == Imf::FLOAT);
return new EncoderImpl < float, 1, -1 > (&file, &info, width);
}
break;
}
case 2: {
if (info.layerDevice->colorSpace()->colorDepthId() == Float16BitsColorDepthID) {
Q_ASSERT(info.pixelType == Imf::HALF);
return new EncoderImpl(&file, &info, width);
} else if (info.layerDevice->colorSpace()->colorDepthId() == Float32BitsColorDepthID) {
Q_ASSERT(info.pixelType == Imf::FLOAT);
return new EncoderImpl(&file, &info, width);
}
break;
}
case 4: {
if (info.layerDevice->colorSpace()->colorDepthId() == Float16BitsColorDepthID) {
Q_ASSERT(info.pixelType == Imf::HALF);
return new EncoderImpl(&file, &info, width);
} else if (info.layerDevice->colorSpace()->colorDepthId() == Float32BitsColorDepthID) {
Q_ASSERT(info.pixelType == Imf::FLOAT);
return new EncoderImpl(&file, &info, width);
}
break;
}
default:
qFatal("Impossible error");
}
return 0;
}
void encodeData(Imf::OutputFile& file, const QList& informationObjects, int width, int height)
{
QList encoders;
Q_FOREACH (const ExrPaintLayerSaveInfo& info, informationObjects) {
encoders.push_back(encoder(file, info, width));
}
for (int y = 0; y < height; ++y) {
Imf::FrameBuffer frameBuffer;
Q_FOREACH (Encoder* encoder, encoders) {
encoder->prepareFrameBuffer(&frameBuffer, y);
}
file.setFrameBuffer(frameBuffer);
Q_FOREACH (Encoder* encoder, encoders) {
encoder->encodeData(y);
}
file.writePixels(1);
}
qDeleteAll(encoders);
}
KisPaintDeviceSP wrapLayerDevice(KisPaintDeviceSP device)
{
const KoColorSpace *cs = device->colorSpace();
if (cs->colorDepthId() != Float16BitsColorDepthID && cs->colorDepthId() != Float32BitsColorDepthID) {
cs = KoColorSpaceRegistry::instance()->colorSpace(
cs->colorModelId() == GrayAColorModelID ?
GrayAColorModelID.id() : RGBAColorModelID.id(),
Float16BitsColorDepthID.id());
} else if (cs->colorModelId() != GrayColorModelID &&
cs->colorModelId() != RGBAColorModelID) {
cs = KoColorSpaceRegistry::instance()->colorSpace(
RGBAColorModelID.id(),
cs->colorDepthId().id());
}
if (*cs != *device->colorSpace()) {
device = new KisPaintDevice(*device);
device->convertTo(cs);
}
return device;
}
KisImportExportErrorCode EXRConverter::buildFile(const QString &filename, KisPaintLayerSP layer)
{
KIS_ASSERT_RECOVER_RETURN_VALUE(layer, ImportExportCodes::InternalError);
KisImageSP image = layer->image();
KIS_ASSERT_RECOVER_RETURN_VALUE(image, ImportExportCodes::InternalError);
ENTER_FUNCTION() << "(1)";
// Make the header
qint32 height = image->height();
qint32 width = image->width();
Imf::Header header(width, height);
ENTER_FUNCTION() << "(2)";
ExrPaintLayerSaveInfo info;
info.layer = layer;
info.layerDevice = wrapLayerDevice(layer->paintDevice());
ENTER_FUNCTION() << "(3)";
Imf::PixelType pixelType = Imf::NUM_PIXELTYPES;
if (info.layerDevice->colorSpace()->colorDepthId() == Float16BitsColorDepthID) {
pixelType = Imf::HALF;
}
else if (info.layerDevice->colorSpace()->colorDepthId() == Float32BitsColorDepthID) {
pixelType = Imf::FLOAT;
}
ENTER_FUNCTION() << "(4)";
header.channels().insert("R", Imf::Channel(pixelType));
header.channels().insert("G", Imf::Channel(pixelType));
header.channels().insert("B", Imf::Channel(pixelType));
header.channels().insert("A", Imf::Channel(pixelType));
info.channels.push_back("R");
info.channels.push_back("G");
info.channels.push_back("B");
info.channels.push_back("A");
info.pixelType = pixelType;
ENTER_FUNCTION() << "(5)";
// Open file for writing
try {
Imf::OutputFile file(QFile::encodeName(filename), header);
QList informationObjects;
informationObjects.push_back(info);
ENTER_FUNCTION() << "(7)";
encodeData(file, informationObjects, width, height);
ENTER_FUNCTION() << "(6)";
return ImportExportCodes::OK;
} catch(std::exception &e) {
dbgFile << "Exception while writing to exr file: " << e.what();
KisImportExportAdditionalChecks checks;
if (!checks.isFileWriteable(QFile::encodeName(filename))) {
return ImportExportCodes::NoAccessToWrite;
}
return ImportExportCodes::Failure;
}
}
QString remap(const QMap& current2original, const QString& current)
{
if (current2original.contains(current)) {
return current2original[current];
}
return current;
}
void EXRConverter::Private::makeLayerNamesUnique(QList& informationObjects)
{
typedef QMultiMap::iterator> NamesMap;
NamesMap namesMap;
{
QList::iterator it = informationObjects.begin();
QList::iterator end = informationObjects.end();
for (; it != end; ++it) {
namesMap.insert(it->name, it);
}
}
Q_FOREACH (const QString &key, namesMap.keys()) {
if (namesMap.count(key) > 1) {
KIS_ASSERT_RECOVER(key.endsWith(".")) { continue; }
QString strippedName = key.left(key.size() - 1); // trim the ending dot
int nameCounter = 0;
NamesMap::iterator it = namesMap.find(key);
NamesMap::iterator end = namesMap.end();
for (; it != end; ++it) {
QString newName =
QString("%1_%2.")
.arg(strippedName)
.arg(nameCounter++);
it.value()->name = newName;
QList::iterator channelsIt = it.value()->channels.begin();
QList::iterator channelsEnd = it.value()->channels.end();
for (; channelsIt != channelsEnd; ++channelsIt) {
channelsIt->replace(key, newName);
}
}
}
}
}
void EXRConverter::Private::recBuildPaintLayerSaveInfo(QList& informationObjects, const QString& name, KisGroupLayerSP parent)
{
QSet layersNotSaved;
for (uint i = 0; i < parent->childCount(); ++i) {
KisNodeSP node = parent->at(i);
if (KisPaintLayerSP paintLayer = dynamic_cast(node.data())) {
QMap current2original;
if (paintLayer->metaData()->containsEntry(KisMetaData::SchemaRegistry::instance()->create("http://krita.org/exrchannels/1.0/" , "exrchannels"), "channelsmap")) {
const KisMetaData::Entry& entry = paintLayer->metaData()->getEntry(KisMetaData::SchemaRegistry::instance()->create("http://krita.org/exrchannels/1.0/" , "exrchannels"), "channelsmap");
QList< KisMetaData::Value> values = entry.value().asArray();
Q_FOREACH (const KisMetaData::Value& value, values) {
QMap map = value.asStructure();
if (map.contains("original") && map.contains("current")) {
current2original[map["current"].toString()] = map["original"].toString();
}
}
}
ExrPaintLayerSaveInfo info;
info.name = name + paintLayer->name() + '.';
info.layer = paintLayer;
info.layerDevice = wrapLayerDevice(paintLayer->paintDevice());
if (info.name == QString(HDR_LAYER) + ".") {
info.channels.push_back("R");
info.channels.push_back("G");
info.channels.push_back("B");
info.channels.push_back("A");
}
else {
if (paintLayer->colorSpace()->colorModelId() == RGBAColorModelID) {
info.channels.push_back(info.name + remap(current2original, "R"));
info.channels.push_back(info.name + remap(current2original, "G"));
info.channels.push_back(info.name + remap(current2original, "B"));
info.channels.push_back(info.name + remap(current2original, "A"));
}
else if (paintLayer->colorSpace()->colorModelId() == GrayAColorModelID) {
info.channels.push_back(info.name + remap(current2original, "G"));
info.channels.push_back(info.name + remap(current2original, "A"));
}
else if (paintLayer->colorSpace()->colorModelId() == GrayColorModelID) {
info.channels.push_back(info.name + remap(current2original, "G"));
}
else if (paintLayer->colorSpace()->colorModelId() == XYZAColorModelID) {
info.channels.push_back(info.name + remap(current2original, "X"));
info.channels.push_back(info.name + remap(current2original, "Y"));
info.channels.push_back(info.name + remap(current2original, "Z"));
info.channels.push_back(info.name + remap(current2original, "A"));
}
}
if (paintLayer->colorSpace()->colorDepthId() == Float16BitsColorDepthID) {
info.pixelType = Imf::HALF;
}
else if (paintLayer->colorSpace()->colorDepthId() == Float32BitsColorDepthID) {
info.pixelType = Imf::FLOAT;
}
else {
info.pixelType = Imf::NUM_PIXELTYPES;
}
if (info.pixelType < Imf::NUM_PIXELTYPES) {
dbgFile << "Going to save layer" << info.name;
informationObjects.push_back(info);
}
else {
warnFile << "Will not save layer" << info.name;
layersNotSaved << node;
}
}
else if (KisGroupLayerSP groupLayer = dynamic_cast(node.data())) {
recBuildPaintLayerSaveInfo(informationObjects, name + groupLayer->name() + '.', groupLayer);
}
else {
/**
* The EXR can store paint and group layers only. The rest will
* go to /dev/null :(
*/
layersNotSaved.insert(node);
}
}
if (!layersNotSaved.isEmpty()) {
reportLayersNotSaved(layersNotSaved);
}
}
void EXRConverter::Private::reportLayersNotSaved(const QSet &layersNotSaved)
{
QString layersList;
QTextStream textStream(&layersList);
textStream.setCodec("UTF-8");
Q_FOREACH (KisNodeSP node, layersNotSaved) {
textStream << "" << i18nc("@item:unsupported-node-message", "%1 (type: \"%2\")", node->name(), node->metaObject()->className()) << "";
}
QString msg =
i18nc("@info",
"The following layers have a type that is not supported by EXR format:
"
"
"
"these layers have not been saved to the final EXR file
", layersList);
errorMessage = msg;
}
QString EXRConverter::Private::fetchExtraLayersInfo(QList& informationObjects)
{
KIS_ASSERT_RECOVER_NOOP(!informationObjects.isEmpty());
if (informationObjects.size() == 1 && informationObjects[0].name == QString(HDR_LAYER) + ".") {
return QString();
}
QDomDocument doc("krita-extra-layers-info");
doc.appendChild(doc.createElement("root"));
QDomElement rootElement = doc.documentElement();
for (int i = 0; i < informationObjects.size(); i++) {
ExrPaintLayerSaveInfo &info = informationObjects[i];
quint32 unused;
KisSaveXmlVisitor visitor(doc, rootElement, unused, QString(), false);
QDomElement el = visitor.savePaintLayerAttributes(info.layer.data(), doc);
// cut the ending '.'
QString strippedName = info.name.left(info.name.size() - 1);
el.setAttribute(EXR_NAME, strippedName);
rootElement.appendChild(el);
}
return doc.toString();
}
KisImportExportErrorCode EXRConverter::buildFile(const QString &filename, KisGroupLayerSP layer, bool flatten)
{
ENTER_FUNCTION();
KIS_ASSERT_RECOVER_RETURN_VALUE(layer, ImportExportCodes::InternalError);
KisImageSP image = layer->image();
KIS_ASSERT_RECOVER_RETURN_VALUE(image, ImportExportCodes::InternalError);
qint32 height = image->height();
qint32 width = image->width();
Imf::Header header(width, height);
- ENTER_FUNCTION() << "(1)";
-
if (flatten) {
- ENTER_FUNCTION() << "(2)";
KisPaintDeviceSP pd = new KisPaintDevice(*image->projection());
KisPaintLayerSP l = new KisPaintLayer(image, "projection", OPACITY_OPAQUE_U8, pd);
- ENTER_FUNCTION() << "(3)";
return buildFile(filename, l);
}
else {
- ENTER_FUNCTION() << "(4)";
QList informationObjects;
d->recBuildPaintLayerSaveInfo(informationObjects, "", layer);
if(informationObjects.isEmpty()) {
return ImportExportCodes::FormatColorSpaceUnsupported;
}
- ENTER_FUNCTION() << "(5)";
d->makeLayerNamesUnique(informationObjects);
QByteArray extraLayersInfo = d->fetchExtraLayersInfo(informationObjects).toUtf8();
if (!extraLayersInfo.isNull()) {
header.insert(EXR_KRITA_LAYERS, Imf::StringAttribute(extraLayersInfo.constData()));
}
- ENTER_FUNCTION() << "(6)";
dbgFile << informationObjects.size() << " layers to save";
- ENTER_FUNCTION() << "(7)";
Q_FOREACH (const ExrPaintLayerSaveInfo& info, informationObjects) {
if (info.pixelType < Imf::NUM_PIXELTYPES) {
Q_FOREACH (const QString& channel, info.channels) {
dbgFile << channel << " " << info.pixelType;
header.channels().insert(channel.toUtf8().data(), Imf::Channel(info.pixelType));
}
}
}
- ENTER_FUNCTION() << "(8)";
// Open file for writing
try {
Imf::OutputFile file(QFile::encodeName(filename), header);
encodeData(file, informationObjects, width, height);
- ENTER_FUNCTION() << "(9)";
return ImportExportCodes::OK;
} catch(std::exception &e) {
dbgFile << "Exception while writing to exr file: " << e.what();
KisImportExportAdditionalChecks checks;
if (!checks.isFileWriteable(QFile::encodeName(filename))) {
return ImportExportCodes::NoAccessToWrite;
}
return ImportExportCodes::Failure;
}
}
}
void EXRConverter::cancel()
{
warnKrita << "WARNING: Cancelling of an EXR loading is not supported!";
}
diff --git a/plugins/impex/exr/tests/data/readonlyFile.txt b/plugins/impex/exr/tests/data/readonlyFile.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/impex/exr/tests/data/writeonlyFile.txt b/plugins/impex/exr/tests/data/writeonlyFile.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/impex/exr/tests/kis_exr_test.cpp b/plugins/impex/exr/tests/kis_exr_test.cpp
index 3ad1e84221..eb005df761 100644
--- a/plugins/impex/exr/tests/kis_exr_test.cpp
+++ b/plugins/impex/exr/tests/kis_exr_test.cpp
@@ -1,99 +1,110 @@
/*
* Copyright (C) 2007 Cyrille Berger
*
* 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 of the License, 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_exr_test.h"
#include
#include
#include
#include
#include
#include "filestest.h"
#ifndef FILES_DATA_DIR
#error "FILES_DATA_DIR not set. A directory with the data used for testing the importing of files in krita"
#endif
+const QString ExrMimetype = "application/x-extension-exr";
void KisExrTest::testFiles()
{
TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), 5);
}
-void KisExrTest::testWriteonly()
+void KisExrTest::testImportFromWriteonly()
{
- TestUtil::testWriteonly(QString(FILES_DATA_DIR));
+ TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), ExrMimetype);
+}
+
+void KisExrTest::testExportToReadonly()
+{
+ TestUtil::testExportToReadonly(QString(FILES_DATA_DIR), ExrMimetype);
+}
+
+void KisExrTest::testImportIncorrectFormat()
+{
+ TestUtil::testImportIncorrectFormat(QString(FILES_DATA_DIR), ExrMimetype);
}
void KisExrTest::testRoundTrip()
{
QString inputFileName(TestUtil::fetchDataFileLazy("CandleGlass.exr"));
KisDocument *doc1 = KisPart::instance()->createDocument();
doc1->setFileBatchMode(true);
bool r = doc1->importDocument(QUrl::fromLocalFile(inputFileName));
QVERIFY(r);
QVERIFY(doc1->errorMessage().isEmpty());
QVERIFY(doc1->image());
QTemporaryFile savedFile(QDir::tempPath() + QLatin1String("/krita_XXXXXX") + QLatin1String(".exr"));
savedFile.setAutoRemove(true);
savedFile.open();
QString savedFileName(savedFile.fileName());
QString typeName = KisMimeDatabase::mimeTypeForFile(savedFileName, false);
QByteArray mimeType(typeName.toLatin1());
r = doc1->exportDocumentSync(QUrl::fromLocalFile(savedFileName), mimeType);
QVERIFY(r);
QVERIFY(QFileInfo(savedFileName).exists());
{
KisDocument *doc2 = KisPart::instance()->createDocument();
doc2->setFileBatchMode(true);
r = doc2->importDocument(QUrl::fromLocalFile(savedFileName));
QVERIFY(r);
QVERIFY(doc2->errorMessage().isEmpty());
QVERIFY(doc2->image());
doc1->image()->root()->firstChild()->paintDevice()->convertToQImage(0).save("1.png");
doc2->image()->root()->firstChild()->paintDevice()->convertToQImage(0).save("2.png");
QVERIFY(TestUtil::comparePaintDevicesClever(
doc1->image()->root()->firstChild()->paintDevice(),
doc2->image()->root()->firstChild()->paintDevice(),
0.01 /* meaningless alpha */));
delete doc2;
}
savedFile.close();
delete doc1;
}
KISTEST_MAIN(KisExrTest)
diff --git a/plugins/impex/exr/tests/kis_exr_test.h b/plugins/impex/exr/tests/kis_exr_test.h
index 568b5fb61d..7119e96947 100644
--- a/plugins/impex/exr/tests/kis_exr_test.h
+++ b/plugins/impex/exr/tests/kis_exr_test.h
@@ -1,33 +1,35 @@
/*
* Copyright (C) 2007 Cyrille Berger
*
* 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 of the License, 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_EXR_TEST_H_
#define _KIS_EXR_TEST_H_
#include
class KisExrTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testFiles();
- void testWriteonly();
+ void testImportFromWriteonly();
+ void testExportToReadonly();
+ void testImportIncorrectFormat();
void testRoundTrip();
};
#endif
diff --git a/plugins/impex/jpeg/tests/data/incorrectFormatFile.txt b/plugins/impex/jpeg/tests/data/incorrectFormatFile.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/impex/jpeg/tests/data/readonlyFile.txt b/plugins/impex/jpeg/tests/data/readonlyFile.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/plugins/impex/jpeg/tests/kis_jpeg_test.cpp b/plugins/impex/jpeg/tests/kis_jpeg_test.cpp
index d81631faa3..7d84ef5cdc 100644
--- a/plugins/impex/jpeg/tests/kis_jpeg_test.cpp
+++ b/plugins/impex/jpeg/tests/kis_jpeg_test.cpp
@@ -1,64 +1,74 @@
/*
* Copyright (C) 2007 Cyrille Berger
*
* 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 of the License, 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_jpeg_test.h"
#include
#include
#include "kisexiv2/kis_exiv2.h"
#include "filestest.h"
#include "jpeglib.h"
#include
#ifndef FILES_DATA_DIR
#error "FILES_DATA_DIR not set. A directory with the data used for testing the importing of files in krita"
#endif
#ifndef JPEG_LIB_VERSION
#error "JPEG_LIB_VERSION not set. libjpeg should set it."
#endif
const QString JpegMimetype = "image/jpeg";
void KisJpegTest::testFiles()
{
KisExiv2::initialize();
/**
* Different versions of JPEG library may produce a bit different
* result, so just compare in a weak way, i.e, only the size for real
*/
const int fuzziness = 1;
const int maxNumFailingPixels = 2592 * 1952; // All pixels can be different...
const bool showDebug = false; // No need to write down all pixels that are different since all of them can be.
if (JPEG_LIB_VERSION == 80){
TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), "_80", fuzziness, maxNumFailingPixels, showDebug);
}else {
TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), fuzziness, maxNumFailingPixels, showDebug);
}
}
-void KisJpegTest::testWriteonly()
+void KisJpegTest::testImportFromWriteonly()
{
- TestUtil::testWriteonly(QString(FILES_DATA_DIR), JpegMimetype);
+ TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), JpegMimetype);
}
+void KisJpegTest::testExportToReadonly()
+{
+ TestUtil::testExportToReadonly(QString(FILES_DATA_DIR), JpegMimetype);
+}
+
+
+void KisJpegTest::testImportIncorrectFormat()
+{
+ TestUtil::testImportIncorrectFormat(QString(FILES_DATA_DIR), JpegMimetype);
+}
KISTEST_MAIN(KisJpegTest)
diff --git a/plugins/impex/jpeg/tests/kis_jpeg_test.h b/plugins/impex/jpeg/tests/kis_jpeg_test.h
index ef64b07c17..dfc815be3c 100644
--- a/plugins/impex/jpeg/tests/kis_jpeg_test.h
+++ b/plugins/impex/jpeg/tests/kis_jpeg_test.h
@@ -1,32 +1,34 @@
/*
* Copyright (C) 2007 Cyrille Berger
*
* 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 of the License, 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _KIS_JPEG_TEST_H_
#define _KIS_JPEG_TEST_H_
#include
class KisJpegTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testFiles();
- void testWriteonly();
+ void testImportFromWriteonly();
+ void testExportToReadonly();
+ void testImportIncorrectFormat();
};
#endif
diff --git a/plugins/impex/png/tests/kis_png_test.cpp b/plugins/impex/png/tests/kis_png_test.cpp
index 01d4842110..2259cb7822 100644
--- a/plugins/impex/png/tests/kis_png_test.cpp
+++ b/plugins/impex/png/tests/kis_png_test.cpp
@@ -1,162 +1,162 @@
/*
* Copyright (C) 2007 Cyrille Berger
*
* 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 of the License, 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_png_test.h"
#include
#include
#include "filestest.h"
#include
#ifndef FILES_DATA_DIR
#error "FILES_DATA_DIR not set. A directory with the data used for testing the importing of files in krita"
#endif
const QString PngMimetype = "image/png";
void KisPngTest::testFiles()
{
TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), 1);
}
void KisPngTest::testWriteonly()
{
- TestUtil::testWriteonly(QString(FILES_DATA_DIR), PngMimetype);
+ TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), PngMimetype);
}
void roudTripHdrImage(const KoColorSpace *savingColorSpace)
{
qDebug() << "Test saving" << savingColorSpace->id() << savingColorSpace->profile()->name();
const KoColorSpace * scRGBF32 =
KoColorSpaceRegistry::instance()->colorSpace(
RGBAColorModelID.id(),
Float32BitsColorDepthID.id(),
KoColorSpaceRegistry::instance()->p709G10Profile());
KoColor fillColor(scRGBF32);
float *pixelPtr = reinterpret_cast(fillColor.data());
pixelPtr[0] = 2.7;
pixelPtr[1] = 1.6;
pixelPtr[2] = 0.8;
pixelPtr[3] = 0.9;
{
QScopedPointer doc(KisPart::instance()->createDocument());
KisImageSP image = new KisImage(0, 3, 3, scRGBF32, "png test");
KisPaintLayerSP paintLayer0 = new KisPaintLayer(image, "paint0", OPACITY_OPAQUE_U8);
paintLayer0->paintDevice()->fill(image->bounds(), fillColor);
image->addNode(paintLayer0, image->root());
// convert image color space before saving
image->convertImageColorSpace(savingColorSpace, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags());
image->waitForDone();
KisImportExportManager manager(doc.data());
doc->setFileBatchMode(true);
doc->setCurrentImage(image);
KisPropertiesConfigurationSP exportConfiguration = new KisPropertiesConfiguration();
exportConfiguration->setProperty("saveAsHDR", true);
exportConfiguration->setProperty("saveSRGBProfile", false);
exportConfiguration->setProperty("forceSRGB", false);
doc->exportDocumentSync(QUrl::fromLocalFile("test.png"), "image/png", exportConfiguration);
}
{
QScopedPointer doc(KisPart::instance()->createDocument());
KisImportExportManager manager(doc.data());
doc->setFileBatchMode(true);
KisImportExportErrorCode loadingStatus =
manager.importDocument("test.png", QString());
QVERIFY(loadingStatus.isOk());
KisImageSP image = doc->image();
image->initialRefreshGraph();
KoColor resultColor;
// qDebug() << ppVar(image->colorSpace()) << image->colorSpace()->profile()->name();
// image->projection()->pixel(1, 1, &resultColor);
// qDebug() << ppVar(resultColor);
image->convertImageColorSpace(scRGBF32, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags());
image->waitForDone();
image->projection()->pixel(1, 1, &resultColor);
// qDebug() << ppVar(resultColor);
const float tolerance = savingColorSpace->colorDepthId() == Integer8BitsColorDepthID ? 0.02 : 0.01;
bool resultIsValid = true;
float *resultPtr = reinterpret_cast(resultColor.data());
for (int i = 0; i < 4; i++) {
resultIsValid &= qAbs(resultPtr[i] - pixelPtr[i]) < tolerance;
}
if (!resultIsValid) {
qDebug() << ppVar(fillColor) << ppVar(resultColor);
}
QVERIFY(resultIsValid);
}
}
void KisPngTest::testSaveHDR()
{
QVector colorDepthIds;
colorDepthIds << Float16BitsColorDepthID;
colorDepthIds << Float32BitsColorDepthID;
QVector profiles;
profiles << KoColorSpaceRegistry::instance()->p709G10Profile();
profiles << KoColorSpaceRegistry::instance()->p2020G10Profile();
profiles << KoColorSpaceRegistry::instance()->p2020PQProfile();
Q_FOREACH(const KoID &depth, colorDepthIds) {
Q_FOREACH(const KoColorProfile *profile, profiles) {
roudTripHdrImage(
KoColorSpaceRegistry::instance()->colorSpace(
RGBAColorModelID.id(),
depth.id(),
profile));
}
}
roudTripHdrImage(
KoColorSpaceRegistry::instance()->colorSpace(
RGBAColorModelID.id(),
Integer16BitsColorDepthID.id(),
KoColorSpaceRegistry::instance()->p2020PQProfile()));
roudTripHdrImage(
KoColorSpaceRegistry::instance()->colorSpace(
RGBAColorModelID.id(),
Integer8BitsColorDepthID.id(),
KoColorSpaceRegistry::instance()->p2020PQProfile()));
}
KISTEST_MAIN(KisPngTest)
diff --git a/sdk/tests/filestest.h b/sdk/tests/filestest.h
index c15ff85496..065895321a 100644
--- a/sdk/tests/filestest.h
+++ b/sdk/tests/filestest.h
@@ -1,170 +1,265 @@
/*
* Copyright (C) 2007 Cyrille Berger
*
* 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 of the License, 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef FILESTEST
#define FILESTEST
#include "testutil.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
namespace TestUtil
{
void testFiles(const QString& _dirname, const QStringList& exclusions, const QString &resultSuffix = QString(), int fuzzy = 0, int maxNumFailingPixels = 0, bool showDebug = true)
{
QDir dirSources(_dirname);
QStringList failuresFileInfo;
QStringList failuresDocImage;
QStringList failuresCompare;
Q_FOREACH (QFileInfo sourceFileInfo, dirSources.entryInfoList()) {
qDebug() << sourceFileInfo.fileName();
if (exclusions.indexOf(sourceFileInfo.fileName()) > -1) {
continue;
}
if (!sourceFileInfo.isHidden() && !sourceFileInfo.isDir()) {
QFileInfo resultFileInfo(QString(FILES_DATA_DIR) + "/results/" + sourceFileInfo.fileName() + resultSuffix + ".png");
if (!resultFileInfo.exists()) {
failuresFileInfo << resultFileInfo.fileName();
continue;
}
KisDocument *doc = qobject_cast(KisPart::instance()->createDocument());
KisImportExportManager manager(doc);
doc->setFileBatchMode(true);
KisImportExportErrorCode status = manager.importDocument(sourceFileInfo.absoluteFilePath(), QString());
Q_UNUSED(status);
if (!doc->image()) {
failuresDocImage << sourceFileInfo.fileName();
continue;
}
QString id = doc->image()->colorSpace()->id();
if (id != "GRAYA" && id != "GRAYAU16" && id != "RGBA" && id != "RGBA16") {
dbgKrita << "Images need conversion";
doc->image()->convertImageColorSpace(KoColorSpaceRegistry::instance()->rgb8(),
KoColorConversionTransformation::IntentAbsoluteColorimetric,
KoColorConversionTransformation::NoOptimization);
}
qApp->processEvents();
doc->image()->waitForDone();
QImage sourceImage = doc->image()->projection()->convertToQImage(0, doc->image()->bounds());
QImage resultImage(resultFileInfo.absoluteFilePath());
resultImage = resultImage.convertToFormat(QImage::Format_ARGB32);
sourceImage = sourceImage.convertToFormat(QImage::Format_ARGB32);
QPoint pt;
if (!TestUtil::compareQImages(pt, resultImage, sourceImage, fuzzy, fuzzy, maxNumFailingPixels, showDebug)) {
failuresCompare << sourceFileInfo.fileName() + ": " + QString("Pixel (%1,%2) has different values").arg(pt.x()).arg(pt.y()).toLatin1();
sourceImage.save(sourceFileInfo.fileName() + ".png");
resultImage.save(resultFileInfo.fileName() + ".expected.png");
continue;
}
delete doc;
}
}
if (failuresCompare.isEmpty() && failuresDocImage.isEmpty() && failuresFileInfo.isEmpty()) {
return;
}
qWarning() << "Comparison failures: " << failuresCompare;
qWarning() << "No image failures: " << failuresDocImage;
qWarning() << "No comparison image: " << failuresFileInfo;
QFAIL("Failed testing files");
}
-void testWriteonly(const QString& _dirname, QString mimetype = "")
+void prepareFile(QFileInfo sourceFileInfo, bool removePermissionToWrite, bool removePermissionToRead)
{
- QString writeonlyFilename = _dirname + "writeonlyFile.txt";
- QFileInfo sourceFileInfo(writeonlyFilename);
QFileDevice::Permissions permissionsBefore;
if (sourceFileInfo.exists()) {
permissionsBefore = QFile::permissions(sourceFileInfo.absoluteFilePath());
ENTER_FUNCTION() << permissionsBefore;
} else {
- QFile file(writeonlyFilename);
+ QFile file(sourceFileInfo.absoluteFilePath());
file.open(QIODevice::WriteOnly);
permissionsBefore = file.permissions();
file.close();
}
- QFileDevice::Permissions permissionsNow = permissionsBefore &
- (~QFileDevice::ReadUser & ~QFileDevice::ReadOwner
- & ~QFileDevice::ReadGroup & ~QFileDevice::ReadOther);
+ QFileDevice::Permissions permissionsNow = permissionsBefore;
+ if (removePermissionToRead) {
+ permissionsNow = permissionsBefore &
+ (~QFileDevice::ReadUser & ~QFileDevice::ReadOwner
+ & ~QFileDevice::ReadGroup & ~QFileDevice::ReadOther);
+ }
+ if (removePermissionToWrite) {
+ permissionsNow = permissionsBefore &
+ (~QFileDevice::WriteUser & ~QFileDevice::WriteOwner
+ & ~QFileDevice::WriteGroup & ~QFileDevice::WriteOther);
+ }
+
QFile::setPermissions(sourceFileInfo.absoluteFilePath(), permissionsNow);
+}
+
+void restorePermissionsToReadAndWrite(QFileInfo sourceFileInfo)
+{
+ QFileDevice::Permissions permissionsNow = sourceFileInfo.permissions();
+ QFileDevice::Permissions permissionsAfter = permissionsNow
+ | (QFileDevice::ReadUser | QFileDevice::ReadOwner
+ | QFileDevice::ReadGroup | QFileDevice::ReadOther)
+ | (QFileDevice::WriteUser | QFileDevice::WriteOwner
+ | QFileDevice::WriteGroup | QFileDevice::WriteOther);
+ QFile::setPermissions(sourceFileInfo.absoluteFilePath(), permissionsAfter);
+}
+
+
+void testImportFromWriteonly(const QString& _dirname, QString mimetype = "")
+{
+ QString writeonlyFilename = _dirname + "writeonlyFile.txt";
+ QFileInfo sourceFileInfo(writeonlyFilename);
+
+ prepareFile(sourceFileInfo, false, true);
KisDocument *doc = qobject_cast(KisPart::instance()->createDocument());
KisImportExportManager manager(doc);
doc->setFileBatchMode(true);
KisImportExportErrorCode status = manager.importDocument(sourceFileInfo.absoluteFilePath(), mimetype);
qDebug() << "import result = " << status;
QVERIFY(!status.isOk());
qApp->processEvents();
if (doc->image()) {
doc->image()->waitForDone();
}
delete doc;
- QFileDevice::Permissions permissionsAfter = permissionsNow |
- (QFileDevice::ReadUser | QFileDevice::ReadOwner
- | QFileDevice::ReadGroup | QFileDevice::ReadOther);
- QFile::setPermissions(sourceFileInfo.absoluteFilePath(), permissionsAfter);
+ restorePermissionsToReadAndWrite(sourceFileInfo);
+
+}
+
+
+void testExportToReadonly(const QString& _dirname, QString mimetype = "")
+{
+ QString readonlyFilename = _dirname + "readonlyFile.txt";
+
+ QFileInfo sourceFileInfo(readonlyFilename);
+ prepareFile(sourceFileInfo, true, false);
+
+ KisDocument *doc = qobject_cast(KisPart::instance()->createDocument());
+
+ KisImportExportManager manager(doc);
+ doc->setFileBatchMode(true);
+
+ {
+ MaskParent p;
+ ENTER_FUNCTION() << doc->image();
+
+ doc->setCurrentImage(p.image);
+
+ KisImportExportErrorCode status = manager.exportDocument(sourceFileInfo.absoluteFilePath(), sourceFileInfo.absoluteFilePath(), mimetype.toUtf8());
+ qDebug() << "export result = " << status;
+
+ QVERIFY(!status.isOk());
+
+
+ qApp->processEvents();
+
+ if (doc->image()) {
+ doc->image()->waitForDone();
+ }
+
+ }
+ delete doc;
+
+ restorePermissionsToReadAndWrite(sourceFileInfo);
+}
+
+
+
+void testImportIncorrectFormat(const QString& _dirname, QString mimetype = "")
+{
+ QString incorrectFormatFilename = _dirname + "incorrectFormatFile.txt";
+ QFileInfo sourceFileInfo(incorrectFormatFilename);
+
+ prepareFile(sourceFileInfo, false, false);
+
+ KisDocument *doc = qobject_cast(KisPart::instance()->createDocument());
+
+ KisImportExportManager manager(doc);
+ doc->setFileBatchMode(true);
+
+ KisImportExportErrorCode status = manager.importDocument(sourceFileInfo.absoluteFilePath(), mimetype);
+ qDebug() << "import result = " << status;
+
+ QVERIFY(!status.isOk());
+ QVERIFY(status == KisImportExportErrorCode(ImportExportCodes::FileFormatIncorrect));
+
+
+ qApp->processEvents();
+
+ if (doc->image()) {
+ doc->image()->waitForDone();
+ }
+
+ delete doc;
+
}
+
+
}
#endif