diff --git a/autotests/read/jxl/orientation6_notranfs.jxl b/autotests/read/jxl/orientation6_notranfs.jxl new file mode 100644 index 0000000..c274d89 Binary files /dev/null and b/autotests/read/jxl/orientation6_notranfs.jxl differ diff --git a/autotests/read/jxl/orientation6_notranfs.jxl.json b/autotests/read/jxl/orientation6_notranfs.jxl.json new file mode 100644 index 0000000..26cbd56 --- /dev/null +++ b/autotests/read/jxl/orientation6_notranfs.jxl.json @@ -0,0 +1,19 @@ +[ + { + "minQtVersion" : "6.5.7", + "maxQtVersion" : "6.5.99", + "disableAutoTransform": true, + "fileName" : "orientation6_notranfs.png", + "comment" : "Test with automatic transformation disabled." + }, + { + "minQtVersion" : "6.7.3", + "disableAutoTransform": true, + "fileName" : "orientation6_notranfs.png", + "comment" : "Test with automatic transformation disabled." + }, + { + "unsupportedFormat" : true, + "comment" : "It is not possible to disable the transformation with the current version of the plugin." + } +] diff --git a/autotests/read/jxl/orientation6_notranfs.png b/autotests/read/jxl/orientation6_notranfs.png new file mode 100644 index 0000000..6cf6d30 Binary files /dev/null and b/autotests/read/jxl/orientation6_notranfs.png differ diff --git a/autotests/readtest.cpp b/autotests/readtest.cpp index a92dca2..07f030a 100644 --- a/autotests/readtest.cpp +++ b/autotests/readtest.cpp @@ -267,10 +267,13 @@ int main(int argc, char **argv) continue; } - bool skipTest = false; - QFileInfo expFileInfo = timg.compareImage(skipTest); - if (skipTest) { - QTextStream(stdout) << "SKIP : " << fi.fileName() << ": image format not supported by current Qt version!\n"; + TemplateImage::TestFlags flags = TemplateImage::None; + QString comment; + QFileInfo expFileInfo = timg.compareImage(flags, comment); + if ((flags & TemplateImage::SkipTest) == TemplateImage::SkipTest) { + if(comment.isEmpty()) + comment = QStringLiteral("image format not supported by current Qt version!"); + QTextStream(stdout) << "SKIP : " << fi.fileName() << QStringLiteral(": %1\n").arg(comment); ++skipped; continue; } @@ -291,7 +294,7 @@ int main(int argc, char **argv) QImage expImage; // inputImage is auto-rotated to final orientation - inputReader.setAutoTransform(true); + inputReader.setAutoTransform((flags & TemplateImage::DisableAutotransform) != TemplateImage::DisableAutotransform); if (!expReader.read(&expImage)) { QTextStream(stdout) << "ERROR: " << fi.fileName() << ": could not load " << expfilename << ": " << expReader.errorString() << "\n"; diff --git a/autotests/templateimage.cpp b/autotests/templateimage.cpp index 4aef330..0a29710 100644 --- a/autotests/templateimage.cpp +++ b/autotests/templateimage.cpp @@ -28,10 +28,10 @@ bool TemplateImage::isTemplate() const return false; } -QFileInfo TemplateImage::compareImage(bool &skipTest) const +QFileInfo TemplateImage::compareImage(TestFlags &flags, QString& comment) const { - auto fi = jsonImage(skipTest); - if (skipTest) { + auto fi = jsonImage(flags, comment); + if ((flags & TestFlag::SkipTest) == TestFlag::SkipTest) { return {}; } if (fi.exists()) { @@ -58,8 +58,9 @@ QFileInfo TemplateImage::legacyImage() const return {}; } -QFileInfo TemplateImage::jsonImage(bool &skipTest) const +QFileInfo TemplateImage::jsonImage(TestFlags &flags, QString& comment) const { + flags = TestFlag::None; auto fi = QFileInfo(QStringLiteral("%1.json").arg(m_fi.filePath())); if (!fi.exists()) { return {}; @@ -86,6 +87,10 @@ QFileInfo TemplateImage::jsonImage(bool &skipTest) const auto maxQt = QVersionNumber::fromString(obj.value("maxQtVersion").toString()); auto name = obj.value("fileName").toString(); auto unsupportedFormat = obj.value("unsupportedFormat").toBool(); + comment = obj.value("comment").toString(); + + if(obj.value("disableAutoTransform").toBool()) + flags |= TestFlag::DisableAutotransform; // filter if (name.isEmpty() && !unsupportedFormat) @@ -95,7 +100,7 @@ QFileInfo TemplateImage::jsonImage(bool &skipTest) const if (!maxQt.isNull() && currentQt > maxQt) continue; if (unsupportedFormat) { - skipTest = true; + flags |= TestFlag::SkipTest; break; } return QFileInfo(QStringLiteral("%1/%2").arg(fi.path(), name)); diff --git a/autotests/templateimage.h b/autotests/templateimage.h index b6bf350..1674c29 100644 --- a/autotests/templateimage.h +++ b/autotests/templateimage.h @@ -16,6 +16,13 @@ class TemplateImage { public: + enum TestFlag { + None = 0x0, + SkipTest = 0x1, + DisableAutotransform = 0x2 + }; + Q_DECLARE_FLAGS(TestFlags, TestFlag) + /*! * \brief TemplateImage * \param fi The image to test. @@ -42,10 +49,10 @@ public: /*! * \brief compareImage - * \param skipTest True if the test should be skipped (e.g. image format not supported by current Qt version). + * \param flags Flags for modifying test behavior (e.g. image format not supported by current Qt version). * \return The template image to use for the comparison. */ - QFileInfo compareImage(bool &skipTest) const; + QFileInfo compareImage(TestFlags &flags, QString& comment) const; /*! * \brief suffixes @@ -62,13 +69,15 @@ private: /*! * \brief jsonImage - * \param skipTest True if the test should be skipped (not supported). + * \param flags Flags for modifying test behavior. * \return The template image read from the corresponding JSON. */ - QFileInfo jsonImage(bool &skipTest) const; + QFileInfo jsonImage(TestFlags &flags, QString& comment) const; private: QFileInfo m_fi; }; +Q_DECLARE_OPERATORS_FOR_FLAGS(TemplateImage::TestFlags) + #endif // TEMPLATEIMAGE_H diff --git a/src/imageformats/jxl.cpp b/src/imageformats/jxl.cpp index 25220d1..ec021e6 100644 --- a/src/imageformats/jxl.cpp +++ b/src/imageformats/jxl.cpp @@ -16,11 +16,19 @@ #include #include +// Avoid rotation on buggy Qts (see also https://bugreports.qt.io/browse/QTBUG-126575) +#if (QT_VERSION >= QT_VERSION_CHECK(6, 5, 7) && QT_VERSION < QT_VERSION_CHECK(6, 6, 0)) || (QT_VERSION >= QT_VERSION_CHECK(6, 7, 3)) +#ifndef JXL_QT_AUTOTRANSFORM +#define JXL_QT_AUTOTRANSFORM +#endif +#endif + QJpegXLHandler::QJpegXLHandler() : m_parseState(ParseJpegXLNotParsed) , m_quality(90) , m_currentimage_index(0) , m_previousimage_index(-1) + , m_transformations(QImageIOHandler::TransformationNone) , m_decoder(nullptr) , m_runner(nullptr) , m_next_image_delay(0) @@ -129,6 +137,11 @@ bool QJpegXLHandler::ensureDecoder() return false; } +#ifdef JXL_QT_AUTOTRANSFORM + // Let Qt handle the orientation. + JxlDecoderSetKeepOrientation(m_decoder, true); +#endif + int num_worker_threads = QThread::idealThreadCount(); if (!m_runner && num_worker_threads >= 4) { /* use half of the threads because plug-in is usually used in environment @@ -568,10 +581,25 @@ bool QJpegXLHandler::write(const QImage &image) pixel_format.endianness = JXL_NATIVE_ENDIAN; pixel_format.align = 0; - output_info.orientation = JXL_ORIENT_IDENTITY; output_info.num_color_channels = 3; output_info.animation.tps_numerator = 10; output_info.animation.tps_denominator = 1; + output_info.orientation = JXL_ORIENT_IDENTITY; + if (m_transformations == QImageIOHandler::TransformationMirror) { + output_info.orientation = JXL_ORIENT_FLIP_HORIZONTAL; + } else if (m_transformations == QImageIOHandler::TransformationRotate180) { + output_info.orientation = JXL_ORIENT_ROTATE_180; + } else if (m_transformations == QImageIOHandler::TransformationFlip) { + output_info.orientation = JXL_ORIENT_FLIP_VERTICAL; + } else if (m_transformations == QImageIOHandler::TransformationFlipAndRotate90) { + output_info.orientation = JXL_ORIENT_TRANSPOSE; + } else if (m_transformations == QImageIOHandler::TransformationRotate90) { + output_info.orientation = JXL_ORIENT_ROTATE_90_CW; + } else if (m_transformations == QImageIOHandler::TransformationMirrorAndRotate90) { + output_info.orientation = JXL_ORIENT_ANTI_TRANSPOSE; + } else if (m_transformations == QImageIOHandler::TransformationRotate270) { + output_info.orientation = JXL_ORIENT_ROTATE_90_CCW; + } if (save_depth > 8) { // 16bit depth pixel_format.data_type = JXL_TYPE_UINT16; @@ -777,14 +805,24 @@ bool QJpegXLHandler::write(const QImage &image) QVariant QJpegXLHandler::option(ImageOption option) const { + if (!supportsOption(option)) { + return QVariant(); + } + if (option == Quality) { return m_quality; } - if (!supportsOption(option) || !ensureParsed()) { + if (!ensureParsed()) { +#ifdef JXL_QT_AUTOTRANSFORM + if (option == ImageTransformation) { + return int(m_transformations); + } +#endif return QVariant(); } + switch (option) { case Size: return QSize(m_basicinfo.xsize, m_basicinfo.ysize); @@ -794,9 +832,31 @@ QVariant QJpegXLHandler::option(ImageOption option) const } else { return false; } +#ifdef JXL_QT_AUTOTRANSFORM + case ImageTransformation: + if (m_basicinfo.orientation == JXL_ORIENT_IDENTITY) { + return int(QImageIOHandler::TransformationNone); + } else if (m_basicinfo.orientation == JXL_ORIENT_FLIP_HORIZONTAL) { + return int(QImageIOHandler::TransformationMirror); + } else if (m_basicinfo.orientation == JXL_ORIENT_ROTATE_180) { + return int(QImageIOHandler::TransformationRotate180); + } else if (m_basicinfo.orientation == JXL_ORIENT_FLIP_VERTICAL) { + return int(QImageIOHandler::TransformationFlip); + } else if (m_basicinfo.orientation == JXL_ORIENT_TRANSPOSE) { + return int(QImageIOHandler::TransformationFlipAndRotate90); + } else if (m_basicinfo.orientation == JXL_ORIENT_ROTATE_90_CW) { + return int(QImageIOHandler::TransformationRotate90); + } else if (m_basicinfo.orientation == JXL_ORIENT_ANTI_TRANSPOSE) { + return int(QImageIOHandler::TransformationMirrorAndRotate90); + } else if (m_basicinfo.orientation == JXL_ORIENT_ROTATE_90_CCW) { + return int(QImageIOHandler::TransformationRotate270); + } +#endif default: return QVariant(); } + + return QVariant(); } void QJpegXLHandler::setOption(ImageOption option, const QVariant &value) @@ -810,6 +870,14 @@ void QJpegXLHandler::setOption(ImageOption option, const QVariant &value) m_quality = 90; } return; +#ifdef JXL_QT_AUTOTRANSFORM + case ImageTransformation: + if (auto t = value.toInt()) { + if (t > 0 && t < 8) + m_transformations = QImageIOHandler::Transformations(t); + } + break; +#endif default: break; } @@ -818,7 +886,11 @@ void QJpegXLHandler::setOption(ImageOption option, const QVariant &value) bool QJpegXLHandler::supportsOption(ImageOption option) const { - return option == Quality || option == Size || option == Animation; + auto supported = option == Quality || option == Size || option == Animation; +#ifdef JXL_QT_AUTOTRANSFORM + supported = supported || option == ImageTransformation; +#endif + return supported; } int QJpegXLHandler::imageCount() const diff --git a/src/imageformats/jxl_p.h b/src/imageformats/jxl_p.h index 3e93808..1563012 100644 --- a/src/imageformats/jxl_p.h +++ b/src/imageformats/jxl_p.h @@ -64,6 +64,7 @@ private: int m_quality; int m_currentimage_index; int m_previousimage_index; + QImageIOHandler::Transformations m_transformations; QByteArray m_rawData;