Compare commits

...

13 Commits

Author SHA1 Message Date
799ac37660 Update dependency version to 6.8.0 2024-11-02 16:13:04 +01:00
0378bd67e1 TGA: Fixed GrayA image loading error
Gray TGA images with alpha were loading incorrectly and tests did not detect the error since the BW(A).TGA images were actually RGB(A) images.
2024-10-24 15:07:44 +00:00
7d696a81d2 exr: Fix read/write with openexr 3.3
It really wants to have a filename

Also it uses seek and tell a lot so sequential devices are for now not
supported

BUGS: 494571
2024-10-12 01:47:22 +02:00
b5d5abe0ea JXL improvements
Highlights of the patch:
- Supersede MR !249
- Added FP16 and FP32 images support thus preserving HDR values (read / write, required libjxl 0.9+).
- Added Gray8 and Gray16 support (read / write).
- Indexed images are saved as Gray8 when palette is gray scale.
- Binary images are saved as Gray8 (does JXL natively support binary images?).
- Simplified writing process by partially removing the use of additional buffers.
- Added XMP metadata support by decoding/encoding Boxes.
- Changed maximum image size in pixels in accordance with JXL feature level 5 (still limited to 256 megapixels).

Compatibility: 
- Older versions of this plugin load FP images correctly as UINT16 (obviously losing HDR info).
- HDR images saved with this patch are also loaded correctly by Gimp and Photoshop.
- Grayscale images saved with this patch are also loaded correctly by Gimp and Photoshop.

Compilation modifiers for cmake file:
- `JXL_HDR_PRESERVATION_DISABLED`: disable the FP support (behaves like previous versions).
- `JXL_DECODE_BOXES_DISABLED`: disable metadata reading (behaves like previous versions).
2024-10-11 12:42:42 +00:00
3f4690d1e9 Update version to 6.8.0 2024-10-11 13:35:32 +02:00
ac1006cc66 JXR: Fixed image reading on sequential devices
The following changes are done:
- Fixed an error when copying image from sequential device
- Return error when the image exceed 4GB size on writing*
- Enabled JXR tests

(*) Note that when writing an image larger than 4GiB, the JXRLib does not give any error but the resulting saved image id wrong.
2024-10-09 21:34:32 +00:00
97120b2537 Simplified read/verify header process
Where possible, QIODevice::peek has been used instead of transactions or instead of using ungetchar() for sequential access devices and seek() for random access devices.

Furthermore:
- RAS format gained the ability of read on sequential devices.
- Removed unused code in XCF (still related to ungetchar and sequential devices).
- These changes should prevent errors like the ones fixed by MR !258
2024-10-06 17:26:25 +00:00
fee0165bef Update dependency version to 6.7.0 2024-10-04 16:57:57 +02:00
ae641f7e94 Fix endianness bug in PCX reader on big endian architectures
When reading from a sequential device, the peekHeader() method in
the PCX readers reads the header its defined little endian into
arch-specific endianness for multibyte types.

Being a "peek" method, it then it tries to push back the bytes into
the device after reading for its next use, but it doesn’t convert
multibyte types correctly from arch-specifice endianness to the
initial little endian format.

Subsequent reading of the data from the device will thus lead to
incorrect values for multibyte types on the next use.

This patch reuses the same technique as the TGA reader to read the
whole header as bytes before deserializing it, so that the bytes
can be pushed back into the sequential device in the same order.
2024-09-22 01:31:13 +02:00
46f7b90ce6 Fixed read of BGR32 and RGB555 formats 2024-09-16 17:16:28 +02:00
f7c8eaa140 FIxed comparison of unsigned expression
Fix of [Issue 9](https://invent.kde.org/frameworks/kimageformats/-/issues/9)

Same of MR !253 to solve the `Fix of [Issue 9](https://invent.kde.org/frameworks/kimageformats/-/issues/9)` when rebasing.
2024-09-15 15:00:11 +00:00
36bfee8ae3 raw: Getting the image size does not need unpacking
According to the libraw documentation, the sizes are available directly
after open_datastream.
2024-09-13 19:17:31 +02:00
e2aaf89ec5 Update version to 6.7.0 2024-09-06 14:21:04 +02:00
32 changed files with 442 additions and 364 deletions

View File

@ -7,3 +7,4 @@ Dependencies:
Options: Options:
test-before-installing: True test-before-installing: True
require-passing-tests-on: [ 'Linux', 'FreeBSD', 'Windows' ] require-passing-tests-on: [ 'Linux', 'FreeBSD', 'Windows' ]
cmake-options: "-DKIMAGEFORMATS_JXR=ON"

View File

@ -1,11 +1,11 @@
cmake_minimum_required(VERSION 3.16) cmake_minimum_required(VERSION 3.16)
set(KF_VERSION "6.6.0") # handled by release scripts set(KF_VERSION "6.8.0") # handled by release scripts
set(KF_DEP_VERSION "6.6.0") # handled by release scripts set(KF_DEP_VERSION "6.8.0") # handled by release scripts
project(KImageFormats VERSION ${KF_VERSION}) project(KImageFormats VERSION ${KF_VERSION})
include(FeatureSummary) include(FeatureSummary)
find_package(ECM 6.6.0 NO_MODULE) find_package(ECM 6.8.0 NO_MODULE)
set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://commits.kde.org/extra-cmake-modules") set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://commits.kde.org/extra-cmake-modules")
feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES) feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES)

View File

@ -78,8 +78,8 @@ For example, native support for CMYK images is only available since Qt 6.8.
### HDR images ### HDR images
HDR images are supported via floating point image formats from EXR, HDR, JXR, HDR images are supported via floating point image formats from EXR, HDR, JXL,
PFM and PSD plugins. JXR, PFM and PSD plugins.
It is important to note that in the past these plugins stripped away HDR It is important to note that in the past these plugins stripped away HDR
information, returning SDR images. information, returning SDR images.
@ -116,8 +116,8 @@ plugin ('n/a' means no limit, i.e. the limit depends on the format encoding).
- EXR: 300,000 x 300,000 pixels - EXR: 300,000 x 300,000 pixels
- HDR: n/a (large image) - HDR: n/a (large image)
- HEIF: n/a - HEIF: n/a
- JXL: 65,535 x 65,535 pixels, in any case no larger than 256 megapixels - JXL: 262,144 x 262,144 pixels, in any case no larger than 256 megapixels
- JXR: n/a - JXR: n/a, in any case no larger than 4 GB
- PCX: 65,535 x 65,535 pixels - PCX: 65,535 x 65,535 pixels
- PFM: n/a (large image) - PFM: n/a (large image)
- PIC: 65,535 x 65,535 pixels - PIC: 65,535 x 65,535 pixels
@ -181,6 +181,10 @@ The following defines can be defined in cmake to modify the behavior of the plug
**The current version of the plugin limits the image size to 256 megapixels **The current version of the plugin limits the image size to 256 megapixels
according to feature level 5 of the JXL stream encoding.** according to feature level 5 of the JXL stream encoding.**
The following defines can be defined in cmake to modify the behavior of the plugin:
- `JXL_HDR_PRESERVATION_DISABLED`: disable floating point images (both read and write) by converting them to UINT16 images. Any HDR data is lost. Note that FP images are always disabled when compiling with libJXL less than v0.9.
- `JXL_DECODE_BOXES_DISABLED`: disable reading of metadata (e.g. XMP).
### The JXR plugin ### The JXR plugin
**This plugin is disabled by default. It can be enabled with the **This plugin is disabled by default. It can be enabled with the

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

View File

