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:
|
||||
test-before-installing: True
|
||||
require-passing-tests-on: [ 'Linux', 'FreeBSD', 'Windows' ]
|
||||
cmake-options: "-DKIMAGEFORMATS_JXR=ON"
|
||||
|
@ -1,11 +1,11 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
set(KF_VERSION "6.6.0") # handled by release scripts
|
||||
set(KF_DEP_VERSION "6.6.0") # handled by release scripts
|
||||
set(KF_VERSION "6.8.0") # handled by release scripts
|
||||
set(KF_DEP_VERSION "6.8.0") # handled by release scripts
|
||||
project(KImageFormats VERSION ${KF_VERSION})
|
||||
|
||||
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")
|
||||
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 are supported via floating point image formats from EXR, HDR, JXR,
|
||||
PFM and PSD plugins.
|
||||
HDR images are supported via floating point image formats from EXR, HDR, JXL,
|
||||
JXR, PFM and PSD plugins.
|
||||
It is important to note that in the past these plugins stripped away HDR
|
||||
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
|
||||
- HDR: n/a (large image)
|
||||
- HEIF: n/a
|
||||
- JXL: 65,535 x 65,535 pixels, in any case no larger than 256 megapixels
|
||||
- JXR: n/a
|
||||
- JXL: 262,144 x 262,144 pixels, in any case no larger than 256 megapixels
|
||||
- JXR: n/a, in any case no larger than 4 GB
|
||||
- PCX: 65,535 x 65,535 pixels
|
||||
- PFM: n/a (large image)
|
||||
- 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
|
||||
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
|
||||
|
||||
**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)) {
|
||||
m_transformations = reader->transformation();
|
||||
if (m_transformations < 0 || m_transformations > 7)
|
||||
if (int(m_transformations) < 0 || int(m_transformations) > 7)
|
||||
ok = false;
|
||||
}
|
||||
return ok;
|
||||
|
@ -86,8 +86,8 @@
|
||||
class K_IStream : public Imf::IStream
|
||||
{
|
||||
public:
|
||||
K_IStream(QIODevice *dev, const QByteArray &fileName)
|
||||
: IStream(fileName.data())
|
||||
K_IStream(QIODevice *dev)
|
||||
: IStream("K_IStream")
|
||||
, m_dev(dev)
|
||||
{
|
||||
}
|
||||
@ -145,8 +145,8 @@ void K_IStream::clear()
|
||||
class K_OStream : public Imf::OStream
|
||||
{
|
||||
public:
|
||||
K_OStream(QIODevice *dev, const QByteArray &fileName)
|
||||
: OStream(fileName.data())
|
||||
K_OStream(QIODevice *dev)
|
||||
: OStream("K_OStream")
|
||||
, 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);
|
||||
auto &&header = file.header();
|
||||
|
||||
@ -583,7 +583,7 @@ bool EXRHandler::write(const QImage &image)
|
||||
setMetadata(image, header);
|
||||
|
||||
// write the EXR
|
||||
K_OStream ostr(device(), QByteArray());
|
||||
K_OStream ostr(device());
|
||||
auto channelsType = image.hasAlphaChannel() ? Imf::RgbaChannels::WRITE_RGBA : Imf::RgbaChannels::WRITE_RGB;
|
||||
if (image.format() == QImage::Format_Mono ||
|
||||
image.format() == QImage::Format_MonoLSB ||
|
||||
@ -673,7 +673,7 @@ QVariant EXRHandler::option(ImageOption option) const
|
||||
d->seek(m_startPos);
|
||||
}
|
||||
try {
|
||||
K_IStream istr(d, QByteArray());
|
||||
K_IStream istr(d);
|
||||
Imf::RgbaInputFile file(istr);
|
||||
if (m_imageNumber > -1) { // set the image to read
|
||||
auto views = viewList(file.header());
|
||||
@ -698,7 +698,7 @@ QVariant EXRHandler::option(ImageOption option) const
|
||||
d->seek(m_startPos);
|
||||
}
|
||||
try {
|
||||
K_IStream istr(d, QByteArray());
|
||||
K_IStream istr(d);
|
||||
Imf::RgbaInputFile file(istr);
|
||||
v = QVariant::fromValue(imageFormat(file));
|
||||
} catch (const std::exception &) {
|
||||
@ -747,7 +747,7 @@ int EXRHandler::imageCount() const
|
||||
d->startTransaction();
|
||||
|
||||
try {
|
||||
K_IStream istr(d, QByteArray());
|
||||
K_IStream istr(d);
|
||||
Imf::RgbaInputFile file(istr);
|
||||
auto views = viewList(file.header());
|
||||
if (!views.isEmpty()) {
|
||||
@ -774,6 +774,13 @@ bool EXRHandler::canRead(QIODevice *device)
|
||||
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);
|
||||
|
||||
return Imf::isImfMagic(head.data());
|
||||
|
@ -23,6 +23,34 @@
|
||||
#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()
|
||||
: m_parseState(ParseJpegXLNotParsed)
|
||||
, m_quality(90)
|
||||
@ -164,23 +192,18 @@ bool QJpegXLHandler::ensureDecoder()
|
||||
}
|
||||
|
||||
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);
|
||||
#endif
|
||||
if (status == JXL_DEC_ERROR) {
|
||||
qWarning("ERROR: JxlDecoderSubscribeEvents failed");
|
||||
m_parseState = ParseJpegXLError;
|
||||
return false;
|
||||
}
|
||||
|
||||
status = JxlDecoderProcessInput(m_decoder);
|
||||
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;
|
||||
if (!decodeBoxes()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -197,32 +220,12 @@ bool QJpegXLHandler::ensureDecoder()
|
||||
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);
|
||||
m_parseState = ParseJpegXLError;
|
||||
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;
|
||||
return true;
|
||||
}
|
||||
@ -256,27 +259,50 @@ bool QJpegXLHandler::countALLFrames()
|
||||
|
||||
m_input_pixel_format.endianness = JXL_NATIVE_ENDIAN;
|
||||
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
|
||||
m_input_pixel_format.data_type = JXL_TYPE_UINT16;
|
||||
m_buffer_size = 8 * (size_t)m_basicinfo.xsize * (size_t)m_basicinfo.ysize;
|
||||
m_input_image_format = QImage::Format_RGBA64;
|
||||
#ifdef JXL_HDR_PRESERVATION_DISABLED
|
||||
bool is_fp = false;
|
||||
#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) {
|
||||
m_target_image_format = QImage::Format_RGBA64;
|
||||
if (m_basicinfo.num_color_channels == 1) {
|
||||
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 {
|
||||
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
|
||||
m_input_pixel_format.data_type = JXL_TYPE_UINT8;
|
||||
m_buffer_size = 4 * (size_t)m_basicinfo.xsize * (size_t)m_basicinfo.ysize;
|
||||
m_input_image_format = QImage::Format_RGBA8888;
|
||||
m_buffer_size = (size_t)m_basicinfo.xsize * (size_t)m_basicinfo.ysize * m_input_pixel_format.num_channels;
|
||||
|
||||
if (loadalpha) {
|
||||
m_target_image_format = QImage::Format_ARGB32;
|
||||
if (m_basicinfo.num_color_channels == 1) {
|
||||
m_input_image_format = m_target_image_format = QImage::Format_Grayscale8;
|
||||
} 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;
|
||||
}
|
||||
|
||||
#ifndef JXL_DECODE_BOXES_DISABLED
|
||||
if (!decodeBoxes()) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!rewind()) {
|
||||
return false;
|
||||
}
|
||||
@ -402,6 +434,9 @@ bool QJpegXLHandler::decode_one_frame()
|
||||
}
|
||||
|
||||
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) {
|
||||
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)
|
||||
{
|
||||
if (image.format() == QImage::Format_Invalid) {
|
||||
@ -470,36 +529,47 @@ bool QJpegXLHandler::write(const QImage &image)
|
||||
return false;
|
||||
}
|
||||
|
||||
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 {
|
||||
if ((image.width() == 0) || (image.height() == 0)) {
|
||||
qWarning("Image has zero dimension!");
|
||||
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
|
||||
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_A2BGR30_Premultiplied:
|
||||
case QImage::Format_RGB30:
|
||||
case QImage::Format_A2RGB30_Premultiplied:
|
||||
case QImage::Format_Grayscale16:
|
||||
case QImage::Format_RGBX64:
|
||||
case QImage::Format_RGBA64:
|
||||
case QImage::Format_RGBA64_Premultiplied:
|
||||
@ -514,6 +584,21 @@ bool QJpegXLHandler::write(const QImage &image)
|
||||
case QImage::Format_RGBA8888_Premultiplied:
|
||||
save_depth = 8;
|
||||
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:
|
||||
if (image.depth() > 32) {
|
||||
save_depth = 16;
|
||||
@ -528,6 +613,7 @@ bool QJpegXLHandler::write(const QImage &image)
|
||||
qWarning("Failed to create Jxl encoder");
|
||||
return false;
|
||||
}
|
||||
JxlEncoderUseBoxes(encoder);
|
||||
|
||||
if (m_quality > 100) {
|
||||
m_quality = 100;
|
||||
@ -538,28 +624,28 @@ bool QJpegXLHandler::write(const QImage &image)
|
||||
JxlBasicInfo output_info;
|
||||
JxlEncoderInitBasicInfo(&output_info);
|
||||
|
||||
bool convert_color_profile;
|
||||
QByteArray iccprofile;
|
||||
|
||||
if (image.colorSpace().isValid() && (m_quality < 100)) {
|
||||
if (image.colorSpace().primaries() != QColorSpace::Primaries::SRgb || image.colorSpace().transferFunction() != QColorSpace::TransferFunction::SRgb) {
|
||||
convert_color_profile = true;
|
||||
} else {
|
||||
convert_color_profile = false;
|
||||
}
|
||||
} else { // lossless or no profile or Qt-unsupported ICC profile
|
||||
convert_color_profile = false;
|
||||
iccprofile = image.colorSpace().iccProfile();
|
||||
QColorSpace tmpcs = image.colorSpace();
|
||||
if (!tmpcs.isValid() || tmpcs.primaries() != QColorSpace::Primaries::SRgb || tmpcs.transferFunction() != QColorSpace::TransferFunction::SRgb || m_quality == 100) {
|
||||
// no profile or Qt-unsupported ICC profile
|
||||
iccprofile = tmpcs.iccProfile();
|
||||
// note: lossless encoding requires uses_original_profile = JXL_TRUE
|
||||
if (iccprofile.size() > 0 || m_quality == 100) {
|
||||
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;
|
||||
JxlEncoderUseContainer(encoder, JXL_TRUE);
|
||||
JxlEncoderSetCodestreamLevel(encoder, 10);
|
||||
}
|
||||
// clang-format on
|
||||
|
||||
void *runner = nullptr;
|
||||
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.align = 0;
|
||||
|
||||
output_info.num_color_channels = 3;
|
||||
output_info.animation.tps_numerator = 10;
|
||||
output_info.animation.tps_denominator = 1;
|
||||
output_info.orientation = JXL_ORIENT_IDENTITY;
|
||||
@ -601,24 +686,60 @@ bool QJpegXLHandler::write(const QImage &image)
|
||||
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.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;
|
||||
|
||||
if (image.hasAlphaChannel()) {
|
||||
tmpformat = QImage::Format_RGBA64;
|
||||
tmpformat = save_fp ? QImage::Format_RGBA16FPx4 : QImage::Format_RGBA64;
|
||||
pixel_format.num_channels = 4;
|
||||
output_info.alpha_bits = 16;
|
||||
output_info.alpha_exponent_bits = save_fp ? 5 : 0;
|
||||
output_info.num_extra_channels = 1;
|
||||
} else {
|
||||
tmpformat = QImage::Format_RGBX64;
|
||||
tmpformat = save_fp ? QImage::Format_RGBX16FPx4 : QImage::Format_RGBX64;
|
||||
pixel_format.num_channels = 3;
|
||||
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.align = 4;
|
||||
output_info.num_color_channels = 3;
|
||||
output_info.bits_per_sample = 8;
|
||||
|
||||
if (image.hasAlphaChannel()) {
|
||||
@ -630,15 +751,13 @@ bool QJpegXLHandler::write(const QImage &image)
|
||||
tmpformat = QImage::Format_RGB888;
|
||||
pixel_format.num_channels = 3;
|
||||
output_info.alpha_bits = 0;
|
||||
output_info.num_extra_channels = 0;
|
||||
}
|
||||
}
|
||||
|
||||
const QImage tmpimage =
|
||||
convert_color_profile ? image.convertToFormat(tmpformat).convertedToColorSpace(QColorSpace(QColorSpace::SRgb)) : image.convertToFormat(tmpformat);
|
||||
|
||||
QImage tmpimage = image.convertToFormat(tmpformat);
|
||||
const size_t xsize = tmpimage.width();
|
||||
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()) {
|
||||
qWarning("Unable to allocate memory for output image");
|
||||
@ -662,7 +781,22 @@ bool QJpegXLHandler::write(const QImage &image)
|
||||
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());
|
||||
if (status != JXL_ENC_SUCCESS) {
|
||||
qWarning("JxlEncoderSetICCProfile failed!");
|
||||
@ -693,61 +827,30 @@ bool QJpegXLHandler::write(const QImage &image)
|
||||
|
||||
JxlEncoderSetFrameLossless(encoder_options, (m_quality == 100) ? JXL_TRUE : JXL_FALSE);
|
||||
|
||||
if (image.hasAlphaChannel() || ((save_depth == 8) && (xsize % 4 == 0))) {
|
||||
status = JxlEncoderAddImageFrame(encoder_options, &pixel_format, static_cast<const void *>(tmpimage.constBits()), buffer_size);
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
size_t buffer_size = size_t(tmpimage.bytesPerLine()) * tmpimage.height();
|
||||
if (!image.hasAlphaChannel() && save_depth > 8 && !is_gray) { // pack pixel on tmpimage
|
||||
buffer_size = (size_t(save_depth / 8) * pixel_format.num_channels * xsize * ysize);
|
||||
|
||||
uint16_t *dest_pixels = tmp_buffer;
|
||||
for (int y = 0; y < tmpimage.height(); y++) {
|
||||
const uint16_t *src_pixels = reinterpret_cast<const uint16_t *>(tmpimage.constScanLine(y));
|
||||
for (int x = 0; x < tmpimage.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
|
||||
}
|
||||
// detaching image
|
||||
tmpimage.detach();
|
||||
if (tmpimage.isNull()) {
|
||||
qWarning("Memory allocation error");
|
||||
if (runner) {
|
||||
JxlThreadParallelRunnerDestroy(runner);
|
||||
}
|
||||
status = JxlEncoderAddImageFrame(encoder_options, &pixel_format, static_cast<const void *>(tmp_buffer), buffer_size);
|
||||
delete[] tmp_buffer;
|
||||
} 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;
|
||||
JxlEncoderDestroy(encoder);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
qWarning("JxlEncoderAddImageFrame failed!");
|
||||
@ -1060,6 +1163,37 @@ bool QJpegXLHandler::rewind()
|
||||
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
|
||||
{
|
||||
if (format == "jxl") {
|
||||
|
@ -51,6 +51,7 @@ private:
|
||||
bool countALLFrames();
|
||||
bool decode_one_frame();
|
||||
bool rewind();
|
||||
bool decodeBoxes();
|
||||
|
||||
enum ParseJpegXLState {
|
||||
ParseJpegXLError = -1,
|
||||
@ -77,6 +78,7 @@ private:
|
||||
|
||||
QImage m_current_image;
|
||||
QColorSpace m_colorspace;
|
||||
QByteArray m_xmp;
|
||||
|
||||
QImage::Format m_input_image_format;
|
||||
QImage::Format m_target_image_format;
|
||||
|
@ -175,8 +175,8 @@ public:
|
||||
|
||||
// 32-bit
|
||||
if (IsEqualGUID(jxrfmt, GUID_PKPixelFormat32bppBGR)) {
|
||||
*conversionFormat = GUID_PKPixelFormat32bppRGB;
|
||||
return QImage::Format_RGBX8888; // Format_RGB32 (?)
|
||||
*conversionFormat = GUID_PKPixelFormat24bppRGB;
|
||||
return QImage::Format_RGB888;
|
||||
};
|
||||
if (IsEqualGUID(jxrfmt, GUID_PKPixelFormat32bppBGRA)) {
|
||||
*conversionFormat = GUID_PKPixelFormat32bppRGBA;
|
||||
@ -484,11 +484,13 @@ public:
|
||||
qi = qi.convertToFormat(alpha ? QImage::Format_RGBA8888 : QImage::Format_RGB888);
|
||||
}
|
||||
#ifndef JXR_DENY_FLOAT_IMAGE
|
||||
} else if(qi.format() == QImage::Format_RGBA16FPx4 ||
|
||||
qi.format() == QImage::Format_RGBX16FPx4 ||
|
||||
qi.format() == QImage::Format_RGBA32FPx4 ||
|
||||
qi.format() == QImage::Format_RGBA32FPx4_Premultiplied ||
|
||||
qi.format() == QImage::Format_RGBX32FPx4) {
|
||||
// clang-format off
|
||||
} else if (qi.format() == QImage::Format_RGBA16FPx4 ||
|
||||
qi.format() == QImage::Format_RGBX16FPx4 ||
|
||||
qi.format() == QImage::Format_RGBA32FPx4 ||
|
||||
qi.format() == QImage::Format_RGBA32FPx4_Premultiplied ||
|
||||
qi.format() == QImage::Format_RGBX32FPx4) {
|
||||
// clang-format on
|
||||
auto cs = qi.colorSpace();
|
||||
if (cs.isValid() && cs.transferFunction() != QColorSpace::TransferFunction::Linear) {
|
||||
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_Grayscale8, GUID_PKPixelFormat8bppGray)
|
||||
<< 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_BGR888, GUID_PKPixelFormat24bppBGR)
|
||||
<< std::pair<QImage::Format, PKPixelFormatGUID>(QImage::Format_RGB888, GUID_PKPixelFormat24bppRGB)
|
||||
@ -670,9 +672,12 @@ private:
|
||||
return false;
|
||||
}
|
||||
QByteArray buff(32768 * 4, char());
|
||||
for (; !source->atEnd();) {
|
||||
for (;;) {
|
||||
auto read = source->read(buff.data(), buff.size());
|
||||
if (read < 1) {
|
||||
if (read == 0) {
|
||||
break;
|
||||
}
|
||||
if (read < 0) {
|
||||
return false;
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
if(!img.colorSpace().isValid()) {
|
||||
if (!img.colorSpace().isValid()) {
|
||||
img.setColorSpace(QColorSpace(QColorSpace::SRgbLinear));
|
||||
}
|
||||
}
|
||||
@ -880,6 +885,13 @@ bool JXRHandler::read(QImage *outImage)
|
||||
|
||||
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()) {
|
||||
return false;
|
||||
}
|
||||
@ -1064,11 +1076,6 @@ bool JXRHandler::canRead(QIODevice *device)
|
||||
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)
|
||||
if (device->peek(4) == QByteArray::fromRawData("\x49\x49\xbc\x01", 4)) {
|
||||
return true;
|
||||
|
@ -266,30 +266,16 @@ PCXHEADER::PCXHEADER()
|
||||
|
||||
bool peekHeader(QIODevice *d, PCXHEADER& h)
|
||||
{
|
||||
qint64 pos = 0;
|
||||
if (!d->isSequential()) {
|
||||
pos = d->pos();
|
||||
auto head = d->peek(sizeof(PCXHEADER));
|
||||
if (size_t(head.size()) < sizeof(PCXHEADER)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto ok = false;
|
||||
{ // datastream is destroyed before working on device
|
||||
QDataStream ds(d);
|
||||
ds.setByteOrder(QDataStream::LittleEndian);
|
||||
ds >> h;
|
||||
ok = ds.status() == QDataStream::Ok && h.isValid();
|
||||
}
|
||||
QDataStream ds(head);
|
||||
ds.setByteOrder(QDataStream::LittleEndian);
|
||||
ds >> h;
|
||||
|
||||
if (!d->isSequential()) {
|
||||
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;
|
||||
return ds.status() == QDataStream::Ok && h.isValid();
|
||||
}
|
||||
|
||||
static bool readLine(QDataStream &s, QByteArray &buf, const PCXHEADER &header)
|
||||
|
@ -121,10 +121,8 @@ public:
|
||||
|
||||
bool peek(QIODevice *d)
|
||||
{
|
||||
d->startTransaction();
|
||||
auto ok = read(d);
|
||||
d->rollbackTransaction();
|
||||
return ok;
|
||||
m_rawHeader = d->peek(512);
|
||||
return isValid();
|
||||
}
|
||||
|
||||
bool jumpToImageData(QIODevice *d) const
|
||||
|
@ -341,12 +341,8 @@ bool QOIHandler::canRead(QIODevice *device)
|
||||
return false;
|
||||
}
|
||||
|
||||
device->startTransaction();
|
||||
QByteArray head = device->read(QOI_HEADER_SIZE);
|
||||
qsizetype readBytes = head.size();
|
||||
device->rollbackTransaction();
|
||||
|
||||
if (readBytes < QOI_HEADER_SIZE) {
|
||||
auto head = device->peek(QOI_HEADER_SIZE);
|
||||
if (head.size() < QOI_HEADER_SIZE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -430,12 +426,7 @@ QVariant QOIHandler::option(ImageOption option) const
|
||||
if (IsSupported(header)) {
|
||||
v = QVariant::fromValue(QSize(header.Width, header.Height));
|
||||
} else if (auto d = device()) {
|
||||
// transactions works on both random and sequential devices
|
||||
d->startTransaction();
|
||||
auto ba = d->read(sizeof(QoiHeader));
|
||||
d->rollbackTransaction();
|
||||
|
||||
QDataStream s(ba);
|
||||
QDataStream s(d->peek(sizeof(QoiHeader)));
|
||||
s.setByteOrder(QDataStream::BigEndian);
|
||||
s >> header;
|
||||
if (s.status() == QDataStream::Ok && IsSupported(header)) {
|
||||
@ -449,12 +440,7 @@ QVariant QOIHandler::option(ImageOption option) const
|
||||
if (IsSupported(header)) {
|
||||
v = QVariant::fromValue(imageFormat(header));
|
||||
} else if (auto d = device()) {
|
||||
// transactions works on both random and sequential devices
|
||||
d->startTransaction();
|
||||
auto ba = d->read(sizeof(QoiHeader));
|
||||
d->rollbackTransaction();
|
||||
|
||||
QDataStream s(ba);
|
||||
QDataStream s(d->peek(sizeof(QoiHeader)));
|
||||
s.setByteOrder(QDataStream::BigEndian);
|
||||
s >> header;
|
||||
if (s.status() == QDataStream::Ok && IsSupported(header)) {
|
||||
|
@ -202,8 +202,6 @@ private:
|
||||
|
||||
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.
|
||||
auto rasLineSize = (qint64(ras.Width) * ras.Depth + 7) / 8;
|
||||
if (rasLineSize & 1)
|
||||
@ -368,18 +366,8 @@ bool RASHandler::canRead(QIODevice *device)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (device->isSequential()) {
|
||||
// qWarning("Reading ras files from sequential devices not supported");
|
||||
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) {
|
||||
auto head = device->peek(RasHeader::SIZE); // header is exactly 32 bytes, always FIXME
|
||||
if (head.size() < RasHeader::SIZE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -411,9 +399,7 @@ bool RASHandler::read(QImage *outImage)
|
||||
}
|
||||
|
||||
QImage img;
|
||||
bool result = LoadRAS(s, ras, img);
|
||||
|
||||
if (result == false) {
|
||||
if (!LoadRAS(s, ras, img)) {
|
||||
// qDebug() << "Error loading RAS file.";
|
||||
return false;
|
||||
}
|
||||
@ -443,12 +429,7 @@ QVariant RASHandler::option(ImageOption option) const
|
||||
v = QVariant::fromValue(QSize(header.Width, header.Height));
|
||||
}
|
||||
else if (auto dev = device()) {
|
||||
// transactions works on both random and sequential devices
|
||||
dev->startTransaction();
|
||||
auto ba = dev->read(RasHeader::SIZE);
|
||||
dev->rollbackTransaction();
|
||||
|
||||
QDataStream s(ba);
|
||||
QDataStream s(dev->peek(RasHeader::SIZE));
|
||||
s.setByteOrder(QDataStream::BigEndian);
|
||||
s >> header;
|
||||
if (s.status() == QDataStream::Ok && IsSupported(header)) {
|
||||
@ -463,12 +444,7 @@ QVariant RASHandler::option(ImageOption option) const
|
||||
v = QVariant::fromValue(imageFormat(header));
|
||||
}
|
||||
else if (auto dev = device()) {
|
||||
// transactions works on both random and sequential devices
|
||||
dev->startTransaction();
|
||||
auto ba = dev->read(RasHeader::SIZE);
|
||||
dev->rollbackTransaction();
|
||||
|
||||
QDataStream s(ba);
|
||||
QDataStream s(dev->peek(RasHeader::SIZE));
|
||||
s.setByteOrder(QDataStream::BigEndian);
|
||||
s >> header;
|
||||
if (s.status() == QDataStream::Ok && IsSupported(header)) {
|
||||
|
@ -806,12 +806,10 @@ QVariant RAWHandler::option(ImageOption option) const
|
||||
rawProcessor->imgdata.rawparams.shot_select = currentImageNumber();
|
||||
#endif
|
||||
if (rawProcessor->open_datastream(&stream) == LIBRAW_SUCCESS) {
|
||||
if (rawProcessor->unpack() == LIBRAW_SUCCESS) {
|
||||
auto w = libraw_get_iwidth(&rawProcessor->imgdata);
|
||||
auto h = libraw_get_iheight(&rawProcessor->imgdata);
|
||||
// flip & 4: taken from LibRaw code
|
||||
v = (rawProcessor->imgdata.sizes.flip & 4) ? QSize(h, w) : QSize(w, h);
|
||||
}
|
||||
auto w = libraw_get_iwidth(&rawProcessor->imgdata);
|
||||
auto h = libraw_get_iheight(&rawProcessor->imgdata);
|
||||
// flip & 4: taken from LibRaw code
|
||||
v = (rawProcessor->imgdata.sizes.flip & 4) ? QSize(h, w) : QSize(w, h);
|
||||
}
|
||||
d->rollbackTransaction();
|
||||
}
|
||||
|
@ -578,30 +578,8 @@ bool SGIImagePrivate::isSupported() const
|
||||
|
||||
bool SGIImagePrivate::peekHeader(QIODevice *device)
|
||||
{
|
||||
qint64 pos = 0;
|
||||
if (!device->isSequential()) {
|
||||
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;
|
||||
QDataStream ds(device->peek(512));
|
||||
return SGIImagePrivate::readHeader(ds, this) && isValid();
|
||||
}
|
||||
|
||||
QSize SGIImagePrivate::size() const
|
||||
|
@ -174,15 +174,21 @@ static QImage::Format imageFormat(const TgaHeader &head)
|
||||
{
|
||||
auto format = QImage::Format_Invalid;
|
||||
if (IsSupported(head)) {
|
||||
TgaHeaderInfo info(head);
|
||||
|
||||
// Bits 0-3 are the numbers of alpha bits (can be zero!)
|
||||
const int numAlphaBits = head.flags & 0xf;
|
||||
// However alpha exists only in the 32 bit format.
|
||||
if ((head.pixel_size == 32) && (head.flags & 0xf)) {
|
||||
// However alpha should exists only in the 32 bit format.
|
||||
if ((head.pixel_size == 32) && (numAlphaBits)) {
|
||||
if (numAlphaBits <= 8) {
|
||||
format = QImage::Format_ARGB32;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Anyway, GIMP also saves gray images with alpha in TGA format
|
||||
} else if((info.grey) && (head.pixel_size == 16) && (numAlphaBits)) {
|
||||
if (numAlphaBits == 8) {
|
||||
format = QImage::Format_ARGB32;
|
||||
}
|
||||
} else {
|
||||
format = QImage::Format_RGB32;
|
||||
}
|
||||
}
|
||||
@ -195,26 +201,13 @@ static QImage::Format imageFormat(const TgaHeader &head)
|
||||
*/
|
||||
static bool peekHeader(QIODevice *device, TgaHeader &header)
|
||||
{
|
||||
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) {
|
||||
auto head = device->peek(TgaHeader::SIZE);
|
||||
if (head.size() < TgaHeader::SIZE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QDataStream stream(head);
|
||||
stream.setByteOrder(QDataStream::LittleEndian);
|
||||
stream >> header;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -361,8 +354,7 @@ static bool LoadTGA(QDataStream &s, const TgaHeader &tga, QImage &img)
|
||||
uchar *src = image;
|
||||
|
||||
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) {
|
||||
// Paletted.
|
||||
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) {
|
||||
// Greyscale.
|
||||
for (int x = 0; x < tga.width; x++) {
|
||||
scanline[x] = qRgb(*src, *src, *src);
|
||||
src++;
|
||||
if (tga.pixel_size == 16) {
|
||||
scanline[x] = qRgba(*src, *src, *src, *(src + 1));
|
||||
src += 2;
|
||||
}
|
||||
else {
|
||||
scanline[x] = qRgb(*src, *src, *src);
|
||||
src++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// True Color.
|
||||
@ -561,22 +559,6 @@ bool TGAHandler::canRead(QIODevice *device)
|
||||
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;
|
||||
if (!peekHeader(device, tga)) {
|
||||
qWarning("TGAHandler::canRead() error while reading the header");
|
||||
|
@ -4229,66 +4229,43 @@ bool XCFHandler::canRead(QIODevice *device)
|
||||
}
|
||||
|
||||
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) {
|
||||
case XCFImageFormat::GIMP_PRECISION_HALF_LINEAR:
|
||||
case XCFImageFormat::GIMP_PRECISION_HALF_NON_LINEAR:
|
||||
case XCFImageFormat::GIMP_PRECISION_HALF_PERCEPTUAL:
|
||||
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;
|
||||
}
|
||||
QDataStream ds(device);
|
||||
XCFImageFormat::XCFImage::Header header;
|
||||
bool failed = !XCFImageFormat::readXCFHeader(ds, &header);
|
||||
ds.setDevice(nullptr);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
device->seek(oldPos);
|
||||
if (failed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (device->isSequential()) {
|
||||
while (readBytes > 0) {
|
||||
device->ungetChar(head[readBytes-- - 1]);
|
||||
}
|
||||
} else {
|
||||
device->seek(oldPos);
|
||||
switch (header.precision) {
|
||||
case XCFImageFormat::GIMP_PRECISION_HALF_LINEAR:
|
||||
case XCFImageFormat::GIMP_PRECISION_HALF_NON_LINEAR:
|
||||
case XCFImageFormat::GIMP_PRECISION_HALF_PERCEPTUAL:
|
||||
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 qstrncmp(head, "gimp xcf", 8) == 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
QImageIOPlugin::Capabilities XCFPlugin::capabilities(QIODevice *device, const QByteArray &format) const
|
||||
|