Compare commits
28 Commits
Author | SHA1 | Date | |
---|---|---|---|
bfc73ca260 | |||
f6bb59228e | |||
ee381958b2 | |||
478e49b8a6 | |||
c3daf86079 | |||
a981cefdd2 | |||
723f72930b | |||
99bb24803a | |||
63a9de758f | |||
240e28aac5 | |||
906ecce500 | |||
b2b677b8a5 | |||
bcec942cc9 | |||
66cb8c91d0 | |||
899a2df42d | |||
47920ed63c | |||
274f30e008 | |||
4348a09733 | |||
dd4576a472 | |||
9438540735 | |||
cf78907ff4 | |||
bcb5308545 | |||
c3a91c3bc6 | |||
034b8f331b | |||
ed6a3c520d | |||
bf1c7e8508 | |||
3cb6519dcc | |||
6cbf7529ee |
@ -6,9 +6,5 @@ include:
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux-static.yml
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/android.yml
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/freebsd.yml
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux-qt6.yml
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/android-qt6.yml
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/windows.yml
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/windows-static.yml
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/freebsd-qt6.yml
|
||||
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/windows-qt6.yml
|
||||
|
@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.16)
|
||||
project(KImageFormats)
|
||||
|
||||
include(FeatureSummary)
|
||||
find_package(ECM 5.107.0 NO_MODULE)
|
||||
find_package(ECM 5.112.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)
|
||||
|
||||
@ -51,7 +51,10 @@ set_package_properties(OpenEXR PROPERTIES
|
||||
PURPOSE "Required for the QImage plugin for OpenEXR images"
|
||||
)
|
||||
|
||||
find_package(libavif 0.8.2 CONFIG)
|
||||
find_package(libavif 0.8.2 CONFIG QUIET)
|
||||
if(NOT libavif_FOUND)
|
||||
find_package(libavif 1 CONFIG)
|
||||
endif()
|
||||
set_package_properties(libavif PROPERTIES
|
||||
TYPE OPTIONAL
|
||||
PURPOSE "Required for the QImage plugin for AVIF images"
|
||||
@ -65,8 +68,8 @@ add_feature_info(LibHeif LibHeif_FOUND "required for the QImage plugin for HEIF/
|
||||
|
||||
option(KIMAGEFORMATS_JXL "Enable plugin for JPEG XL format" ON)
|
||||
if(KIMAGEFORMATS_JXL)
|
||||
pkg_check_modules(LibJXL IMPORTED_TARGET libjxl>=0.6.1)
|
||||
pkg_check_modules(LibJXLThreads IMPORTED_TARGET libjxl_threads>=0.6.1)
|
||||
pkg_check_modules(LibJXL IMPORTED_TARGET libjxl>=0.7.0)
|
||||
pkg_check_modules(LibJXLThreads IMPORTED_TARGET libjxl_threads>=0.7.0)
|
||||
endif()
|
||||
add_feature_info(LibJXL LibJXL_FOUND "required for the QImage plugin for JPEG XL images")
|
||||
|
||||
|
@ -14,11 +14,12 @@ image formats.
|
||||
The following image formats have read-only support:
|
||||
|
||||
- Animated Windows cursors (ani)
|
||||
- Camera RAW images (arw, cr2, cr3, dcs, dng, ...)
|
||||
- Gimp (xcf)
|
||||
- OpenEXR (exr)
|
||||
- Photoshop documents (psd, psb, pdd, psdt)
|
||||
- Radiance HDR (hdr)
|
||||
- Sun Raster (ras)
|
||||
- Camera RAW images (arw, cr2, cr3, dcs, dng, ...)
|
||||
|
||||
The following image formats have read and write support:
|
||||
|
||||
@ -26,6 +27,7 @@ The following image formats have read and write support:
|
||||
- Encapsulated PostScript (eps)
|
||||
- JPEG XL (jxl)
|
||||
- Personal Computer Exchange (pcx)
|
||||
- Quite OK Image format (qoi)
|
||||
- SGI images (rgb, rgba, sgi, bw)
|
||||
- Softimage PIC (pic)
|
||||
- Targa (tga): supports more formats than Qt's version
|
||||
|
@ -1,7 +1,6 @@
|
||||
#find_package(Qt5Test ${REQUIRED_QT_VERSION} NO_MODULE)
|
||||
|
||||
include(ECMMarkAsTest)
|
||||
include(CMakeParseArguments)
|
||||
|
||||
add_definitions(-DPLUGIN_DIR="${CMAKE_CURRENT_BINARY_DIR}/../bin")
|
||||
remove_definitions(-DQT_NO_CAST_FROM_ASCII)
|
||||
@ -70,6 +69,7 @@ kimageformats_read_tests(
|
||||
hdr
|
||||
pcx
|
||||
psd
|
||||
qoi
|
||||
ras
|
||||
rgb
|
||||
tga
|
||||
@ -125,6 +125,7 @@ kimageformats_read_tests(FUZZ 1
|
||||
kimageformats_write_tests(
|
||||
pcx-lossless
|
||||
pic-lossless
|
||||
qoi-lossless
|
||||
rgb-lossless
|
||||
tga # fixme: the alpha images appear not to be written properly
|
||||
)
|
||||
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 2.7 KiB |
BIN
autotests/read/psd/mch3-16bits.png
Normal file
After Width: | Height: | Size: 71 KiB |
BIN
autotests/read/psd/mch3-16bits.psb
Normal file
BIN
autotests/read/psd/mch3-8bits.png
Normal file
After Width: | Height: | Size: 39 KiB |
BIN
autotests/read/psd/mch3-8bits.psb
Normal file
BIN
autotests/read/qoi/1px.png
Normal file
After Width: | Height: | Size: 551 B |
BIN
autotests/read/qoi/1px.qoi
Normal file
BIN
autotests/read/qoi/2px.png
Normal file
After Width: | Height: | Size: 561 B |
BIN
autotests/read/qoi/2px.qoi
Normal file
BIN
autotests/read/qoi/bnm_rgb.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
autotests/read/qoi/bnm_rgb.qoi
Normal file
BIN
autotests/read/qoi/bnm_rgba.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
autotests/read/qoi/bnm_rgba.qoi
Normal file
BIN
autotests/read/qoi/testcard.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
autotests/read/qoi/testcard.qoi
Normal file
BIN
autotests/read/qoi/testcard_rgba.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
autotests/read/qoi/testcard_rgba.qoi
Normal file
BIN
autotests/read/xcf/birthday.png
Normal file
After Width: | Height: | Size: 96 KiB |
BIN
autotests/read/xcf/birthday.xcf
Normal file
BIN
autotests/read/xcf/birthday16.png
Normal file
After Width: | Height: | Size: 93 KiB |
BIN
autotests/read/xcf/birthday16.xcf
Normal file
BIN
autotests/read/xcf/birthday16_gray.png
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
autotests/read/xcf/birthday16_gray.xcf
Normal file
BIN
autotests/read/xcf/birthday16_grayA.png
Normal file
After Width: | Height: | Size: 84 KiB |
BIN
autotests/read/xcf/birthday16_grayA.xcf
Normal file
BIN
autotests/read/xcf/birthday16fp.png
Normal file
After Width: | Height: | Size: 82 KiB |
BIN
autotests/read/xcf/birthday16fp.xcf
Normal file
BIN
autotests/read/xcf/birthday16fp_gray.png
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
autotests/read/xcf/birthday16fp_gray.xcf
Normal file
BIN
autotests/read/xcf/birthday16fp_grayA.png
Normal file
After Width: | Height: | Size: 72 KiB |
BIN
autotests/read/xcf/birthday16fp_grayA.xcf
Normal file
BIN
autotests/read/xcf/birthday32.png
Normal file
After Width: | Height: | Size: 93 KiB |
BIN
autotests/read/xcf/birthday32.xcf
Normal file
BIN
autotests/read/xcf/birthday32_gray.png
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
autotests/read/xcf/birthday32_gray.xcf
Normal file
BIN
autotests/read/xcf/birthday32_grayA.png
Normal file
After Width: | Height: | Size: 84 KiB |
BIN
autotests/read/xcf/birthday32_grayA.xcf
Normal file
BIN
autotests/read/xcf/birthday32fp.png
Normal file
After Width: | Height: | Size: 82 KiB |
BIN
autotests/read/xcf/birthday32fp.xcf
Normal file
BIN
autotests/read/xcf/birthday32fp_gray.png
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
autotests/read/xcf/birthday32fp_gray.xcf
Normal file
BIN
autotests/read/xcf/birthday32fp_grayA.png
Normal file
After Width: | Height: | Size: 72 KiB |
BIN
autotests/read/xcf/birthday32fp_grayA.xcf
Normal file
BIN
autotests/read/xcf/birthday_grayA.png
Normal file
After Width: | Height: | Size: 80 KiB |
BIN
autotests/read/xcf/birthday_grayA.xcf
Normal file
Before Width: | Height: | Size: 114 KiB After Width: | Height: | Size: 115 KiB |
BIN
autotests/read/xcf/fruktpilot16_icc.png
Normal file
After Width: | Height: | Size: 115 KiB |
BIN
autotests/read/xcf/fruktpilot16_icc.xcf
Normal file
BIN
autotests/read/xcf/fruktpilot16fplin_icc.png
Normal file
After Width: | Height: | Size: 112 KiB |
BIN
autotests/read/xcf/fruktpilot16fplin_icc.xcf
Normal file
BIN
autotests/read/xcf/fruktpilot32_icc.png
Normal file
After Width: | Height: | Size: 115 KiB |
BIN
autotests/read/xcf/fruktpilot32_icc.xcf
Normal file
BIN
autotests/read/xcf/fruktpilot32fplin_icc.png
Normal file
After Width: | Height: | Size: 112 KiB |
BIN
autotests/read/xcf/fruktpilot32fplin_icc.xcf
Normal file
Before Width: | Height: | Size: 115 KiB After Width: | Height: | Size: 115 KiB |
BIN
autotests/write/rgb.qoi
Normal file
BIN
autotests/write/rgba.qoi
Normal file
@ -12,3 +12,4 @@ Ignacio Castaño <castano@ludicon.com> -- DDS and PDS format reader.
|
||||
Christoph Hormann <chris_hormann@gmx.de> -- HDR format read support.
|
||||
Michael Ritzert <kde@ritzert.de> -- JPEG 2000 format read/write support
|
||||
Troy Unrau <troy@kde.org> -- Sun RASter read support
|
||||
Ernest Gupik <ernestgupik@wp.pl> -- QOI format read support
|
||||
|
@ -104,9 +104,6 @@ endif()
|
||||
if (LibJXL_FOUND AND LibJXLThreads_FOUND)
|
||||
kimageformats_add_plugin(kimg_jxl SOURCES jxl.cpp)
|
||||
target_link_libraries(kimg_jxl PkgConfig::LibJXL PkgConfig::LibJXLThreads)
|
||||
if (LibJXL_VERSION VERSION_GREATER_EQUAL "0.7.0")
|
||||
target_compile_definitions(kimg_jxl PRIVATE KIMG_JXL_API_VERSION=70)
|
||||
endif()
|
||||
|
||||
if (QT_MAJOR_VERSION STREQUAL "5")
|
||||
install(FILES jxl.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||
@ -136,6 +133,13 @@ endif()
|
||||
|
||||
##################################
|
||||
|
||||
kimageformats_add_plugin(kimg_qoi SOURCES qoi.cpp scanlineconverter.cpp)
|
||||
if (QT_MAJOR_VERSION STREQUAL "5")
|
||||
install(FILES qoi.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||
endif()
|
||||
|
||||
##################################
|
||||
|
||||
kimageformats_add_plugin(kimg_ras SOURCES ras.cpp)
|
||||
if (QT_MAJOR_VERSION STREQUAL "5")
|
||||
install(FILES ras.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||
|
@ -570,3 +570,5 @@ QImageIOHandler *ANIPlugin::create(QIODevice *device, const QByteArray &format)
|
||||
handler->setFormat(format);
|
||||
return handler;
|
||||
}
|
||||
|
||||
#include "moc_ani_p.cpp"
|
||||
|
@ -330,6 +330,10 @@ bool QAVIFHandler::decode_one_frame()
|
||||
avifRGBImage rgb;
|
||||
avifRGBImageSetDefaults(&rgb, m_decoder->image);
|
||||
|
||||
#if AVIF_VERSION >= 1000000
|
||||
rgb.maxThreads = m_decoder->maxThreads;
|
||||
#endif
|
||||
|
||||
if (m_decoder->image->depth > 8) {
|
||||
rgb.depth = 16;
|
||||
rgb.format = AVIF_RGB_FORMAT_RGBA;
|
||||
@ -424,7 +428,7 @@ bool QAVIFHandler::decode_one_frame()
|
||||
}
|
||||
|
||||
if (m_decoder->image->transformFlags & AVIF_TRANSFORM_IMIR) {
|
||||
#if AVIF_VERSION > 90100
|
||||
#if AVIF_VERSION > 90100 && AVIF_VERSION < 1000000
|
||||
switch (m_decoder->image->imir.mode) {
|
||||
#else
|
||||
switch (m_decoder->image->imir.axis) {
|
||||
@ -714,9 +718,9 @@ bool QAVIFHandler::write(const QImage &image)
|
||||
if (save_depth == 8) {
|
||||
save_depth = 10;
|
||||
if (tmpcolorimage.hasAlphaChannel()) {
|
||||
tmpcolorimage = tmpcolorimage.convertToFormat(QImage::Format_RGBA64);
|
||||
tmpcolorimage.convertTo(QImage::Format_RGBA64);
|
||||
} else {
|
||||
tmpcolorimage = tmpcolorimage.convertToFormat(QImage::Format_RGBX64);
|
||||
tmpcolorimage.convertTo(QImage::Format_RGBX64);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1039,6 +1043,11 @@ int QAVIFHandler::loopCount() const
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if AVIF_VERSION >= 1000000
|
||||
if (m_decoder->repetitionCount >= 0) {
|
||||
return m_decoder->repetitionCount;
|
||||
}
|
||||
#endif
|
||||
// Endless loop to work around https://github.com/AOMediaCodec/libavif/issues/347
|
||||
return -1;
|
||||
}
|
||||
@ -1103,3 +1112,5 @@ QImageIOHandler *QAVIFPlugin::create(QIODevice *device, const QByteArray &format
|
||||
handler->setFormat(format);
|
||||
return handler;
|
||||
}
|
||||
|
||||
#include "moc_avif_p.cpp"
|
||||
|
@ -365,3 +365,5 @@ QImageIOHandler *EPSPlugin::create(QIODevice *device, const QByteArray &format)
|
||||
handler->setFormat(format);
|
||||
return handler;
|
||||
}
|
||||
|
||||
#include "moc_eps_p.cpp"
|
||||
|
@ -3,10 +3,27 @@
|
||||
in the high dynamic range EXR format.
|
||||
|
||||
SPDX-FileCopyrightText: 2003 Brad Hards <bradh@frogmouth.net>
|
||||
SPDX-FileCopyrightText: 2023 Mirco Miranda <mircomir@outlook.com>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
/* *** EXR_USE_LEGACY_CONVERSIONS ***
|
||||
* If defined, the result image is an 8-bit RGB(A) converted
|
||||
* without icc profiles. Otherwise, a 16-bit images is generated.
|
||||
* NOTE: The use of legacy conversions are discouraged due to
|
||||
* imprecise image result.
|
||||
*/
|
||||
//#define EXR_USE_LEGACY_CONVERSIONS // default commented -> you should define it in your cmake file
|
||||
|
||||
/* *** EXR_ALLOW_LINEAR_COLORSPACE ***
|
||||
* If defined, the linear data is kept and it is the display program that
|
||||
* must convert to the monitor profile. Otherwise the data is converted to sRGB
|
||||
* to accommodate programs that do not support color profiles.
|
||||
* NOTE: If EXR_USE_LEGACY_CONVERSIONS is active, this is ignored.
|
||||
*/
|
||||
//#define EXR_ALLOW_LINEAR_COLORSPACE // default: commented -> you should define it in your cmake file
|
||||
|
||||
#include "exr_p.h"
|
||||
#include "util_p.h"
|
||||
|
||||
@ -30,11 +47,24 @@
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <QColorSpace>
|
||||
#include <QDataStream>
|
||||
#include <QDebug>
|
||||
#include <QFloat16>
|
||||
#include <QImage>
|
||||
#include <QImageIOPlugin>
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
|
||||
#include <QTimeZone>
|
||||
#endif
|
||||
|
||||
// Allow the code to works on all QT versions supported by KDE
|
||||
// project (Qt 5.15 and Qt 6.x) to easy backports fixes.
|
||||
#if (QT_VERSION_MAJOR >= 6) && !defined(EXR_USE_LEGACY_CONVERSIONS)
|
||||
// If uncommented, the image is rendered in a float16 format, the result is very precise
|
||||
#define EXR_USE_QT6_FLOAT_IMAGE // default uncommented
|
||||
#endif
|
||||
|
||||
class K_IStream : public Imf::IStream
|
||||
{
|
||||
public:
|
||||
@ -94,77 +124,21 @@ void K_IStream::clear()
|
||||
// TODO
|
||||
}
|
||||
|
||||
/* this does a conversion from the ILM Half (equal to Nvidia Half)
|
||||
* format into the normal 32 bit pixel format. Process is from the
|
||||
* ILM code.
|
||||
*/
|
||||
QRgb RgbaToQrgba(struct Imf::Rgba &imagePixel)
|
||||
#ifdef EXR_USE_LEGACY_CONVERSIONS
|
||||
// source: https://openexr.com/en/latest/ReadingAndWritingImageFiles.html
|
||||
inline unsigned char gamma(float x)
|
||||
{
|
||||
float r;
|
||||
float g;
|
||||
float b;
|
||||
float a;
|
||||
|
||||
// 1) Compensate for fogging by subtracting defog
|
||||
// from the raw pixel values.
|
||||
// Response: We work with defog of 0.0, so this is a no-op
|
||||
|
||||
// 2) Multiply the defogged pixel values by
|
||||
// 2^(exposure + 2.47393).
|
||||
// Response: We work with exposure of 0.0.
|
||||
// (2^2.47393) is 5.55555
|
||||
r = imagePixel.r * 5.55555;
|
||||
g = imagePixel.g * 5.55555;
|
||||
b = imagePixel.b * 5.55555;
|
||||
a = imagePixel.a * 5.55555;
|
||||
|
||||
// 3) Values, which are now 1.0, are called "middle gray".
|
||||
// If defog and exposure are both set to 0.0, then
|
||||
// middle gray corresponds to a raw pixel value of 0.18.
|
||||
// In step 6, middle gray values will be mapped to an
|
||||
// intensity 3.5 f-stops below the display's maximum
|
||||
// intensity.
|
||||
// Response: no apparent content.
|
||||
|
||||
// 4) Apply a knee function. The knee function has two
|
||||
// parameters, kneeLow and kneeHigh. Pixel values
|
||||
// below 2^kneeLow are not changed by the knee
|
||||
// function. Pixel values above kneeLow are lowered
|
||||
// according to a logarithmic curve, such that the
|
||||
// value 2^kneeHigh is mapped to 2^3.5 (in step 6,
|
||||
// this value will be mapped to the display's
|
||||
// maximum intensity).
|
||||
// Response: kneeLow = 0.0 (2^0.0 => 1); kneeHigh = 5.0 (2^5 =>32)
|
||||
if (r > 1.0) {
|
||||
r = 1.0 + std::log((r - 1.0) * 0.184874 + 1) / 0.184874;
|
||||
}
|
||||
if (g > 1.0) {
|
||||
g = 1.0 + std::log((g - 1.0) * 0.184874 + 1) / 0.184874;
|
||||
}
|
||||
if (b > 1.0) {
|
||||
b = 1.0 + std::log((b - 1.0) * 0.184874 + 1) / 0.184874;
|
||||
}
|
||||
if (a > 1.0) {
|
||||
a = 1.0 + std::log((a - 1.0) * 0.184874 + 1) / 0.184874;
|
||||
}
|
||||
//
|
||||
// 5) Gamma-correct the pixel values, assuming that the
|
||||
// screen's gamma is 0.4545 (or 1/2.2).
|
||||
r = std::pow(r, 0.4545);
|
||||
g = std::pow(g, 0.4545);
|
||||
b = std::pow(b, 0.4545);
|
||||
a = std::pow(a, 0.4545);
|
||||
|
||||
// 6) Scale the values such that pixels middle gray
|
||||
// pixels are mapped to 84.66 (or 3.5 f-stops below
|
||||
// the display's maximum intensity).
|
||||
//
|
||||
// 7) Clamp the values to [0, 255].
|
||||
return qRgba((unsigned char)(Imath::clamp(r * 84.66f, 0.f, 255.f)),
|
||||
(unsigned char)(Imath::clamp(g * 84.66f, 0.f, 255.f)),
|
||||
(unsigned char)(Imath::clamp(b * 84.66f, 0.f, 255.f)),
|
||||
(unsigned char)(Imath::clamp(a * 84.66f, 0.f, 255.f)));
|
||||
x = std::pow(5.5555f * std::max(0.f, x), 0.4545f) * 84.66f;
|
||||
return (unsigned char)qBound(0.f, x, 255.f);
|
||||
}
|
||||
inline QRgb RgbaToQrgba(struct Imf::Rgba &imagePixel)
|
||||
{
|
||||
return qRgba(gamma(float(imagePixel.r)),
|
||||
gamma(float(imagePixel.g)),
|
||||
gamma(float(imagePixel.b)),
|
||||
(unsigned char)(qBound(0.f, imagePixel.a * 255.f, 255.f) + 0.5f));
|
||||
}
|
||||
#endif
|
||||
|
||||
EXRHandler::EXRHandler()
|
||||
{
|
||||
@ -188,29 +162,98 @@ bool EXRHandler::read(QImage *outImage)
|
||||
K_IStream istr(device(), QByteArray());
|
||||
Imf::RgbaInputFile file(istr);
|
||||
Imath::Box2i dw = file.dataWindow();
|
||||
bool isRgba = file.channels() & Imf::RgbaChannels::WRITE_A;
|
||||
|
||||
width = dw.max.x - dw.min.x + 1;
|
||||
height = dw.max.y - dw.min.y + 1;
|
||||
|
||||
QImage image = imageAlloc(width, height, QImage::Format_RGB32);
|
||||
#if defined(EXR_USE_LEGACY_CONVERSIONS)
|
||||
QImage image = imageAlloc(width, height, isRgba ? QImage::Format_ARGB32 : QImage::Format_RGB32);
|
||||
#elif defined(EXR_USE_QT6_FLOAT_IMAGE)
|
||||
QImage image = imageAlloc(width, height, isRgba ? QImage::Format_RGBA16FPx4 : QImage::Format_RGBX16FPx4);
|
||||
#else
|
||||
QImage image = imageAlloc(width, height, isRgba ? QImage::Format_RGBA64 : QImage::Format_RGBX64);
|
||||
#endif
|
||||
if (image.isNull()) {
|
||||
qWarning() << "Failed to allocate image, invalid size?" << QSize(width, height);
|
||||
return false;
|
||||
}
|
||||
|
||||
Imf::Array2D<Imf::Rgba> pixels;
|
||||
pixels.resizeErase(height, width);
|
||||
|
||||
file.setFrameBuffer(&pixels[0][0] - dw.min.x - dw.min.y * width, 1, width);
|
||||
file.readPixels(dw.min.y, dw.max.y);
|
||||
|
||||
// somehow copy pixels into image
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
// copy pixels(x,y) into image(x,y)
|
||||
image.setPixel(x, y, RgbaToQrgba(pixels[y][x]));
|
||||
// set some useful metadata
|
||||
auto &&h = file.header();
|
||||
if (auto comments = h.findTypedAttribute<Imf::StringAttribute>("comments")) {
|
||||
image.setText(QStringLiteral("Comment"), QString::fromStdString(comments->value()));
|
||||
}
|
||||
if (auto owner = h.findTypedAttribute<Imf::StringAttribute>("owner")) {
|
||||
image.setText(QStringLiteral("Owner"), QString::fromStdString(owner->value()));
|
||||
}
|
||||
if (auto capDate = h.findTypedAttribute<Imf::StringAttribute>("capDate")) {
|
||||
float off = 0;
|
||||
if (auto utcOffset = h.findTypedAttribute<Imf::FloatAttribute>("utcOffset")) {
|
||||
off = utcOffset->value();
|
||||
}
|
||||
auto dateTime = QDateTime::fromString(QString::fromStdString(capDate->value()), QStringLiteral("yyyy:MM:dd HH:mm:ss"));
|
||||
if (dateTime.isValid()) {
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
|
||||
dateTime.setTimeZone(QTimeZone::fromSecondsAheadOfUtc(off));
|
||||
#else
|
||||
dateTime.setOffsetFromUtc(off);
|
||||
#endif
|
||||
image.setText(QStringLiteral("Date"), dateTime.toString(Qt::ISODate));
|
||||
}
|
||||
}
|
||||
if (auto xDensity = h.findTypedAttribute<Imf::FloatAttribute>("xDensity")) {
|
||||
float par = 1;
|
||||
if (auto pixelAspectRatio = h.findTypedAttribute<Imf::FloatAttribute>("pixelAspectRatio")) {
|
||||
par = pixelAspectRatio->value();
|
||||
}
|
||||
image.setDotsPerMeterX(qRound(xDensity->value() * 100.0 / 2.54));
|
||||
image.setDotsPerMeterY(qRound(xDensity->value() * par * 100.0 / 2.54));
|
||||
}
|
||||
|
||||
Imf::Array<Imf::Rgba> pixels;
|
||||
pixels.resizeErase(width);
|
||||
|
||||
// somehow copy pixels into image
|
||||
for (int y = 0; y < height; ++y) {
|
||||
auto my = dw.min.y + y;
|
||||
if (my <= dw.max.y) { // paranoia check
|
||||
file.setFrameBuffer(&pixels[0] - dw.min.x - qint64(my) * width, 1, width);
|
||||
file.readPixels(my, my);
|
||||
|
||||
#if defined(EXR_USE_LEGACY_CONVERSIONS)
|
||||
auto scanLine = reinterpret_cast<QRgb *>(image.scanLine(y));
|
||||
for (int x = 0; x < width; ++x) {
|
||||
*(scanLine + x) = RgbaToQrgba(pixels[x]);
|
||||
}
|
||||
#elif defined(EXR_USE_QT6_FLOAT_IMAGE)
|
||||
auto scanLine = reinterpret_cast<qfloat16 *>(image.scanLine(y));
|
||||
for (int x = 0; x < width; ++x) {
|
||||
auto xcs = x * 4;
|
||||
*(scanLine + xcs) = qfloat16(qBound(0.f, float(pixels[x].r), 1.f));
|
||||
*(scanLine + xcs + 1) = qfloat16(qBound(0.f, float(pixels[x].g), 1.f));
|
||||
*(scanLine + xcs + 2) = qfloat16(qBound(0.f, float(pixels[x].b), 1.f));
|
||||
*(scanLine + xcs + 3) = qfloat16(isRgba ? qBound(0.f, float(pixels[x].a), 1.f) : 1.f);
|
||||
}
|
||||
#else
|
||||
auto scanLine = reinterpret_cast<QRgba64 *>(image.scanLine(y));
|
||||
for (int x = 0; x < width; ++x) {
|
||||
*(scanLine + x) = QRgba64::fromRgba64(quint16(qBound(0.f, float(pixels[x].r) * 65535.f + 0.5f, 65535.f)),
|
||||
quint16(qBound(0.f, float(pixels[x].g) * 65535.f + 0.5f, 65535.f)),
|
||||
quint16(qBound(0.f, float(pixels[x].b) * 65535.f + 0.5f, 65535.f)),
|
||||
isRgba ? quint16(qBound(0.f, float(pixels[x].a) * 65535.f + 0.5f, 65535.f)) : quint16(65535));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// final color operations
|
||||
#ifndef EXR_USE_LEGACY_CONVERSIONS
|
||||
image.setColorSpace(QColorSpace(QColorSpace::SRgbLinear));
|
||||
#ifndef EXR_ALLOW_LINEAR_COLORSPACE
|
||||
image.convertToColorSpace(QColorSpace(QColorSpace::SRgb));
|
||||
#endif // !EXR_ALLOW_LINEAR_COLORSPACE
|
||||
#endif // !EXR_USE_LEGACY_CONVERSIONS
|
||||
|
||||
*outImage = image;
|
||||
|
||||
@ -259,3 +302,5 @@ QImageIOHandler *EXRPlugin::create(QIODevice *device, const QByteArray &format)
|
||||
handler->setFormat(format);
|
||||
return handler;
|
||||
}
|
||||
|
||||
#include "moc_exr_p.cpp"
|
||||
|
@ -46,33 +46,6 @@ typedef enum {
|
||||
|
||||
// From GIMP "libgimp/gimpenums.h" v2.4
|
||||
|
||||
//! Effect to apply when layers are merged together.
|
||||
|
||||
typedef enum {
|
||||
NORMAL_MODE,
|
||||
DISSOLVE_MODE,
|
||||
BEHIND_MODE,
|
||||
MULTIPLY_MODE,
|
||||
SCREEN_MODE,
|
||||
OVERLAY_MODE,
|
||||
DIFFERENCE_MODE,
|
||||
ADDITION_MODE,
|
||||
SUBTRACT_MODE,
|
||||
DARKEN_ONLY_MODE,
|
||||
LIGHTEN_ONLY_MODE,
|
||||
HUE_MODE,
|
||||
SATURATION_MODE,
|
||||
COLOR_MODE,
|
||||
VALUE_MODE,
|
||||
DIVIDE_MODE,
|
||||
DODGE_MODE,
|
||||
BURN_MODE,
|
||||
HARDLIGHT_MODE,
|
||||
SOFTLIGHT_MODE,
|
||||
GRAIN_EXTRACT_MODE,
|
||||
GRAIN_MERGE_MODE
|
||||
} LayerModeEffects;
|
||||
|
||||
// From GIMP "paint_funcs.c" v1.2
|
||||
|
||||
/*!
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include "hdr_p.h"
|
||||
#include "util_p.h"
|
||||
|
||||
#include <QColorSpace>
|
||||
#include <QDataStream>
|
||||
#include <QImage>
|
||||
#include <QLoggingCategory>
|
||||
@ -28,11 +29,8 @@ namespace // Private.
|
||||
|
||||
static inline uchar ClipToByte(float value)
|
||||
{
|
||||
if (value > 255.0f) {
|
||||
return 255;
|
||||
}
|
||||
// else if (value < 0.0f) return 0; // we know value is positive.
|
||||
return uchar(value);
|
||||
// we know value is positive.
|
||||
return uchar(std::min(value + 0.5f, 255.0f));
|
||||
}
|
||||
|
||||
// read an old style line from the hdr image file
|
||||
@ -42,6 +40,7 @@ static bool Read_Old_Line(uchar *image, int width, QDataStream &s)
|
||||
int rshift = 0;
|
||||
int i;
|
||||
|
||||
uchar *start = image;
|
||||
while (width > 0) {
|
||||
s >> image[0];
|
||||
s >> image[1];
|
||||
@ -53,7 +52,14 @@ static bool Read_Old_Line(uchar *image, int width, QDataStream &s)
|
||||
}
|
||||
|
||||
if ((image[0] == 1) && (image[1] == 1) && (image[2] == 1)) {
|
||||
for (i = image[3] << rshift; i > 0; i--) {
|
||||
// NOTE: we don't have an image sample that cover this code
|
||||
if (rshift > 31) {
|
||||
return false;
|
||||
}
|
||||
for (i = image[3] << rshift; i > 0 && width > 0; i--) {
|
||||
if (image == start) {
|
||||
return false; // you cannot be here at the first run
|
||||
}
|
||||
// memcpy(image, image-4, 4);
|
||||
(uint &)image[0] = (uint &)image[0 - 4];
|
||||
image += 4;
|
||||
@ -74,7 +80,7 @@ static void RGBE_To_QRgbLine(uchar *image, QRgb *scanline, int width)
|
||||
for (int j = 0; j < width; j++) {
|
||||
// v = ldexp(1.0, int(image[3]) - 128);
|
||||
float v;
|
||||
int e = int(image[3]) - 128;
|
||||
int e = qBound(-31, int(image[3]) - 128, 31);
|
||||
if (e > 0) {
|
||||
v = float(1 << e);
|
||||
} else {
|
||||
@ -148,7 +154,7 @@ static bool LoadHDR(QDataStream &s, const int width, const int height, QImage &i
|
||||
}
|
||||
|
||||
// read each component
|
||||
for (int i = 0; i < 4; i++) {
|
||||
for (int i = 0, len = int(lineArray.size()); i < 4; i++) {
|
||||
for (int j = 0; j < width;) {
|
||||
s >> code;
|
||||
if (s.atEnd()) {
|
||||
@ -160,14 +166,20 @@ static bool LoadHDR(QDataStream &s, const int width, const int height, QImage &i
|
||||
code &= 127;
|
||||
s >> val;
|
||||
while (code != 0) {
|
||||
image[i + j * 4] = val;
|
||||
auto idx = i + j * 4;
|
||||
if (idx < len) {
|
||||
image[idx] = val;
|
||||
}
|
||||
j++;
|
||||
code--;
|
||||
}
|
||||
} else {
|
||||
// non-run
|
||||
while (code != 0) {
|
||||
s >> image[i + j * 4];
|
||||
auto idx = i + j * 4;
|
||||
if (idx < len) {
|
||||
s >> image[idx];
|
||||
}
|
||||
j++;
|
||||
code--;
|
||||
}
|
||||
@ -242,6 +254,9 @@ bool HDRHandler::read(QImage *outImage)
|
||||
// qDebug() << "Error loading HDR file.";
|
||||
return false;
|
||||
}
|
||||
// The images read by Gimp and Photoshop (including those of the tests) are interpreted with linear color space.
|
||||
// By setting the linear color space, programs that support profiles display HDR files as in GIMP and Photoshop.
|
||||
img.setColorSpace(QColorSpace(QColorSpace::SRgbLinear));
|
||||
|
||||
*outImage = img;
|
||||
return true;
|
||||
@ -296,3 +311,5 @@ QImageIOHandler *HDRPlugin::create(QIODevice *device, const QByteArray &format)
|
||||
handler->setFormat(format);
|
||||
return handler;
|
||||
}
|
||||
|
||||
#include "moc_hdr_p.cpp"
|
||||
|
@ -926,3 +926,5 @@ QImageIOHandler *HEIFPlugin::create(QIODevice *device, const QByteArray &format)
|
||||
handler->setFormat(format);
|
||||
return handler;
|
||||
}
|
||||
|
||||
#include "moc_heif_p.cpp"
|
||||
|
@ -150,9 +150,7 @@ bool QJpegXLHandler::ensureDecoder()
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef KIMG_JXL_API_VERSION
|
||||
JxlDecoderCloseInput(m_decoder);
|
||||
#endif
|
||||
|
||||
JxlDecoderStatus status = JxlDecoderSubscribeEvents(m_decoder, JXL_DEC_BASIC_INFO | JXL_DEC_COLOR_ENCODING | JXL_DEC_FRAME);
|
||||
if (status == JXL_DEC_ERROR) {
|
||||
@ -269,18 +267,31 @@ bool QJpegXLHandler::countALLFrames()
|
||||
}
|
||||
}
|
||||
|
||||
status = JxlDecoderGetColorAsEncodedProfile(m_decoder, &m_input_pixel_format, JXL_COLOR_PROFILE_TARGET_DATA, &color_encoding);
|
||||
status = JxlDecoderGetColorAsEncodedProfile(m_decoder,
|
||||
#if JPEGXL_NUMERIC_VERSION < JPEGXL_COMPUTE_NUMERIC_VERSION(0, 9, 0)
|
||||
&m_input_pixel_format,
|
||||
#endif
|
||||
JXL_COLOR_PROFILE_TARGET_DATA,
|
||||
&color_encoding);
|
||||
|
||||
if (status == JXL_DEC_SUCCESS && color_encoding.color_space == JXL_COLOR_SPACE_RGB && color_encoding.white_point == JXL_WHITE_POINT_D65
|
||||
&& color_encoding.primaries == JXL_PRIMARIES_SRGB && color_encoding.transfer_function == JXL_TRANSFER_FUNCTION_SRGB) {
|
||||
m_colorspace = QColorSpace(QColorSpace::SRgb);
|
||||
} else {
|
||||
size_t icc_size = 0;
|
||||
if (JxlDecoderGetICCProfileSize(m_decoder, &m_input_pixel_format, JXL_COLOR_PROFILE_TARGET_DATA, &icc_size) == JXL_DEC_SUCCESS) {
|
||||
if (JxlDecoderGetICCProfileSize(m_decoder,
|
||||
#if JPEGXL_NUMERIC_VERSION < JPEGXL_COMPUTE_NUMERIC_VERSION(0, 9, 0)
|
||||
&m_input_pixel_format,
|
||||
#endif
|
||||
JXL_COLOR_PROFILE_TARGET_DATA,
|
||||
&icc_size)
|
||||
== JXL_DEC_SUCCESS) {
|
||||
if (icc_size > 0) {
|
||||
QByteArray icc_data(icc_size, 0);
|
||||
if (JxlDecoderGetColorAsICCProfile(m_decoder,
|
||||
#if JPEGXL_NUMERIC_VERSION < JPEGXL_COMPUTE_NUMERIC_VERSION(0, 9, 0)
|
||||
&m_input_pixel_format,
|
||||
#endif
|
||||
JXL_COLOR_PROFILE_TARGET_DATA,
|
||||
reinterpret_cast<uint8_t *>(icc_data.data()),
|
||||
icc_data.size())
|
||||
@ -534,9 +545,7 @@ bool QJpegXLHandler::write(const QImage &image)
|
||||
if (save_depth == 16 && (image.hasAlphaChannel() || output_info.uses_original_profile)) {
|
||||
output_info.have_container = JXL_TRUE;
|
||||
JxlEncoderUseContainer(encoder, JXL_TRUE);
|
||||
#ifdef KIMG_JXL_API_VERSION
|
||||
JxlEncoderSetCodestreamLevel(encoder, 10);
|
||||
#endif
|
||||
}
|
||||
|
||||
void *runner = nullptr;
|
||||
@ -650,19 +659,11 @@ bool QJpegXLHandler::write(const QImage &image)
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef KIMG_JXL_API_VERSION
|
||||
JxlEncoderFrameSettings *encoder_options = JxlEncoderFrameSettingsCreate(encoder, nullptr);
|
||||
|
||||
JxlEncoderSetFrameDistance(encoder_options, (100.0f - m_quality) / 10.0f);
|
||||
|
||||
JxlEncoderSetFrameLossless(encoder_options, (m_quality == 100) ? JXL_TRUE : JXL_FALSE);
|
||||
#else
|
||||
JxlEncoderOptions *encoder_options = JxlEncoderOptionsCreate(encoder, nullptr);
|
||||
|
||||
JxlEncoderOptionsSetDistance(encoder_options, (100.0f - m_quality) / 10.0f);
|
||||
|
||||
JxlEncoderOptionsSetLossless(encoder_options, (m_quality == 100) ? JXL_TRUE : JXL_FALSE);
|
||||
#endif
|
||||
|
||||
if (image.hasAlphaChannel() || ((save_depth == 8) && (xsize % 4 == 0))) {
|
||||
status = JxlEncoderAddImageFrame(encoder_options, &pixel_format, static_cast<const void *>(tmpimage.constBits()), buffer_size);
|
||||
@ -957,9 +958,7 @@ bool QJpegXLHandler::rewind()
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef KIMG_JXL_API_VERSION
|
||||
JxlDecoderCloseInput(m_decoder);
|
||||
#endif
|
||||
|
||||
if (m_basicinfo.uses_original_profile) {
|
||||
if (JxlDecoderSubscribeEvents(m_decoder, JXL_DEC_FULL_IMAGE) != JXL_DEC_SUCCESS) {
|
||||
@ -1021,3 +1020,5 @@ QImageIOHandler *QJpegXLPlugin::create(QIODevice *device, const QByteArray &form
|
||||
handler->setFormat(format);
|
||||
return handler;
|
||||
}
|
||||
|
||||
#include "moc_jxl_p.cpp"
|
||||
|
@ -95,3 +95,5 @@ QImageIOHandler *KraPlugin::create(QIODevice *device, const QByteArray &format)
|
||||
handler->setFormat(format);
|
||||
return handler;
|
||||
}
|
||||
|
||||
#include "moc_kra.cpp"
|
||||
|
@ -94,3 +94,5 @@ QImageIOHandler *OraPlugin::create(QIODevice *device, const QByteArray &format)
|
||||
handler->setFormat(format);
|
||||
return handler;
|
||||
}
|
||||
|
||||
#include "moc_ora.cpp"
|
||||
|
@ -792,3 +792,5 @@ QImageIOHandler *PCXPlugin::create(QIODevice *device, const QByteArray &format)
|
||||
handler->setFormat(format);
|
||||
return handler;
|
||||
}
|
||||
|
||||
#include "moc_pcx_p.cpp"
|
||||
|
@ -452,3 +452,5 @@ QImageIOHandler *SoftimagePICPlugin::create(QIODevice *device, const QByteArray
|
||||
handler->setFormat(format);
|
||||
return handler;
|
||||
}
|
||||
|
||||
#include "moc_pic_p.cpp"
|
||||
|
@ -42,6 +42,7 @@
|
||||
#include <QColorSpace>
|
||||
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
|
||||
typedef quint32 uint;
|
||||
typedef quint16 ushort;
|
||||
@ -669,7 +670,7 @@ static bool IsSupported(const PSDHeader &header)
|
||||
return false;
|
||||
}
|
||||
if (header.color_mode == CM_MULTICHANNEL &&
|
||||
header.channel_count < 4) {
|
||||
header.channel_count < 3) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@ -808,6 +809,26 @@ inline quint32 xchg(quint32 v) {
|
||||
#endif
|
||||
}
|
||||
|
||||
inline float xchg(float v)
|
||||
{
|
||||
#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
|
||||
# ifdef Q_CC_MSVC
|
||||
float *pf = &v;
|
||||
quint32 f = xchg(*reinterpret_cast<quint32*>(pf));
|
||||
quint32 *pi = &f;
|
||||
return *reinterpret_cast<float*>(pi);
|
||||
# else
|
||||
quint32 t;
|
||||
std::memcpy(&t, &v, sizeof(quint32));
|
||||
t = xchg(t);
|
||||
std::memcpy(&v, &t, sizeof(quint32));
|
||||
return v;
|
||||
# endif
|
||||
#else
|
||||
return v; // never tested
|
||||
#endif
|
||||
}
|
||||
|
||||
template<class T>
|
||||
inline void planarToChunchy(uchar *target, const char *source, qint32 width, qint32 c, qint32 cn)
|
||||
{
|
||||
@ -818,15 +839,13 @@ inline void planarToChunchy(uchar *target, const char *source, qint32 width, qin
|
||||
}
|
||||
}
|
||||
|
||||
template<class T, T min = 0, T max = 1>
|
||||
inline void planarToChunchyFloat(uchar *target, const char *source, qint32 width, qint32 c, qint32 cn)
|
||||
template<class T>
|
||||
inline void planarToChunchyFloatToUInt16(uchar *target, const char *source, qint32 width, qint32 c, qint32 cn)
|
||||
{
|
||||
auto s = reinterpret_cast<const T*>(source);
|
||||
auto t = reinterpret_cast<quint16*>(target);
|
||||
for (qint32 x = 0; x < width; ++x) {
|
||||
auto tmp = xchg(s[x]);
|
||||
auto ftmp = (*reinterpret_cast<float*>(&tmp) - double(min)) / (double(max) - double(min));
|
||||
t[x * cn + c] = quint16(std::min(ftmp * std::numeric_limits<quint16>::max() + 0.5, double(std::numeric_limits<quint16>::max())));
|
||||
t[x * cn + c] = quint16(std::min(xchg(s[x]) * std::numeric_limits<quint16>::max() + 0.5, double(std::numeric_limits<quint16>::max())));
|
||||
}
|
||||
}
|
||||
|
||||
@ -896,8 +915,8 @@ inline void cmykToRgb(uchar *target, qint32 targetChannels, const char *source,
|
||||
auto max = double(std::numeric_limits<T>::max());
|
||||
auto invmax = 1.0 / max; // speed improvements by ~10%
|
||||
|
||||
if (sourceChannels < 4) {
|
||||
qDebug() << "cmykToRgb: image is not a valid CMYK!";
|
||||
if (sourceChannels < 3) {
|
||||
qDebug() << "cmykToRgb: image is not a valid CMY/CMYK!";
|
||||
return;
|
||||
}
|
||||
|
||||
@ -906,7 +925,7 @@ inline void cmykToRgb(uchar *target, qint32 targetChannels, const char *source,
|
||||
auto C = 1 - *(ps + 0) * invmax;
|
||||
auto M = 1 - *(ps + 1) * invmax;
|
||||
auto Y = 1 - *(ps + 2) * invmax;
|
||||
auto K = 1 - *(ps + 3) * invmax;
|
||||
auto K = sourceChannels > 3 ? 1 - *(ps + 3) * invmax : 0.0;
|
||||
|
||||
auto pt = t + targetChannels * w;
|
||||
*(pt + 0) = T(std::min(max - (C * (1 - K) + K) * max + 0.5, max));
|
||||
@ -1140,7 +1159,7 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img)
|
||||
} else if (header.depth == 16) {
|
||||
planarToChunchy<quint16>(scanLine, rawStride.data(), header.width, c, header.channel_count);
|
||||
} else if (header.depth == 32) {
|
||||
planarToChunchyFloat<quint32>(scanLine, rawStride.data(), header.width, c, header.channel_count);
|
||||
planarToChunchyFloatToUInt16<float>(scanLine, rawStride.data(), header.width, c, header.channel_count);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1204,7 +1223,7 @@ static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img)
|
||||
} else if (header.depth == 16) { // 16-bits integer images: Grayscale, RGB/RGBA
|
||||
planarToChunchy<quint16>(scanLine, rawStride.data(), header.width, c, imgChannels);
|
||||
} else if (header.depth == 32) { // 32-bits float images: Grayscale, RGB/RGBA (coverted to equivalent integer 16-bits)
|
||||
planarToChunchyFloat<quint32>(scanLine, rawStride.data(), header.width, c, imgChannels);
|
||||
planarToChunchyFloatToUInt16<float>(scanLine, rawStride.data(), header.width, c, imgChannels);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1380,3 +1399,5 @@ QImageIOHandler *PSDPlugin::create(QIODevice *device, const QByteArray &format)
|
||||
handler->setFormat(format);
|
||||
return handler;
|
||||
}
|
||||
|
||||
#include "moc_psd_p.cpp"
|
||||
|
475
src/imageformats/qoi.cpp
Normal file
@ -0,0 +1,475 @@
|
||||
/*
|
||||
This file is part of the KDE project
|
||||
SPDX-FileCopyrightText: 2023 Ernest Gupik <ernestgupik@wp.pl>
|
||||
SPDX-FileCopyrightText: 2023 Mirco Miranda <mircomir@outlook.com>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "qoi_p.h"
|
||||
#include "scanlineconverter_p.h"
|
||||
#include "util_p.h"
|
||||
|
||||
#include <QColorSpace>
|
||||
#include <QFile>
|
||||
#include <QIODevice>
|
||||
#include <QImage>
|
||||
|
||||
namespace // Private
|
||||
{
|
||||
|
||||
#define QOI_OP_INDEX 0x00 /* 00xxxxxx */
|
||||
#define QOI_OP_DIFF 0x40 /* 01xxxxxx */
|
||||
#define QOI_OP_LUMA 0x80 /* 10xxxxxx */
|
||||
#define QOI_OP_RUN 0xc0 /* 11xxxxxx */
|
||||
#define QOI_OP_RGB 0xfe /* 11111110 */
|
||||
#define QOI_OP_RGBA 0xff /* 11111111 */
|
||||
#define QOI_MASK_2 0xc0 /* 11000000 */
|
||||
|
||||
#define QOI_MAGIC (((unsigned int)'q') << 24 | ((unsigned int)'o') << 16 | ((unsigned int)'i') << 8 | ((unsigned int)'f'))
|
||||
#define QOI_HEADER_SIZE 14
|
||||
#define QOI_END_STREAM_PAD 8
|
||||
|
||||
struct QoiHeader {
|
||||
quint32 MagicNumber;
|
||||
quint32 Width;
|
||||
quint32 Height;
|
||||
quint8 Channels;
|
||||
quint8 Colorspace;
|
||||
};
|
||||
|
||||
struct Px {
|
||||
bool operator==(const Px &other) const
|
||||
{
|
||||
return r == other.r && g == other.g && b == other.b && a == other.a;
|
||||
}
|
||||
quint8 r;
|
||||
quint8 g;
|
||||
quint8 b;
|
||||
quint8 a;
|
||||
};
|
||||
|
||||
static QDataStream &operator>>(QDataStream &s, QoiHeader &head)
|
||||
{
|
||||
s >> head.MagicNumber;
|
||||
s >> head.Width;
|
||||
s >> head.Height;
|
||||
s >> head.Channels;
|
||||
s >> head.Colorspace;
|
||||
return s;
|
||||
}
|
||||
|
||||
static QDataStream &operator<<(QDataStream &s, const QoiHeader &head)
|
||||
{
|
||||
s << head.MagicNumber;
|
||||
s << head.Width;
|
||||
s << head.Height;
|
||||
s << head.Channels;
|
||||
s << head.Colorspace;
|
||||
return s;
|
||||
}
|
||||
|
||||
static bool IsSupported(const QoiHeader &head)
|
||||
{
|
||||
// Check magic number
|
||||
if (head.MagicNumber != QOI_MAGIC) {
|
||||
return false;
|
||||
}
|
||||
// Check if the header is a valid QOI header
|
||||
if (head.Width == 0 || head.Height == 0 || head.Channels < 3 || head.Colorspace > 1) {
|
||||
return false;
|
||||
}
|
||||
// Set a reasonable upper limit
|
||||
if (head.Width > 300000 || head.Height > 300000) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static int QoiHash(const Px &px)
|
||||
{
|
||||
return px.r * 3 + px.g * 5 + px.b * 7 + px.a * 11;
|
||||
}
|
||||
|
||||
static QImage::Format imageFormat(const QoiHeader &head)
|
||||
{
|
||||
if (IsSupported(head)) {
|
||||
return (head.Channels == 3 ? QImage::Format_RGB32 : QImage::Format_ARGB32);
|
||||
}
|
||||
return QImage::Format_Invalid;
|
||||
}
|
||||
|
||||
static bool LoadQOI(QIODevice *device, const QoiHeader &qoi, QImage &img)
|
||||
{
|
||||
Px index[64] = {Px{0, 0, 0, 0}};
|
||||
Px px = Px{0, 0, 0, 255};
|
||||
|
||||
// The px_len should be enough to read a complete "compressed" row: an uncompressible row can become
|
||||
// larger than the row itself. It should never be more than 1/3 (RGB) or 1/4 (RGBA) the length of the
|
||||
// row itself (see test bnm_rgb*.qoi) so I set the extra data to 1/2.
|
||||
// The minimum value is to ensure that enough bytes are read when the image is very small (e.g. 1x1px):
|
||||
// it can be set as large as you like.
|
||||
quint64 px_len = std::max(quint64(1024), quint64(qoi.Width) * qoi.Channels * 3 / 2);
|
||||
if (px_len > kMaxQVectorSize) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allocate image
|
||||
img = imageAlloc(qoi.Width, qoi.Height, imageFormat(qoi));
|
||||
if (img.isNull()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set the image colorspace based on the qoi.Colorspace value
|
||||
// As per specification: 0 = sRGB with linear alpha, 1 = all channels linear
|
||||
if (qoi.Colorspace) {
|
||||
img.setColorSpace(QColorSpace(QColorSpace::SRgbLinear));
|
||||
} else {
|
||||
img.setColorSpace(QColorSpace(QColorSpace::SRgb));
|
||||
}
|
||||
|
||||
// Handle the byte stream
|
||||
QByteArray ba;
|
||||
for (quint32 y = 0, run = 0; y < qoi.Height; ++y) {
|
||||
if (quint64(ba.size()) < px_len) {
|
||||
ba.append(device->read(px_len));
|
||||
}
|
||||
|
||||
if (ba.size() < QOI_END_STREAM_PAD) {
|
||||
return false;
|
||||
}
|
||||
|
||||
quint64 chunks_len = ba.size() - QOI_END_STREAM_PAD;
|
||||
quint64 p = 0;
|
||||
QRgb *scanline = reinterpret_cast<QRgb *>(img.scanLine(y));
|
||||
const quint8 *input = reinterpret_cast<const quint8 *>(ba.constData());
|
||||
for (quint32 x = 0; x < qoi.Width; ++x) {
|
||||
if (run > 0) {
|
||||
run--;
|
||||
} else if (p < chunks_len) {
|
||||
quint32 b1 = input[p++];
|
||||
|
||||
if (b1 == QOI_OP_RGB) {
|
||||
px.r = input[p++];
|
||||
px.g = input[p++];
|
||||
px.b = input[p++];
|
||||
} else if (b1 == QOI_OP_RGBA) {
|
||||
px.r = input[p++];
|
||||
px.g = input[p++];
|
||||
px.b = input[p++];
|
||||
px.a = input[p++];
|
||||
} else if ((b1 & QOI_MASK_2) == QOI_OP_INDEX) {
|
||||
px = index[b1];
|
||||
} else if ((b1 & QOI_MASK_2) == QOI_OP_DIFF) {
|
||||
px.r += ((b1 >> 4) & 0x03) - 2;
|
||||
px.g += ((b1 >> 2) & 0x03) - 2;
|
||||
px.b += (b1 & 0x03) - 2;
|
||||
} else if ((b1 & QOI_MASK_2) == QOI_OP_LUMA) {
|
||||
quint32 b2 = input[p++];
|
||||
quint32 vg = (b1 & 0x3f) - 32;
|
||||
px.r += vg - 8 + ((b2 >> 4) & 0x0f);
|
||||
px.g += vg;
|
||||
px.b += vg - 8 + (b2 & 0x0f);
|
||||
} else if ((b1 & QOI_MASK_2) == QOI_OP_RUN) {
|
||||
run = (b1 & 0x3f);
|
||||
}
|
||||
index[QoiHash(px) & 0x3F] = px;
|
||||
}
|
||||
// Set the values for the pixel at (x, y)
|
||||
scanline[x] = qRgba(px.r, px.g, px.b, px.a);
|
||||
}
|
||||
|
||||
if (p) {
|
||||
ba.remove(0, p);
|
||||
}
|
||||
}
|
||||
|
||||
// From specs the byte stream's end is marked with 7 0x00 bytes followed by a single 0x01 byte.
|
||||
// NOTE: Instead of using "ba == QByteArray::fromRawData("\x00\x00\x00\x00\x00\x00\x00\x01", 8)"
|
||||
// we preferred a generic check that allows data to exist after the end of the file.
|
||||
return (ba.startsWith(QByteArray::fromRawData("\x00\x00\x00\x00\x00\x00\x00\x01", 8)));
|
||||
}
|
||||
|
||||
static bool SaveQOI(QIODevice *device, const QoiHeader &qoi, const QImage &img)
|
||||
{
|
||||
Px index[64] = {Px{0, 0, 0, 0}};
|
||||
Px px = Px{0, 0, 0, 255};
|
||||
Px px_prev = px;
|
||||
|
||||
auto run = 0;
|
||||
auto channels = qoi.Channels;
|
||||
|
||||
QByteArray ba;
|
||||
ba.reserve(img.width() * channels * 3 / 2);
|
||||
|
||||
ScanLineConverter converter(channels == 3 ? QImage::Format_RGB888 : QImage::Format_RGBA8888);
|
||||
converter.setTargetColorSpace(QColorSpace(qoi.Colorspace == 1 ? QColorSpace::SRgbLinear : QColorSpace::SRgb));
|
||||
|
||||
for (auto h = img.height(), y = 0; y < h; ++y) {
|
||||
auto pixels = converter.convertedScanLine(img, y);
|
||||
if (pixels == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto w = img.width() * channels, px_pos = 0; px_pos < w; px_pos += channels) {
|
||||
px.r = pixels[px_pos + 0];
|
||||
px.g = pixels[px_pos + 1];
|
||||
px.b = pixels[px_pos + 2];
|
||||
|
||||
if (channels == 4) {
|
||||
px.a = pixels[px_pos + 3];
|
||||
}
|
||||
|
||||
if (px == px_prev) {
|
||||
run++;
|
||||
if (run == 62 || (px_pos == w - channels && y == h - 1)) {
|
||||
ba.append(QOI_OP_RUN | (run - 1));
|
||||
run = 0;
|
||||
}
|
||||
} else {
|
||||
int index_pos;
|
||||
|
||||
if (run > 0) {
|
||||
ba.append(QOI_OP_RUN | (run - 1));
|
||||
run = 0;
|
||||
}
|
||||
|
||||
index_pos = QoiHash(px) & 0x3F;
|
||||
|
||||
if (index[index_pos] == px) {
|
||||
ba.append(QOI_OP_INDEX | index_pos);
|
||||
} else {
|
||||
index[index_pos] = px;
|
||||
|
||||
if (px.a == px_prev.a) {
|
||||
signed char vr = px.r - px_prev.r;
|
||||
signed char vg = px.g - px_prev.g;
|
||||
signed char vb = px.b - px_prev.b;
|
||||
|
||||
signed char vg_r = vr - vg;
|
||||
signed char vg_b = vb - vg;
|
||||
|
||||
if (vr > -3 && vr < 2 && vg > -3 && vg < 2 && vb > -3 && vb < 2) {
|
||||
ba.append(QOI_OP_DIFF | (vr + 2) << 4 | (vg + 2) << 2 | (vb + 2));
|
||||
} else if (vg_r > -9 && vg_r < 8 && vg > -33 && vg < 32 && vg_b > -9 && vg_b < 8) {
|
||||
ba.append(QOI_OP_LUMA | (vg + 32));
|
||||
ba.append((vg_r + 8) << 4 | (vg_b + 8));
|
||||
} else {
|
||||
ba.append(char(QOI_OP_RGB));
|
||||
ba.append(px.r);
|
||||
ba.append(px.g);
|
||||
ba.append(px.b);
|
||||
}
|
||||
} else {
|
||||
ba.append(char(QOI_OP_RGBA));
|
||||
ba.append(px.r);
|
||||
ba.append(px.g);
|
||||
ba.append(px.b);
|
||||
ba.append(px.a);
|
||||
}
|
||||
}
|
||||
}
|
||||
px_prev = px;
|
||||
}
|
||||
|
||||
auto written = device->write(ba);
|
||||
if (written < 0) {
|
||||
return false;
|
||||
}
|
||||
if (written) {
|
||||
ba.remove(0, written);
|
||||
}
|
||||
}
|
||||
|
||||
// QOI end of stream
|
||||
ba.append(QByteArray::fromRawData("\x00\x00\x00\x00\x00\x00\x00\x01", 8));
|
||||
|
||||
// write remaining data
|
||||
for (qint64 w = 0, write = 0, size = ba.size(); write < size; write += w) {
|
||||
w = device->write(ba.constData() + write, size - write);
|
||||
if (w < 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
QOIHandler::QOIHandler()
|
||||
{
|
||||
}
|
||||
|
||||
bool QOIHandler::canRead() const
|
||||
{
|
||||
if (canRead(device())) {
|
||||
setFormat("qoi");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool QOIHandler::canRead(QIODevice *device)
|
||||
{
|
||||
if (!device) {
|
||||
qWarning("QOIHandler::canRead() called with no device");
|
||||
return false;
|
||||
}
|
||||
|
||||
device->startTransaction();
|
||||
QByteArray head = device->read(QOI_HEADER_SIZE);
|
||||
qsizetype readBytes = head.size();
|
||||
device->rollbackTransaction();
|
||||
|
||||
if (readBytes < QOI_HEADER_SIZE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QDataStream stream(head);
|
||||
stream.setByteOrder(QDataStream::BigEndian);
|
||||
QoiHeader qoi = {0, 0, 0, 0, 2};
|
||||
stream >> qoi;
|
||||
|
||||
return IsSupported(qoi);
|
||||
}
|
||||
|
||||
bool QOIHandler::read(QImage *image)
|
||||
{
|
||||
QDataStream s(device());
|
||||
s.setByteOrder(QDataStream::BigEndian);
|
||||
|
||||
// Read image header
|
||||
QoiHeader qoi = {0, 0, 0, 0, 2};
|
||||
s >> qoi;
|
||||
|
||||
// Check if file is supported
|
||||
if (!IsSupported(qoi)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QImage img;
|
||||
bool result = LoadQOI(s.device(), qoi, img);
|
||||
|
||||
if (result == false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*image = img;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool QOIHandler::write(const QImage &image)
|
||||
{
|
||||
if (image.isNull()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QoiHeader qoi;
|
||||
qoi.MagicNumber = QOI_MAGIC;
|
||||
qoi.Width = image.width();
|
||||
qoi.Height = image.height();
|
||||
qoi.Channels = image.hasAlphaChannel() ? 4 : 3;
|
||||
qoi.Colorspace = image.colorSpace().transferFunction() == QColorSpace::TransferFunction::Linear ? 1 : 0;
|
||||
|
||||
if (!IsSupported(qoi)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QDataStream s(device());
|
||||
s.setByteOrder(QDataStream::BigEndian);
|
||||
s << qoi;
|
||||
if (s.status() != QDataStream::Ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return SaveQOI(s.device(), qoi, image);
|
||||
}
|
||||
|
||||
bool QOIHandler::supportsOption(ImageOption option) const
|
||||
{
|
||||
if (option == QImageIOHandler::Size) {
|
||||
return true;
|
||||
}
|
||||
if (option == QImageIOHandler::ImageFormat) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
QVariant QOIHandler::option(ImageOption option) const
|
||||
{
|
||||
QVariant v;
|
||||
|
||||
if (option == QImageIOHandler::Size) {
|
||||
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);
|
||||
s.setByteOrder(QDataStream::BigEndian);
|
||||
|
||||
QoiHeader header = {0, 0, 0, 0, 2};
|
||||
s >> header;
|
||||
|
||||
if (s.status() == QDataStream::Ok && IsSupported(header)) {
|
||||
v = QVariant::fromValue(QSize(header.Width, header.Height));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (option == QImageIOHandler::ImageFormat) {
|
||||
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);
|
||||
s.setByteOrder(QDataStream::BigEndian);
|
||||
|
||||
QoiHeader header = {0, 0, 0, 0, 2};
|
||||
s >> header;
|
||||
|
||||
if (s.status() == QDataStream::Ok && IsSupported(header)) {
|
||||
v = QVariant::fromValue(imageFormat(header));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
QImageIOPlugin::Capabilities QOIPlugin::capabilities(QIODevice *device, const QByteArray &format) const
|
||||
{
|
||||
if (format == "qoi" || format == "QOI") {
|
||||
return Capabilities(CanRead | CanWrite);
|
||||
}
|
||||
if (!format.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
if (!device->isOpen()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
Capabilities cap;
|
||||
if (device->isReadable() && QOIHandler::canRead(device)) {
|
||||
cap |= CanRead;
|
||||
}
|
||||
if (device->isWritable()) {
|
||||
cap |= CanWrite;
|
||||
}
|
||||
return cap;
|
||||
}
|
||||
|
||||
QImageIOHandler *QOIPlugin::create(QIODevice *device, const QByteArray &format) const
|
||||
{
|
||||
QImageIOHandler *handler = new QOIHandler;
|
||||
handler->setDevice(device);
|
||||
handler->setFormat(format);
|
||||
return handler;
|
||||
}
|
||||
|
||||
#include "moc_qoi_p.cpp"
|
7
src/imageformats/qoi.desktop
Normal file
@ -0,0 +1,7 @@
|
||||
[Desktop Entry]
|
||||
Type=Service
|
||||
X-KDE-ServiceTypes=QImageIOPlugins
|
||||
X-KDE-ImageFormat=qoi
|
||||
X-KDE-MimeType=image/qoi
|
||||
X-KDE-Read=true
|
||||
X-KDE-Write=true
|
4
src/imageformats/qoi.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"Keys": [ "qoi" ],
|
||||
"MimeTypes": [ "image/qoi" ]
|
||||
}
|
38
src/imageformats/qoi_p.h
Normal file
@ -0,0 +1,38 @@
|
||||
/*
|
||||
This file is part of the KDE project
|
||||
SPDX-FileCopyrightText: 2023 Ernest Gupik <ernestgupik@wp.pl>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef KIMG_QOI_P_H
|
||||
#define KIMG_QOI_P_H
|
||||
|
||||
#include <QImageIOPlugin>
|
||||
|
||||
class QOIHandler : public QImageIOHandler
|
||||
{
|
||||
public:
|
||||
QOIHandler();
|
||||
|
||||
bool canRead() const override;
|
||||
bool read(QImage *image) override;
|
||||
bool write(const QImage &image) override;
|
||||
|
||||
bool supportsOption(QImageIOHandler::ImageOption option) const override;
|
||||
QVariant option(QImageIOHandler::ImageOption option) const override;
|
||||
|
||||
static bool canRead(QIODevice *device);
|
||||
};
|
||||
|
||||
class QOIPlugin : public QImageIOPlugin
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QImageIOHandlerFactoryInterface" FILE "qoi.json")
|
||||
|
||||
public:
|
||||
Capabilities capabilities(QIODevice *device, const QByteArray &format) const override;
|
||||
QImageIOHandler *create(QIODevice *device, const QByteArray &format = QByteArray()) const override;
|
||||
};
|
||||
|
||||
#endif // KIMG_QOI_P_H
|
@ -340,3 +340,5 @@ QImageIOHandler *RASPlugin::create(QIODevice *device, const QByteArray &format)
|
||||
handler->setFormat(format);
|
||||
return handler;
|
||||
}
|
||||
|
||||
#include "moc_ras_p.cpp"
|
||||
|
@ -443,7 +443,9 @@ void setParams(QImageIOHandler *handler, LibRaw *rawProcessor)
|
||||
auto &&rawparams = rawProcessor->imgdata.rawparams;
|
||||
#endif
|
||||
// Select one raw image from input file (0 - first, ...)
|
||||
rawparams.shot_select = handler->currentImageNumber();
|
||||
if (handler->currentImageNumber() > -1) {
|
||||
rawparams.shot_select = handler->currentImageNumber();
|
||||
}
|
||||
|
||||
// *** Set processing parameters
|
||||
|
||||
@ -723,6 +725,7 @@ RAWHandler::RAWHandler()
|
||||
: m_imageNumber(0)
|
||||
, m_imageCount(0)
|
||||
, m_quality(-1)
|
||||
, m_startPos(-1)
|
||||
{
|
||||
}
|
||||
|
||||
@ -739,6 +742,15 @@ bool RAWHandler::read(QImage *image)
|
||||
{
|
||||
auto dev = device();
|
||||
|
||||
// set the image position after the first run.
|
||||
if (!dev->isSequential()) {
|
||||
if (m_startPos < 0) {
|
||||
m_startPos = dev->pos();
|
||||
} else {
|
||||
dev->seek(m_startPos);
|
||||
}
|
||||
}
|
||||
|
||||
// Check image file format.
|
||||
if (dev->atEnd()) {
|
||||
return false;
|
||||
@ -820,7 +832,7 @@ bool RAWHandler::jumpToNextImage()
|
||||
|
||||
bool RAWHandler::jumpToImage(int imageNumber)
|
||||
{
|
||||
if (imageNumber >= imageCount()) {
|
||||
if (imageNumber < 0 || imageNumber >= imageCount()) {
|
||||
return false;
|
||||
}
|
||||
m_imageNumber = imageNumber;
|
||||
@ -910,3 +922,5 @@ QImageIOHandler *RAWPlugin::create(QIODevice *device, const QByteArray &format)
|
||||
handler->setFormat(format);
|
||||
return handler;
|
||||
}
|
||||
|
||||
#include "moc_raw_p.cpp"
|
||||
|
@ -78,6 +78,12 @@ private:
|
||||
* @note It is safe to set both W and A: W is used if camera white balance is found, otherwise A is used.
|
||||
*/
|
||||
qint32 m_quality;
|
||||
|
||||
/*!
|
||||
* \brief m_startPos
|
||||
* The initial device position to allow multi image load (cache value).
|
||||
*/
|
||||
qint64 m_startPos;
|
||||
};
|
||||
|
||||
class RAWPlugin : public QImageIOPlugin
|
||||
|
@ -806,3 +806,5 @@ QImageIOHandler *RGBPlugin::create(QIODevice *device, const QByteArray &format)
|
||||
handler->setFormat(format);
|
||||
return handler;
|
||||
}
|
||||
|
||||
#include "moc_rgb_p.cpp"
|
||||
|
101
src/imageformats/scanlineconverter.cpp
Normal file
@ -0,0 +1,101 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2023 Mirco Miranda <mircomir@outlook.com>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "scanlineconverter_p.h"
|
||||
#include <cstring>
|
||||
|
||||
ScanLineConverter::ScanLineConverter(const QImage::Format &targetFormat)
|
||||
: _targetFormat(targetFormat)
|
||||
{
|
||||
}
|
||||
|
||||
ScanLineConverter::ScanLineConverter(const ScanLineConverter &other)
|
||||
: _targetFormat(other._targetFormat)
|
||||
, _colorSpace(other._colorSpace)
|
||||
{
|
||||
}
|
||||
|
||||
ScanLineConverter &ScanLineConverter::operator=(const ScanLineConverter &other)
|
||||
{
|
||||
this->_targetFormat = other._targetFormat;
|
||||
this->_colorSpace = other._colorSpace;
|
||||
return (*this);
|
||||
}
|
||||
|
||||
QImage::Format ScanLineConverter::targetFormat() const
|
||||
{
|
||||
return _targetFormat;
|
||||
}
|
||||
|
||||
void ScanLineConverter::setTargetColorSpace(const QColorSpace &colorSpace)
|
||||
{
|
||||
_colorSpace = colorSpace;
|
||||
}
|
||||
|
||||
QColorSpace ScanLineConverter::targetColorSpace() const
|
||||
{
|
||||
return _colorSpace;
|
||||
}
|
||||
|
||||
const uchar *ScanLineConverter::convertedScanLine(const QImage &image, qint32 y)
|
||||
{
|
||||
auto colorSpaceConversion = isColorSpaceConversionNeeded(image, _colorSpace);
|
||||
if (image.format() == _targetFormat && !colorSpaceConversion) {
|
||||
return image.constScanLine(y);
|
||||
}
|
||||
if (image.width() != _tmpBuffer.width() || image.format() != _tmpBuffer.format()) {
|
||||
_tmpBuffer = QImage(image.width(), 1, image.format());
|
||||
}
|
||||
if (_tmpBuffer.isNull()) {
|
||||
return nullptr;
|
||||
}
|
||||
std::memcpy(_tmpBuffer.bits(), image.constScanLine(y), std::min(_tmpBuffer.bytesPerLine(), image.bytesPerLine()));
|
||||
if (colorSpaceConversion) {
|
||||
_tmpBuffer.setColorSpace(image.colorSpace());
|
||||
_tmpBuffer.convertToColorSpace(_colorSpace);
|
||||
}
|
||||
_convBuffer = _tmpBuffer.convertToFormat(_targetFormat);
|
||||
if (_convBuffer.isNull()) {
|
||||
return nullptr;
|
||||
}
|
||||
return _convBuffer.constBits();
|
||||
}
|
||||
|
||||
qsizetype ScanLineConverter::bytesPerLine() const
|
||||
{
|
||||
if (_convBuffer.isNull()) {
|
||||
return 0;
|
||||
}
|
||||
return _convBuffer.bytesPerLine();
|
||||
}
|
||||
|
||||
bool ScanLineConverter::isColorSpaceConversionNeeded(const QImage &image, const QColorSpace &targetColorSpace) const
|
||||
{
|
||||
if (image.depth() < 24) { // RGB 8 bit or grater only
|
||||
return false;
|
||||
}
|
||||
auto sourceColorSpace = image.colorSpace();
|
||||
if (!sourceColorSpace.isValid() || !targetColorSpace.isValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto stf = sourceColorSpace.transferFunction();
|
||||
auto spr = sourceColorSpace.primaries();
|
||||
auto ttf = targetColorSpace.transferFunction();
|
||||
auto tpr = targetColorSpace.primaries();
|
||||
// clang-format off
|
||||
if (stf == QColorSpace::TransferFunction::Custom ||
|
||||
ttf == QColorSpace::TransferFunction::Custom ||
|
||||
spr == QColorSpace::Primaries::Custom ||
|
||||
tpr == QColorSpace::Primaries::Custom) {
|
||||
return true;
|
||||
}
|
||||
// clang-format on
|
||||
if (stf == ttf && spr == tpr) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
79
src/imageformats/scanlineconverter_p.h
Normal file
@ -0,0 +1,79 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2023 Mirco Miranda <mircomir@outlook.com>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef SCANLINECONVERTER_P_H
|
||||
#define SCANLINECONVERTER_P_H
|
||||
|
||||
#include <QColorSpace>
|
||||
#include <QImage>
|
||||
|
||||
/*!
|
||||
* \brief The scanlineFormatConversion class
|
||||
* A class to convert an image scan line. It introduces some overhead on small images
|
||||
* but performs better on large images. :)
|
||||
*/
|
||||
class ScanLineConverter
|
||||
{
|
||||
public:
|
||||
ScanLineConverter(const QImage::Format &targetFormat);
|
||||
ScanLineConverter(const ScanLineConverter &other);
|
||||
ScanLineConverter &operator=(const ScanLineConverter &other);
|
||||
|
||||
/*!
|
||||
* \brief targetFormat
|
||||
* \return The target format set in the constructor.
|
||||
*/
|
||||
QImage::Format targetFormat() const;
|
||||
|
||||
/*!
|
||||
* \brief setTargetColorSpace
|
||||
* Set the colorspace conversion.
|
||||
*
|
||||
* In addition to format conversion, it is also possible to convert the color
|
||||
* space if the source image has a different one set.
|
||||
* The conversion is done on the source format if and only if the image
|
||||
* has a color depth greater than 24 bit and the color profile set is different
|
||||
* from that of the image itself.
|
||||
* \param colorSpace
|
||||
*/
|
||||
void setTargetColorSpace(const QColorSpace &colorSpace);
|
||||
QColorSpace targetColorSpace() const;
|
||||
|
||||
/*!
|
||||
* \brief convertedScanLine
|
||||
* Convert the scanline \a y.
|
||||
* \note If the image format (and color space) is the same of converted format, it returns the image scan line.
|
||||
* \return The scan line converted.
|
||||
*/
|
||||
const uchar *convertedScanLine(const QImage &image, qint32 y);
|
||||
|
||||
/*!
|
||||
* \brief bytesPerLine
|
||||
* \return The size of the last converted scanline.
|
||||
*/
|
||||
qsizetype bytesPerLine() const;
|
||||
|
||||
/*!
|
||||
* \brief isColorSpaceConversionNeeded
|
||||
* Calculates if a color space conversion is needed.
|
||||
* \note Only 24 bit or grater images.
|
||||
* \param image The source image.
|
||||
* \param targetColorSpace The target color space.
|
||||
* \return True if the conversion should be done otherwise false.
|
||||
*/
|
||||
bool isColorSpaceConversionNeeded(const QImage &image, const QColorSpace &targetColorSpace) const;
|
||||
|
||||
private:
|
||||
// data
|
||||
QImage::Format _targetFormat;
|
||||
QColorSpace _colorSpace;
|
||||
|
||||
// internal buffers
|
||||
QImage _tmpBuffer;
|
||||
QImage _convBuffer;
|
||||
};
|
||||
|
||||
#endif // SCANLINECONVERTER_P_H
|
@ -527,3 +527,5 @@ QImageIOHandler *TGAPlugin::create(QIODevice *device, const QByteArray &format)
|
||||
handler->setFormat(format);
|
||||
return handler;
|
||||
}
|
||||
|
||||
#include "moc_tga_p.cpp"
|
||||
|