@ -0,0 +1,21 @@
[
{
"minQtVersion" : "6.2.11",
"maxQtVersion" : "6.2.99",
"fileName" : "testcard_rgba_fp16.png"
},
{
"minQtVersion" : "6.5.5",
"maxQtVersion" : "6.5.99",
"fileName" : "testcard_rgba_fp16.png"
},
{
"minQtVersion" : "6.6.2",
"fileName" : "testcard_rgba_fp16.png"
},
{
"unsupportedFormat" : true,
"comment" : "Skipped due to QTBUG-120614.",
"seeAlso" : "https://bugreports.qt.io/browse/QTBUG-120614"
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

View File

@ -0,0 +1,21 @@
[
{
"minQtVersion" : "6.2.11",
"maxQtVersion" : "6.2.99",
"fileName" : "testcard_rgba_fp32.png"
},
{
"minQtVersion" : "6.5.5",
"maxQtVersion" : "6.5.99",
"fileName" : "testcard_rgba_fp32.png"
},
{
"minQtVersion" : "6.6.2",
"fileName" : "testcard_rgba_fp32.png"
},
{
"unsupportedFormat" : true,
"comment" : "Skipped due to QTBUG-120614.",
"seeAlso" : "https://bugreports.qt.io/browse/QTBUG-120614"
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 485 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 737 B

View File

@ -136,7 +136,7 @@ public:
} }
if (reader->supportsOption(QImageIOHandler::ImageTransformation)) { if (reader->supportsOption(QImageIOHandler::ImageTransformation)) {
m_transformations = reader->transformation(); m_transformations = reader->transformation();
if (m_transformations < 0 || m_transformations > 7) if (int(m_transformations) < 0 || int(m_transformations) > 7)
ok = false; ok = false;
} }
return ok; return ok;

View File

@ -86,8 +86,8 @@
class K_IStream : public Imf::IStream class K_IStream : public Imf::IStream
{ {
public: public:
K_IStream(QIODevice *dev, const QByteArray &fileName) K_IStream(QIODevice *dev)
: IStream(fileName.data()) : IStream("K_IStream")
, m_dev(dev) , m_dev(dev)
{ {
} }
@ -145,8 +145,8 @@ void K_IStream::clear()
class K_OStream : public Imf::OStream class K_OStream : public Imf::OStream
{ {
public: public:
K_OStream(QIODevice *dev, const QByteArray &fileName) K_OStream(QIODevice *dev)
: OStream(fileName.data()) : OStream("K_OStream")
, m_dev(dev) , m_dev(dev)
{ {
} }
@ -360,7 +360,7 @@ bool EXRHandler::read(QImage *outImage)
} }
} }
K_IStream istr(d, QByteArray()); K_IStream istr(d);
Imf::RgbaInputFile file(istr); Imf::RgbaInputFile file(istr);
auto &&header = file.header(); auto &&header = file.header();
@ -583,7 +583,7 @@ bool EXRHandler::write(const QImage &image)
setMetadata(image, header); setMetadata(image, header);
// write the EXR // write the EXR
K_OStream ostr(device(), QByteArray()); K_OStream ostr(device());
auto channelsType = image.hasAlphaChannel() ? Imf::RgbaChannels::WRITE_RGBA : Imf::RgbaChannels::WRITE_RGB; auto channelsType = image.hasAlphaChannel() ? Imf::RgbaChannels::WRITE_RGBA : Imf::RgbaChannels::WRITE_RGB;
if (image.format() == QImage::Format_Mono || if (image.format() == QImage::Format_Mono ||
image.format() == QImage::Format_MonoLSB || image.format() == QImage::Format_MonoLSB ||
@ -673,7 +673,7 @@ QVariant EXRHandler::option(ImageOption option) const
d->seek(m_startPos); d->seek(m_startPos);
} }
try { try {
K_IStream istr(d, QByteArray()); K_IStream istr(d);
Imf::RgbaInputFile file(istr); Imf::RgbaInputFile file(istr);
if (m_imageNumber > -1) { // set the image to read if (m_imageNumber > -1) { // set the image to read
auto views = viewList(file.header()); auto views = viewList(file.header());
@ -698,7 +698,7 @@ QVariant EXRHandler::option(ImageOption option) const
d->seek(m_startPos); d->seek(m_startPos);
} }
try { try {
K_IStream istr(d, QByteArray()); K_IStream istr(d);
Imf::RgbaInputFile file(istr); Imf::RgbaInputFile file(istr);
v = QVariant::fromValue(imageFormat(file)); v = QVariant::fromValue(imageFormat(file));
} catch (const std::exception &) { } catch (const std::exception &) {
@ -747,7 +747,7 @@ int EXRHandler::imageCount() const
d->startTransaction(); d->startTransaction();
try { try {
K_IStream istr(d, QByteArray()); K_IStream istr(d);
Imf::RgbaInputFile file(istr); Imf::RgbaInputFile file(istr);
auto views = viewList(file.header()); auto views = viewList(file.header());
if (!views.isEmpty()) { if (!views.isEmpty()) {
@ -774,6 +774,13 @@ bool EXRHandler::canRead(QIODevice *device)
return false; return false;
} }
#if OPENEXR_VERSION_MAJOR == 3 && OPENEXR_VERSION_MINOR > 2
// openexpr >= 3.3 uses seek and tell extensively
if (device->isSequential()) {
return false;
}
#endif
const QByteArray head = device->peek(4); const QByteArray head = device->peek(4);
return Imf::isImfMagic(head.data()); return Imf::isImfMagic(head.data());

View File

@ -23,6 +23,34 @@
#endif #endif
#endif #endif
#if JPEGXL_NUMERIC_VERSION < JPEGXL_COMPUTE_NUMERIC_VERSION(0, 9, 0)
#ifndef JXL_HDR_PRESERVATION_DISABLED
// Define JXL_HDR_PRESERVATION_DISABLED to disable HDR preservation
// (HDR images are saved as UINT16).
#define JXL_HDR_PRESERVATION_DISABLED
#endif
#endif
#ifndef JXL_DECODE_BOXES_DISABLED
// Decode Boxes in order to read optional metadata (XMP, Exif, etc...).
// Define JXL_DECODE_BOXES_DISABLED to disable Boxes decoding.
// #define JXL_DECODE_BOXES_DISABLED
#endif
#define FEATURE_LEVEL_5_WIDTH 262144
#define FEATURE_LEVEL_5_HEIGHT 262144
#define FEATURE_LEVEL_5_PIXELS 268435456
#if QT_POINTER_SIZE < 8
#define MAX_IMAGE_WIDTH 32767
#define MAX_IMAGE_HEIGHT 32767
#define MAX_IMAGE_PIXELS FEATURE_LEVEL_5_PIXELS
#else // JXL code stream level 5
#define MAX_IMAGE_WIDTH FEATURE_LEVEL_5_WIDTH
#define MAX_IMAGE_HEIGHT FEATURE_LEVEL_5_HEIGHT
#define MAX_IMAGE_PIXELS FEATURE_LEVEL_5_PIXELS
#endif
QJpegXLHandler::QJpegXLHandler() QJpegXLHandler::QJpegXLHandler()
: m_parseState(ParseJpegXLNotParsed) : m_parseState(ParseJpegXLNotParsed)
, m_quality(90) , m_quality(90)
@ -164,23 +192,18 @@ bool QJpegXLHandler::ensureDecoder()
} }
JxlDecoderCloseInput(m_decoder); JxlDecoderCloseInput(m_decoder);
#ifndef JXL_DECODE_BOXES_DISABLED
JxlDecoderStatus status = JxlDecoderSubscribeEvents(m_decoder, JXL_DEC_BASIC_INFO | JXL_DEC_COLOR_ENCODING | JXL_DEC_FRAME | JXL_DEC_BOX);
#else
JxlDecoderStatus status = JxlDecoderSubscribeEvents(m_decoder, JXL_DEC_BASIC_INFO | JXL_DEC_COLOR_ENCODING | JXL_DEC_FRAME); JxlDecoderStatus status = JxlDecoderSubscribeEvents(m_decoder, JXL_DEC_BASIC_INFO | JXL_DEC_COLOR_ENCODING | JXL_DEC_FRAME);
#endif
if (status == JXL_DEC_ERROR) { if (status == JXL_DEC_ERROR) {
qWarning("ERROR: JxlDecoderSubscribeEvents failed"); qWarning("ERROR: JxlDecoderSubscribeEvents failed");
m_parseState = ParseJpegXLError; m_parseState = ParseJpegXLError;
return false; return false;
} }
status = JxlDecoderProcessInput(m_decoder); if (!decodeBoxes()) {
if (status == JXL_DEC_ERROR) {
qWarning("ERROR: JXL decoding failed");
m_parseState = ParseJpegXLError;
return false;
}
if (status == JXL_DEC_NEED_MORE_INPUT) {
qWarning("ERROR: JXL data incomplete");
m_parseState = ParseJpegXLError;
return false; return false;
} }
@ -197,32 +220,12 @@ bool QJpegXLHandler::ensureDecoder()
return false; return false;
} }
if (m_basicinfo.xsize > 65535 || m_basicinfo.ysize > 65535) { if (m_basicinfo.xsize > MAX_IMAGE_WIDTH || m_basicinfo.ysize > MAX_IMAGE_HEIGHT) {
qWarning("JXL image (%dx%d) is too large", m_basicinfo.xsize, m_basicinfo.ysize); qWarning("JXL image (%dx%d) is too large", m_basicinfo.xsize, m_basicinfo.ysize);
m_parseState = ParseJpegXLError; m_parseState = ParseJpegXLError;
return false; return false;
} }
if (sizeof(void *) <= 4) {
/* On 32bit systems, there is limited address space.
* We skip imagess bigger than 8192 x 8192 pixels.
* If we don't do it, abort() in libjxl may close whole application */
if (m_basicinfo.xsize > ((8192 * 8192) / m_basicinfo.ysize)) {
qWarning("JXL image (%dx%d) is too large for 32bit build of the plug-in", m_basicinfo.xsize, m_basicinfo.ysize);
m_parseState = ParseJpegXLError;
return false;
}
} else {
/* On 64bit systems
* We skip images bigger than 16384 x 16384 pixels.
* It is an artificial limit not to use extreme amount of memory */
if (m_basicinfo.xsize > ((16384 * 16384) / m_basicinfo.ysize)) {
qWarning("JXL image (%dx%d) is bigger than security limit 256 megapixels", m_basicinfo.xsize, m_basicinfo.ysize);
m_parseState = ParseJpegXLError;
return false;
}
}
m_parseState = ParseJpegXLBasicInfoParsed; m_parseState = ParseJpegXLBasicInfoParsed;
return true; return true;
} }
@ -256,27 +259,50 @@ bool QJpegXLHandler::countALLFrames()
m_input_pixel_format.endianness = JXL_NATIVE_ENDIAN; m_input_pixel_format.endianness = JXL_NATIVE_ENDIAN;
m_input_pixel_format.align = 0; m_input_pixel_format.align = 0;
m_input_pixel_format.num_channels = 4; m_input_pixel_format.num_channels = m_basicinfo.num_color_channels == 1 ? 1 : 4;
if (m_basicinfo.bits_per_sample > 8) { // high bit depth if (m_basicinfo.bits_per_sample > 8) { // high bit depth
m_input_pixel_format.data_type = JXL_TYPE_UINT16; #ifdef JXL_HDR_PRESERVATION_DISABLED
m_buffer_size = 8 * (size_t)m_basicinfo.xsize * (size_t)m_basicinfo.ysize; bool is_fp = false;
m_input_image_format = QImage::Format_RGBA64; #else
bool is_fp = m_basicinfo.exponent_bits_per_sample > 0 && m_basicinfo.num_color_channels == 3;
#endif
m_input_pixel_format.data_type = is_fp ? JXL_TYPE_FLOAT16 : JXL_TYPE_UINT16;
m_buffer_size = (size_t)m_basicinfo.xsize * (size_t)m_basicinfo.ysize * m_input_pixel_format.num_channels * 2;
if (loadalpha) { if (m_basicinfo.num_color_channels == 1) {
m_target_image_format = QImage::Format_RGBA64; m_input_pixel_format.data_type = JXL_TYPE_UINT16;
m_input_image_format = m_target_image_format = QImage::Format_Grayscale16;
m_buffer_size = (size_t)m_basicinfo.xsize * (size_t)m_basicinfo.ysize * m_input_pixel_format.num_channels * 2;
} else if (m_basicinfo.bits_per_sample > 16 && is_fp) {
m_input_pixel_format.data_type = JXL_TYPE_FLOAT;
m_input_image_format = QImage::Format_RGBA32FPx4;
m_buffer_size = (size_t)m_basicinfo.xsize * (size_t)m_basicinfo.ysize * m_input_pixel_format.num_channels * 4;
if (loadalpha)
m_target_image_format = QImage::Format_RGBA32FPx4;
else
m_target_image_format = QImage::Format_RGBX32FPx4;
} else { } else {
m_target_image_format = QImage::Format_RGBX64; m_buffer_size = (size_t)m_basicinfo.xsize * (size_t)m_basicinfo.ysize * m_input_pixel_format.num_channels * 2;
m_input_image_format = is_fp ? QImage::Format_RGBA16FPx4 : QImage::Format_RGBA64;
if (loadalpha)
m_target_image_format = is_fp ? QImage::Format_RGBA16FPx4 : QImage::Format_RGBA64;
else
m_target_image_format = is_fp ? QImage::Format_RGBX16FPx4 : QImage::Format_RGBX64;
} }
} else { // 8bit depth } else { // 8bit depth
m_input_pixel_format.data_type = JXL_TYPE_UINT8; m_input_pixel_format.data_type = JXL_TYPE_UINT8;
m_buffer_size = 4 * (size_t)m_basicinfo.xsize * (size_t)m_basicinfo.ysize; m_buffer_size = (size_t)m_basicinfo.xsize * (size_t)m_basicinfo.ysize * m_input_pixel_format.num_channels;
m_input_image_format = QImage::Format_RGBA8888;
if (loadalpha) { if (m_basicinfo.num_color_channels == 1) {
m_target_image_format = QImage::Format_ARGB32; m_input_image_format = m_target_image_format = QImage::Format_Grayscale8;
} else { } else {
m_target_image_format = QImage::Format_RGB32; m_input_image_format = QImage::Format_RGBA8888;
if (loadalpha) {
m_target_image_format = QImage::Format_ARGB32;
} else {
m_target_image_format = QImage::Format_RGB32;
}
} }
} }
@ -376,6 +402,12 @@ bool QJpegXLHandler::countALLFrames()
m_framedelays[0] = 0; m_framedelays[0] = 0;
} }
#ifndef JXL_DECODE_BOXES_DISABLED
if (!decodeBoxes()) {
return false;
}
#endif
if (!rewind()) { if (!rewind()) {
return false; return false;
} }
@ -402,6 +434,9 @@ bool QJpegXLHandler::decode_one_frame()
} }
m_current_image.setColorSpace(m_colorspace); m_current_image.setColorSpace(m_colorspace);
if (!m_xmp.isEmpty()) {
m_current_image.setText(QStringLiteral(META_KEY_XMP_ADOBE), QString::fromUtf8(m_xmp));
}
if (JxlDecoderSetImageOutBuffer(m_decoder, &m_input_pixel_format, m_current_image.bits(), m_buffer_size) != JXL_DEC_SUCCESS) { if (JxlDecoderSetImageOutBuffer(m_decoder, &m_input_pixel_format, m_current_image.bits(), m_buffer_size) != JXL_DEC_SUCCESS) {
qWarning("ERROR: JxlDecoderSetImageOutBuffer failed"); qWarning("ERROR: JxlDecoderSetImageOutBuffer failed");
@ -463,6 +498,30 @@ bool QJpegXLHandler::read(QImage *image)
} }
} }
template<class T>
void packRGBPixels(QImage &img)
{
// pack pixel data
auto dest_pixels = reinterpret_cast<T *>(img.bits());
for (qint32 y = 0; y < img.height(); y++) {
auto src_pixels = reinterpret_cast<const T *>(img.constScanLine(y));
for (qint32 x = 0; x < img.width(); x++) {
// R
*dest_pixels = *src_pixels;
dest_pixels++;
src_pixels++;
// G
*dest_pixels = *src_pixels;
dest_pixels++;
src_pixels++;
// B
*dest_pixels = *src_pixels;
dest_pixels++;
src_pixels += 2; // skipalpha
}
}
}
bool QJpegXLHandler::write(const QImage &image) bool QJpegXLHandler::write(const QImage &image)
{ {
if (image.format() == QImage::Format_Invalid) { if (image.format() == QImage::Format_Invalid) {
@ -470,36 +529,47 @@ bool QJpegXLHandler::write(const QImage &image)
return false; return false;
} }
if ((image.width() > 0) && (image.height() > 0)) { if ((image.width() == 0) || (image.height() == 0)) {
if ((image.width() > 65535) || (image.height() > 65535)) {
qWarning("Image (%dx%d) is too large to save!", image.width(), image.height());
return false;
}
if (sizeof(void *) <= 4) {
if (image.width() > ((8192 * 8192) / image.height())) {
qWarning("Image (%dx%d) is too large save via 32bit build of JXL plug-in", image.width(), image.height());
return false;
}
} else {
if (image.width() > ((16384 * 16384) / image.height())) {
qWarning("Image (%dx%d) will not be saved because it has more than 256 megapixels", image.width(), image.height());
return false;
}
}
} else {
qWarning("Image has zero dimension!"); qWarning("Image has zero dimension!");
return false; return false;
} }
int save_depth = 8; // 8 or 16 if ((image.width() > MAX_IMAGE_WIDTH) || (image.height() > MAX_IMAGE_HEIGHT)) {
qWarning("Image (%dx%d) is too large to save!", image.width(), image.height());
return false;
}
size_t pixel_count = size_t(image.width()) * image.height();
if (MAX_IMAGE_PIXELS && pixel_count > MAX_IMAGE_PIXELS) {
qWarning("Image (%dx%d) will not be saved because it has more than %d megapixels!", image.width(), image.height(), MAX_IMAGE_PIXELS / 1024 / 1024);
return false;
}
int save_depth = 8; // 8 / 16 / 32
bool save_fp = false;
bool is_gray = false;
// depth detection // depth detection
switch (image.format()) { switch (image.format()) {
case QImage::Format_RGBX32FPx4:
case QImage::Format_RGBA32FPx4:
case QImage::Format_RGBA32FPx4_Premultiplied:
#ifndef JXL_HDR_PRESERVATION_DISABLED
save_depth = 32;
save_fp = true;
break;
#endif
case QImage::Format_RGBX16FPx4:
case QImage::Format_RGBA16FPx4:
case QImage::Format_RGBA16FPx4_Premultiplied:
#ifndef JXL_HDR_PRESERVATION_DISABLED
save_depth = 16;
save_fp = true;
break;
#endif
case QImage::Format_BGR30: case QImage::Format_BGR30:
case QImage::Format_A2BGR30_Premultiplied: case QImage::Format_A2BGR30_Premultiplied:
case QImage::Format_RGB30: case QImage::Format_RGB30:
case QImage::Format_A2RGB30_Premultiplied: case QImage::Format_A2RGB30_Premultiplied:
case QImage::Format_Grayscale16:
case QImage::Format_RGBX64: case QImage::Format_RGBX64:
case QImage::Format_RGBA64: case QImage::Format_RGBA64:
case QImage::Format_RGBA64_Premultiplied: case QImage::Format_RGBA64_Premultiplied:
@ -514,6 +584,21 @@ bool QJpegXLHandler::write(const QImage &image)
case QImage::Format_RGBA8888_Premultiplied: case QImage::Format_RGBA8888_Premultiplied:
save_depth = 8; save_depth = 8;
break; break;
case QImage::Format_Grayscale16:
save_depth = 16;
is_gray = true;
break;
case QImage::Format_Grayscale8:
case QImage::Format_Alpha8:
case QImage::Format_Mono:
case QImage::Format_MonoLSB:
save_depth = 8;
is_gray = true;
break;
case QImage::Format_Indexed8:
save_depth = 8;
is_gray = image.isGrayscale();
break;
default: default:
if (image.depth() > 32) { if (image.depth() > 32) {
save_depth = 16; save_depth = 16;
@ -528,6 +613,7 @@ bool QJpegXLHandler::write(const QImage &image)
qWarning("Failed to create Jxl encoder"); qWarning("Failed to create Jxl encoder");
return false; return false;
} }
JxlEncoderUseBoxes(encoder);
if (m_quality > 100) { if (m_quality > 100) {
m_quality = 100; m_quality = 100;
@ -538,28 +624,28 @@ bool QJpegXLHandler::write(const QImage &image)
JxlBasicInfo output_info; JxlBasicInfo output_info;
JxlEncoderInitBasicInfo(&output_info); JxlEncoderInitBasicInfo(&output_info);
bool convert_color_profile;
QByteArray iccprofile; QByteArray iccprofile;
QColorSpace tmpcs = image.colorSpace();
if (image.colorSpace().isValid() && (m_quality < 100)) { if (!tmpcs.isValid() || tmpcs.primaries() != QColorSpace::Primaries::SRgb || tmpcs.transferFunction() != QColorSpace::TransferFunction::SRgb || m_quality == 100) {
if (image.colorSpace().primaries() != QColorSpace::Primaries::SRgb || image.colorSpace().transferFunction() != QColorSpace::TransferFunction::SRgb) { // no profile or Qt-unsupported ICC profile
convert_color_profile = true; iccprofile = tmpcs.iccProfile();
} else { // note: lossless encoding requires uses_original_profile = JXL_TRUE
convert_color_profile = false;
}
} else { // lossless or no profile or Qt-unsupported ICC profile
convert_color_profile = false;
iccprofile = image.colorSpace().iccProfile();
if (iccprofile.size() > 0 || m_quality == 100) { if (iccprofile.size() > 0 || m_quality == 100) {
output_info.uses_original_profile = JXL_TRUE; output_info.uses_original_profile = JXL_TRUE;
} }
} }
if (save_depth == 16 && (image.hasAlphaChannel() || output_info.uses_original_profile)) { // clang-format off
if ( (save_depth > 8 && (image.hasAlphaChannel() || output_info.uses_original_profile))
|| (save_depth > 16)
|| (pixel_count > FEATURE_LEVEL_5_PIXELS)
|| (image.width() > FEATURE_LEVEL_5_WIDTH)
|| (image.height() > FEATURE_LEVEL_5_HEIGHT)) {
output_info.have_container = JXL_TRUE; output_info.have_container = JXL_TRUE;
JxlEncoderUseContainer(encoder, JXL_TRUE); JxlEncoderUseContainer(encoder, JXL_TRUE);
JxlEncoderSetCodestreamLevel(encoder, 10); JxlEncoderSetCodestreamLevel(encoder, 10);
} }
// clang-format on
void *runner = nullptr; void *runner = nullptr;
int num_worker_threads = qBound(1, QThread::idealThreadCount(), 64); int num_worker_threads = qBound(1, QThread::idealThreadCount(), 64);
@ -581,7 +667,6 @@ bool QJpegXLHandler::write(const QImage &image)
pixel_format.endianness = JXL_NATIVE_ENDIAN; pixel_format.endianness = JXL_NATIVE_ENDIAN;
pixel_format.align = 0; pixel_format.align = 0;
output_info.num_color_channels = 3;
output_info.animation.tps_numerator = 10; output_info.animation.tps_numerator = 10;
output_info.animation.tps_denominator = 1; output_info.animation.tps_denominator = 1;
output_info.orientation = JXL_ORIENT_IDENTITY; output_info.orientation = JXL_ORIENT_IDENTITY;
@ -601,24 +686,60 @@ bool QJpegXLHandler::write(const QImage &image)
output_info.orientation = JXL_ORIENT_ROTATE_90_CCW; output_info.orientation = JXL_ORIENT_ROTATE_90_CCW;
} }
if (save_depth > 8) { // 16bit depth if (save_depth > 8 && is_gray) { // 16bit depth gray
pixel_format.data_type = JXL_TYPE_UINT16; pixel_format.data_type = JXL_TYPE_UINT16;
pixel_format.align = 4;
output_info.num_color_channels = 1;
output_info.bits_per_sample = 16;
tmpformat = QImage::Format_Grayscale16;
pixel_format.num_channels = 1;
} else if (is_gray) { // 8bit depth gray
pixel_format.data_type = JXL_TYPE_UINT8;
pixel_format.align = 4;
output_info.num_color_channels = 1;
output_info.bits_per_sample = 8;
tmpformat = QImage::Format_Grayscale8;
pixel_format.num_channels = 1;
} else if (save_depth > 16) { // 32bit depth rgb
pixel_format.data_type = JXL_TYPE_FLOAT;
output_info.exponent_bits_per_sample = 8;
output_info.num_color_channels = 3;
output_info.bits_per_sample = 32;
if (image.hasAlphaChannel()) {
tmpformat = QImage::Format_RGBA32FPx4;
pixel_format.num_channels = 4;
output_info.alpha_bits = 32;
output_info.alpha_exponent_bits = 8;
output_info.num_extra_channels = 1;
} else {
tmpformat = QImage::Format_RGBX32FPx4;
pixel_format.num_channels = 3;
output_info.alpha_bits = 0;
output_info.num_extra_channels = 0;
}
} else if (save_depth > 8) { // 16bit depth rgb
pixel_format.data_type = save_fp ? JXL_TYPE_FLOAT16 : JXL_TYPE_UINT16;
output_info.exponent_bits_per_sample = save_fp ? 5 : 0;
output_info.num_color_channels = 3;
output_info.bits_per_sample = 16; output_info.bits_per_sample = 16;
if (image.hasAlphaChannel()) { if (image.hasAlphaChannel()) {
tmpformat = QImage::Format_RGBA64; tmpformat = save_fp ? QImage::Format_RGBA16FPx4 : QImage::Format_RGBA64;
pixel_format.num_channels = 4; pixel_format.num_channels = 4;
output_info.alpha_bits = 16; output_info.alpha_bits = 16;
output_info.alpha_exponent_bits = save_fp ? 5 : 0;
output_info.num_extra_channels = 1; output_info.num_extra_channels = 1;
} else { } else {
tmpformat = QImage::Format_RGBX64; tmpformat = save_fp ? QImage::Format_RGBX16FPx4 : QImage::Format_RGBX64;
pixel_format.num_channels = 3; pixel_format.num_channels = 3;
output_info.alpha_bits = 0; output_info.alpha_bits = 0;
output_info.num_extra_channels = 0;
} }
} else { // 8bit depth } else { // 8bit depth rgb
pixel_format.data_type = JXL_TYPE_UINT8; pixel_format.data_type = JXL_TYPE_UINT8;
pixel_format.align = 4;
output_info.num_color_channels = 3;
output_info.bits_per_sample = 8; output_info.bits_per_sample = 8;
if (image.hasAlphaChannel()) { if (image.hasAlphaChannel()) {
@ -630,15 +751,13 @@ bool QJpegXLHandler::write(const QImage &image)
tmpformat = QImage::Format_RGB888; tmpformat = QImage::Format_RGB888;
pixel_format.num_channels = 3; pixel_format.num_channels = 3;
output_info.alpha_bits = 0; output_info.alpha_bits = 0;
output_info.num_extra_channels = 0;
} }
} }
const QImage tmpimage = QImage tmpimage = image.convertToFormat(tmpformat);
convert_color_profile ? image.convertToFormat(tmpformat).convertedToColorSpace(QColorSpace(QColorSpace::SRgb)) : image.convertToFormat(tmpformat);
const size_t xsize = tmpimage.width(); const size_t xsize = tmpimage.width();
const size_t ysize = tmpimage.height(); const size_t ysize = tmpimage.height();
const size_t buffer_size = (save_depth > 8) ? (2 * pixel_format.num_channels * xsize * ysize) : (pixel_format.num_channels * xsize * ysize);
if (xsize == 0 || ysize == 0 || tmpimage.isNull()) { if (xsize == 0 || ysize == 0 || tmpimage.isNull()) {
qWarning("Unable to allocate memory for output image"); qWarning("Unable to allocate memory for output image");
@ -662,7 +781,22 @@ bool QJpegXLHandler::write(const QImage &image)
return false; return false;
} }
if (!convert_color_profile && iccprofile.size() > 0) { auto xmp_data = image.text(QStringLiteral(META_KEY_XMP_ADOBE)).toUtf8();
if (!xmp_data.isEmpty()) {
const char *box_type = "xml ";
status = JxlEncoderAddBox(encoder, box_type, reinterpret_cast<const uint8_t *>(xmp_data.constData()), xmp_data.size(), JXL_FALSE);
if (status != JXL_ENC_SUCCESS) {
qWarning("JxlEncoderAddBox failed!");
if (runner) {
JxlThreadParallelRunnerDestroy(runner);
}
JxlEncoderDestroy(encoder);
return false;
}
}
JxlEncoderCloseBoxes(encoder); // no more metadata
if (iccprofile.size() > 0) {
status = JxlEncoderSetICCProfile(encoder, reinterpret_cast<const uint8_t *>(iccprofile.constData()), iccprofile.size()); status = JxlEncoderSetICCProfile(encoder, reinterpret_cast<const uint8_t *>(iccprofile.constData()), iccprofile.size());
if (status != JXL_ENC_SUCCESS) { if (status != JXL_ENC_SUCCESS) {
qWarning("JxlEncoderSetICCProfile failed!"); qWarning("JxlEncoderSetICCProfile failed!");
@ -693,61 +827,30 @@ bool QJpegXLHandler::write(const QImage &image)
JxlEncoderSetFrameLossless(encoder_options, (m_quality == 100) ? JXL_TRUE : JXL_FALSE); JxlEncoderSetFrameLossless(encoder_options, (m_quality == 100) ? JXL_TRUE : JXL_FALSE);
if (image.hasAlphaChannel() || ((save_depth == 8) && (xsize % 4 == 0))) { size_t buffer_size = size_t(tmpimage.bytesPerLine()) * tmpimage.height();
status = JxlEncoderAddImageFrame(encoder_options, &pixel_format, static_cast<const void *>(tmpimage.constBits()), buffer_size); if (!image.hasAlphaChannel() && save_depth > 8 && !is_gray) { // pack pixel on tmpimage
} else { buffer_size = (size_t(save_depth / 8) * pixel_format.num_channels * xsize * ysize);
if (save_depth > 8) { // 16bit depth without alpha channel
uint16_t *tmp_buffer = new (std::nothrow) uint16_t[3 * xsize * ysize];
if (!tmp_buffer) {
qWarning("Memory allocation error");
if (runner) {
JxlThreadParallelRunnerDestroy(runner);
}
JxlEncoderDestroy(encoder);
return false;
}
uint16_t *dest_pixels = tmp_buffer; // detaching image
for (int y = 0; y < tmpimage.height(); y++) { tmpimage.detach();
const uint16_t *src_pixels = reinterpret_cast<const uint16_t *>(tmpimage.constScanLine(y)); if (tmpimage.isNull()) {
for (int x = 0; x < tmpimage.width(); x++) { qWarning("Memory allocation error");
// R if (runner) {
*dest_pixels = *src_pixels; JxlThreadParallelRunnerDestroy(runner);
dest_pixels++;
src_pixels++;
// G
*dest_pixels = *src_pixels;
dest_pixels++;
src_pixels++;
// B
*dest_pixels = *src_pixels;
dest_pixels++;
src_pixels += 2; // skipalpha
}
} }
status = JxlEncoderAddImageFrame(encoder_options, &pixel_format, static_cast<const void *>(tmp_buffer), buffer_size); JxlEncoderDestroy(encoder);
delete[] tmp_buffer; return false;
} else { // 8bit depth without alpha channel
uchar *tmp_buffer8 = new (std::nothrow) uchar[3 * xsize * ysize];
if (!tmp_buffer8) {
qWarning("Memory allocation error");
if (runner) {
JxlThreadParallelRunnerDestroy(runner);
}
JxlEncoderDestroy(encoder);
return false;
}
uchar *dest_pixels8 = tmp_buffer8;
const size_t rowbytes = 3 * xsize;
for (int y = 0; y < tmpimage.height(); y++) {
memcpy(dest_pixels8, tmpimage.constScanLine(y), rowbytes);
dest_pixels8 += rowbytes;
}
status = JxlEncoderAddImageFrame(encoder_options, &pixel_format, static_cast<const void *>(tmp_buffer8), buffer_size);
delete[] tmp_buffer8;
} }
// pack pixel data
if (save_depth > 16 && save_fp)
packRGBPixels<float>(tmpimage);
else if (save_fp)
packRGBPixels<qfloat16>(tmpimage);
else
packRGBPixels<quint16>(tmpimage);
} }
status = JxlEncoderAddImageFrame(encoder_options, &pixel_format, static_cast<const void *>(tmpimage.constBits()), buffer_size);
if (status == JXL_ENC_ERROR) { if (status == JXL_ENC_ERROR) {
qWarning("JxlEncoderAddImageFrame failed!"); qWarning("JxlEncoderAddImageFrame failed!");
@ -1060,6 +1163,37 @@ bool QJpegXLHandler::rewind()
return true; return true;
} }
bool QJpegXLHandler::decodeBoxes()
{
JxlDecoderStatus status;
do { // decode metadata
status = JxlDecoderProcessInput(m_decoder);
if (status == JXL_DEC_BOX) {
JxlBoxType type;
JxlDecoderGetBoxType(m_decoder, type, JXL_FALSE);
if (memcmp(type, "xml ", 4) == 0) {
uint64_t size;
if (JxlDecoderGetBoxSizeRaw(m_decoder, &size) == JXL_DEC_SUCCESS) {
m_xmp = QByteArray(size, '\0');
JxlDecoderSetBoxBuffer(m_decoder, reinterpret_cast<uint8_t *>(m_xmp.data()), m_xmp.size());
}
}
}
} while (status == JXL_DEC_BOX);
if (status == JXL_DEC_ERROR) {
qWarning("ERROR: JXL decoding failed");
m_parseState = ParseJpegXLError;
return false;
}
if (status == JXL_DEC_NEED_MORE_INPUT) {
qWarning("ERROR: JXL data incomplete");
m_parseState = ParseJpegXLError;
return false;
}
return true;
}
QImageIOPlugin::Capabilities QJpegXLPlugin::capabilities(QIODevice *device, const QByteArray &format) const QImageIOPlugin::Capabilities QJpegXLPlugin::capabilities(QIODevice *device, const QByteArray &format) const
{ {
if (format == "jxl") { if (format == "jxl") {

View File

@ -51,6 +51,7 @@ private:
bool countALLFrames(); bool countALLFrames();
bool decode_one_frame(); bool decode_one_frame();
bool rewind(); bool rewind();
bool decodeBoxes();
enum ParseJpegXLState { enum ParseJpegXLState {
ParseJpegXLError = -1, ParseJpegXLError = -1,
@ -77,6 +78,7 @@ private:
QImage m_current_image; QImage m_current_image;
QColorSpace m_colorspace; QColorSpace m_colorspace;
QByteArray m_xmp;
QImage::Format m_input_image_format; QImage::Format m_input_image_format;
QImage::Format m_target_image_format; QImage::Format m_target_image_format;

View File

@ -175,8 +175,8 @@ public:
// 32-bit // 32-bit
if (IsEqualGUID(jxrfmt, GUID_PKPixelFormat32bppBGR)) { if (IsEqualGUID(jxrfmt, GUID_PKPixelFormat32bppBGR)) {
*conversionFormat = GUID_PKPixelFormat32bppRGB; *conversionFormat = GUID_PKPixelFormat24bppRGB;
return QImage::Format_RGBX8888; // Format_RGB32 (?) return QImage::Format_RGB888;
}; };
if (IsEqualGUID(jxrfmt, GUID_PKPixelFormat32bppBGRA)) { if (IsEqualGUID(jxrfmt, GUID_PKPixelFormat32bppBGRA)) {
*conversionFormat = GUID_PKPixelFormat32bppRGBA; *conversionFormat = GUID_PKPixelFormat32bppRGBA;
@ -484,11 +484,13 @@ public:
qi = qi.convertToFormat(alpha ? QImage::Format_RGBA8888 : QImage::Format_RGB888); qi = qi.convertToFormat(alpha ? QImage::Format_RGBA8888 : QImage::Format_RGB888);
} }
#ifndef JXR_DENY_FLOAT_IMAGE #ifndef JXR_DENY_FLOAT_IMAGE
} else if(qi.format() == QImage::Format_RGBA16FPx4 || // clang-format off
qi.format() == QImage::Format_RGBX16FPx4 || } else if (qi.format() == QImage::Format_RGBA16FPx4 ||
qi.format() == QImage::Format_RGBA32FPx4 || qi.format() == QImage::Format_RGBX16FPx4 ||
qi.format() == QImage::Format_RGBA32FPx4_Premultiplied || qi.format() == QImage::Format_RGBA32FPx4 ||
qi.format() == QImage::Format_RGBX32FPx4) { qi.format() == QImage::Format_RGBA32FPx4_Premultiplied ||
qi.format() == QImage::Format_RGBX32FPx4) {
// clang-format on
auto cs = qi.colorSpace(); auto cs = qi.colorSpace();
if (cs.isValid() && cs.transferFunction() != QColorSpace::TransferFunction::Linear) { if (cs.isValid() && cs.transferFunction() != QColorSpace::TransferFunction::Linear) {
qi = qi.convertedToColorSpace(QColorSpace(QColorSpace::SRgbLinear)); qi = qi.convertedToColorSpace(QColorSpace(QColorSpace::SRgbLinear));
@ -643,7 +645,7 @@ private:
<< std::pair<QImage::Format, PKPixelFormatGUID>(QImage::Format_Mono, GUID_PKPixelFormatBlackWhite) << std::pair<QImage::Format, PKPixelFormatGUID>(QImage::Format_Mono, GUID_PKPixelFormatBlackWhite)
<< std::pair<QImage::Format, PKPixelFormatGUID>(QImage::Format_Grayscale8, GUID_PKPixelFormat8bppGray) << std::pair<QImage::Format, PKPixelFormatGUID>(QImage::Format_Grayscale8, GUID_PKPixelFormat8bppGray)
<< std::pair<QImage::Format, PKPixelFormatGUID>(QImage::Format_Grayscale16, GUID_PKPixelFormat16bppGray) << std::pair<QImage::Format, PKPixelFormatGUID>(QImage::Format_Grayscale16, GUID_PKPixelFormat16bppGray)
<< std::pair<QImage::Format, PKPixelFormatGUID>(QImage::Format_RGB555, GUID_PKPixelFormat16bppRGB565) << std::pair<QImage::Format, PKPixelFormatGUID>(QImage::Format_RGB555, GUID_PKPixelFormat16bppRGB555)
<< std::pair<QImage::Format, PKPixelFormatGUID>(QImage::Format_RGB16, GUID_PKPixelFormat16bppRGB565) << std::pair<QImage::Format, PKPixelFormatGUID>(QImage::Format_RGB16, GUID_PKPixelFormat16bppRGB565)
<< std::pair<QImage::Format, PKPixelFormatGUID>(QImage::Format_BGR888, GUID_PKPixelFormat24bppBGR) << std::pair<QImage::Format, PKPixelFormatGUID>(QImage::Format_BGR888, GUID_PKPixelFormat24bppBGR)
<< std::pair<QImage::Format, PKPixelFormatGUID>(QImage::Format_RGB888, GUID_PKPixelFormat24bppRGB) << std::pair<QImage::Format, PKPixelFormatGUID>(QImage::Format_RGB888, GUID_PKPixelFormat24bppRGB)
@ -670,9 +672,12 @@ private:
return false; return false;
} }
QByteArray buff(32768 * 4, char()); QByteArray buff(32768 * 4, char());
for (; !source->atEnd();) { for (;;) {
auto read = source->read(buff.data(), buff.size()); auto read = source->read(buff.data(), buff.size());
if (read < 1) { if (read == 0) {
break;
}
if (read < 0) {
return false; return false;
} }
if (target->write(buff.data(), read) != read) { if (target->write(buff.data(), read) != read) {
@ -868,7 +873,7 @@ bool JXRHandler::read(QImage *outImage)
line[x + 3] = hasAlpha ? std::clamp(line[x + 3], float(0), float(1)) : float(1); line[x + 3] = hasAlpha ? std::clamp(line[x + 3], float(0), float(1)) : float(1);
} }
} }
if(!img.colorSpace().isValid()) { if (!img.colorSpace().isValid()) {
img.setColorSpace(QColorSpace(QColorSpace::SRgbLinear)); img.setColorSpace(QColorSpace(QColorSpace::SRgbLinear));
} }
} }
@ -880,6 +885,13 @@ bool JXRHandler::read(QImage *outImage)
bool JXRHandler::write(const QImage &image) bool JXRHandler::write(const QImage &image)
{ {
// JXR is stored in a TIFF V6 container that is limited to 4GiB. The size
// is limited to 4GB to leave room for IFDs, Metadata, etc...
if (qint64(image.sizeInBytes()) > 4000000000ll) {
qCWarning(LOG_JXRPLUGIN) << "JXRHandler::write() image too large: the image cannot exceed 4GB.";
return false;
}
if (!d->initForWriting()) { if (!d->initForWriting()) {
return false; return false;
} }
@ -1064,11 +1076,6 @@ bool JXRHandler::canRead(QIODevice *device)
return false; return false;
} }
// Some tests on sequential devices fail: I reject them for now
if (device->isSequential()) {
return false;
}
// JPEG XR image data is stored in TIFF-like container format (II and 0xBC01 version) // JPEG XR image data is stored in TIFF-like container format (II and 0xBC01 version)
if (device->peek(4) == QByteArray::fromRawData("\x49\x49\xbc\x01", 4)) { if (device->peek(4) == QByteArray::fromRawData("\x49\x49\xbc\x01", 4)) {
return true; return true;

View File

@ -266,30 +266,16 @@ PCXHEADER::PCXHEADER()
bool peekHeader(QIODevice *d, PCXHEADER& h) bool peekHeader(QIODevice *d, PCXHEADER& h)
{ {
qint64 pos = 0; auto head = d->peek(sizeof(PCXHEADER));
if (!d->isSequential()) { if (size_t(head.size()) < sizeof(PCXHEADER)) {
pos = d->pos(); return false;
} }
auto ok = false; QDataStream ds(head);
{ // datastream is destroyed before working on device ds.setByteOrder(QDataStream::LittleEndian);
QDataStream ds(d); ds >> h;
ds.setByteOrder(QDataStream::LittleEndian);
ds >> h;
ok = ds.status() == QDataStream::Ok && h.isValid();
}
if (!d->isSequential()) { return ds.status() == QDataStream::Ok && h.isValid();
return d->seek(pos) && ok;
}
// sequential device undo
auto head = reinterpret_cast<char*>(&h);
auto readBytes = sizeof(h);
while (readBytes > 0) {
d->ungetChar(head[readBytes-- - 1]);
}
return ok;
} }
static bool readLine(QDataStream &s, QByteArray &buf, const PCXHEADER &header) static bool readLine(QDataStream &s, QByteArray &buf, const PCXHEADER &header)

View File

@ -121,10 +121,8 @@ public:
bool peek(QIODevice *d) bool peek(QIODevice *d)
{ {
d->startTransaction(); m_rawHeader = d->peek(512);
auto ok = read(d); return isValid();
d->rollbackTransaction();
return ok;
} }
bool jumpToImageData(QIODevice *d) const bool jumpToImageData(QIODevice *d) const

View File

@ -341,12 +341,8 @@ bool QOIHandler::canRead(QIODevice *device)
return false; return false;
} }
device->startTransaction(); auto head = device->peek(QOI_HEADER_SIZE);
QByteArray head = device->read(QOI_HEADER_SIZE); if (head.size() < QOI_HEADER_SIZE) {
qsizetype readBytes = head.size();
device->rollbackTransaction();
if (readBytes < QOI_HEADER_SIZE) {
return false; return false;
} }
@ -430,12 +426,7 @@ QVariant QOIHandler::option(ImageOption option) const
if (IsSupported(header)) { if (IsSupported(header)) {
v = QVariant::fromValue(QSize(header.Width, header.Height)); v = QVariant::fromValue(QSize(header.Width, header.Height));
} else if (auto d = device()) { } else if (auto d = device()) {
// transactions works on both random and sequential devices QDataStream s(d->peek(sizeof(QoiHeader)));
d->startTransaction();
auto ba = d->read(sizeof(QoiHeader));
d->rollbackTransaction();
QDataStream s(ba);
s.setByteOrder(QDataStream::BigEndian); s.setByteOrder(QDataStream::BigEndian);
s >> header; s >> header;
if (s.status() == QDataStream::Ok && IsSupported(header)) { if (s.status() == QDataStream::Ok && IsSupported(header)) {
@ -449,12 +440,7 @@ QVariant QOIHandler::option(ImageOption option) const
if (IsSupported(header)) { if (IsSupported(header)) {
v = QVariant::fromValue(imageFormat(header)); v = QVariant::fromValue(imageFormat(header));
} else if (auto d = device()) { } else if (auto d = device()) {
// transactions works on both random and sequential devices QDataStream s(d->peek(sizeof(QoiHeader)));
d->startTransaction();
auto ba = d->read(sizeof(QoiHeader));
d->rollbackTransaction();
QDataStream s(ba);
s.setByteOrder(QDataStream::BigEndian); s.setByteOrder(QDataStream::BigEndian);
s >> header; s >> header;
if (s.status() == QDataStream::Ok && IsSupported(header)) { if (s.status() == QDataStream::Ok && IsSupported(header)) {

View File

@ -202,8 +202,6 @@ private:
static bool LoadRAS(QDataStream &s, const RasHeader &ras, QImage &img) static bool LoadRAS(QDataStream &s, const RasHeader &ras, QImage &img)
{ {
s.device()->seek(RasHeader::SIZE);
// The width of a scan line is always a multiple of 16 bits, padded when necessary. // The width of a scan line is always a multiple of 16 bits, padded when necessary.
auto rasLineSize = (qint64(ras.Width) * ras.Depth + 7) / 8; auto rasLineSize = (qint64(ras.Width) * ras.Depth + 7) / 8;
if (rasLineSize & 1) if (rasLineSize & 1)
@ -368,18 +366,8 @@ bool RASHandler::canRead(QIODevice *device)
return false; return false;
} }
if (device->isSequential()) { auto head = device->peek(RasHeader::SIZE); // header is exactly 32 bytes, always FIXME
// qWarning("Reading ras files from sequential devices not supported"); if (head.size() < RasHeader::SIZE) {
return false;
}
qint64 oldPos = device->pos();
QByteArray head = device->read(RasHeader::SIZE); // header is exactly 32 bytes, always FIXME
int readBytes = head.size(); // this should always be 32 bytes
device->seek(oldPos);
if (readBytes < RasHeader::SIZE) {
return false; return false;
} }
@ -411,9 +399,7 @@ bool RASHandler::read(QImage *outImage)
} }
QImage img; QImage img;
bool result = LoadRAS(s, ras, img); if (!LoadRAS(s, ras, img)) {
if (result == false) {
// qDebug() << "Error loading RAS file."; // qDebug() << "Error loading RAS file.";
return false; return false;
} }
@ -443,12 +429,7 @@ QVariant RASHandler::option(ImageOption option) const
v = QVariant::fromValue(QSize(header.Width, header.Height)); v = QVariant::fromValue(QSize(header.Width, header.Height));
} }
else if (auto dev = device()) { else if (auto dev = device()) {
// transactions works on both random and sequential devices QDataStream s(dev->peek(RasHeader::SIZE));
dev->startTransaction();
auto ba = dev->read(RasHeader::SIZE);
dev->rollbackTransaction();
QDataStream s(ba);
s.setByteOrder(QDataStream::BigEndian); s.setByteOrder(QDataStream::BigEndian);
s >> header; s >> header;
if (s.status() == QDataStream::Ok && IsSupported(header)) { if (s.status() == QDataStream::Ok && IsSupported(header)) {
@ -463,12 +444,7 @@ QVariant RASHandler::option(ImageOption option) const
v = QVariant::fromValue(imageFormat(header)); v = QVariant::fromValue(imageFormat(header));
} }
else if (auto dev = device()) { else if (auto dev = device()) {
// transactions works on both random and sequential devices QDataStream s(dev->peek(RasHeader::SIZE));
dev->startTransaction();
auto ba = dev->read(RasHeader::SIZE);
dev->rollbackTransaction();
QDataStream s(ba);
s.setByteOrder(QDataStream::BigEndian); s.setByteOrder(QDataStream::BigEndian);
s >> header; s >> header;
if (s.status() == QDataStream::Ok && IsSupported(header)) { if (s.status() == QDataStream::Ok && IsSupported(header)) {

View File

@ -806,12 +806,10 @@ QVariant RAWHandler::option(ImageOption option) const
rawProcessor->imgdata.rawparams.shot_select = currentImageNumber(); rawProcessor->imgdata.rawparams.shot_select = currentImageNumber();
#endif #endif
if (rawProcessor->open_datastream(&stream) == LIBRAW_SUCCESS) { if (rawProcessor->open_datastream(&stream) == LIBRAW_SUCCESS) {
if (rawProcessor->unpack() == LIBRAW_SUCCESS) { auto w = libraw_get_iwidth(&rawProcessor->imgdata);
auto w = libraw_get_iwidth(&rawProcessor->imgdata); auto h = libraw_get_iheight(&rawProcessor->imgdata);
auto h = libraw_get_iheight(&rawProcessor->imgdata); // flip & 4: taken from LibRaw code
// flip & 4: taken from LibRaw code v = (rawProcessor->imgdata.sizes.flip & 4) ? QSize(h, w) : QSize(w, h);
v = (rawProcessor->imgdata.sizes.flip & 4) ? QSize(h, w) : QSize(w, h);
}
} }
d->rollbackTransaction(); d->rollbackTransaction();
} }

View File

@ -578,30 +578,8 @@ bool SGIImagePrivate::isSupported() const
bool SGIImagePrivate::peekHeader(QIODevice *device) bool SGIImagePrivate::peekHeader(QIODevice *device)
{ {
qint64 pos = 0; QDataStream ds(device->peek(512));
if (!device->isSequential()) { return SGIImagePrivate::readHeader(ds, this) && isValid();
pos = device->pos();
}
auto ok = false;
QByteArray header;
{ // datastream is destroyed before working on device
header = device->read(512);
QDataStream ds(header);
ok = SGIImagePrivate::readHeader(ds, this) && isValid();
}
if (!device->isSequential()) {
return device->seek(pos) && ok;
}
// sequential device undo
auto head = header.data();
auto readBytes = header.size();
while (readBytes > 0) {
device->ungetChar(head[readBytes-- - 1]);
}
return ok;
} }
QSize SGIImagePrivate::size() const QSize SGIImagePrivate::size() const

View File

@ -174,15 +174,21 @@ static QImage::Format imageFormat(const TgaHeader &head)
{ {
auto format = QImage::Format_Invalid; auto format = QImage::Format_Invalid;
if (IsSupported(head)) { if (IsSupported(head)) {
TgaHeaderInfo info(head);
// Bits 0-3 are the numbers of alpha bits (can be zero!) // Bits 0-3 are the numbers of alpha bits (can be zero!)
const int numAlphaBits = head.flags & 0xf; const int numAlphaBits = head.flags & 0xf;
// However alpha exists only in the 32 bit format. // However alpha should exists only in the 32 bit format.
if ((head.pixel_size == 32) && (head.flags & 0xf)) { if ((head.pixel_size == 32) && (numAlphaBits)) {
if (numAlphaBits <= 8) { if (numAlphaBits <= 8) {
format = QImage::Format_ARGB32; format = QImage::Format_ARGB32;
} }
} // Anyway, GIMP also saves gray images with alpha in TGA format
else { } else if((info.grey) && (head.pixel_size == 16) && (numAlphaBits)) {
if (numAlphaBits == 8) {
format = QImage::Format_ARGB32;
}
} else {
format = QImage::Format_RGB32; format = QImage::Format_RGB32;
} }
} }
@ -195,26 +201,13 @@ static QImage::Format imageFormat(const TgaHeader &head)
*/ */
static bool peekHeader(QIODevice *device, TgaHeader &header) static bool peekHeader(QIODevice *device, TgaHeader &header)
{ {
qint64 oldPos = device->pos(); auto head = device->peek(TgaHeader::SIZE);
QByteArray head = device->read(TgaHeader::SIZE); if (head.size() < TgaHeader::SIZE) {
int readBytes = head.size();
if (device->isSequential()) {
for (int pos = readBytes - 1; pos >= 0; --pos) {
device->ungetChar(head[pos]);
}
} else {
device->seek(oldPos);
}
if (readBytes < TgaHeader::SIZE) {
return false; return false;
} }
QDataStream stream(head); QDataStream stream(head);
stream.setByteOrder(QDataStream::LittleEndian); stream.setByteOrder(QDataStream::LittleEndian);
stream >> header; stream >> header;
return true; return true;
} }
@ -361,8 +354,7 @@ static bool LoadTGA(QDataStream &s, const TgaHeader &tga, QImage &img)
uchar *src = image; uchar *src = image;
for (int y = y_start; y != y_end; y += y_step) { for (int y = y_start; y != y_end; y += y_step) {
QRgb *scanline = (QRgb *)img.scanLine(y); auto scanline = reinterpret_cast<QRgb *>(img.scanLine(y));
if (info.pal) { if (info.pal) {
// Paletted. // Paletted.
for (int x = 0; x < tga.width; x++) { for (int x = 0; x < tga.width; x++) {
@ -372,8 +364,14 @@ static bool LoadTGA(QDataStream &s, const TgaHeader &tga, QImage &img)
} else if (info.grey) { } else if (info.grey) {
// Greyscale. // Greyscale.
for (int x = 0; x < tga.width; x++) { for (int x = 0; x < tga.width; x++) {
scanline[x] = qRgb(*src, *src, *src); if (tga.pixel_size == 16) {
src++; scanline[x] = qRgba(*src, *src, *src, *(src + 1));
src += 2;
}
else {
scanline[x] = qRgb(*src, *src, *src);
src++;
}
} }
} else { } else {
// True Color. // True Color.
@ -561,22 +559,6 @@ bool TGAHandler::canRead(QIODevice *device)
return false; return false;
} }
qint64 oldPos = device->pos();
QByteArray head = device->read(TgaHeader::SIZE);
int readBytes = head.size();
if (device->isSequential()) {
for (int pos = readBytes - 1; pos >= 0; --pos) {
device->ungetChar(head[pos]);
}
} else {
device->seek(oldPos);
}
if (readBytes < TgaHeader::SIZE) {
return false;
}
TgaHeader tga; TgaHeader tga;
if (!peekHeader(device, tga)) { if (!peekHeader(device, tga)) {
qWarning("TGAHandler::canRead() error while reading the header"); qWarning("TGAHandler::canRead() error while reading the header");

View File

@ -4229,66 +4229,43 @@ bool XCFHandler::canRead(QIODevice *device)
} }
const qint64 oldPos = device->pos(); const qint64 oldPos = device->pos();
if (!device->isSequential()) {
QDataStream ds(device);
XCFImageFormat::XCFImage::Header header;
bool failed = !XCFImageFormat::readXCFHeader(ds, &header);
ds.setDevice(nullptr);
device->seek(oldPos);
if (failed) {
return false;
}
switch (header.precision) { QDataStream ds(device);
case XCFImageFormat::GIMP_PRECISION_HALF_LINEAR: XCFImageFormat::XCFImage::Header header;
case XCFImageFormat::GIMP_PRECISION_HALF_NON_LINEAR: bool failed = !XCFImageFormat::readXCFHeader(ds, &header);
case XCFImageFormat::GIMP_PRECISION_HALF_PERCEPTUAL: ds.setDevice(nullptr);
case XCFImageFormat::GIMP_PRECISION_FLOAT_LINEAR:
case XCFImageFormat::GIMP_PRECISION_FLOAT_NON_LINEAR:
case XCFImageFormat::GIMP_PRECISION_FLOAT_PERCEPTUAL:
case XCFImageFormat::GIMP_PRECISION_U8_LINEAR:
case XCFImageFormat::GIMP_PRECISION_U8_NON_LINEAR:
case XCFImageFormat::GIMP_PRECISION_U8_PERCEPTUAL:
case XCFImageFormat::GIMP_PRECISION_U16_LINEAR:
case XCFImageFormat::GIMP_PRECISION_U16_NON_LINEAR:
case XCFImageFormat::GIMP_PRECISION_U16_PERCEPTUAL:
case XCFImageFormat::GIMP_PRECISION_U32_LINEAR:
case XCFImageFormat::GIMP_PRECISION_U32_NON_LINEAR:
case XCFImageFormat::GIMP_PRECISION_U32_PERCEPTUAL:
break;
case XCFImageFormat::GIMP_PRECISION_DOUBLE_LINEAR:
case XCFImageFormat::GIMP_PRECISION_DOUBLE_NON_LINEAR:
case XCFImageFormat::GIMP_PRECISION_DOUBLE_PERCEPTUAL:
default:
qCDebug(XCFPLUGIN) << "unsupported precision" << header.precision;
return false;
}
return true; device->seek(oldPos);
} if (failed) {
char head[8];
qint64 readBytes = device->read(head, sizeof(head));
if (readBytes != sizeof(head)) {
if (device->isSequential()) {
while (readBytes > 0) {
device->ungetChar(head[readBytes-- - 1]);
}
} else {
device->seek(oldPos);
}
return false; return false;
} }
if (device->isSequential()) { switch (header.precision) {
while (readBytes > 0) { case XCFImageFormat::GIMP_PRECISION_HALF_LINEAR:
device->ungetChar(head[readBytes-- - 1]); case XCFImageFormat::GIMP_PRECISION_HALF_NON_LINEAR:
} case XCFImageFormat::GIMP_PRECISION_HALF_PERCEPTUAL:
} else { case XCFImageFormat::GIMP_PRECISION_FLOAT_LINEAR:
device->seek(oldPos); case XCFImageFormat::GIMP_PRECISION_FLOAT_NON_LINEAR:
case XCFImageFormat::GIMP_PRECISION_FLOAT_PERCEPTUAL:
case XCFImageFormat::GIMP_PRECISION_U8_LINEAR:
case XCFImageFormat::GIMP_PRECISION_U8_NON_LINEAR:
case XCFImageFormat::GIMP_PRECISION_U8_PERCEPTUAL:
case XCFImageFormat::GIMP_PRECISION_U16_LINEAR:
case XCFImageFormat::GIMP_PRECISION_U16_NON_LINEAR:
case XCFImageFormat::GIMP_PRECISION_U16_PERCEPTUAL:
case XCFImageFormat::GIMP_PRECISION_U32_LINEAR:
case XCFImageFormat::GIMP_PRECISION_U32_NON_LINEAR:
case XCFImageFormat::GIMP_PRECISION_U32_PERCEPTUAL:
break;
case XCFImageFormat::GIMP_PRECISION_DOUBLE_LINEAR:
case XCFImageFormat::GIMP_PRECISION_DOUBLE_NON_LINEAR:
case XCFImageFormat::GIMP_PRECISION_DOUBLE_PERCEPTUAL:
default:
qCDebug(XCFPLUGIN) << "unsupported precision" << header.precision;
return false;
} }
return qstrncmp(head, "gimp xcf", 8) == 0; return true;
} }
QImageIOPlugin::Capabilities XCFPlugin::capabilities(QIODevice *device, const QByteArray &format) const QImageIOPlugin::Capabilities XCFPlugin::capabilities(QIODevice *device, const QByteArray &format) const