Compare commits
37 Commits
v6.3.0
...
work/qdoc-
Author | SHA1 | Date | |
---|---|---|---|
e2aaf89ec5 | |||
989a5c70d6 | |||
8588c053b6 | |||
145dedf360 | |||
2405a09e36 | |||
d02dcb064b | |||
0590c6b49d | |||
eb46f0f421 | |||
8c23e74ef6 | |||
219d9cb2c2 | |||
51921e8ee5 | |||
23e9fec869 | |||
4478bc8d2b | |||
acd6b3970c | |||
638fdfcbdd | |||
a497ab789b | |||
3590a43fc5 | |||
f5a6de7280 | |||
4c0f49295b | |||
e9da5edb9a | |||
e10f5aa9a5 | |||
14020a23d5 | |||
bb17f7bf84 | |||
b849e48ef4 | |||
81b7263d73 | |||
63e21ee5f3 | |||
06f097046c | |||
950ed43623 | |||
863c424390 | |||
bd083ff354 | |||
99663607b2 | |||
7499e3b8d4 | |||
cb5ca7fc48 | |||
4f61e3912c | |||
b8a9c75c80 | |||
4995c9cd15 | |||
a54c5e876c |
12
.gitattributes
vendored
@ -1 +1,13 @@
|
|||||||
autotests/read/raw/RAW_KODAK_C330_FORMAT_NONE_YRGB.raw binary
|
autotests/read/raw/RAW_KODAK_C330_FORMAT_NONE_YRGB.raw binary
|
||||||
|
autotests/read/hdr/orientation1.hdr binary
|
||||||
|
autotests/read/hdr/orientation2.hdr binary
|
||||||
|
autotests/read/hdr/orientation3.hdr binary
|
||||||
|
autotests/read/hdr/orientation4.hdr binary
|
||||||
|
autotests/read/hdr/orientation5.hdr binary
|
||||||
|
autotests/read/hdr/orientation6.hdr binary
|
||||||
|
autotests/read/hdr/orientation7.hdr binary
|
||||||
|
autotests/read/hdr/orientation8.hdr binary
|
||||||
|
autotests/read/hdr/fake_earth.hdr binary
|
||||||
|
autotests/read/hdr/rgb.hdr binary
|
||||||
|
autotests/read/hdr/rgb-landscape.hdr binary
|
||||||
|
autotests/read/hdr/rgb-portrait.hdr binary
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
cmake_minimum_required(VERSION 3.16)
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
|
||||||
set(KF_VERSION "6.3.0") # handled by release scripts
|
set(KF_VERSION "6.7.0") # handled by release scripts
|
||||||
set(KF_DEP_VERSION "6.3.0") # handled by release scripts
|
set(KF_DEP_VERSION "6.6.0") # handled by release scripts
|
||||||
project(KImageFormats VERSION ${KF_VERSION})
|
project(KImageFormats VERSION ${KF_VERSION})
|
||||||
|
|
||||||
include(FeatureSummary)
|
include(FeatureSummary)
|
||||||
find_package(ECM 6.3.0 NO_MODULE)
|
find_package(ECM 6.6.0 NO_MODULE)
|
||||||
set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://commits.kde.org/extra-cmake-modules")
|
set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://commits.kde.org/extra-cmake-modules")
|
||||||
feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES)
|
feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES)
|
||||||
|
|
||||||
@ -75,13 +75,18 @@ if(KIMAGEFORMATS_JXL)
|
|||||||
endif()
|
endif()
|
||||||
add_feature_info(LibJXL LibJXL_FOUND "required for the QImage plugin for JPEG XL images")
|
add_feature_info(LibJXL LibJXL_FOUND "required for the QImage plugin for JPEG XL images")
|
||||||
|
|
||||||
# note: module FindLibRaw missing from https://invent.kde.org/frameworks/extra-cmake-modules
|
|
||||||
find_package(LibRaw 0.20.2)
|
find_package(LibRaw 0.20.2)
|
||||||
set_package_properties(LibRaw PROPERTIES
|
set_package_properties(LibRaw PROPERTIES
|
||||||
TYPE OPTIONAL
|
TYPE OPTIONAL
|
||||||
PURPOSE "Required for the QImage plugin for RAW images"
|
PURPOSE "Required for the QImage plugin for RAW images"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
option(KIMAGEFORMATS_JXR "Enable plugin for JPEG XR format" OFF)
|
||||||
|
if(KIMAGEFORMATS_JXR)
|
||||||
|
find_package(LibJXR)
|
||||||
|
endif()
|
||||||
|
add_feature_info(LibJXR LibJXR_FOUND "required for the QImage plugin for JPEG XR images")
|
||||||
|
|
||||||
ecm_set_disabled_deprecation_versions(
|
ecm_set_disabled_deprecation_versions(
|
||||||
QT 6.5
|
QT 6.5
|
||||||
KF 5.102
|
KF 5.102
|
||||||
|
164
README.md
@ -1,6 +1,6 @@
|
|||||||
# KImageFormats
|
# KImageFormats
|
||||||
|
|
||||||
Plugins to allow QImage to support extra file formats.
|
Plugins to allow `QImage` to support extra file formats.
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
@ -18,16 +18,19 @@ The following image formats have read-only support:
|
|||||||
- Gimp (xcf)
|
- Gimp (xcf)
|
||||||
- Krita (kra)
|
- Krita (kra)
|
||||||
- OpenRaster (ora)
|
- OpenRaster (ora)
|
||||||
|
- Pixar raster (pxr)
|
||||||
|
- Portable FloatMap (pfm)
|
||||||
- Photoshop documents (psd, psb, pdd, psdt)
|
- Photoshop documents (psd, psb, pdd, psdt)
|
||||||
- Radiance HDR (hdr)
|
- Radiance HDR (hdr)
|
||||||
- Sun Raster (im1, im8, im24, im32, ras, sun)
|
- Sun Raster (im1, im8, im24, im32, ras, sun)
|
||||||
|
|
||||||
The following image formats have read and write support:
|
The following image formats have read and write support:
|
||||||
|
|
||||||
- AV1 Image File Format (AVIF)
|
- AV1 Image File Format (avif)
|
||||||
- Encapsulated PostScript (eps)
|
- Encapsulated PostScript (eps)
|
||||||
- High Efficiency Image File Format (heif). Can be enabled with the KIMAGEFORMATS_HEIF build option.
|
- High Efficiency Image File Format (heif)
|
||||||
- JPEG XL (jxl)
|
- JPEG XL (jxl)
|
||||||
|
- JPEG XR (jxr)
|
||||||
- OpenEXR (exr)
|
- OpenEXR (exr)
|
||||||
- Personal Computer Exchange (pcx)
|
- Personal Computer Exchange (pcx)
|
||||||
- Quite OK Image format (qoi)
|
- Quite OK Image format (qoi)
|
||||||
@ -37,7 +40,7 @@ The following image formats have read and write support:
|
|||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
See the QImageIOPlugin documentation for information on how to write a
|
See the [`QImageIOPlugin`](https://doc.qt.io/qt-6/qimageioplugin.html) documentation for information on how to write a
|
||||||
new plugin.
|
new plugin.
|
||||||
|
|
||||||
The main difference between this framework and the qimageformats module
|
The main difference between this framework and the qimageformats module
|
||||||
@ -65,3 +68,156 @@ This framework is licensed under the
|
|||||||
The CMake code in this framework is licensed under the
|
The CMake code in this framework is licensed under the
|
||||||
[BSD license](http://opensource.org/licenses/BSD-3-Clause).
|
[BSD license](http://opensource.org/licenses/BSD-3-Clause).
|
||||||
|
|
||||||
|
## Plugin status
|
||||||
|
|
||||||
|
The current implementation of a plugin may not be complete or may have limitations
|
||||||
|
of various kinds. Typically the limitations are on maximum size and color depth.
|
||||||
|
|
||||||
|
The various plugins are also limited by the formats natively supported by Qt.
|
||||||
|
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.
|
||||||
|
It is important to note that in the past these plugins stripped away HDR
|
||||||
|
information, returning SDR images.
|
||||||
|
|
||||||
|
HDR images return R, G and B values outside the range 0.0 - 1.0.
|
||||||
|
While Qt painters handles HDR data correctly, some older programs may display
|
||||||
|
strange artifacts if they do not use a tone mapping operator (or at least a
|
||||||
|
clamp). This is not a plugin issue.
|
||||||
|
|
||||||
|
### Metadata
|
||||||
|
|
||||||
|
Metadata support is implemented in all formats that support it. In particular,
|
||||||
|
in addition to the classic `"Description"`, `"Author"`, `"Copyright"`, etc... where
|
||||||
|
possible, XMP data is supported via the `"XML:com.adobe.xmp"` key.
|
||||||
|
|
||||||
|
Please note that only the most common metadata is supported.
|
||||||
|
|
||||||
|
### ICC profile support
|
||||||
|
|
||||||
|
ICC support is fully implemented in all formats that support it. When saving,
|
||||||
|
some formats convert the image using color profiles according to
|
||||||
|
specifications. In particular, HDR formats almost always convert to linear
|
||||||
|
RGB.
|
||||||
|
|
||||||
|
### Maximum image size
|
||||||
|
|
||||||
|
Where possible, plugins support large images. By convention, many of the
|
||||||
|
large image plugins are limited to a maximum of 300,000 x 300,000 pixels.
|
||||||
|
Anyway, all plugins are also limited by the
|
||||||
|
`QImageIOReader::allocationLimit()`. Below are the maximum sizes for each
|
||||||
|
plugin ('n/a' means no limit, i.e. the limit depends on the format encoding).
|
||||||
|
|
||||||
|
- ANI: n/a
|
||||||
|
- AVIF: 32,768 x 32,768 pixels, in any case no larger than 256 megapixels
|
||||||
|
- 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
|
||||||
|
- PCX: 65,535 x 65,535 pixels
|
||||||
|
- PFM: n/a (large image)
|
||||||
|
- PIC: 65,535 x 65,535 pixels
|
||||||
|
- PSD: 300,000 x 300,000 pixels
|
||||||
|
- PXR: 65,535 x 65,535 pixels
|
||||||
|
- QOI: 300,000 x 300,000 pixels
|
||||||
|
- RAS: n/a (large image)
|
||||||
|
- RAW: n/a (depends on the RAW format loaded)
|
||||||
|
- RGB: 65,535 x 65,535 pixels
|
||||||
|
- TGA: 65,535 x 65,535 pixels
|
||||||
|
- XCF: 300,000 x 300,000 pixels
|
||||||
|
|
||||||
|
### Sequential and random access devices
|
||||||
|
|
||||||
|
All plugins work fine on random access devices while only some work on
|
||||||
|
sequential access devices.
|
||||||
|
Some plugins, such as PSD, allow reading RGB images on sequential access
|
||||||
|
devices, but cannot do the same for Lab files.
|
||||||
|
|
||||||
|
**Important: some plugins use `QIODevice` transactions and/or
|
||||||
|
`QIODevice::ungetChar()`. Therefore, the device used to read the image must not
|
||||||
|
have any active transactions.**
|
||||||
|
|
||||||
|
### Memory usage
|
||||||
|
|
||||||
|
Qt has added many image formats over time. In older plugins, to support new
|
||||||
|
formats, `QImage` conversion functions have been used, causing memory
|
||||||
|
consumption proportional to the size of the image to be saved.
|
||||||
|
Normally this is not a source of problems because the affected plugins
|
||||||
|
are limited to maximum images of 2GiB or less.
|
||||||
|
|
||||||
|
On plugins for formats that support large images, progressive conversion has
|
||||||
|
been used or the maximum size of the image that can be saved has been limited.
|
||||||
|
|
||||||
|
### Non-RGB formats
|
||||||
|
|
||||||
|
PSD plugin loads CMYK, Lab and Multichannel images and converts them to RGB
|
||||||
|
without using the ICC profile.
|
||||||
|
|
||||||
|
JXR and PSD plugins natively support 4-channel CMYK images when compiled
|
||||||
|
with Qt 6.8+.
|
||||||
|
|
||||||
|
### The HEIF plugin
|
||||||
|
|
||||||
|
**This plugin is disabled by default. It can be enabled with the
|
||||||
|
`KIMAGEFORMATS_HEIF` build option in the cmake file.**
|
||||||
|
|
||||||
|
### The EXR plugin
|
||||||
|
|
||||||
|
The following defines can be defined in cmake to modify the behavior of the plugin:
|
||||||
|
- `EXR_CONVERT_TO_SRGB`: the linear data is converted to sRGB on read to accommodate programs that do not support color profiles.
|
||||||
|
- `EXR_DISABLE_XMP_ATTRIBUTE`: disables the stores XMP values in a non-standard attribute named "xmp". Note that Gimp reads the "xmp" attribute and Darktable writes it as well.
|
||||||
|
|
||||||
|
### The HDR plugin
|
||||||
|
|
||||||
|
The following defines can be defined in cmake to modify the behavior of the plugin:
|
||||||
|
- `HDR_HALF_QUALITY`: on read, a 16-bit float image is returned instead of a 32-bit float one.
|
||||||
|
|
||||||
|
### The JXL plugin
|
||||||
|
|
||||||
|
**The current version of the plugin limits the image size to 256 megapixels
|
||||||
|
according to feature level 5 of the JXL stream encoding.**
|
||||||
|
|
||||||
|
### The JXR plugin
|
||||||
|
|
||||||
|
**This plugin is disabled by default. It can be enabled with the
|
||||||
|
`KIMAGEFORMATS_JXR` build option in the cmake file.**
|
||||||
|
|
||||||
|
The following defines can be defined in cmake to modify the behavior of the plugin:
|
||||||
|
- `JXR_DENY_FLOAT_IMAGE`: disables the use of float images and consequently any HDR data will be lost.
|
||||||
|
- `JXR_DISABLE_DEPTH_CONVERSION`: remove the neeeds of additional memory by disabling the conversion between different color depths (e.g. RGBA64bpp to RGBA32bpp) at the cost of reduced compatibility.
|
||||||
|
- `JXR_DISABLE_BGRA_HACK`: Windows displays and opens JXR files correctly out of the box. Unfortunately it doesn't seem to open (P)RGBA @32bpp files as it only wants (P)BGRA32bpp files (a format not supported by Qt). Only for this format an hack is activated to guarantee total compatibility of the plugin with Windows.
|
||||||
|
- `JXR_ENABLE_ADVANCED_METADATA`: enable metadata support (e.g. XMP). Some distributions use an incomplete JXR library that does not allow reading metadata, causing compilation errors.
|
||||||
|
|
||||||
|
### The PSD plugin
|
||||||
|
|
||||||
|
PSD support has the following limitations:
|
||||||
|
- Only images saved by Photoshop using compatibility mode enabled (Photoshop default) can be decoded.
|
||||||
|
- Multichannel images are treated as CMY/CMYK and are only loaded if they have 3 or more channels.
|
||||||
|
- Duotone images are treated as grayscale images.
|
||||||
|
- Extra channels other than alpha are discarded.
|
||||||
|
|
||||||
|
The following defines can be defined in cmake to modify the behavior of the plugin:
|
||||||
|
- `PSD_FAST_LAB_CONVERSION`: the LAB image is converted to linear sRGB instead of sRGB which significantly increases performance.
|
||||||
|
- `PSD_NATIVE_CMYK_SUPPORT_DISABLED`: disable native support for CMYK images when compiled with Qt 6.8+
|
||||||
|
|
||||||
|
### The RAW plugin
|
||||||
|
|
||||||
|
Loading RAW images always requires a conversion. To allow the user to
|
||||||
|
choose how to convert the image, it was chosen to use the quality parameter
|
||||||
|
to act on the converter. The quality parameter can be used with values from
|
||||||
|
0 to 100 (0 = fast, 100 = maximum quality) or by setting flags to
|
||||||
|
selectively change the conversion (see also [raw_p.h](./src/imageformats/raw_p.h)).
|
||||||
|
|
||||||
|
The default setting tries to balance quality and conversion speed.
|
||||||
|
|
||||||
|
### The XCF plugin
|
||||||
|
|
||||||
|
XCF support has the following limitations:
|
||||||
|
- XCF format up to [version 12](https://testing.developer.gimp.org/core/standards/xcf/#version-history) (no support for GIMP 3).
|
||||||
|
- The returned image is always 8-bit.
|
||||||
|
- Cannot read zlib compressed files.
|
||||||
|
- The rendered image may be slightly different (colors/transparencies) than in GIMP.
|
||||||
|
@ -11,7 +11,7 @@ macro(kimageformats_read_tests)
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (NOT TARGET readtest)
|
if (NOT TARGET readtest)
|
||||||
add_executable(readtest readtest.cpp)
|
add_executable(readtest readtest.cpp templateimage.cpp)
|
||||||
target_link_libraries(readtest Qt6::Gui)
|
target_link_libraries(readtest Qt6::Gui)
|
||||||
target_compile_definitions(readtest
|
target_compile_definitions(readtest
|
||||||
PRIVATE IMAGEDIR="${CMAKE_CURRENT_SOURCE_DIR}/read")
|
PRIVATE IMAGEDIR="${CMAKE_CURRENT_SOURCE_DIR}/read")
|
||||||
@ -66,7 +66,9 @@ endmacro()
|
|||||||
kimageformats_read_tests(
|
kimageformats_read_tests(
|
||||||
hdr
|
hdr
|
||||||
pcx
|
pcx
|
||||||
|
pfm
|
||||||
psd
|
psd
|
||||||
|
pxr
|
||||||
qoi
|
qoi
|
||||||
ras
|
ras
|
||||||
rgb
|
rgb
|
||||||
@ -114,6 +116,15 @@ if (LibJXL_FOUND AND LibJXLThreads_FOUND)
|
|||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if (LibJXR_FOUND)
|
||||||
|
kimageformats_read_tests(
|
||||||
|
jxr
|
||||||
|
)
|
||||||
|
kimageformats_write_tests(
|
||||||
|
jxr-nodatacheck
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
# Allow some fuzziness when reading this formats, to allow for
|
# Allow some fuzziness when reading this formats, to allow for
|
||||||
# rounding errors (eg: in alpha blending).
|
# rounding errors (eg: in alpha blending).
|
||||||
kimageformats_read_tests(FUZZ 1
|
kimageformats_read_tests(FUZZ 1
|
||||||
|
47
autotests/read/hdr/orientation1.hdr
Normal file
5
autotests/read/hdr/orientation1.hdr.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"fileName" : "orientation_all.png"
|
||||||
|
}
|
||||||
|
]
|
47
autotests/read/hdr/orientation2.hdr
Normal file
5
autotests/read/hdr/orientation2.hdr.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"fileName" : "orientation_all.png"
|
||||||
|
}
|
||||||
|
]
|
47
autotests/read/hdr/orientation3.hdr
Normal file
5
autotests/read/hdr/orientation3.hdr.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"fileName" : "orientation_all.png"
|
||||||
|
}
|
||||||
|
]
|
47
autotests/read/hdr/orientation4.hdr
Normal file
5
autotests/read/hdr/orientation4.hdr.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"fileName" : "orientation_all.png"
|
||||||
|
}
|
||||||
|
]
|
47
autotests/read/hdr/orientation5.hdr
Normal file
5
autotests/read/hdr/orientation5.hdr.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"fileName" : "orientation_all.png"
|
||||||
|
}
|
||||||
|
]
|
47
autotests/read/hdr/orientation6.hdr
Normal file
5
autotests/read/hdr/orientation6.hdr.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"fileName" : "orientation_all.png"
|
||||||
|
}
|
||||||
|
]
|
47
autotests/read/hdr/orientation7.hdr
Normal file
5
autotests/read/hdr/orientation7.hdr.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"fileName" : "orientation_all.png"
|
||||||
|
}
|
||||||
|
]
|
47
autotests/read/hdr/orientation8.hdr
Normal file
5
autotests/read/hdr/orientation8.hdr.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"fileName" : "orientation_all.png"
|
||||||
|
}
|
||||||
|
]
|
BIN
autotests/read/hdr/orientation_all.png
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
autotests/read/jxl/orientation6_notranfs.jxl
Normal file
19
autotests/read/jxl/orientation6_notranfs.jxl.json
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"minQtVersion" : "6.5.7",
|
||||||
|
"maxQtVersion" : "6.5.99",
|
||||||
|
"disableAutoTransform": true,
|
||||||
|
"fileName" : "orientation6_notranfs.png",
|
||||||
|
"comment" : "Test with automatic transformation disabled."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"minQtVersion" : "6.7.3",
|
||||||
|
"disableAutoTransform": true,
|
||||||
|
"fileName" : "orientation6_notranfs.png",
|
||||||
|
"comment" : "Test with automatic transformation disabled."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"unsupportedFormat" : true,
|
||||||
|
"comment" : "It is not possible to disable the transformation with the current version of the plugin."
|
||||||
|
}
|
||||||
|
]
|
BIN
autotests/read/jxl/orientation6_notranfs.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
autotests/read/jxr/abydos_bgra32.jxr
Normal file
BIN
autotests/read/jxr/abydos_bgra32.png
Normal file
After Width: | Height: | Size: 206 KiB |
BIN
autotests/read/jxr/testcard_bgra8.jxr
Normal file
BIN
autotests/read/jxr/testcard_bgra8.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
autotests/read/jxr/testcard_cmyk8.jxr
Normal file
11
autotests/read/jxr/testcard_cmyk8.jxr.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"minQtVersion" : "6.8.0",
|
||||||
|
"fileName" : "testcard_cmyk8.tif"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"maxQtVersion" : "6.7.99",
|
||||||
|
"unsupportedFormat" : true,
|
||||||
|
"comment" : "Qt versions lower than 6.8 do not support CMYK format so this test should be skipped."
|
||||||
|
}
|
||||||
|
]
|
BIN
autotests/read/jxr/testcard_cmyk8.tif
Normal file
BIN
autotests/read/jxr/testcard_gray16.jxr
Normal file
BIN
autotests/read/jxr/testcard_gray16.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
autotests/read/jxr/testcard_gray8.jxr
Normal file
BIN
autotests/read/jxr/testcard_gray8.png
Normal file
After Width: | Height: | Size: 6.8 KiB |
BIN
autotests/read/jxr/testcard_mono.jxr
Normal file
BIN
autotests/read/jxr/testcard_mono.png
Normal file
After Width: | Height: | Size: 8.2 KiB |
BIN
autotests/read/jxr/testcard_rgb16.jxr
Normal file
BIN
autotests/read/jxr/testcard_rgb16.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
autotests/read/jxr/testcard_rgb8.jxr
Normal file
BIN
autotests/read/jxr/testcard_rgb8.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
autotests/read/jxr/testcard_rgba16.jxr
Normal file
BIN
autotests/read/jxr/testcard_rgba16.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
autotests/read/jxr/testcard_rgba8.jxr
Normal file
BIN
autotests/read/jxr/testcard_rgba8.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
autotests/read/pcx/16color.pcx
Normal file
BIN
autotests/read/pcx/16color.png
Normal file
After Width: | Height: | Size: 4.3 KiB |
BIN
autotests/read/pcx/indexed4.pcx
Normal file
BIN
autotests/read/pcx/indexed4.png
Normal file
After Width: | Height: | Size: 593 B |
BIN
autotests/read/pfm/testcard_gray.pfm
Normal file
BIN
autotests/read/pfm/testcard_gray.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
autotests/read/pfm/testcard_rgb.pfm
Normal file
BIN
autotests/read/pfm/testcard_rgb.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
autotests/read/pfm/testcard_rgb_ps.pfm
Normal file
BIN
autotests/read/pfm/testcard_rgb_ps.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
autotests/read/psd/cmyk16_testcard.png
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
autotests/read/psd/cmyk16_testcard.psd
Normal file
11
autotests/read/psd/cmyk16_testcard.psd.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"minQtVersion" : "6.8.0",
|
||||||
|
"fileName" : "cmyk16_testcard_qt6_8.tif"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"minQtVersion" : "6.0.0",
|
||||||
|
"maxQtVersion" : "6.7.99",
|
||||||
|
"fileName" : "cmyk16_testcard.png"
|
||||||
|
}
|
||||||
|
]
|
BIN
autotests/read/psd/cmyk16_testcard_qt6_8.tif
Normal file
BIN
autotests/read/psd/cmyk8_testcard.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
autotests/read/psd/cmyk8_testcard.psd
Normal file
11
autotests/read/psd/cmyk8_testcard.psd.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"minQtVersion" : "6.8.0",
|
||||||
|
"fileName" : "cmyk8_testcard_qt6_8.tif"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"minQtVersion" : "6.0.0",
|
||||||
|
"maxQtVersion" : "6.7.99",
|
||||||
|
"fileName" : "cmyk8_testcard.png"
|
||||||
|
}
|
||||||
|
]
|
BIN
autotests/read/psd/cmyk8_testcard_qt6_8.tif
Normal file
11
autotests/read/psd/cmyka-16bits.psd.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"minQtVersion" : "6.8.0",
|
||||||
|
"fileName" : "cmyka-16bits_qt6_8.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"minQtVersion" : "6.0.0",
|
||||||
|
"maxQtVersion" : "6.7.99",
|
||||||
|
"fileName" : "cmyka-16bits.png"
|
||||||
|
}
|
||||||
|
]
|
BIN
autotests/read/psd/cmyka-16bits_qt6_8.png
Normal file
After Width: | Height: | Size: 111 KiB |
11
autotests/read/psd/cmyka-8bits.psd.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"minQtVersion" : "6.8.0",
|
||||||
|
"fileName" : "cmyka-8bits_qt6_8.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"minQtVersion" : "6.0.0",
|
||||||
|
"maxQtVersion" : "6.7.99",
|
||||||
|
"fileName" : "cmyka-8bits.png"
|
||||||
|
}
|
||||||
|
]
|
BIN
autotests/read/psd/cmyka-8bits_qt6_8.png
Normal file
After Width: | Height: | Size: 77 KiB |
11
autotests/read/psd/mch-16bits.psd.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"minQtVersion" : "6.8.0",
|
||||||
|
"fileName" : "mch-16bits_qt_6_8.tif"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"minQtVersion" : "6.0.0",
|
||||||
|
"maxQtVersion" : "6.7.99",
|
||||||
|
"fileName" : "mch-16bits.png"
|
||||||
|
}
|
||||||
|
]
|
BIN
autotests/read/psd/mch-16bits_qt_6_8.tif
Normal file
11
autotests/read/psd/mch-8bits.psd.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"minQtVersion" : "6.8.0",
|
||||||
|
"fileName" : "mch-8bits_qt_6.8.tif"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"minQtVersion" : "6.0.0",
|
||||||
|
"maxQtVersion" : "6.7.99",
|
||||||
|
"fileName" : "mch-8bits.png"
|
||||||
|
}
|
||||||
|
]
|
BIN
autotests/read/psd/mch-8bits_qt_6.8.tif
Normal file
BIN
autotests/read/pxr/testcard_gray.png
Normal file
After Width: | Height: | Size: 5.4 KiB |
BIN
autotests/read/pxr/testcard_gray.pxr
Normal file
BIN
autotests/read/pxr/testcard_rgb.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
autotests/read/pxr/testcard_rgb.pxr
Normal file
BIN
autotests/read/rgb/testcard_rgba.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
autotests/read/rgb/testcard_rgba.rgb
Normal file
32
autotests/read/xcf/birthday16.xcf.json
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"minQtVersion" : "6.7.0",
|
||||||
|
"fileName" : "birthday16.png",
|
||||||
|
"seeAlso" : "https://bugreports.qt.io/browse/QTBUG-120614"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"minQtVersion" : "6.0.0",
|
||||||
|
"maxQtVersion" : "6.2.10",
|
||||||
|
"fileName" : "birthday16_alphabug.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"minQtVersion" : "6.3.0",
|
||||||
|
"maxQtVersion" : "6.3.2",
|
||||||
|
"fileName" : "birthday32_alphabug.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"minQtVersion" : "6.4.0",
|
||||||
|
"maxQtVersion" : "6.4.3",
|
||||||
|
"fileName" : "birthday32_alphabug.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"minQtVersion" : "6.5.0",
|
||||||
|
"maxQtVersion" : "6.5.4",
|
||||||
|
"fileName" : "birthday16_alphabug.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"minQtVersion" : "6.6.0",
|
||||||
|
"maxQtVersion" : "6.6.1",
|
||||||
|
"fileName" : "birthday16_alphabug.png"
|
||||||
|
}
|
||||||
|
]
|
BIN
autotests/read/xcf/birthday16_alphabug.png
Normal file
After Width: | Height: | Size: 91 KiB |
32
autotests/read/xcf/birthday32.xcf.json
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"minQtVersion" : "6.7.0",
|
||||||
|
"fileName" : "birthday32.png",
|
||||||
|
"seeAlso" : "https://bugreports.qt.io/browse/QTBUG-120614"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"minQtVersion" : "6.0.0",
|
||||||
|
"maxQtVersion" : "6.2.10",
|
||||||
|
"fileName" : "birthday32_alphabug.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"minQtVersion" : "6.3.0",
|
||||||
|
"maxQtVersion" : "6.3.2",
|
||||||
|
"fileName" : "birthday32_alphabug.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"minQtVersion" : "6.4.0",
|
||||||
|
"maxQtVersion" : "6.4.3",
|
||||||
|
"fileName" : "birthday32_alphabug.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"minQtVersion" : "6.5.0",
|
||||||
|
"maxQtVersion" : "6.5.4",
|
||||||
|
"fileName" : "birthday32_alphabug.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"minQtVersion" : "6.6.0",
|
||||||
|
"maxQtVersion" : "6.6.1",
|
||||||
|
"fileName" : "birthday32_alphabug.png"
|
||||||
|
}
|
||||||
|
]
|
BIN
autotests/read/xcf/birthday32_alphabug.png
Normal file
After Width: | Height: | Size: 91 KiB |
BIN
autotests/read/xcf/bug_476755_gray_layers.png
Normal file
After Width: | Height: | Size: 234 KiB |
BIN
autotests/read/xcf/bug_476755_gray_layers.xcf
Normal file
BIN
autotests/read/xcf/bug_476755_rgb_layers.png
Normal file
After Width: | Height: | Size: 234 KiB |
BIN
autotests/read/xcf/bug_476755_rgb_layers.xcf
Normal file
@ -16,6 +16,7 @@
|
|||||||
#include <QTextStream>
|
#include <QTextStream>
|
||||||
|
|
||||||
#include "../tests/format-enum.h"
|
#include "../tests/format-enum.h"
|
||||||
|
#include "templateimage.h"
|
||||||
|
|
||||||
#include "fuzzyeq.cpp"
|
#include "fuzzyeq.cpp"
|
||||||
|
|
||||||
@ -89,13 +90,115 @@ static QImage::Format preferredFormat(QImage::Format fmt)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief The OptionTest class
|
||||||
|
* Class for testing image options.
|
||||||
|
* Supports the most common options:
|
||||||
|
* - Size
|
||||||
|
* - ImageFormat
|
||||||
|
* - ImageTransformation (rotations)
|
||||||
|
* \todo Add missing options if needed.
|
||||||
|
*/
|
||||||
|
class OptionTest
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
OptionTest()
|
||||||
|
: m_size(QSize())
|
||||||
|
, m_format(QImage::Format_Invalid)
|
||||||
|
, m_transformations(QImageIOHandler::TransformationNone)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
OptionTest(const OptionTest&) = default;
|
||||||
|
OptionTest& operator =(const OptionTest&) = default;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief store
|
||||||
|
* Stores the supported options of the reader.
|
||||||
|
* \param reader
|
||||||
|
* \return True on success, otherwise false.
|
||||||
|
*/
|
||||||
|
bool store(const QImageReader *reader = nullptr)
|
||||||
|
{
|
||||||
|
if (reader == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bool ok = true;
|
||||||
|
if (reader->supportsOption(QImageIOHandler::Size)) {
|
||||||
|
m_size = reader->size();
|
||||||
|
if (m_size.isEmpty())
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
if (reader->supportsOption(QImageIOHandler::ImageFormat)) {
|
||||||
|
m_format = reader->imageFormat();
|
||||||
|
if (m_format == QImage::Format_Invalid)
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
if (reader->supportsOption(QImageIOHandler::ImageTransformation)) {
|
||||||
|
m_transformations = reader->transformation();
|
||||||
|
if (m_transformations < 0 || m_transformations > 7)
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief compare
|
||||||
|
* Compare the stored values with the ones read from the image reader.
|
||||||
|
* \param reader
|
||||||
|
* \return True on success, otherwise false.
|
||||||
|
*/
|
||||||
|
bool compare(const QImageReader *reader)
|
||||||
|
{
|
||||||
|
if (reader == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bool ok = true;
|
||||||
|
if (reader->supportsOption(QImageIOHandler::Size)) {
|
||||||
|
ok = ok && (m_size == reader->size());
|
||||||
|
}
|
||||||
|
if (reader->supportsOption(QImageIOHandler::ImageFormat)) {
|
||||||
|
ok = ok && (m_format == reader->imageFormat());
|
||||||
|
}
|
||||||
|
if (reader->supportsOption(QImageIOHandler::ImageTransformation)) {
|
||||||
|
ok = ok && (m_transformations == reader->transformation());
|
||||||
|
}
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief compare
|
||||||
|
* Compare the image properties with the ones stored.
|
||||||
|
* \param image
|
||||||
|
* \return True on success, otherwise false.
|
||||||
|
*/
|
||||||
|
bool compare(const QImage& image)
|
||||||
|
{
|
||||||
|
bool ok = true;
|
||||||
|
if (!m_size.isEmpty()) {
|
||||||
|
// Size option return the size without transformation (tested with Qt TIFF plugin).
|
||||||
|
ok = ok && (m_size == image.size() || m_size == image.size().transposed());
|
||||||
|
}
|
||||||
|
if (m_format != QImage::Format_Invalid) {
|
||||||
|
ok = ok && (m_format == image.format());
|
||||||
|
}
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
QSize m_size;
|
||||||
|
QImage::Format m_format;
|
||||||
|
QImageIOHandler::Transformations m_transformations;
|
||||||
|
};
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
QCoreApplication app(argc, argv);
|
QCoreApplication app(argc, argv);
|
||||||
QCoreApplication::removeLibraryPath(QStringLiteral(PLUGIN_DIR));
|
QCoreApplication::removeLibraryPath(QStringLiteral(PLUGIN_DIR));
|
||||||
QCoreApplication::addLibraryPath(QStringLiteral(PLUGIN_DIR));
|
QCoreApplication::addLibraryPath(QStringLiteral(PLUGIN_DIR));
|
||||||
QCoreApplication::setApplicationName(QStringLiteral("readtest"));
|
QCoreApplication::setApplicationName(QStringLiteral("readtest"));
|
||||||
QCoreApplication::setApplicationVersion(QStringLiteral("1.1.0"));
|
QCoreApplication::setApplicationVersion(QStringLiteral("1.2.0"));
|
||||||
|
|
||||||
QCommandLineParser parser;
|
QCommandLineParser parser;
|
||||||
parser.setApplicationDescription(QStringLiteral("Performs basic image conversion checking."));
|
parser.setApplicationDescription(QStringLiteral("Performs basic image conversion checking."));
|
||||||
@ -159,28 +262,39 @@ int main(int argc, char **argv)
|
|||||||
QTextStream(stdout) << "* Run on RANDOM ACCESS device\n";
|
QTextStream(stdout) << "* Run on RANDOM ACCESS device\n";
|
||||||
}
|
}
|
||||||
for (const QFileInfo &fi : lstImgDir) {
|
for (const QFileInfo &fi : lstImgDir) {
|
||||||
if (!fi.suffix().compare("png", Qt::CaseInsensitive) || !fi.suffix().compare("tif", Qt::CaseInsensitive)) {
|
TemplateImage timg(fi);
|
||||||
|
if (timg.isTemplate()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
int suffixPos = fi.filePath().size() - suffix.size();
|
|
||||||
QString inputfile = fi.filePath();
|
|
||||||
QString fmt = QStringLiteral("png");
|
|
||||||
QString expfile = fi.filePath().replace(suffixPos, suffix.size(), fmt);
|
|
||||||
if (!QFile::exists(expfile)) { // try with tiff
|
|
||||||
fmt = QStringLiteral("tif");
|
|
||||||
expfile = fi.filePath().replace(suffixPos, suffix.size(), fmt);
|
|
||||||
}
|
|
||||||
QString expfilename = QFileInfo(expfile).fileName();
|
|
||||||
|
|
||||||
std::unique_ptr<QIODevice> inputDevice(seq ? new SequentialFile(inputfile) : new QFile(inputfile));
|
TemplateImage::TestFlags flags = TemplateImage::None;
|
||||||
|
QString comment;
|
||||||
|
QFileInfo expFileInfo = timg.compareImage(flags, comment);
|
||||||
|
if ((flags & TemplateImage::SkipTest) == TemplateImage::SkipTest) {
|
||||||
|
if(comment.isEmpty())
|
||||||
|
comment = QStringLiteral("image format not supported by current Qt version!");
|
||||||
|
QTextStream(stdout) << "SKIP : " << fi.fileName() << QStringLiteral(": %1\n").arg(comment);
|
||||||
|
++skipped;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!formatStrings.contains(expFileInfo.suffix(), Qt::CaseInsensitive)) {
|
||||||
|
// Work Around for CCBUG: 468288
|
||||||
|
QTextStream(stdout) << "SKIP : " << fi.fileName() << ": comparison image " << expFileInfo.fileName() << " cannot be loaded due to the lack of "
|
||||||
|
<< expFileInfo.suffix().toUpper() << " plugin!\n";
|
||||||
|
++skipped;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
QString expfilename = expFileInfo.fileName();
|
||||||
|
|
||||||
|
std::unique_ptr<QIODevice> inputDevice(seq ? new SequentialFile(fi.filePath()) : new QFile(fi.filePath()));
|
||||||
QImageReader inputReader(inputDevice.get(), format);
|
QImageReader inputReader(inputDevice.get(), format);
|
||||||
QImageReader expReader(expfile, fmt.toLatin1());
|
QImageReader expReader(expFileInfo.filePath());
|
||||||
|
|
||||||
QImage inputImage;
|
QImage inputImage;
|
||||||
QImage expImage;
|
QImage expImage;
|
||||||
|
|
||||||
// inputImage is auto-rotated to final orientation
|
// inputImage is auto-rotated to final orientation
|
||||||
inputReader.setAutoTransform(true);
|
inputReader.setAutoTransform((flags & TemplateImage::DisableAutotransform) != TemplateImage::DisableAutotransform);
|
||||||
|
|
||||||
if (!expReader.read(&expImage)) {
|
if (!expReader.read(&expImage)) {
|
||||||
QTextStream(stdout) << "ERROR: " << fi.fileName() << ": could not load " << expfilename << ": " << expReader.errorString() << "\n";
|
QTextStream(stdout) << "ERROR: " << fi.fileName() << ": could not load " << expfilename << ": " << expReader.errorString() << "\n";
|
||||||
@ -199,11 +313,32 @@ int main(int argc, char **argv)
|
|||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OptionTest optionTest;
|
||||||
|
if (!optionTest.store(&inputReader)) {
|
||||||
|
QTextStream(stdout) << "FAIL : " << fi.fileName() << ": error while reading options\n";
|
||||||
|
++failed;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (!inputReader.read(&inputImage)) {
|
if (!inputReader.read(&inputImage)) {
|
||||||
QTextStream(stdout) << "FAIL : " << fi.fileName() << ": failed to load: " << inputReader.errorString() << "\n";
|
QTextStream(stdout) << "FAIL : " << fi.fileName() << ": failed to load: " << inputReader.errorString() << "\n";
|
||||||
++failed;
|
++failed;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!optionTest.compare(&inputReader)) {
|
||||||
|
QTextStream(stdout) << "FAIL : " << fi.fileName() << ": error while comparing options\n";
|
||||||
|
++failed;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!optionTest.compare(inputImage)) {
|
||||||
|
QTextStream(stdout) << "FAIL : " << fi.fileName() << ": error while comparing the image properties with options\n";
|
||||||
|
++failed;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (expImage.width() != inputImage.width()) {
|
if (expImage.width() != inputImage.width()) {
|
||||||
QTextStream(stdout) << "FAIL : " << fi.fileName() << ": width was " << inputImage.width() << " but " << expfilename << " width was "
|
QTextStream(stdout) << "FAIL : " << fi.fileName() << ": width was " << inputImage.width() << " but " << expfilename << " width was "
|
||||||
<< expImage.width() << "\n";
|
<< expImage.width() << "\n";
|
||||||
|
111
autotests/templateimage.cpp
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
/*
|
||||||
|
SPDX-FileCopyrightText: 2024 Mirco Miranda <mircomir@outlook.com>
|
||||||
|
|
||||||
|
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "templateimage.h"
|
||||||
|
|
||||||
|
#include <QFile>
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QVersionNumber>
|
||||||
|
|
||||||
|
TemplateImage::TemplateImage(const QFileInfo &fi) :
|
||||||
|
m_fi(fi)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TemplateImage::isTemplate() const
|
||||||
|
{
|
||||||
|
auto list = suffixes();
|
||||||
|
for (auto&& suffix : list) {
|
||||||
|
if (!m_fi.suffix().compare(suffix, Qt::CaseInsensitive))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QFileInfo TemplateImage::compareImage(TestFlags &flags, QString& comment) const
|
||||||
|
{
|
||||||
|
auto fi = jsonImage(flags, comment);
|
||||||
|
if ((flags & TestFlag::SkipTest) == TestFlag::SkipTest) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
if (fi.exists()) {
|
||||||
|
return fi;
|
||||||
|
}
|
||||||
|
return legacyImage();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
QStringList TemplateImage::suffixes()
|
||||||
|
{
|
||||||
|
return QStringList({"png", "tif", "tiff", "json"});
|
||||||
|
}
|
||||||
|
|
||||||
|
QFileInfo TemplateImage::legacyImage() const
|
||||||
|
{
|
||||||
|
auto list = suffixes();
|
||||||
|
for (auto&& suffix : list) {
|
||||||
|
auto fi = QFileInfo(QStringLiteral("%1/%2.%3").arg(m_fi.path(), m_fi.completeBaseName(), suffix));
|
||||||
|
if (fi.exists()) {
|
||||||
|
return fi;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
QFileInfo TemplateImage::jsonImage(TestFlags &flags, QString& comment) const
|
||||||
|
{
|
||||||
|
flags = TestFlag::None;
|
||||||
|
auto fi = QFileInfo(QStringLiteral("%1.json").arg(m_fi.filePath()));
|
||||||
|
if (!fi.exists()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
QFile f(fi.filePath());
|
||||||
|
if (!f.open(QFile::ReadOnly)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonParseError err;
|
||||||
|
auto doc = QJsonDocument::fromJson(f.readAll(), &err);
|
||||||
|
if (err.error != QJsonParseError::NoError || !doc.isArray()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto currentQt = QVersionNumber::fromString(qVersion());
|
||||||
|
auto arr = doc.array();
|
||||||
|
for (auto val : arr) {
|
||||||
|
if (!val.isObject())
|
||||||
|
continue;
|
||||||
|
auto obj = val.toObject();
|
||||||
|
auto minQt = QVersionNumber::fromString(obj.value("minQtVersion").toString());
|
||||||
|
auto maxQt = QVersionNumber::fromString(obj.value("maxQtVersion").toString());
|
||||||
|
auto name = obj.value("fileName").toString();
|
||||||
|
auto unsupportedFormat = obj.value("unsupportedFormat").toBool();
|
||||||
|
comment = obj.value("comment").toString();
|
||||||
|
|
||||||
|
if(obj.value("disableAutoTransform").toBool())
|
||||||
|
flags |= TestFlag::DisableAutotransform;
|
||||||
|
|
||||||
|
// filter
|
||||||
|
if (name.isEmpty() && !unsupportedFormat)
|
||||||
|
continue;
|
||||||
|
if (!minQt.isNull() && currentQt < minQt)
|
||||||
|
continue;
|
||||||
|
if (!maxQt.isNull() && currentQt > maxQt)
|
||||||
|
continue;
|
||||||
|
if (unsupportedFormat) {
|
||||||
|
flags |= TestFlag::SkipTest;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return QFileInfo(QStringLiteral("%1/%2").arg(fi.path(), name));
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
83
autotests/templateimage.h
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
SPDX-FileCopyrightText: 2024 Mirco Miranda <mircomir@outlook.com>
|
||||||
|
|
||||||
|
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef TEMPLATEIMAGE_H
|
||||||
|
#define TEMPLATEIMAGE_H
|
||||||
|
|
||||||
|
#include <QFileInfo>
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief The TemplateImage class
|
||||||
|
* Given an image name, it decides the template image to compare it with.
|
||||||
|
*/
|
||||||
|
class TemplateImage
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum TestFlag {
|
||||||
|
None = 0x0,
|
||||||
|
SkipTest = 0x1,
|
||||||
|
DisableAutotransform = 0x2
|
||||||
|
};
|
||||||
|
Q_DECLARE_FLAGS(TestFlags, TestFlag)
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief TemplateImage
|
||||||
|
* \param fi The image to test.
|
||||||
|
*/
|
||||||
|
TemplateImage(const QFileInfo& fi);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief TemplateImage
|
||||||
|
* Default copy constructor.
|
||||||
|
*/
|
||||||
|
TemplateImage(const TemplateImage& other) = default;
|
||||||
|
/*!
|
||||||
|
* \brief operator =
|
||||||
|
* Default copy operator
|
||||||
|
*/
|
||||||
|
TemplateImage& operator=(const TemplateImage& other) = default;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief isTemplate
|
||||||
|
* \return True if the image is a template, false otherwise.
|
||||||
|
* \sa suffixes
|
||||||
|
*/
|
||||||
|
bool isTemplate() const;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief compareImage
|
||||||
|
* \param flags Flags for modifying test behavior (e.g. image format not supported by current Qt version).
|
||||||
|
* \return The template image to use for the comparison.
|
||||||
|
*/
|
||||||
|
QFileInfo compareImage(TestFlags &flags, QString& comment) const;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief suffixes
|
||||||
|
* \return The list of suffixes considered templates.
|
||||||
|
*/
|
||||||
|
static QStringList suffixes();
|
||||||
|
|
||||||
|
private:
|
||||||
|
/*!
|
||||||
|
* \brief legacyImage
|
||||||
|
* \return The template image calculated from the source image name.
|
||||||
|
*/
|
||||||
|
QFileInfo legacyImage() const;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief jsonImage
|
||||||
|
* \param flags Flags for modifying test behavior.
|
||||||
|
* \return The template image read from the corresponding JSON.
|
||||||
|
*/
|
||||||
|
QFileInfo jsonImage(TestFlags &flags, QString& comment) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QFileInfo m_fi;
|
||||||
|
};
|
||||||
|
|
||||||
|
Q_DECLARE_OPERATORS_FOR_FLAGS(TemplateImage::TestFlags)
|
||||||
|
|
||||||
|
#endif // TEMPLATEIMAGE_H
|
24
cmake/find-modules/FindLibJXR.cmake
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# - Find LibJXR
|
||||||
|
# Find the JXR library
|
||||||
|
# This module defines
|
||||||
|
# LIBJXR_INCLUDE_DIRS, where to find jxrlib/JXRGlue.h
|
||||||
|
# LIBJXR_LIBRARIES, the libraries needed to use JXR
|
||||||
|
#
|
||||||
|
# Based on cmake code found at https://github.com/microsoft/vcpkg/blob/master/ports/jxrlib/FindJXR.cmake
|
||||||
|
|
||||||
|
find_path(LIBJXR_INCLUDE_DIRS
|
||||||
|
NAMES JXRGlue.h
|
||||||
|
PATH_SUFFIXES jxrlib
|
||||||
|
)
|
||||||
|
mark_as_advanced(LIBJXR_INCLUDE_DIRS)
|
||||||
|
|
||||||
|
include(SelectLibraryConfigurations)
|
||||||
|
|
||||||
|
find_library(LIBJPEGXR_LIBRARY NAMES jpegxr)
|
||||||
|
find_library(LIBJXRGLUE_LIBRARY NAMES jxrglue)
|
||||||
|
|
||||||
|
set(LIBJXR_LIBRARIES ${LIBJPEGXR_LIBRARY} ${LIBJXRGLUE_LIBRARY})
|
||||||
|
mark_as_advanced(LIBJXR_LIBRARIES)
|
||||||
|
|
||||||
|
include(FindPackageHandleStandardArgs)
|
||||||
|
FIND_PACKAGE_HANDLE_STANDARD_ARGS(LibJXR DEFAULT_MSG LIBJXR_INCLUDE_DIRS LIBJXR_LIBRARIES)
|
@ -83,7 +83,15 @@ kimageformats_add_plugin(kimg_pic SOURCES pic.cpp)
|
|||||||
|
|
||||||
##################################
|
##################################
|
||||||
|
|
||||||
kimageformats_add_plugin(kimg_psd SOURCES psd.cpp)
|
kimageformats_add_plugin(kimg_pfm SOURCES pfm.cpp)
|
||||||
|
|
||||||
|
##################################
|
||||||
|
|
||||||
|
kimageformats_add_plugin(kimg_psd SOURCES psd.cpp scanlineconverter.cpp)
|
||||||
|
|
||||||
|
##################################
|
||||||
|
|
||||||
|
kimageformats_add_plugin(kimg_pxr SOURCES pxr.cpp)
|
||||||
|
|
||||||
##################################
|
##################################
|
||||||
|
|
||||||
@ -115,6 +123,20 @@ endif()
|
|||||||
|
|
||||||
##################################
|
##################################
|
||||||
|
|
||||||
|
if (LibJXR_FOUND)
|
||||||
|
kimageformats_add_plugin(kimg_jxr SOURCES jxr.cpp)
|
||||||
|
kde_enable_exceptions()
|
||||||
|
target_include_directories(kimg_jxr PRIVATE ${LIBJXR_INCLUDE_DIRS})
|
||||||
|
target_link_libraries(kimg_jxr PRIVATE jpegxr jxrglue)
|
||||||
|
target_compile_definitions(kimg_jxr PRIVATE INITGUID)
|
||||||
|
if (NOT CMAKE_SYSTEM_NAME STREQUAL "Windows")
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-error=undef")
|
||||||
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-error=undef")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
##################################
|
||||||
|
|
||||||
if (KF6Archive_FOUND)
|
if (KF6Archive_FOUND)
|
||||||
|
|
||||||
kimageformats_add_plugin(kimg_kra SOURCES kra.cpp)
|
kimageformats_add_plugin(kimg_kra SOURCES kra.cpp)
|
||||||
|
@ -619,7 +619,15 @@ bool QAVIFHandler::write(const QImage &image)
|
|||||||
QImage tmpgrayimage = image.convertToFormat(tmpformat);
|
QImage tmpgrayimage = image.convertToFormat(tmpformat);
|
||||||
|
|
||||||
avif = avifImageCreate(tmpgrayimage.width(), tmpgrayimage.height(), save_depth, AVIF_PIXEL_FORMAT_YUV400);
|
avif = avifImageCreate(tmpgrayimage.width(), tmpgrayimage.height(), save_depth, AVIF_PIXEL_FORMAT_YUV400);
|
||||||
|
#if AVIF_VERSION >= 110000
|
||||||
|
res = avifImageAllocatePlanes(avif, AVIF_PLANES_YUV);
|
||||||
|
if (res != AVIF_RESULT_OK) {
|
||||||
|
qWarning("ERROR in avifImageAllocatePlanes: %s", avifResultToString(res));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#else
|
||||||
avifImageAllocatePlanes(avif, AVIF_PLANES_YUV);
|
avifImageAllocatePlanes(avif, AVIF_PLANES_YUV);
|
||||||
|
#endif
|
||||||
|
|
||||||
if (tmpgrayimage.colorSpace().isValid()) {
|
if (tmpgrayimage.colorSpace().isValid()) {
|
||||||
avif->colorPrimaries = (avifColorPrimaries)1;
|
avif->colorPrimaries = (avifColorPrimaries)1;
|
||||||
@ -806,7 +814,15 @@ bool QAVIFHandler::write(const QImage &image)
|
|||||||
avif->transferCharacteristics = transfer_to_save;
|
avif->transferCharacteristics = transfer_to_save;
|
||||||
|
|
||||||
if (iccprofile.size() > 0) {
|
if (iccprofile.size() > 0) {
|
||||||
|
#if AVIF_VERSION >= 1000000
|
||||||
|
res = avifImageSetProfileICC(avif, reinterpret_cast<const uint8_t *>(iccprofile.constData()), iccprofile.size());
|
||||||
|
if (res != AVIF_RESULT_OK) {
|
||||||
|
qWarning("ERROR in avifImageSetProfileICC: %s", avifResultToString(res));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#else
|
||||||
avifImageSetProfileICC(avif, reinterpret_cast<const uint8_t *>(iccprofile.constData()), iccprofile.size());
|
avifImageSetProfileICC(avif, reinterpret_cast<const uint8_t *>(iccprofile.constData()), iccprofile.size());
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
avifRGBImage rgb;
|
avifRGBImage rgb;
|
||||||
@ -971,6 +987,8 @@ bool QAVIFHandler::jumpToNextImage()
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
avifResult decodeResult;
|
||||||
|
|
||||||
if (m_decoder->imageIndex >= 0) {
|
if (m_decoder->imageIndex >= 0) {
|
||||||
if (m_decoder->imageCount < 2) {
|
if (m_decoder->imageCount < 2) {
|
||||||
m_parseState = ParseAvifSuccess;
|
m_parseState = ParseAvifSuccess;
|
||||||
@ -978,11 +996,16 @@ bool QAVIFHandler::jumpToNextImage()
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (m_decoder->imageIndex >= m_decoder->imageCount - 1) { // start from beginning
|
if (m_decoder->imageIndex >= m_decoder->imageCount - 1) { // start from beginning
|
||||||
avifDecoderReset(m_decoder);
|
decodeResult = avifDecoderReset(m_decoder);
|
||||||
|
if (decodeResult != AVIF_RESULT_OK) {
|
||||||
|
qWarning("ERROR in avifDecoderReset: %s", avifResultToString(decodeResult));
|
||||||
|
m_parseState = ParseAvifError;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
avifResult decodeResult = avifDecoderNextImage(m_decoder);
|
decodeResult = avifDecoderNextImage(m_decoder);
|
||||||
|
|
||||||
if (decodeResult != AVIF_RESULT_OK) {
|
if (decodeResult != AVIF_RESULT_OK) {
|
||||||
qWarning("ERROR: Failed to decode Next image in sequence: %s", avifResultToString(decodeResult));
|
qWarning("ERROR: Failed to decode Next image in sequence: %s", avifResultToString(decodeResult));
|
||||||
|
@ -7,20 +7,11 @@
|
|||||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
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_CONVERT_TO_SRGB ***
|
/* *** EXR_CONVERT_TO_SRGB ***
|
||||||
* If defined, the linear data is converted to sRGB on read to accommodate
|
* If defined, the linear data is converted to sRGB on read to accommodate
|
||||||
* programs that do not support color profiles.
|
* programs that do not support color profiles.
|
||||||
* Otherwise the data are kept as is and it is the display program that
|
* Otherwise the data are kept as is and it is the display program that
|
||||||
* must convert to the monitor profile.
|
* must convert to the monitor profile.
|
||||||
* NOTE: If EXR_USE_LEGACY_CONVERSIONS is active, this is ignored.
|
|
||||||
*/
|
*/
|
||||||
//#define EXR_CONVERT_TO_SRGB // default: commented -> you should define it in your cmake file
|
//#define EXR_CONVERT_TO_SRGB // default: commented -> you should define it in your cmake file
|
||||||
|
|
||||||
@ -92,13 +83,6 @@
|
|||||||
#include <QThread>
|
#include <QThread>
|
||||||
#include <QTimeZone>
|
#include <QTimeZone>
|
||||||
|
|
||||||
// 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 !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
|
class K_IStream : public Imf::IStream
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -209,22 +193,6 @@ void K_OStream::seekg(Imf::Int64 pos)
|
|||||||
m_dev->seek(pos);
|
m_dev->seek(pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef EXR_USE_LEGACY_CONVERSIONS
|
|
||||||
// source: https://openexr.com/en/latest/ReadingAndWritingImageFiles.html
|
|
||||||
inline unsigned char gamma(float x)
|
|
||||||
{
|
|
||||||
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()
|
EXRHandler::EXRHandler()
|
||||||
: m_compressionRatio(-1)
|
: m_compressionRatio(-1)
|
||||||
, m_quality(-1)
|
, m_quality(-1)
|
||||||
@ -248,13 +216,7 @@ bool EXRHandler::canRead() const
|
|||||||
static QImage::Format imageFormat(const Imf::RgbaInputFile &file)
|
static QImage::Format imageFormat(const Imf::RgbaInputFile &file)
|
||||||
{
|
{
|
||||||
auto isRgba = file.channels() & Imf::RgbaChannels::WRITE_A;
|
auto isRgba = file.channels() & Imf::RgbaChannels::WRITE_A;
|
||||||
#if defined(EXR_USE_LEGACY_CONVERSIONS)
|
|
||||||
return (isRgba ? QImage::Format_ARGB32 : QImage::Format_RGB32);
|
|
||||||
#elif defined(EXR_USE_QT6_FLOAT_IMAGE)
|
|
||||||
return (isRgba ? QImage::Format_RGBA16FPx4 : QImage::Format_RGBX16FPx4);
|
return (isRgba ? QImage::Format_RGBA16FPx4 : QImage::Format_RGBX16FPx4);
|
||||||
#else
|
|
||||||
return (isRgba ? QImage::Format_RGBA64 : QImage::Format_RGBX64);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@ -290,23 +252,23 @@ static void readMetadata(const Imf::Header &header, QImage &image)
|
|||||||
{
|
{
|
||||||
// set some useful metadata
|
// set some useful metadata
|
||||||
if (auto comments = header.findTypedAttribute<Imf::StringAttribute>("comments")) {
|
if (auto comments = header.findTypedAttribute<Imf::StringAttribute>("comments")) {
|
||||||
image.setText(QStringLiteral("Comment"), QString::fromStdString(comments->value()));
|
image.setText(QStringLiteral(META_KEY_COMMENT), QString::fromStdString(comments->value()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auto owner = header.findTypedAttribute<Imf::StringAttribute>("owner")) {
|
if (auto owner = header.findTypedAttribute<Imf::StringAttribute>("owner")) {
|
||||||
image.setText(QStringLiteral("Owner"), QString::fromStdString(owner->value()));
|
image.setText(QStringLiteral(META_KEY_OWNER), QString::fromStdString(owner->value()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auto lat = header.findTypedAttribute<Imf::FloatAttribute>("latitude")) {
|
if (auto lat = header.findTypedAttribute<Imf::FloatAttribute>("latitude")) {
|
||||||
image.setText(QStringLiteral("Latitude"), QLocale::c().toString(lat->value()));
|
image.setText(QStringLiteral(META_KEY_LATITUDE), QLocale::c().toString(lat->value()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auto lon = header.findTypedAttribute<Imf::FloatAttribute>("longitude")) {
|
if (auto lon = header.findTypedAttribute<Imf::FloatAttribute>("longitude")) {
|
||||||
image.setText(QStringLiteral("Longitude"), QLocale::c().toString(lon->value()));
|
image.setText(QStringLiteral(META_KEY_LONGITUDE), QLocale::c().toString(lon->value()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auto alt = header.findTypedAttribute<Imf::FloatAttribute>("altitude")) {
|
if (auto alt = header.findTypedAttribute<Imf::FloatAttribute>("altitude")) {
|
||||||
image.setText(QStringLiteral("Altitude"), QLocale::c().toString(alt->value()));
|
image.setText(QStringLiteral(META_KEY_ALTITUDE), QLocale::c().toString(alt->value()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auto capDate = header.findTypedAttribute<Imf::StringAttribute>("capDate")) {
|
if (auto capDate = header.findTypedAttribute<Imf::StringAttribute>("capDate")) {
|
||||||
@ -317,7 +279,7 @@ static void readMetadata(const Imf::Header &header, QImage &image)
|
|||||||
auto dateTime = QDateTime::fromString(QString::fromStdString(capDate->value()), QStringLiteral("yyyy:MM:dd HH:mm:ss"));
|
auto dateTime = QDateTime::fromString(QString::fromStdString(capDate->value()), QStringLiteral("yyyy:MM:dd HH:mm:ss"));
|
||||||
if (dateTime.isValid()) {
|
if (dateTime.isValid()) {
|
||||||
dateTime.setTimeZone(QTimeZone::fromSecondsAheadOfUtc(off));
|
dateTime.setTimeZone(QTimeZone::fromSecondsAheadOfUtc(off));
|
||||||
image.setText(QStringLiteral("CreationDate"), dateTime.toString(Qt::ISODate));
|
image.setText(QStringLiteral(META_KEY_CREATIONDATE), dateTime.toString(Qt::ISODate));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -332,26 +294,30 @@ static void readMetadata(const Imf::Header &header, QImage &image)
|
|||||||
|
|
||||||
// Non-standard attribute
|
// Non-standard attribute
|
||||||
if (auto xmp = header.findTypedAttribute<Imf::StringAttribute>("xmp")) {
|
if (auto xmp = header.findTypedAttribute<Imf::StringAttribute>("xmp")) {
|
||||||
image.setText(QStringLiteral("XML:com.adobe.xmp"), QString::fromStdString(xmp->value()));
|
image.setText(QStringLiteral(META_KEY_XMP_ADOBE), QString::fromStdString(xmp->value()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TODO: OpenEXR 3.2 metadata
|
// camera metadata
|
||||||
*
|
if (auto manufacturer = header.findTypedAttribute<Imf::StringAttribute>("cameraMake")) {
|
||||||
* New Optional Standard Attributes:
|
image.setText(QStringLiteral(META_KEY_MANUFACTURER), QString::fromStdString(manufacturer->value()));
|
||||||
* - Support automated editorial workflow:
|
}
|
||||||
* reelName, imageCounter, ascFramingDecisionList
|
if (auto model = header.findTypedAttribute<Imf::StringAttribute>("cameraModel")) {
|
||||||
*
|
image.setText(QStringLiteral(META_KEY_MODEL), QString::fromStdString(model->value()));
|
||||||
* - Support forensics (“which other shots used that camera and lens before the camera firmware was updated?”):
|
}
|
||||||
* cameraMake, cameraModel, cameraSerialNumber, cameraFirmware, cameraUuid, cameraLabel, lensMake, lensModel,
|
if (auto serial = header.findTypedAttribute<Imf::StringAttribute>("cameraSerialNumber")) {
|
||||||
* lensSerialNumber, lensFirmware, cameraColorBalance
|
image.setText(QStringLiteral(META_KEY_SERIALNUMBER), QString::fromStdString(serial->value()));
|
||||||
*
|
}
|
||||||
* -Support pickup shots (reproduce critical camera settings):
|
|
||||||
* shutterAngle, cameraCCTSetting, cameraTintSetting
|
// lens metadata
|
||||||
*
|
if (auto manufacturer = header.findTypedAttribute<Imf::StringAttribute>("lensMake")) {
|
||||||
* - Support metadata-driven match move:
|
image.setText(QStringLiteral(META_KEY_LENS_MANUFACTURER), QString::fromStdString(manufacturer->value()));
|
||||||
* sensorCenterOffset, sensorOverallDimensions, sensorPhotositePitch, sensorAcquisitionRectanglenominalFocalLength,
|
}
|
||||||
* effectiveFocalLength, pinholeFocalLength, entrancePupilOffset, tStop(complementing existing 'aperture')
|
if (auto model = header.findTypedAttribute<Imf::StringAttribute>("lensModel")) {
|
||||||
*/
|
image.setText(QStringLiteral(META_KEY_LENS_MODEL), QString::fromStdString(model->value()));
|
||||||
|
}
|
||||||
|
if (auto serial = header.findTypedAttribute<Imf::StringAttribute>("lensSerialNumber")) {
|
||||||
|
image.setText(QStringLiteral(META_KEY_LENS_SERIALNUMBER), QString::fromStdString(serial->value()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@ -361,8 +327,6 @@ static void readMetadata(const Imf::Header &header, QImage &image)
|
|||||||
static void readColorSpace(const Imf::Header &header, QImage &image)
|
static void readColorSpace(const Imf::Header &header, QImage &image)
|
||||||
{
|
{
|
||||||
// final color operations
|
// final color operations
|
||||||
#ifndef EXR_USE_LEGACY_CONVERSIONS
|
|
||||||
|
|
||||||
QColorSpace cs;
|
QColorSpace cs;
|
||||||
if (auto chroma = header.findTypedAttribute<Imf::ChromaticitiesAttribute>("chromaticities")) {
|
if (auto chroma = header.findTypedAttribute<Imf::ChromaticitiesAttribute>("chromaticities")) {
|
||||||
auto &&v = chroma->value();
|
auto &&v = chroma->value();
|
||||||
@ -380,8 +344,6 @@ static void readColorSpace(const Imf::Header &header, QImage &image)
|
|||||||
#ifdef EXR_CONVERT_TO_SRGB
|
#ifdef EXR_CONVERT_TO_SRGB
|
||||||
image.convertToColorSpace(QColorSpace(QColorSpace::SRgb));
|
image.convertToColorSpace(QColorSpace(QColorSpace::SRgb));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#endif // !EXR_USE_LEGACY_CONVERSIONS
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EXRHandler::read(QImage *outImage)
|
bool EXRHandler::read(QImage *outImage)
|
||||||
@ -432,7 +394,6 @@ bool EXRHandler::read(QImage *outImage)
|
|||||||
pixels.resizeErase(EXR_LINES_PER_BLOCK, width);
|
pixels.resizeErase(EXR_LINES_PER_BLOCK, width);
|
||||||
bool isRgba = image.hasAlphaChannel();
|
bool isRgba = image.hasAlphaChannel();
|
||||||
|
|
||||||
// somehow copy pixels into image
|
|
||||||
for (int y = 0, n = 0; y < height; y += n) {
|
for (int y = 0, n = 0; y < height; y += n) {
|
||||||
auto my = dw.min.y + y;
|
auto my = dw.min.y + y;
|
||||||
if (my > dw.max.y) { // paranoia check
|
if (my > dw.max.y) { // paranoia check
|
||||||
@ -443,30 +404,14 @@ bool EXRHandler::read(QImage *outImage)
|
|||||||
file.readPixels(my, std::min(my + EXR_LINES_PER_BLOCK - 1, dw.max.y));
|
file.readPixels(my, std::min(my + EXR_LINES_PER_BLOCK - 1, dw.max.y));
|
||||||
|
|
||||||
for (n = 0; n < std::min(EXR_LINES_PER_BLOCK, height - y); ++n) {
|
for (n = 0; n < std::min(EXR_LINES_PER_BLOCK, height - y); ++n) {
|
||||||
#if defined(EXR_USE_LEGACY_CONVERSIONS)
|
|
||||||
Q_UNUSED(isRgba)
|
|
||||||
auto scanLine = reinterpret_cast<QRgb *>(image.scanLine(y + n));
|
|
||||||
for (int x = 0; x < width; ++x) {
|
|
||||||
*(scanLine + x) = RgbaToQrgba(pixels[n][x]);
|
|
||||||
}
|
|
||||||
#elif defined(EXR_USE_QT6_FLOAT_IMAGE)
|
|
||||||
auto scanLine = reinterpret_cast<qfloat16 *>(image.scanLine(y + n));
|
auto scanLine = reinterpret_cast<qfloat16 *>(image.scanLine(y + n));
|
||||||
for (int x = 0; x < width; ++x) {
|
for (int x = 0; x < width; ++x) {
|
||||||
auto xcs = x * 4;
|
auto xcs = x * 4;
|
||||||
*(scanLine + xcs) = qfloat16(qBound(0.f, float(pixels[n][x].r), 1.f));
|
*(scanLine + xcs) = qfloat16(float(pixels[n][x].r));
|
||||||
*(scanLine + xcs + 1) = qfloat16(qBound(0.f, float(pixels[n][x].g), 1.f));
|
*(scanLine + xcs + 1) = qfloat16(float(pixels[n][x].g));
|
||||||
*(scanLine + xcs + 2) = qfloat16(qBound(0.f, float(pixels[n][x].b), 1.f));
|
*(scanLine + xcs + 2) = qfloat16(float(pixels[n][x].b));
|
||||||
*(scanLine + xcs + 3) = qfloat16(isRgba ? qBound(0.f, float(pixels[n][x].a), 1.f) : 1.f);
|
*(scanLine + xcs + 3) = qfloat16(isRgba ? std::clamp(float(pixels[n][x].a), 0.f, 1.f) : 1.f);
|
||||||
}
|
}
|
||||||
#else
|
|
||||||
auto scanLine = reinterpret_cast<QRgba64 *>(image.scanLine(y + n));
|
|
||||||
for (int x = 0; x < width; ++x) {
|
|
||||||
*(scanLine + x) = QRgba64::fromRgba64(quint16(qBound(0.f, float(pixels[n][x].r) * 65535.f + 0.5f, 65535.f)),
|
|
||||||
quint16(qBound(0.f, float(pixels[n][x].g) * 65535.f + 0.5f, 65535.f)),
|
|
||||||
quint16(qBound(0.f, float(pixels[n][x].b) * 65535.f + 0.5f, 65535.f)),
|
|
||||||
isRgba ? quint16(qBound(0.f, float(pixels[n][x].a) * 65535.f + 0.5f, 65535.f)) : quint16(65535));
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -530,18 +475,18 @@ static void setMetadata(const QImage &image, Imf::Header &header)
|
|||||||
auto dateTime = QDateTime::currentDateTime();
|
auto dateTime = QDateTime::currentDateTime();
|
||||||
for (auto &&key : image.textKeys()) {
|
for (auto &&key : image.textKeys()) {
|
||||||
auto text = image.text(key);
|
auto text = image.text(key);
|
||||||
if (!key.compare(QStringLiteral("Comment"), Qt::CaseInsensitive)) {
|
if (!key.compare(QStringLiteral(META_KEY_COMMENT), Qt::CaseInsensitive)) {
|
||||||
header.insert("comments", Imf::StringAttribute(text.toStdString()));
|
header.insert("comments", Imf::StringAttribute(text.toStdString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!key.compare(QStringLiteral("Owner"), Qt::CaseInsensitive)) {
|
if (!key.compare(QStringLiteral(META_KEY_OWNER), Qt::CaseInsensitive)) {
|
||||||
header.insert("owner", Imf::StringAttribute(text.toStdString()));
|
header.insert("owner", Imf::StringAttribute(text.toStdString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// clang-format off
|
// clang-format off
|
||||||
if (!key.compare(QStringLiteral("Latitude"), Qt::CaseInsensitive) ||
|
if (!key.compare(QStringLiteral(META_KEY_LATITUDE), Qt::CaseInsensitive) ||
|
||||||
!key.compare(QStringLiteral("Longitude"), Qt::CaseInsensitive) ||
|
!key.compare(QStringLiteral(META_KEY_LONGITUDE), Qt::CaseInsensitive) ||
|
||||||
!key.compare(QStringLiteral("Altitude"), Qt::CaseInsensitive)) {
|
!key.compare(QStringLiteral(META_KEY_ALTITUDE), Qt::CaseInsensitive)) {
|
||||||
// clang-format on
|
// clang-format on
|
||||||
auto ok = false;
|
auto ok = false;
|
||||||
auto value = QLocale::c().toFloat(text, &ok);
|
auto value = QLocale::c().toFloat(text, &ok);
|
||||||
@ -550,7 +495,7 @@ static void setMetadata(const QImage &image, Imf::Header &header)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!key.compare(QStringLiteral("CreationDate"), Qt::CaseInsensitive)) {
|
if (!key.compare(QStringLiteral(META_KEY_CREATIONDATE), Qt::CaseInsensitive)) {
|
||||||
auto dt = QDateTime::fromString(text, Qt::ISODate);
|
auto dt = QDateTime::fromString(text, Qt::ISODate);
|
||||||
if (dt.isValid()) {
|
if (dt.isValid()) {
|
||||||
dateTime = dt;
|
dateTime = dt;
|
||||||
@ -558,10 +503,30 @@ static void setMetadata(const QImage &image, Imf::Header &header)
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifndef EXR_DISABLE_XMP_ATTRIBUTE // warning: Non-standard attribute!
|
#ifndef EXR_DISABLE_XMP_ATTRIBUTE // warning: Non-standard attribute!
|
||||||
if (!key.compare(QStringLiteral("XML:com.adobe.xmp"), Qt::CaseInsensitive)) {
|
if (!key.compare(QStringLiteral(META_KEY_XMP_ADOBE), Qt::CaseInsensitive)) {
|
||||||
header.insert("xmp", Imf::StringAttribute(text.toStdString()));
|
header.insert("xmp", Imf::StringAttribute(text.toStdString()));
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
if (!key.compare(QStringLiteral(META_KEY_MANUFACTURER), Qt::CaseInsensitive)) {
|
||||||
|
header.insert("cameraMake", Imf::StringAttribute(text.toStdString()));
|
||||||
|
}
|
||||||
|
if (!key.compare(QStringLiteral(META_KEY_MODEL), Qt::CaseInsensitive)) {
|
||||||
|
header.insert("cameraModel", Imf::StringAttribute(text.toStdString()));
|
||||||
|
}
|
||||||
|
if (!key.compare(QStringLiteral(META_KEY_SERIALNUMBER), Qt::CaseInsensitive)) {
|
||||||
|
header.insert("cameraSerialNumber", Imf::StringAttribute(text.toStdString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!key.compare(QStringLiteral(META_KEY_LENS_MANUFACTURER), Qt::CaseInsensitive)) {
|
||||||
|
header.insert("lensMake", Imf::StringAttribute(text.toStdString()));
|
||||||
|
}
|
||||||
|
if (!key.compare(QStringLiteral(META_KEY_LENS_MODEL), Qt::CaseInsensitive)) {
|
||||||
|
header.insert("lensModel", Imf::StringAttribute(text.toStdString()));
|
||||||
|
}
|
||||||
|
if (!key.compare(QStringLiteral(META_KEY_LENS_SERIALNUMBER), Qt::CaseInsensitive)) {
|
||||||
|
header.insert("lensSerialNumber", Imf::StringAttribute(text.toStdString()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (dateTime.isValid()) {
|
if (dateTime.isValid()) {
|
||||||
header.insert("capDate", Imf::StringAttribute(dateTime.toString(QStringLiteral("yyyy:MM:dd HH:mm:ss")).toStdString()));
|
header.insert("capDate", Imf::StringAttribute(dateTime.toString(QStringLiteral("yyyy:MM:dd HH:mm:ss")).toStdString()));
|
||||||
@ -578,8 +543,6 @@ static void setMetadata(const QImage &image, Imf::Header &header)
|
|||||||
// If a file doesn’t have a chromaticities attribute, display software should assume that the
|
// If a file doesn’t have a chromaticities attribute, display software should assume that the
|
||||||
// file’s primaries and the white point match Rec. ITU-R BT.709-3.
|
// file’s primaries and the white point match Rec. ITU-R BT.709-3.
|
||||||
// header.insert("chromaticities", Imf::ChromaticitiesAttribute(Imf::Chromaticities()));
|
// header.insert("chromaticities", Imf::ChromaticitiesAttribute(Imf::Chromaticities()));
|
||||||
|
|
||||||
// TODO: EXR 3.2 attributes (see readMetadata())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EXRHandler::write(const QImage &image)
|
bool EXRHandler::write(const QImage &image)
|
||||||
@ -633,17 +596,12 @@ bool EXRHandler::write(const QImage &image)
|
|||||||
pixels.resizeErase(EXR_LINES_PER_BLOCK, width);
|
pixels.resizeErase(EXR_LINES_PER_BLOCK, width);
|
||||||
|
|
||||||
// convert the image and write into the stream
|
// convert the image and write into the stream
|
||||||
#if defined(EXR_USE_QT6_FLOAT_IMAGE)
|
|
||||||
auto convFormat = image.hasAlphaChannel() ? QImage::Format_RGBA16FPx4 : QImage::Format_RGBX16FPx4;
|
auto convFormat = image.hasAlphaChannel() ? QImage::Format_RGBA16FPx4 : QImage::Format_RGBX16FPx4;
|
||||||
#else
|
|
||||||
auto convFormat = image.hasAlphaChannel() ? QImage::Format_RGBA64 : QImage::Format_RGBX64;
|
|
||||||
#endif
|
|
||||||
ScanLineConverter slc(convFormat);
|
ScanLineConverter slc(convFormat);
|
||||||
slc.setDefaultSourceColorSpace(QColorSpace(QColorSpace::SRgb));
|
slc.setDefaultSourceColorSpace(QColorSpace(QColorSpace::SRgb));
|
||||||
slc.setTargetColorSpace(QColorSpace(QColorSpace::SRgbLinear));
|
slc.setTargetColorSpace(QColorSpace(QColorSpace::SRgbLinear));
|
||||||
for (int y = 0, n = 0; y < height; y += n) {
|
for (int y = 0, n = 0; y < height; y += n) {
|
||||||
for (n = 0; n < std::min(EXR_LINES_PER_BLOCK, height - y); ++n) {
|
for (n = 0; n < std::min(EXR_LINES_PER_BLOCK, height - y); ++n) {
|
||||||
#if defined(EXR_USE_QT6_FLOAT_IMAGE)
|
|
||||||
auto scanLine = reinterpret_cast<const qfloat16 *>(slc.convertedScanLine(image, y + n));
|
auto scanLine = reinterpret_cast<const qfloat16 *>(slc.convertedScanLine(image, y + n));
|
||||||
if (scanLine == nullptr) {
|
if (scanLine == nullptr) {
|
||||||
return false;
|
return false;
|
||||||
@ -655,18 +613,6 @@ bool EXRHandler::write(const QImage &image)
|
|||||||
pixels[n][x].b = float(*(scanLine + xcs + 2));
|
pixels[n][x].b = float(*(scanLine + xcs + 2));
|
||||||
pixels[n][x].a = float(*(scanLine + xcs + 3));
|
pixels[n][x].a = float(*(scanLine + xcs + 3));
|
||||||
}
|
}
|
||||||
#else
|
|
||||||
auto scanLine = reinterpret_cast<const QRgba64 *>(slc.convertedScanLine(image, y + n));
|
|
||||||
if (scanLine == nullptr) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (int x = 0; x < width; ++x) {
|
|
||||||
pixels[n][x].r = float((scanLine + x)->red() / 65535.f);
|
|
||||||
pixels[n][x].g = float((scanLine + x)->green() / 65535.f);
|
|
||||||
pixels[n][x].b = float((scanLine + x)->blue() / 65535.f);
|
|
||||||
pixels[n][x].a = float((scanLine + x)->alpha() / 65535.f);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
file.setFrameBuffer(&pixels[0][0] - qint64(y) * width, 1, width);
|
file.setFrameBuffer(&pixels[0][0] - qint64(y) * width, 1, width);
|
||||||
file.writePixels(n);
|
file.writePixels(n);
|
||||||
@ -699,10 +645,12 @@ void EXRHandler::setOption(ImageOption option, const QVariant &value)
|
|||||||
bool EXRHandler::supportsOption(ImageOption option) const
|
bool EXRHandler::supportsOption(ImageOption option) const
|
||||||
{
|
{
|
||||||
if (option == QImageIOHandler::Size) {
|
if (option == QImageIOHandler::Size) {
|
||||||
return true;
|
if (auto d = device())
|
||||||
|
return !d->isSequential();
|
||||||
}
|
}
|
||||||
if (option == QImageIOHandler::ImageFormat) {
|
if (option == QImageIOHandler::ImageFormat) {
|
||||||
return true;
|
if (auto d = device())
|
||||||
|
return !d->isSequential();
|
||||||
}
|
}
|
||||||
if (option == QImageIOHandler::CompressionRatio) {
|
if (option == QImageIOHandler::CompressionRatio) {
|
||||||
return true;
|
return true;
|
||||||
@ -721,6 +669,9 @@ QVariant EXRHandler::option(ImageOption option) const
|
|||||||
if (auto d = device()) {
|
if (auto d = device()) {
|
||||||
// transactions works on both random and sequential devices
|
// transactions works on both random and sequential devices
|
||||||
d->startTransaction();
|
d->startTransaction();
|
||||||
|
if (m_startPos > -1) {
|
||||||
|
d->seek(m_startPos);
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
K_IStream istr(d, QByteArray());
|
K_IStream istr(d, QByteArray());
|
||||||
Imf::RgbaInputFile file(istr);
|
Imf::RgbaInputFile file(istr);
|
||||||
@ -743,6 +694,9 @@ QVariant EXRHandler::option(ImageOption option) const
|
|||||||
if (auto d = device()) {
|
if (auto d = device()) {
|
||||||
// transactions works on both random and sequential devices
|
// transactions works on both random and sequential devices
|
||||||
d->startTransaction();
|
d->startTransaction();
|
||||||
|
if (m_startPos > -1) {
|
||||||
|
d->seek(m_startPos);
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
K_IStream istr(d, QByteArray());
|
K_IStream istr(d, QByteArray());
|
||||||
Imf::RgbaInputFile file(istr);
|
Imf::RgbaInputFile file(istr);
|
||||||
|
@ -27,12 +27,194 @@ typedef unsigned char uchar;
|
|||||||
|
|
||||||
Q_LOGGING_CATEGORY(HDRPLUGIN, "kf.imageformats.plugins.hdr", QtWarningMsg)
|
Q_LOGGING_CATEGORY(HDRPLUGIN, "kf.imageformats.plugins.hdr", QtWarningMsg)
|
||||||
|
|
||||||
namespace // Private.
|
|
||||||
{
|
|
||||||
#define MAXLINE 1024
|
#define MAXLINE 1024
|
||||||
#define MINELEN 8 // minimum scanline length for encoding
|
#define MINELEN 8 // minimum scanline length for encoding
|
||||||
#define MAXELEN 0x7fff // maximum scanline length for encoding
|
#define MAXELEN 0x7fff // maximum scanline length for encoding
|
||||||
|
|
||||||
|
class Header
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Header()
|
||||||
|
{
|
||||||
|
m_colorSpace = QColorSpace(QColorSpace::SRgbLinear);
|
||||||
|
m_transformation = QImageIOHandler::TransformationNone;
|
||||||
|
}
|
||||||
|
Header(const Header&) = default;
|
||||||
|
Header& operator=(const Header&) = default;
|
||||||
|
|
||||||
|
bool isValid() const { return width() > 0 && height() > 0; }
|
||||||
|
qint32 width() const { return(m_size.width()); }
|
||||||
|
qint32 height() const { return(m_size.height()); }
|
||||||
|
QString software() const { return(m_software); }
|
||||||
|
QImageIOHandler::Transformations transformation() const { return(m_transformation); }
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief colorSpace
|
||||||
|
*
|
||||||
|
* The color space for the image.
|
||||||
|
*
|
||||||
|
* The CIE (x,y) chromaticity coordinates of the three (RGB)
|
||||||
|
* primaries and the white point used to standardize the picture's
|
||||||
|
* color system. This is used mainly by the ra_xyze program to
|
||||||
|
* convert between color systems. If no PRIMARIES line
|
||||||
|
* appears, we assume the standard primaries defined in
|
||||||
|
* src/common/color.h, namely "0.640 0.330 0.290
|
||||||
|
* 0.600 0.150 0.060 0.333 0.333" for red, green, blue
|
||||||
|
* and white, respectively.
|
||||||
|
*/
|
||||||
|
QColorSpace colorSpace() const { return(m_colorSpace); }
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief exposure
|
||||||
|
*
|
||||||
|
* A single floating point number indicating a multiplier that has
|
||||||
|
* been applied to all the pixels in the file. EXPOSURE values are
|
||||||
|
* cumulative, so the original pixel values (i.e., radiances in
|
||||||
|
* watts/steradian/m^2) must be derived by taking the values in the
|
||||||
|
* file and dividing by all the EXPOSURE settings multiplied
|
||||||
|
* together. No EXPOSURE setting implies that no exposure
|
||||||
|
* changes have taken place.
|
||||||
|
*/
|
||||||
|
float exposure() const {
|
||||||
|
float mul = 1;
|
||||||
|
for (auto&& v : m_exposure)
|
||||||
|
mul *= v;
|
||||||
|
return mul;
|
||||||
|
}
|
||||||
|
|
||||||
|
QImageIOHandler::Transformations m_transformation;
|
||||||
|
QColorSpace m_colorSpace;
|
||||||
|
QString m_software;
|
||||||
|
QSize m_size;
|
||||||
|
QList<float> m_exposure;
|
||||||
|
};
|
||||||
|
|
||||||
|
class HDRHandlerPrivate
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
HDRHandlerPrivate()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
~HDRHandlerPrivate()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
const Header& header(QIODevice *device)
|
||||||
|
{
|
||||||
|
auto&& h = m_header;
|
||||||
|
if (h.isValid()) {
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
h = readHeader(device);
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Header readHeader(QIODevice *device)
|
||||||
|
{
|
||||||
|
Header h;
|
||||||
|
|
||||||
|
int len;
|
||||||
|
QByteArray line(MAXLINE + 1, Qt::Uninitialized);
|
||||||
|
QByteArray format;
|
||||||
|
|
||||||
|
// Parse header
|
||||||
|
do {
|
||||||
|
len = device->readLine(line.data(), MAXLINE);
|
||||||
|
|
||||||
|
if (line.startsWith("FORMAT=")) {
|
||||||
|
format = line.mid(7, len - 7).trimmed();
|
||||||
|
}
|
||||||
|
if (line.startsWith("SOFTWARE=")) {
|
||||||
|
h.m_software = QString::fromUtf8(line.mid(9, len - 9)).trimmed();
|
||||||
|
}
|
||||||
|
if (line.startsWith("EXPOSURE=")) {
|
||||||
|
auto ok = false;
|
||||||
|
auto ex = QLocale::c().toFloat(QString::fromLatin1(line.mid(9, len - 9)).trimmed(), &ok);
|
||||||
|
if (ok)
|
||||||
|
h.m_exposure << ex;
|
||||||
|
}
|
||||||
|
if (line.startsWith("PRIMARIES=")) {
|
||||||
|
auto list = line.mid(10, len - 10).trimmed().split(' ');
|
||||||
|
QList<double> primaries;
|
||||||
|
for (auto&& v : list) {
|
||||||
|
auto ok = false;
|
||||||
|
auto d = QLocale::c().toDouble(QString::fromLatin1(v), &ok);
|
||||||
|
if (ok)
|
||||||
|
primaries << d;
|
||||||
|
}
|
||||||
|
if (primaries.size() == 8) {
|
||||||
|
auto cs = QColorSpace(QPointF(primaries.at(6), primaries.at(7)),
|
||||||
|
QPointF(primaries.at(0), primaries.at(1)),
|
||||||
|
QPointF(primaries.at(2), primaries.at(3)),
|
||||||
|
QPointF(primaries.at(4), primaries.at(5)),
|
||||||
|
QColorSpace::TransferFunction::Linear);
|
||||||
|
cs.setDescription(QStringLiteral("Embedded RGB"));
|
||||||
|
if (cs.isValid())
|
||||||
|
h.m_colorSpace = cs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} while ((len > 0) && (line[0] != '\n'));
|
||||||
|
|
||||||
|
if (format != "32-bit_rle_rgbe") {
|
||||||
|
qCDebug(HDRPLUGIN) << "Unknown HDR format:" << format;
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
len = device->readLine(line.data(), MAXLINE);
|
||||||
|
line.resize(len);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Handle flipping and rotation, as per the spec below.
|
||||||
|
* The single resolution line consists of 4 values, a X and Y label each followed by a numerical
|
||||||
|
* integer value. The X and Y are immediately preceded by a sign which can be used to indicate
|
||||||
|
* flipping, the order of the X and Y indicate rotation. The standard coordinate system for
|
||||||
|
* Radiance images would have the following resolution string -Y N +X N. This indicates that the
|
||||||
|
* vertical axis runs down the file and the X axis is to the right (imagining the image as a
|
||||||
|
* rectangular block of data). A -X would indicate a horizontal flip of the image. A +Y would
|
||||||
|
* indicate a vertical flip. If the X value appears before the Y value then that indicates that
|
||||||
|
* the image is stored in column order rather than row order, that is, it is rotated by 90 degrees.
|
||||||
|
* The reader can convince themselves that the 8 combinations cover all the possible image orientations
|
||||||
|
* and rotations.
|
||||||
|
*/
|
||||||
|
QRegularExpression resolutionRegExp(QStringLiteral("([+\\-][XY])\\s+([0-9]+)\\s+([+\\-][XY])\\s+([0-9]+)\n"));
|
||||||
|
QRegularExpressionMatch match = resolutionRegExp.match(QString::fromLatin1(line));
|
||||||
|
if (!match.hasMatch()) {
|
||||||
|
qCDebug(HDRPLUGIN) << "Invalid HDR file, the first line after the header didn't have the expected format:" << line;
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto c0 = match.captured(1);
|
||||||
|
auto c1 = match.captured(3);
|
||||||
|
if (c0.at(1) == u'Y') {
|
||||||
|
if (c0.at(0) == u'-' && c1.at(0) == u'+')
|
||||||
|
h.m_transformation = QImageIOHandler::TransformationNone;
|
||||||
|
if (c0.at(0) == u'-' && c1.at(0) == u'-')
|
||||||
|
h.m_transformation = QImageIOHandler::TransformationMirror;
|
||||||
|
if (c0.at(0) == u'+' && c1.at(0) == u'+')
|
||||||
|
h.m_transformation = QImageIOHandler::TransformationFlip;
|
||||||
|
if (c0.at(0) == u'+' && c1.at(0) == u'-')
|
||||||
|
h.m_transformation = QImageIOHandler::TransformationRotate180;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (c0.at(0) == u'-' && c1.at(0) == u'+')
|
||||||
|
h.m_transformation = QImageIOHandler::TransformationRotate90;
|
||||||
|
if (c0.at(0) == u'-' && c1.at(0) == u'-')
|
||||||
|
h.m_transformation = QImageIOHandler::TransformationMirrorAndRotate90;
|
||||||
|
if (c0.at(0) == u'+' && c1.at(0) == u'+')
|
||||||
|
h.m_transformation = QImageIOHandler::TransformationFlipAndRotate90;
|
||||||
|
if (c0.at(0) == u'+' && c1.at(0) == u'-')
|
||||||
|
h.m_transformation = QImageIOHandler::TransformationRotate270;
|
||||||
|
}
|
||||||
|
|
||||||
|
h.m_size = QSize(match.captured(4).toInt(), match.captured(2).toInt());
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Header m_header;
|
||||||
|
};
|
||||||
|
|
||||||
// read an old style line from the hdr image file
|
// read an old style line from the hdr image file
|
||||||
// if 'first' is true the first byte is already read
|
// if 'first' is true the first byte is already read
|
||||||
static bool Read_Old_Line(uchar *image, int width, QDataStream &s)
|
static bool Read_Old_Line(uchar *image, int width, QDataStream &s)
|
||||||
@ -76,9 +258,10 @@ static bool Read_Old_Line(uchar *image, int width, QDataStream &s)
|
|||||||
}
|
}
|
||||||
|
|
||||||
template<class float_T>
|
template<class float_T>
|
||||||
void RGBE_To_QRgbLine(uchar *image, float_T *scanline, int width)
|
void RGBE_To_QRgbLine(uchar *image, float_T *scanline, const Header& h)
|
||||||
{
|
{
|
||||||
for (int j = 0; j < width; j++) {
|
auto exposure = h.exposure();
|
||||||
|
for (int j = 0, width = h.width(); j < width; j++) {
|
||||||
// v = ldexp(1.0, int(image[3]) - 128);
|
// v = ldexp(1.0, int(image[3]) - 128);
|
||||||
float v;
|
float v;
|
||||||
int e = qBound(-31, int(image[3]) - 128, 31);
|
int e = qBound(-31, int(image[3]) - 128, 31);
|
||||||
@ -90,9 +273,13 @@ void RGBE_To_QRgbLine(uchar *image, float_T *scanline, int width)
|
|||||||
|
|
||||||
auto j4 = j * 4;
|
auto j4 = j * 4;
|
||||||
auto vn = v / 255.0f;
|
auto vn = v / 255.0f;
|
||||||
scanline[j4] = float_T(std::min(float(image[0]) * vn, 1.0f));
|
if (exposure > 0) {
|
||||||
scanline[j4 + 1] = float_T(std::min(float(image[1]) * vn, 1.0f));
|
vn /= exposure;
|
||||||
scanline[j4 + 2] = float_T(std::min(float(image[2]) * vn, 1.0f));
|
}
|
||||||
|
|
||||||
|
scanline[j4] = float_T(float(image[0]) * vn);
|
||||||
|
scanline[j4 + 1] = float_T(float(image[1]) * vn);
|
||||||
|
scanline[j4 + 2] = float_T(float(image[2]) * vn);
|
||||||
scanline[j4 + 3] = float_T(1.0f);
|
scanline[j4 + 3] = float_T(1.0f);
|
||||||
image += 4;
|
image += 4;
|
||||||
}
|
}
|
||||||
@ -108,11 +295,14 @@ QImage::Format imageFormat()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Load the HDR image.
|
// Load the HDR image.
|
||||||
static bool LoadHDR(QDataStream &s, const int width, const int height, QImage &img)
|
static bool LoadHDR(QDataStream &s, const Header& h, QImage &img)
|
||||||
{
|
{
|
||||||
uchar val;
|
uchar val;
|
||||||
uchar code;
|
uchar code;
|
||||||
|
|
||||||
|
const int width = h.width();
|
||||||
|
const int height = h.height();
|
||||||
|
|
||||||
// Create dst image.
|
// Create dst image.
|
||||||
img = imageAlloc(width, height, imageFormat());
|
img = imageAlloc(width, height, imageFormat());
|
||||||
if (img.isNull()) {
|
if (img.isNull()) {
|
||||||
@ -134,7 +324,7 @@ static bool LoadHDR(QDataStream &s, const int width, const int height, QImage &i
|
|||||||
// determine scanline type
|
// determine scanline type
|
||||||
if ((width < MINELEN) || (MAXELEN < width)) {
|
if ((width < MINELEN) || (MAXELEN < width)) {
|
||||||
Read_Old_Line(image, width, s);
|
Read_Old_Line(image, width, s);
|
||||||
RGBE_To_QRgbLine(image, scanline, width);
|
RGBE_To_QRgbLine(image, scanline, h);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,7 +337,7 @@ static bool LoadHDR(QDataStream &s, const int width, const int height, QImage &i
|
|||||||
if (val != 2) {
|
if (val != 2) {
|
||||||
s.device()->ungetChar(val);
|
s.device()->ungetChar(val);
|
||||||
Read_Old_Line(image, width, s);
|
Read_Old_Line(image, width, s);
|
||||||
RGBE_To_QRgbLine(image, scanline, width);
|
RGBE_To_QRgbLine(image, scanline, h);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,7 +352,7 @@ static bool LoadHDR(QDataStream &s, const int width, const int height, QImage &i
|
|||||||
if ((image[1] != 2) || (image[2] & 128)) {
|
if ((image[1] != 2) || (image[2] & 128)) {
|
||||||
image[0] = 2;
|
image[0] = 2;
|
||||||
Read_Old_Line(image + 4, width - 1, s);
|
Read_Old_Line(image + 4, width - 1, s);
|
||||||
RGBE_To_QRgbLine(image, scanline, width);
|
RGBE_To_QRgbLine(image, scanline, h);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -204,84 +394,34 @@ static bool LoadHDR(QDataStream &s, const int width, const int height, QImage &i
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
RGBE_To_QRgbLine(image, scanline, h);
|
||||||
RGBE_To_QRgbLine(image, scanline, width);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static QSize readHeaderSize(QIODevice *device)
|
|
||||||
{
|
|
||||||
int len;
|
|
||||||
QByteArray line(MAXLINE + 1, Qt::Uninitialized);
|
|
||||||
QByteArray format;
|
|
||||||
|
|
||||||
// Parse header
|
|
||||||
do {
|
|
||||||
len = device->readLine(line.data(), MAXLINE);
|
|
||||||
|
|
||||||
if (line.startsWith("FORMAT=")) {
|
|
||||||
format = line.mid(7, len - 7 - 1 /*\n*/);
|
|
||||||
}
|
|
||||||
|
|
||||||
} while ((len > 0) && (line[0] != '\n'));
|
|
||||||
|
|
||||||
if (format != "32-bit_rle_rgbe") {
|
|
||||||
qCDebug(HDRPLUGIN) << "Unknown HDR format:" << format;
|
|
||||||
return QSize();
|
|
||||||
}
|
|
||||||
|
|
||||||
len = device->readLine(line.data(), MAXLINE);
|
|
||||||
line.resize(len);
|
|
||||||
|
|
||||||
/*
|
|
||||||
TODO: handle flipping and rotation, as per the spec below
|
|
||||||
The single resolution line consists of 4 values, a X and Y label each followed by a numerical
|
|
||||||
integer value. The X and Y are immediately preceded by a sign which can be used to indicate
|
|
||||||
flipping, the order of the X and Y indicate rotation. The standard coordinate system for
|
|
||||||
Radiance images would have the following resolution string -Y N +X N. This indicates that the
|
|
||||||
vertical axis runs down the file and the X axis is to the right (imagining the image as a
|
|
||||||
rectangular block of data). A -X would indicate a horizontal flip of the image. A +Y would
|
|
||||||
indicate a vertical flip. If the X value appears before the Y value then that indicates that
|
|
||||||
the image is stored in column order rather than row order, that is, it is rotated by 90 degrees.
|
|
||||||
The reader can convince themselves that the 8 combinations cover all the possible image orientations
|
|
||||||
and rotations.
|
|
||||||
*/
|
|
||||||
QRegularExpression resolutionRegExp(QStringLiteral("([+\\-][XY]) ([0-9]+) ([+\\-][XY]) ([0-9]+)\n"));
|
|
||||||
QRegularExpressionMatch match = resolutionRegExp.match(QString::fromLatin1(line));
|
|
||||||
if (!match.hasMatch()) {
|
|
||||||
qCDebug(HDRPLUGIN) << "Invalid HDR file, the first line after the header didn't have the expected format:" << line;
|
|
||||||
return QSize();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((match.captured(1).at(1) != u'Y') || (match.captured(3).at(1) != u'X')) {
|
|
||||||
qCDebug(HDRPLUGIN) << "Unsupported image orientation in HDR file.";
|
|
||||||
return QSize();
|
|
||||||
}
|
|
||||||
|
|
||||||
return QSize(match.captured(4).toInt(), match.captured(2).toInt());
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
bool HDRHandler::read(QImage *outImage)
|
bool HDRHandler::read(QImage *outImage)
|
||||||
{
|
{
|
||||||
QDataStream s(device());
|
QDataStream s(device());
|
||||||
|
|
||||||
QSize size = readHeaderSize(s.device());
|
const Header& h = d->header(s.device());
|
||||||
if (!size.isValid()) {
|
if (!h.isValid()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
QImage img;
|
QImage img;
|
||||||
if (!LoadHDR(s, size.width(), size.height(), img)) {
|
if (!LoadHDR(s, h, img)) {
|
||||||
// qDebug() << "Error loading HDR file.";
|
// qDebug() << "Error loading HDR file.";
|
||||||
return false;
|
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.
|
// By setting the linear color space, programs that support profiles display HDR files as in GIMP and Photoshop.
|
||||||
img.setColorSpace(QColorSpace(QColorSpace::SRgbLinear));
|
img.setColorSpace(h.colorSpace());
|
||||||
|
|
||||||
|
// Metadata
|
||||||
|
if (!h.software().isEmpty()) {
|
||||||
|
img.setText(QStringLiteral(META_KEY_SOFTWARE), h.software());
|
||||||
|
}
|
||||||
|
|
||||||
*outImage = img;
|
*outImage = img;
|
||||||
return true;
|
return true;
|
||||||
@ -295,6 +435,9 @@ bool HDRHandler::supportsOption(ImageOption option) const
|
|||||||
if (option == QImageIOHandler::ImageFormat) {
|
if (option == QImageIOHandler::ImageFormat) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (option == QImageIOHandler::ImageTransformation) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -303,13 +446,10 @@ QVariant HDRHandler::option(ImageOption option) const
|
|||||||
QVariant v;
|
QVariant v;
|
||||||
|
|
||||||
if (option == QImageIOHandler::Size) {
|
if (option == QImageIOHandler::Size) {
|
||||||
if (auto d = device()) {
|
if (auto dev = device()) {
|
||||||
// transactions works on both random and sequential devices
|
auto&& h = d->header(dev);
|
||||||
d->startTransaction();
|
if (h.isValid()) {
|
||||||
auto size = readHeaderSize(d);
|
v = QVariant::fromValue(h.m_size);
|
||||||
d->rollbackTransaction();
|
|
||||||
if (size.isValid()) {
|
|
||||||
v = QVariant::fromValue(size);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -318,10 +458,21 @@ QVariant HDRHandler::option(ImageOption option) const
|
|||||||
v = QVariant::fromValue(imageFormat());
|
v = QVariant::fromValue(imageFormat());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (option == QImageIOHandler::ImageTransformation) {
|
||||||
|
if (auto dev = device()) {
|
||||||
|
auto&& h = d->header(dev);
|
||||||
|
if (h.isValid()) {
|
||||||
|
v = QVariant::fromValue(h.transformation());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
HDRHandler::HDRHandler()
|
HDRHandler::HDRHandler()
|
||||||
|
: QImageIOHandler()
|
||||||
|
, d(new HDRHandlerPrivate)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -348,9 +499,9 @@ bool HDRHandler::canRead(QIODevice *device)
|
|||||||
|
|
||||||
// allow to load offical test cases: https://radsite.lbl.gov/radiance/framed.html
|
// allow to load offical test cases: https://radsite.lbl.gov/radiance/framed.html
|
||||||
device->startTransaction();
|
device->startTransaction();
|
||||||
QSize size = readHeaderSize(device);
|
auto h = HDRHandlerPrivate::readHeader(device);
|
||||||
device->rollbackTransaction();
|
device->rollbackTransaction();
|
||||||
if (size.isValid()) {
|
if (h.isValid()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,7 +9,9 @@
|
|||||||
#define KIMG_HDR_P_H
|
#define KIMG_HDR_P_H
|
||||||
|
|
||||||
#include <QImageIOPlugin>
|
#include <QImageIOPlugin>
|
||||||
|
#include <QScopedPointer>
|
||||||
|
|
||||||
|
class HDRHandlerPrivate;
|
||||||
class HDRHandler : public QImageIOHandler
|
class HDRHandler : public QImageIOHandler
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -22,6 +24,9 @@ public:
|
|||||||
QVariant option(QImageIOHandler::ImageOption option) const override;
|
QVariant option(QImageIOHandler::ImageOption option) const override;
|
||||||
|
|
||||||
static bool canRead(QIODevice *device);
|
static bool canRead(QIODevice *device);
|
||||||
|
|
||||||
|
private:
|
||||||
|
const QScopedPointer<HDRHandlerPrivate> d;
|
||||||
};
|
};
|
||||||
|
|
||||||
class HDRPlugin : public QImageIOPlugin
|
class HDRPlugin : public QImageIOPlugin
|
||||||
|
@ -16,11 +16,19 @@
|
|||||||
#include <jxl/thread_parallel_runner.h>
|
#include <jxl/thread_parallel_runner.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
// Avoid rotation on buggy Qts (see also https://bugreports.qt.io/browse/QTBUG-126575)
|
||||||
|
#if (QT_VERSION >= QT_VERSION_CHECK(6, 5, 7) && QT_VERSION < QT_VERSION_CHECK(6, 6, 0)) || (QT_VERSION >= QT_VERSION_CHECK(6, 7, 3))
|
||||||
|
#ifndef JXL_QT_AUTOTRANSFORM
|
||||||
|
#define JXL_QT_AUTOTRANSFORM
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
QJpegXLHandler::QJpegXLHandler()
|
QJpegXLHandler::QJpegXLHandler()
|
||||||
: m_parseState(ParseJpegXLNotParsed)
|
: m_parseState(ParseJpegXLNotParsed)
|
||||||
, m_quality(90)
|
, m_quality(90)
|
||||||
, m_currentimage_index(0)
|
, m_currentimage_index(0)
|
||||||
, m_previousimage_index(-1)
|
, m_previousimage_index(-1)
|
||||||
|
, m_transformations(QImageIOHandler::TransformationNone)
|
||||||
, m_decoder(nullptr)
|
, m_decoder(nullptr)
|
||||||
, m_runner(nullptr)
|
, m_runner(nullptr)
|
||||||
, m_next_image_delay(0)
|
, m_next_image_delay(0)
|
||||||
@ -129,6 +137,11 @@ bool QJpegXLHandler::ensureDecoder()
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef JXL_QT_AUTOTRANSFORM
|
||||||
|
// Let Qt handle the orientation.
|
||||||
|
JxlDecoderSetKeepOrientation(m_decoder, true);
|
||||||
|
#endif
|
||||||
|
|
||||||
int num_worker_threads = QThread::idealThreadCount();
|
int num_worker_threads = QThread::idealThreadCount();
|
||||||
if (!m_runner && num_worker_threads >= 4) {
|
if (!m_runner && num_worker_threads >= 4) {
|
||||||
/* use half of the threads because plug-in is usually used in environment
|
/* use half of the threads because plug-in is usually used in environment
|
||||||
@ -568,10 +581,25 @@ bool QJpegXLHandler::write(const QImage &image)
|
|||||||
pixel_format.endianness = JXL_NATIVE_ENDIAN;
|
pixel_format.endianness = JXL_NATIVE_ENDIAN;
|
||||||
pixel_format.align = 0;
|
pixel_format.align = 0;
|
||||||
|
|
||||||
output_info.orientation = JXL_ORIENT_IDENTITY;
|
|
||||||
output_info.num_color_channels = 3;
|
output_info.num_color_channels = 3;
|
||||||
output_info.animation.tps_numerator = 10;
|
output_info.animation.tps_numerator = 10;
|
||||||
output_info.animation.tps_denominator = 1;
|
output_info.animation.tps_denominator = 1;
|
||||||
|
output_info.orientation = JXL_ORIENT_IDENTITY;
|
||||||
|
if (m_transformations == QImageIOHandler::TransformationMirror) {
|
||||||
|
output_info.orientation = JXL_ORIENT_FLIP_HORIZONTAL;
|
||||||
|
} else if (m_transformations == QImageIOHandler::TransformationRotate180) {
|
||||||
|
output_info.orientation = JXL_ORIENT_ROTATE_180;
|
||||||
|
} else if (m_transformations == QImageIOHandler::TransformationFlip) {
|
||||||
|
output_info.orientation = JXL_ORIENT_FLIP_VERTICAL;
|
||||||
|
} else if (m_transformations == QImageIOHandler::TransformationFlipAndRotate90) {
|
||||||
|
output_info.orientation = JXL_ORIENT_TRANSPOSE;
|
||||||
|
} else if (m_transformations == QImageIOHandler::TransformationRotate90) {
|
||||||
|
output_info.orientation = JXL_ORIENT_ROTATE_90_CW;
|
||||||
|
} else if (m_transformations == QImageIOHandler::TransformationMirrorAndRotate90) {
|
||||||
|
output_info.orientation = JXL_ORIENT_ANTI_TRANSPOSE;
|
||||||
|
} else if (m_transformations == QImageIOHandler::TransformationRotate270) {
|
||||||
|
output_info.orientation = JXL_ORIENT_ROTATE_90_CCW;
|
||||||
|
}
|
||||||
|
|
||||||
if (save_depth > 8) { // 16bit depth
|
if (save_depth > 8) { // 16bit depth
|
||||||
pixel_format.data_type = JXL_TYPE_UINT16;
|
pixel_format.data_type = JXL_TYPE_UINT16;
|
||||||
@ -777,14 +805,24 @@ bool QJpegXLHandler::write(const QImage &image)
|
|||||||
|
|
||||||
QVariant QJpegXLHandler::option(ImageOption option) const
|
QVariant QJpegXLHandler::option(ImageOption option) const
|
||||||
{
|
{
|
||||||
|
if (!supportsOption(option)) {
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
|
|
||||||
if (option == Quality) {
|
if (option == Quality) {
|
||||||
return m_quality;
|
return m_quality;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!supportsOption(option) || !ensureParsed()) {
|
if (!ensureParsed()) {
|
||||||
|
#ifdef JXL_QT_AUTOTRANSFORM
|
||||||
|
if (option == ImageTransformation) {
|
||||||
|
return int(m_transformations);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
return QVariant();
|
return QVariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
switch (option) {
|
switch (option) {
|
||||||
case Size:
|
case Size:
|
||||||
return QSize(m_basicinfo.xsize, m_basicinfo.ysize);
|
return QSize(m_basicinfo.xsize, m_basicinfo.ysize);
|
||||||
@ -794,9 +832,31 @@ QVariant QJpegXLHandler::option(ImageOption option) const
|
|||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
#ifdef JXL_QT_AUTOTRANSFORM
|
||||||
|
case ImageTransformation:
|
||||||
|
if (m_basicinfo.orientation == JXL_ORIENT_IDENTITY) {
|
||||||
|
return int(QImageIOHandler::TransformationNone);
|
||||||
|
} else if (m_basicinfo.orientation == JXL_ORIENT_FLIP_HORIZONTAL) {
|
||||||
|
return int(QImageIOHandler::TransformationMirror);
|
||||||
|
} else if (m_basicinfo.orientation == JXL_ORIENT_ROTATE_180) {
|
||||||
|
return int(QImageIOHandler::TransformationRotate180);
|
||||||
|
} else if (m_basicinfo.orientation == JXL_ORIENT_FLIP_VERTICAL) {
|
||||||
|
return int(QImageIOHandler::TransformationFlip);
|
||||||
|
} else if (m_basicinfo.orientation == JXL_ORIENT_TRANSPOSE) {
|
||||||
|
return int(QImageIOHandler::TransformationFlipAndRotate90);
|
||||||
|
} else if (m_basicinfo.orientation == JXL_ORIENT_ROTATE_90_CW) {
|
||||||
|
return int(QImageIOHandler::TransformationRotate90);
|
||||||
|
} else if (m_basicinfo.orientation == JXL_ORIENT_ANTI_TRANSPOSE) {
|
||||||
|
return int(QImageIOHandler::TransformationMirrorAndRotate90);
|
||||||
|
} else if (m_basicinfo.orientation == JXL_ORIENT_ROTATE_90_CCW) {
|
||||||
|
return int(QImageIOHandler::TransformationRotate270);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
default:
|
default:
|
||||||
return QVariant();
|
return QVariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return QVariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
void QJpegXLHandler::setOption(ImageOption option, const QVariant &value)
|
void QJpegXLHandler::setOption(ImageOption option, const QVariant &value)
|
||||||
@ -810,6 +870,14 @@ void QJpegXLHandler::setOption(ImageOption option, const QVariant &value)
|
|||||||
m_quality = 90;
|
m_quality = 90;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
#ifdef JXL_QT_AUTOTRANSFORM
|
||||||
|
case ImageTransformation:
|
||||||
|
if (auto t = value.toInt()) {
|
||||||
|
if (t > 0 && t < 8)
|
||||||
|
m_transformations = QImageIOHandler::Transformations(t);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -818,7 +886,11 @@ void QJpegXLHandler::setOption(ImageOption option, const QVariant &value)
|
|||||||
|
|
||||||
bool QJpegXLHandler::supportsOption(ImageOption option) const
|
bool QJpegXLHandler::supportsOption(ImageOption option) const
|
||||||
{
|
{
|
||||||
return option == Quality || option == Size || option == Animation;
|
auto supported = option == Quality || option == Size || option == Animation;
|
||||||
|
#ifdef JXL_QT_AUTOTRANSFORM
|
||||||
|
supported = supported || option == ImageTransformation;
|
||||||
|
#endif
|
||||||
|
return supported;
|
||||||
}
|
}
|
||||||
|
|
||||||
int QJpegXLHandler::imageCount() const
|
int QJpegXLHandler::imageCount() const
|
||||||
|
@ -64,6 +64,7 @@ private:
|
|||||||
int m_quality;
|
int m_quality;
|
||||||
int m_currentimage_index;
|
int m_currentimage_index;
|
||||||
int m_previousimage_index;
|
int m_previousimage_index;
|
||||||
|
QImageIOHandler::Transformations m_transformations;
|
||||||
|
|
||||||
QByteArray m_rawData;
|
QByteArray m_rawData;
|
||||||
|
|
||||||
|
1110
src/imageformats/jxr.cpp
Normal file
4
src/imageformats/jxr.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"Keys": [ "jxr", "wdp", "hdp" ],
|
||||||
|
"MimeTypes": [ "image/jxr", "image/vnd.ms-photo", "image/vnd.ms-photo" ]
|
||||||
|
}
|
47
src/imageformats/jxr_p.h
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
This file is part of the KDE project
|
||||||
|
SPDX-FileCopyrightText: 2024 Mirco Miranda <mircomir@outlook.com>
|
||||||
|
|
||||||
|
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef KIMG_JXR_P_H
|
||||||
|
#define KIMG_JXR_P_H
|
||||||
|
|
||||||
|
#include <QImageIOPlugin>
|
||||||
|
#include <QSharedDataPointer>
|
||||||
|
|
||||||
|
class JXRHandlerPrivate;
|
||||||
|
|
||||||
|
class JXRHandler : public QImageIOHandler
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
JXRHandler();
|
||||||
|
|
||||||
|
bool canRead() const override;
|
||||||
|
bool read(QImage *outImage) override;
|
||||||
|
bool write(const QImage &image) override;
|
||||||
|
|
||||||
|
void setOption(ImageOption option, const QVariant &value) override;
|
||||||
|
bool supportsOption(QImageIOHandler::ImageOption option) const override;
|
||||||
|
QVariant option(QImageIOHandler::ImageOption option) const override;
|
||||||
|
|
||||||
|
static bool canRead(QIODevice *device);
|
||||||
|
|
||||||
|
private:
|
||||||
|
mutable QSharedDataPointer<JXRHandlerPrivate> d;
|
||||||
|
|
||||||
|
qint32 m_quality;
|
||||||
|
};
|
||||||
|
|
||||||
|
class JXRPlugin : public QImageIOPlugin
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QImageIOHandlerFactoryInterface" FILE "jxr.json")
|
||||||
|
|
||||||
|
public:
|
||||||
|
Capabilities capabilities(QIODevice *device, const QByteArray &format) const override;
|
||||||
|
QImageIOHandler *create(QIODevice *device, const QByteArray &format = QByteArray()) const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // KIMG_JXR_P_H
|
@ -67,6 +67,40 @@ public:
|
|||||||
{
|
{
|
||||||
return (Encoding == 1);
|
return (Encoding == 1);
|
||||||
}
|
}
|
||||||
|
/*!
|
||||||
|
* \brief isValid
|
||||||
|
* Checks if the header data are valid for the PCX.
|
||||||
|
* \note Put here the header sanity checks.
|
||||||
|
* \return True if the header is a valid PCX header, otherwise false.
|
||||||
|
*/
|
||||||
|
inline bool isValid() const
|
||||||
|
{
|
||||||
|
return Manufacturer == 10 && BytesPerLine != 0;
|
||||||
|
}
|
||||||
|
/*!
|
||||||
|
* \brief isSupported
|
||||||
|
* \return True if the header is valid and the PCX format is supported by the plugin. Otherwise false.
|
||||||
|
*/
|
||||||
|
inline bool isSupported() const
|
||||||
|
{
|
||||||
|
return isValid() && format() != QImage::Format_Invalid;
|
||||||
|
}
|
||||||
|
inline QImage::Format format() const
|
||||||
|
{
|
||||||
|
auto fmt = QImage::Format_Invalid;
|
||||||
|
if (Bpp == 1 && NPlanes == 1) {
|
||||||
|
fmt = QImage::Format_Mono;
|
||||||
|
} else if (Bpp == 1 && NPlanes == 4) {
|
||||||
|
fmt = QImage::Format_Indexed8;
|
||||||
|
} else if (Bpp == 4 && NPlanes == 1) {
|
||||||
|
fmt = QImage::Format_Indexed8;
|
||||||
|
} else if (Bpp == 8 && NPlanes == 1) {
|
||||||
|
fmt = QImage::Format_Indexed8;
|
||||||
|
} else if (Bpp == 8 && NPlanes == 3) {
|
||||||
|
fmt = QImage::Format_RGB32;
|
||||||
|
}
|
||||||
|
return fmt;
|
||||||
|
}
|
||||||
|
|
||||||
quint8 Manufacturer; // Constant Flag, 10 = ZSoft .pcx
|
quint8 Manufacturer; // Constant Flag, 10 = ZSoft .pcx
|
||||||
quint8 Version; // Version information·
|
quint8 Version; // Version information·
|
||||||
@ -100,6 +134,8 @@ public:
|
|||||||
// found only in PB IV/IV Plus
|
// found only in PB IV/IV Plus
|
||||||
quint16 VScreenSize; // Vertical screen size in pixels. New field
|
quint16 VScreenSize; // Vertical screen size in pixels. New field
|
||||||
// found only in PB IV/IV Plus
|
// found only in PB IV/IV Plus
|
||||||
|
|
||||||
|
quint8 unused[54];
|
||||||
};
|
};
|
||||||
|
|
||||||
#pragma pack(pop)
|
#pragma pack(pop)
|
||||||
@ -173,9 +209,8 @@ static QDataStream &operator>>(QDataStream &s, PCXHEADER &ph)
|
|||||||
ph.VScreenSize = vscreensize;
|
ph.VScreenSize = vscreensize;
|
||||||
|
|
||||||
// Skip the rest of the header
|
// Skip the rest of the header
|
||||||
quint8 byte;
|
for (size_t i = 0, n = sizeof(ph.unused); i < n; ++i) {
|
||||||
for (auto i = 0; i < 54; ++i) {
|
s >> ph.unused[i];
|
||||||
s >> byte;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return s;
|
return s;
|
||||||
@ -213,9 +248,8 @@ static QDataStream &operator<<(QDataStream &s, const PCXHEADER &ph)
|
|||||||
s << ph.HScreenSize;
|
s << ph.HScreenSize;
|
||||||
s << ph.VScreenSize;
|
s << ph.VScreenSize;
|
||||||
|
|
||||||
quint8 byte = 0;
|
for (size_t i = 0, n = sizeof(ph.unused); i < n; ++i) {
|
||||||
for (int i = 0; i < 54; ++i) {
|
s << ph.unused[i];
|
||||||
s << byte;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return s;
|
return s;
|
||||||
@ -230,6 +264,34 @@ PCXHEADER::PCXHEADER()
|
|||||||
s >> *this;
|
s >> *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool peekHeader(QIODevice *d, PCXHEADER& h)
|
||||||
|
{
|
||||||
|
qint64 pos = 0;
|
||||||
|
if (!d->isSequential()) {
|
||||||
|
pos = d->pos();
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
static bool readLine(QDataStream &s, QByteArray &buf, const PCXHEADER &header)
|
static bool readLine(QDataStream &s, QByteArray &buf, const PCXHEADER &header)
|
||||||
{
|
{
|
||||||
quint32 i = 0;
|
quint32 i = 0;
|
||||||
@ -265,7 +327,7 @@ static bool readImage1(QImage &img, QDataStream &s, const PCXHEADER &header)
|
|||||||
{
|
{
|
||||||
QByteArray buf(header.BytesPerLine, 0);
|
QByteArray buf(header.BytesPerLine, 0);
|
||||||
|
|
||||||
img = imageAlloc(header.width(), header.height(), QImage::Format_Mono);
|
img = imageAlloc(header.width(), header.height(), header.format());
|
||||||
img.setColorCount(2);
|
img.setColorCount(2);
|
||||||
|
|
||||||
if (img.isNull()) {
|
if (img.isNull()) {
|
||||||
@ -301,13 +363,18 @@ static bool readImage4(QImage &img, QDataStream &s, const PCXHEADER &header)
|
|||||||
QByteArray buf(header.BytesPerLine * 4, 0);
|
QByteArray buf(header.BytesPerLine * 4, 0);
|
||||||
QByteArray pixbuf(header.width(), 0);
|
QByteArray pixbuf(header.width(), 0);
|
||||||
|
|
||||||
img = imageAlloc(header.width(), header.height(), QImage::Format_Indexed8);
|
img = imageAlloc(header.width(), header.height(), header.format());
|
||||||
img.setColorCount(16);
|
img.setColorCount(16);
|
||||||
if (img.isNull()) {
|
if (img.isNull()) {
|
||||||
qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height());
|
qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (header.BytesPerLine < (header.width() + 7) / 8) {
|
||||||
|
qWarning() << "PCX image has invalid BytesPerLine value";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
for (int y = 0; y < header.height(); ++y) {
|
for (int y = 0; y < header.height(); ++y) {
|
||||||
if (s.atEnd()) {
|
if (s.atEnd()) {
|
||||||
return false;
|
return false;
|
||||||
@ -344,11 +411,52 @@ static bool readImage4(QImage &img, QDataStream &s, const PCXHEADER &header)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool readImage4v2(QImage &img, QDataStream &s, const PCXHEADER &header)
|
||||||
|
{
|
||||||
|
QByteArray buf(header.BytesPerLine, 0);
|
||||||
|
|
||||||
|
img = imageAlloc(header.width(), header.height(), header.format());
|
||||||
|
img.setColorCount(16);
|
||||||
|
|
||||||
|
if (img.isNull()) {
|
||||||
|
qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int y = 0; y < header.height(); ++y) {
|
||||||
|
if (s.atEnd()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!readLine(s, buf, header)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uchar *p = img.scanLine(y);
|
||||||
|
if (!p) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const unsigned int bpl = std::min(header.BytesPerLine, static_cast<quint16>(header.width() / 2));
|
||||||
|
for (unsigned int x = 0; x < bpl; ++x) {
|
||||||
|
p[x * 2] = (buf[x] & 240) >> 4;
|
||||||
|
p[x * 2 + 1] = buf[x] & 15;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the palette
|
||||||
|
for (int i = 0; i < 16; ++i) {
|
||||||
|
img.setColor(i, header.ColorMap.color(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
return (s.status() == QDataStream::Ok);
|
||||||
|
}
|
||||||
|
|
||||||
static bool readImage8(QImage &img, QDataStream &s, const PCXHEADER &header)
|
static bool readImage8(QImage &img, QDataStream &s, const PCXHEADER &header)
|
||||||
{
|
{
|
||||||
QByteArray buf(header.BytesPerLine, 0);
|
QByteArray buf(header.BytesPerLine, 0);
|
||||||
|
|
||||||
img = imageAlloc(header.width(), header.height(), QImage::Format_Indexed8);
|
img = imageAlloc(header.width(), header.height(), header.format());
|
||||||
img.setColorCount(256);
|
img.setColorCount(256);
|
||||||
|
|
||||||
if (img.isNull()) {
|
if (img.isNull()) {
|
||||||
@ -411,13 +519,15 @@ static bool readImage24(QImage &img, QDataStream &s, const PCXHEADER &header)
|
|||||||
QByteArray g_buf(header.BytesPerLine, 0);
|
QByteArray g_buf(header.BytesPerLine, 0);
|
||||||
QByteArray b_buf(header.BytesPerLine, 0);
|
QByteArray b_buf(header.BytesPerLine, 0);
|
||||||
|
|
||||||
img = imageAlloc(header.width(), header.height(), QImage::Format_RGB32);
|
img = imageAlloc(header.width(), header.height(), header.format());
|
||||||
|
|
||||||
if (img.isNull()) {
|
if (img.isNull()) {
|
||||||
qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height());
|
qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const unsigned int bpl = std::min(header.BytesPerLine, static_cast<quint16>(header.width()));
|
||||||
|
|
||||||
for (int y = 0; y < header.height(); ++y) {
|
for (int y = 0; y < header.height(); ++y) {
|
||||||
if (s.atEnd()) {
|
if (s.atEnd()) {
|
||||||
return false;
|
return false;
|
||||||
@ -434,7 +544,8 @@ static bool readImage24(QImage &img, QDataStream &s, const PCXHEADER &header)
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint *p = (uint *)img.scanLine(y);
|
uint *p = (uint *)img.scanLine(y);
|
||||||
for (int x = 0; x < header.width(); ++x) {
|
|
||||||
|
for (unsigned int x = 0; x < bpl; ++x) {
|
||||||
p[x] = qRgb(r_buf[x], g_buf[x], b_buf[x]);
|
p[x] = qRgb(r_buf[x], g_buf[x], b_buf[x]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -636,7 +747,18 @@ static bool writeImage24(QImage &img, QDataStream &s, PCXHEADER &header)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class PCXHandlerPrivate
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
PCXHandlerPrivate() {}
|
||||||
|
~PCXHandlerPrivate() {}
|
||||||
|
|
||||||
|
PCXHEADER m_header;
|
||||||
|
};
|
||||||
|
|
||||||
PCXHandler::PCXHandler()
|
PCXHandler::PCXHandler()
|
||||||
|
: QImageIOHandler()
|
||||||
|
, d(new PCXHandlerPrivate)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -658,11 +780,14 @@ bool PCXHandler::read(QImage *outImage)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
PCXHEADER header;
|
auto&& header = d->m_header;
|
||||||
|
|
||||||
s >> header;
|
s >> header;
|
||||||
|
|
||||||
if (header.Manufacturer != 10 || header.BytesPerLine == 0 || s.atEnd()) {
|
if (s.status() != QDataStream::Ok || s.atEnd()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!header.isSupported()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -672,6 +797,8 @@ bool PCXHandler::read(QImage *outImage)
|
|||||||
ok = readImage1(img, s, header);
|
ok = readImage1(img, s, header);
|
||||||
} else if (header.Bpp == 1 && header.NPlanes == 4) {
|
} else if (header.Bpp == 1 && header.NPlanes == 4) {
|
||||||
ok = readImage4(img, s, header);
|
ok = readImage4(img, s, header);
|
||||||
|
} else if (header.Bpp == 4 && header.NPlanes == 1) {
|
||||||
|
ok = readImage4v2(img, s, header);
|
||||||
} else if (header.Bpp == 8 && header.NPlanes == 1) {
|
} else if (header.Bpp == 8 && header.NPlanes == 1) {
|
||||||
ok = readImage8(img, s, header);
|
ok = readImage8(img, s, header);
|
||||||
} else if (header.Bpp == 8 && header.NPlanes == 3) {
|
} else if (header.Bpp == 8 && header.NPlanes == 3) {
|
||||||
@ -730,6 +857,46 @@ bool PCXHandler::write(const QImage &image)
|
|||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool PCXHandler::supportsOption(ImageOption option) const
|
||||||
|
{
|
||||||
|
if (option == QImageIOHandler::Size) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (option == QImageIOHandler::ImageFormat) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant PCXHandler::option(ImageOption option) const
|
||||||
|
{
|
||||||
|
QVariant v;
|
||||||
|
|
||||||
|
if (option == QImageIOHandler::Size) {
|
||||||
|
auto&& header = d->m_header;
|
||||||
|
if (header.isSupported()) {
|
||||||
|
v = QVariant::fromValue(QSize(header.width(), header.height()));
|
||||||
|
} else if (auto dev = device()) {
|
||||||
|
if (peekHeader(dev, header) && header.isSupported()) {
|
||||||
|
v = QVariant::fromValue(QSize(header.width(), header.height()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (option == QImageIOHandler::ImageFormat) {
|
||||||
|
auto&& header = d->m_header;
|
||||||
|
if (header.isSupported()) {
|
||||||
|
v = QVariant::fromValue(header.format());
|
||||||
|
} else if (auto dev = device()) {
|
||||||
|
if (peekHeader(dev, header) && header.isSupported()) {
|
||||||
|
v = QVariant::fromValue(header.format());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
bool PCXHandler::canRead(QIODevice *device)
|
bool PCXHandler::canRead(QIODevice *device)
|
||||||
{
|
{
|
||||||
if (!device) {
|
if (!device) {
|
||||||
@ -737,30 +904,11 @@ bool PCXHandler::canRead(QIODevice *device)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
qint64 oldPos = device->pos();
|
PCXHEADER header;
|
||||||
|
if (!peekHeader(device, header)) {
|
||||||
char head[1];
|
|
||||||
qint64 readBytes = device->read(head, sizeof(head));
|
|
||||||
if (readBytes != sizeof(head)) {
|
|
||||||
if (device->isSequential()) {
|
|
||||||
while (readBytes > 0) {
|
|
||||||
device->ungetChar(head[readBytes-- - 1]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
device->seek(oldPos);
|
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
return header.isSupported();
|
||||||
if (device->isSequential()) {
|
|
||||||
while (readBytes > 0) {
|
|
||||||
device->ungetChar(head[readBytes-- - 1]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
device->seek(oldPos);
|
|
||||||
}
|
|
||||||
|
|
||||||
return qstrncmp(head, "\012", 1) == 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QImageIOPlugin::Capabilities PCXPlugin::capabilities(QIODevice *device, const QByteArray &format) const
|
QImageIOPlugin::Capabilities PCXPlugin::capabilities(QIODevice *device, const QByteArray &format) const
|
||||||
|