Compare commits
13 Commits
v6.6.0-rc1
...
v6.8.0
Author | SHA1 | Date | |
---|---|---|---|
799ac37660 | |||
0378bd67e1 | |||
7d696a81d2 | |||
b5d5abe0ea | |||
3f4690d1e9 | |||
ac1006cc66 | |||
97120b2537 | |||
fee0165bef | |||
ae641f7e94 | |||
46f7b90ce6 | |||
f7c8eaa140 | |||
36bfee8ae3 | |||
e2aaf89ec5 |
@ -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"
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
12
README.md
@ -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
|
||||||
|
BIN
autotests/read/jxl/testcard_gray16.jxl
Normal file
BIN
autotests/read/jxl/testcard_gray16.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
autotests/read/jxl/testcard_gray8.jxl
Normal file
BIN
autotests/read/jxl/testcard_gray8.png
Normal file
After Width: | Height: | Size: 5.4 KiB |
BIN
autotests/read/jxl/testcard_rgb_fp16.jxl
Normal file
BIN
autotests/read/jxl/testcard_rgb_fp16.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
autotests/read/jxl/testcard_rgb_fp32.jxl
Normal file
BIN
autotests/read/jxl/testcard_rgb_fp32.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
autotests/read/jxl/testcard_rgba_fp16.jxl
Normal file
21
autotests/read/jxl/testcard_rgba_fp16.jxl.json
Normal 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"
|
||||||
|
}
|
||||||
|
]
|
BIN
autotests/read/jxl/testcard_rgba_fp16.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
autotests/read/jxl/testcard_rgba_fp32.jxl
Normal file
21
autotests/read/jxl/testcard_rgba_fp32.jxl.json
Normal 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"
|
||||||
|
}
|
||||||
|
]
|
BIN
autotests/read/jxl/testcard_rgba_fp32.png
Normal file
After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 485 B |
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 737 B |
@ -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;
|
||||||
|
@ -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());
|
||||||
|
@ -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") {
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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)) {
|
||||||
|
@ -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)) {
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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");
|
||||||
|
@ -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
|
||||||
|