JXL: added ImageTransformation option

Let Qt rotate the image when the ImageAutotransform option is set to true.

In tests it also solves the image size control with the value returned by the options with certain rotations.
This commit is contained in:
Mirco Miranda 2024-07-30 22:46:52 +00:00 committed by Albert Astals Cid
parent 51921e8ee5
commit 219d9cb2c2
8 changed files with 126 additions and 17 deletions

Binary file not shown.

View File

@ -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."
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -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";

View File

@ -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));

View File

@ -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

View File

@ -16,11 +16,19 @@
#include <jxl/thread_parallel_runner.h>
#include <string.h>
// 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

View File

@ -64,6 +64,7 @@ private:
int m_quality;
int m_currentimage_index;
int m_previousimage_index;
QImageIOHandler::Transformations m_transformations;
QByteArray m_rawData;