mirror of
https://invent.kde.org/frameworks/kimageformats.git
synced 2026-06-15 11:19:19 -04:00
Compare commits
20 Commits
Frameworks
...
v6.26.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
84f28e8bc9 | ||
|
|
a936927ec1 | ||
|
|
191e5e6a69 | ||
|
|
51db11eefc | ||
|
|
bc398382ac | ||
|
|
d5e5012cfb | ||
|
|
1b3f32a332 | ||
|
|
7cf60da031 | ||
|
|
d160f268e7 | ||
|
|
276338199a | ||
|
|
742b5097f6 | ||
|
|
2d2ee68cc0 | ||
|
|
d15c3c679d | ||
|
|
eb896efea1 | ||
|
|
142ec14c81 | ||
|
|
38b8b70304 | ||
|
|
e28c48cfeb | ||
|
|
3c08226aec | ||
|
|
7c86ccaefb | ||
|
|
ec0610d5b0 |
@@ -7,5 +7,5 @@ Dependencies:
|
||||
Options:
|
||||
test-before-installing: True
|
||||
require-passing-tests-on: ['Linux', 'FreeBSD', 'Windows']
|
||||
cmake-options: "-DKIMAGEFORMATS_JXR=ON -DKIMAGEFORMATS_HEIF=ON -DKIMAGEFORMATS_HEIF_TEST:STRING=OFF -DKIMAGEFORMATS_HEJ2_TEST:STRING=OFF -DKIMAGEFORMATS_AVCI_TEST:STRING=OFF"
|
||||
cmake-options: "-DKIMAGEFORMATS_WITH_KNOWN_CRASHES_JXR=ON -DKIMAGEFORMATS_HEIF=ON -DKIMAGEFORMATS_HEIF_TEST:STRING=OFF -DKIMAGEFORMATS_HEJ2_TEST:STRING=OFF -DKIMAGEFORMATS_AVCI_TEST:STRING=OFF"
|
||||
per-test-timeout: 90
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
cmake_minimum_required(VERSION 3.27)
|
||||
cmake_minimum_required(VERSION 3.29)
|
||||
|
||||
set(KF_VERSION "6.24.0") # handled by release scripts
|
||||
set(KF_DEP_VERSION "6.24.0") # handled by release scripts
|
||||
set(KF_VERSION "6.26.0") # handled by release scripts
|
||||
set(KF_DEP_VERSION "6.26.0") # handled by release scripts
|
||||
project(KImageFormats VERSION ${KF_VERSION})
|
||||
|
||||
include(FeatureSummary)
|
||||
find_package(ECM 6.24.0 NO_MODULE)
|
||||
find_package(ECM 6.26.0 NO_MODULE)
|
||||
set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://commits.kde.org/extra-cmake-modules")
|
||||
feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES)
|
||||
|
||||
@@ -21,7 +21,7 @@ include(ECMDeprecationSettings)
|
||||
include(CheckIncludeFiles)
|
||||
include(FindPkgConfig)
|
||||
|
||||
set(REQUIRED_QT_VERSION 6.8.0)
|
||||
set(REQUIRED_QT_VERSION 6.9.0)
|
||||
find_package(Qt6Gui ${REQUIRED_QT_VERSION} REQUIRED NO_MODULE)
|
||||
|
||||
find_package(KF6Archive ${KF_DEP_VERSION})
|
||||
@@ -97,9 +97,10 @@ set_package_properties(LibRaw PROPERTIES
|
||||
PURPOSE "Required for the QImage plugin for RAW images"
|
||||
)
|
||||
|
||||
# JXR plugin disabled by default due to security issues
|
||||
option(KIMAGEFORMATS_JXR "Enable plugin for JPEG XR format" OFF)
|
||||
if(KIMAGEFORMATS_JXR)
|
||||
# JXR plugin disabled by default due to security issues.
|
||||
# You should not enable it unless you know what you are doing.
|
||||
option(KIMAGEFORMATS_WITH_KNOWN_CRASHES_JXR "Enable plugin for JPEG XR format" OFF)
|
||||
if(KIMAGEFORMATS_WITH_KNOWN_CRASHES_JXR)
|
||||
find_package(LibJXR)
|
||||
endif()
|
||||
add_feature_info(LibJXR LibJXR_FOUND "required for the QImage plugin for JPEG XR images")
|
||||
|
||||
53
README.md
53
README.md
@@ -21,6 +21,7 @@ The following image formats have read-only support:
|
||||
- Krita (kra)
|
||||
- OpenRaster (ora)
|
||||
- Pixar raster (pxr)
|
||||
- PlayStation graphics (tim)
|
||||
- Portable FloatMap/HalfMap (pfm, phm)
|
||||
- Photoshop documents (psd, psb, pdd, psdt)
|
||||
- Radiance HDR (hdr)
|
||||
@@ -154,8 +155,31 @@ About the image:
|
||||
- `Owner`: Name of the owner of the image.
|
||||
- `Software`: Name and version number of the software package(s) used to
|
||||
create the image.
|
||||
- `Speed`: Floating-point number indicating the speed of GPS receiver
|
||||
movement in Km/h (e.g. 30.2).
|
||||
- `Title`: The title of the image.
|
||||
|
||||
About the shot:
|
||||
- `DigitalZoomRatio`: Floating-point number indicating the digital zoom ratio
|
||||
when the image was shot.
|
||||
- `ExposureMode`: Integer number indicating the exposure mode set when the
|
||||
image was shot as reported in the EXIF specifications.
|
||||
- `ExposureProgram`: Integer number indicating the class of the program used
|
||||
by the camera to set exposure when the picture is taken as reported in the
|
||||
EXIF specifications.
|
||||
- `ExposureTime`: Floating-point number indicating the exposure time,
|
||||
given in seconds (s).
|
||||
- `Flash`: Integer number indicating the status of flash when the image
|
||||
was shot as reported in the EXIF specifications.
|
||||
- `FNumber`: Floating-point number indicating the F number.
|
||||
- `FocalLength`: Floating-point number indicating the actual focal length
|
||||
of the lens, in millimeters (mm).
|
||||
- `ISOSpeedRatings`: Integer number indicating the sensitivity of the camera
|
||||
or input device when the image was shot as reported in the EXIF
|
||||
specifications.
|
||||
- `WhiteBalance`: Integer number indicating the white balance mode set when
|
||||
the image was shot as reported in the EXIF specifications.
|
||||
|
||||
About the camera:
|
||||
- `Manufacturer`: The manufacturer of the recording equipment.
|
||||
- `Model`: The model name or model number of the recording equipment.
|
||||
@@ -250,6 +274,7 @@ limit depends on the format encoding).
|
||||
- RAW: 65,535 x 65,535 pixels
|
||||
- RGB: 65,535 x 65,535 pixels
|
||||
- SCT: 300,000 x 300,000 pixels
|
||||
- TIM: 65,535 x 65,535 pixels
|
||||
- TGA: 65,535 x 65,535 pixels
|
||||
- XCF: 300,000 x 300,000 pixels
|
||||
|
||||
@@ -293,6 +318,11 @@ plugin:
|
||||
- `DDS_DISABLE_STRIDE_ALIGNMENT`: disable the stride alignment based on DDS
|
||||
pitch: it is known that some writers do not set it correctly.
|
||||
|
||||
When writing, it is possible to set which pixel format to use by setting the
|
||||
subtypes. The default is `Automatic` which chooses the most appropriate format
|
||||
based on the image. For a complete list of subformats, please use the
|
||||
appropriate [`QImageWriter`](https://doc.qt.io/qt-6/qimagewriter.html) APIs.
|
||||
|
||||
### The HEIF plugin
|
||||
|
||||
**This plugin is disabled by default. It can be enabled by settings
|
||||
@@ -322,6 +352,10 @@ plugin:
|
||||
attribute named "xmp". Note that Gimp reads the "xmp" attribute and Darktable
|
||||
writes it as well.
|
||||
|
||||
The plugin can set the following additional metadata:
|
||||
- `EXRLayerName`: A string containing the name of the EXR layer used to decode
|
||||
the image.
|
||||
|
||||
### The EPS plugin
|
||||
|
||||
The plugin uses `Ghostscript` to convert the raster image. When reading it
|
||||
@@ -377,6 +411,11 @@ JP2 plugin has the following limitations due to the lack of support by OpenJPEG:
|
||||
- Image resolution is not supported.
|
||||
- To write ICC profiles you need OpenJPEG V2.5.4 or higher
|
||||
|
||||
When writing, it is possible to set which format to use by setting the
|
||||
following subtypes:
|
||||
- `JP2` (default): Save data using the JP2 container.
|
||||
- `J2K`: Save only the compressed codestream.
|
||||
|
||||
### The JXL plugin
|
||||
|
||||
**The current version of the plugin limits the image size to 256 megapixels
|
||||
@@ -392,7 +431,12 @@ plugin:
|
||||
### The JXR plugin
|
||||
|
||||
**This plugin is disabled by default. It can be enabled by settings
|
||||
`KIMAGEFORMATS_JXR` to `ON` in your cmake options.**
|
||||
`KIMAGEFORMATS_WITH_KNOWN_CRASHES_JXR` to `ON` in your cmake options.**
|
||||
|
||||
> [!caution]
|
||||
> The plugin disabled by default due to security issues in [jxrlib](https://github.com/4creators/jxrlib):
|
||||
> the upstream jxrlib is dead and there is no "hope" they will fix the issues.
|
||||
> **You should not enable it unless you know what you are doing.**
|
||||
|
||||
The following defines can be defined in cmake to modify the behavior of the
|
||||
plugin:
|
||||
@@ -441,6 +485,13 @@ plugin:
|
||||
- `PSD_NATIVE_CMYK_SUPPORT_DISABLED`: disable native support for CMYK images
|
||||
when compiled with Qt 6.8+
|
||||
|
||||
The plugin can set the following additional metadata:
|
||||
- `PSDDuotoneOptions`: Byte array in hexadecimal format of color data of the
|
||||
duotone specification (the format of which is not documented). From the PSD
|
||||
specification: *"Other applications that read Photoshop files can treat a
|
||||
duotone image as a gray image, and just preserve the contents of the duotone
|
||||
information when reading and writing the file."*
|
||||
|
||||
### The RAW plugin
|
||||
|
||||
Loading RAW images always requires a conversion. To allow the user to
|
||||
|
||||
@@ -86,6 +86,7 @@ kimageformats_read_tests(
|
||||
ras
|
||||
rgb
|
||||
sct
|
||||
tim
|
||||
tga
|
||||
)
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ Depending on the format, you can specify the following additional options.
|
||||
|
||||
- `--help`: Displays help on commandline options.
|
||||
- `--fuzz <max>`: The fuzziness. Used to add some deviation in ARGB data
|
||||
(nornally used on lossy codec).
|
||||
(normally used on lossy codec).
|
||||
- `--perceptive-fuzz`: Used to scale dynamically the fuzziness based on
|
||||
the alpha channel value. This is useful on images with pre-multiplied and
|
||||
small alphas. Qt can use different roundings based on optimizations resulting
|
||||
|
||||
@@ -180,6 +180,7 @@ HANDLER_TYPES="ANIHandler ani
|
||||
RAWHandler raw
|
||||
RGBHandler rgb
|
||||
ScitexHandler sct
|
||||
TIMHandler tim
|
||||
TGAHandler tga
|
||||
XCFHandler xcf"
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
Usage:
|
||||
python infra/helper.py build_image kimageformats
|
||||
python infra/helper.py build_fuzzers --sanitizer undefined|address|memory kimageformats
|
||||
python infra/helper.py run_fuzzer kimageformats kimgio_[ani|avif|dds|exr|hdr|heif|iff|jp2|jxl|jxr|kra|ora|pcx|pfm|pic|psd|pxr|qoi|ras|raw|rgb|sct|tga|xcf]_fuzzer
|
||||
python infra/helper.py run_fuzzer kimageformats kimgio_[ani|avif|dds|exr|hdr|heif|iff|jp2|jxl|jxr|kra|ora|pcx|pfm|pic|psd|pxr|qoi|ras|raw|rgb|sct|tim|tga|xcf]_fuzzer
|
||||
*/
|
||||
|
||||
#include <QBuffer>
|
||||
@@ -52,6 +52,7 @@
|
||||
#include "raw_p.h"
|
||||
#include "rgb_p.h"
|
||||
#include "sct_p.h"
|
||||
#include "tim_p.h"
|
||||
#include "tga_p.h"
|
||||
#include "xcf_p.h"
|
||||
|
||||
|
||||
BIN
autotests/read/exr/ps2026_testcard_rgb.exr
Normal file
BIN
autotests/read/exr/ps2026_testcard_rgb.exr
Normal file
Binary file not shown.
15
autotests/read/exr/ps2026_testcard_rgb.exr.json
Normal file
15
autotests/read/exr/ps2026_testcard_rgb.exr.json
Normal file
@@ -0,0 +1,15 @@
|
||||
[
|
||||
{
|
||||
"fileName" : "ps2026_testcard_rgb.png",
|
||||
"colorSpace" : {
|
||||
"description" : "sRGB build-in (Profilo RGB lineare)",
|
||||
"primaries" : "SRgb",
|
||||
"transferFunction" : "Linear",
|
||||
"gamma" : 1
|
||||
},
|
||||
"resolution" : {
|
||||
"dotsPerMeterX" : 3937,
|
||||
"dotsPerMeterY" : 3937
|
||||
}
|
||||
}
|
||||
]
|
||||
BIN
autotests/read/exr/ps2026_testcard_rgb.png
Normal file
BIN
autotests/read/exr/ps2026_testcard_rgb.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
@@ -2,7 +2,7 @@
|
||||
{
|
||||
"fileName" : "rgb-gimp.png",
|
||||
"colorSpace" : {
|
||||
"description" : "",
|
||||
"description" : "Embedded RGB (linear)",
|
||||
"primaries" : "Custom",
|
||||
"transferFunction" : "Linear",
|
||||
"gamma" : 1
|
||||
|
||||
Binary file not shown.
@@ -14,7 +14,7 @@
|
||||
},
|
||||
{
|
||||
"key" : "Software" ,
|
||||
"value" : "LIFE Pro 2.18.10"
|
||||
"value" : "LIFE Pro 2.20.35"
|
||||
},
|
||||
{
|
||||
"key" : "Altitude",
|
||||
@@ -32,6 +32,10 @@
|
||||
"key" : "Description",
|
||||
"value" : "TV broadcast test image."
|
||||
},
|
||||
{
|
||||
"key" : "HostComputer",
|
||||
"value" : "Windows 11 Enterprise (25H2)"
|
||||
},
|
||||
{
|
||||
"key" : "Latitude",
|
||||
"value" : "44.6478"
|
||||
|
||||
BIN
autotests/read/tim/testcard_idx4.png
Normal file
BIN
autotests/read/tim/testcard_idx4.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.5 KiB |
BIN
autotests/read/tim/testcard_idx4.tim
Normal file
BIN
autotests/read/tim/testcard_idx4.tim
Normal file
Binary file not shown.
BIN
autotests/read/tim/testcard_idx8.png
Normal file
BIN
autotests/read/tim/testcard_idx8.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.4 KiB |
BIN
autotests/read/tim/testcard_idx8.tim
Normal file
BIN
autotests/read/tim/testcard_idx8.tim
Normal file
Binary file not shown.
BIN
autotests/read/tim/testcard_rgb16.png
Normal file
BIN
autotests/read/tim/testcard_rgb16.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.1 KiB |
BIN
autotests/read/tim/testcard_rgb16.tim
Normal file
BIN
autotests/read/tim/testcard_rgb16.tim
Normal file
Binary file not shown.
BIN
autotests/read/tim/testcard_rgb24.png
Normal file
BIN
autotests/read/tim/testcard_rgb24.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.4 KiB |
BIN
autotests/read/tim/testcard_rgb24.tim
Normal file
BIN
autotests/read/tim/testcard_rgb24.tim
Normal file
Binary file not shown.
@@ -13,14 +13,18 @@
|
||||
"key" : "ModificationDate",
|
||||
"value" : "2025-02-14T15:58:44+01:00"
|
||||
},
|
||||
{
|
||||
"key" : "Software" ,
|
||||
"value" : "Adobe Photoshop 26.2 (Windows)"
|
||||
},
|
||||
{
|
||||
"key" : "Altitude",
|
||||
"value" : "34"
|
||||
},
|
||||
{
|
||||
"key" : "Title",
|
||||
"value" : "A test"
|
||||
},
|
||||
{
|
||||
"key" : "Software",
|
||||
"value" : "KImageFormats write test"
|
||||
},
|
||||
{
|
||||
"key" : "Author",
|
||||
"value" : "KDE Project"
|
||||
@@ -45,6 +49,10 @@
|
||||
"key" : "LensModel",
|
||||
"value" : "A1234"
|
||||
},
|
||||
{
|
||||
"key" : "LensSerialNumber",
|
||||
"value" : "S/N:1234567"
|
||||
},
|
||||
{
|
||||
"key" : "Longitude",
|
||||
"value" : "10.9254"
|
||||
@@ -56,6 +64,50 @@
|
||||
{
|
||||
"key" : "Model",
|
||||
"value" : "KImageFormats"
|
||||
},
|
||||
{
|
||||
"key" : "SerialNumber",
|
||||
"value" : "S/N:7654321"
|
||||
},
|
||||
{
|
||||
"key" : "Speed",
|
||||
"value" : "13.2"
|
||||
},
|
||||
{
|
||||
"key" : "DigitalZoomRatio",
|
||||
"value" : "3.4"
|
||||
},
|
||||
{
|
||||
"key" : "ExposureMode",
|
||||
"value" : "2"
|
||||
},
|
||||
{
|
||||
"key" : "ExposureProgram",
|
||||
"value" : "6"
|
||||
},
|
||||
{
|
||||
"key" : "ExposureTime",
|
||||
"value" : "0.004"
|
||||
},
|
||||
{
|
||||
"key" : "Flash",
|
||||
"value" : "16"
|
||||
},
|
||||
{
|
||||
"key" : "FNumber",
|
||||
"value" : "1.6"
|
||||
},
|
||||
{
|
||||
"key" : "FocalLength",
|
||||
"value" : "5.96"
|
||||
},
|
||||
{
|
||||
"key" : "ISOSpeedRatings",
|
||||
"value" : "50"
|
||||
},
|
||||
{
|
||||
"key" : "WhiteBalance",
|
||||
"value" : "1"
|
||||
}
|
||||
],
|
||||
"resolution" : {
|
||||
|
||||
@@ -41,6 +41,10 @@
|
||||
"key" : "LensModel",
|
||||
"value" : "A1234"
|
||||
},
|
||||
{
|
||||
"key" : "LensSerialNumber",
|
||||
"value" : "S/N:1234567"
|
||||
},
|
||||
{
|
||||
"key" : "Longitude",
|
||||
"value" : "10.9254"
|
||||
@@ -52,6 +56,50 @@
|
||||
{
|
||||
"key" : "Model",
|
||||
"value" : "KImageFormats"
|
||||
},
|
||||
{
|
||||
"key" : "SerialNumber",
|
||||
"value" : "S/N:7654321"
|
||||
},
|
||||
{
|
||||
"key" : "Speed",
|
||||
"value" : "13.2"
|
||||
},
|
||||
{
|
||||
"key" : "DigitalZoomRatio",
|
||||
"value" : "3.4"
|
||||
},
|
||||
{
|
||||
"key" : "ExposureMode",
|
||||
"value" : "2"
|
||||
},
|
||||
{
|
||||
"key" : "ExposureProgram",
|
||||
"value" : "6"
|
||||
},
|
||||
{
|
||||
"key" : "ExposureTime",
|
||||
"value" : "0.004"
|
||||
},
|
||||
{
|
||||
"key" : "Flash",
|
||||
"value" : "16"
|
||||
},
|
||||
{
|
||||
"key" : "FNumber",
|
||||
"value" : "1.6"
|
||||
},
|
||||
{
|
||||
"key" : "FocalLength",
|
||||
"value" : "5.96"
|
||||
},
|
||||
{
|
||||
"key" : "ISOSpeedRatings",
|
||||
"value" : "50"
|
||||
},
|
||||
{
|
||||
"key" : "WhiteBalance",
|
||||
"value" : "1"
|
||||
}
|
||||
],
|
||||
"resolution" : {
|
||||
|
||||
@@ -62,7 +62,7 @@ void setOptionalInfo(QImage &image, const QString &suffix)
|
||||
|
||||
// Set metadata
|
||||
auto meta = obj.value("metadata").toArray();
|
||||
for (auto jv : meta) {
|
||||
for (auto &&jv : meta) {
|
||||
auto obj = jv.toObject();
|
||||
auto key = obj.value("key").toString();
|
||||
auto val = obj.value("value").toString();
|
||||
@@ -106,7 +106,7 @@ bool checkOptionalInfo(QImage &image, const QString &suffix)
|
||||
|
||||
// Test metadata
|
||||
auto meta = obj.value("metadata").toArray();
|
||||
for (auto jv : meta) {
|
||||
for (auto &&jv : meta) {
|
||||
auto obj = jv.toObject();
|
||||
auto key = obj.value("key").toString();
|
||||
auto val = obj.value("value").toString();
|
||||
|
||||
@@ -137,6 +137,10 @@ kimageformats_add_plugin(kimg_sct SOURCES sct.cpp)
|
||||
|
||||
##################################
|
||||
|
||||
kimageformats_add_plugin(kimg_tim SOURCES tim.cpp)
|
||||
|
||||
##################################
|
||||
|
||||
kimageformats_add_plugin(kimg_tga SOURCES tga.cpp microexif.cpp scanlineconverter.cpp)
|
||||
|
||||
##################################
|
||||
|
||||
@@ -513,21 +513,12 @@ bool QAVIFHandler::decode_one_frame()
|
||||
#else
|
||||
switch (m_decoder->image->imir.axis) {
|
||||
#endif
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 9, 0)
|
||||
case 0: // top-to-bottom
|
||||
result = result.mirrored(false, true);
|
||||
break;
|
||||
case 1: // left-to-right
|
||||
result = result.mirrored(true, false);
|
||||
break;
|
||||
#else
|
||||
case 0: // top-to-bottom
|
||||
result = result.flipped(Qt::Vertical);
|
||||
break;
|
||||
case 1: // left-to-right
|
||||
result = result.flipped(Qt::Horizontal);
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1180,7 +1171,7 @@ bool QAVIFHandler::jumpToNextImage()
|
||||
if (m_decoder->imageIndex >= 0) {
|
||||
if (m_decoder->imageCount < 2) {
|
||||
m_parseState = ParseAvifSuccess;
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (m_decoder->imageIndex >= m_decoder->imageCount - 1) { // start from beginning
|
||||
|
||||
@@ -3092,7 +3092,8 @@ quint32 IDATChunk::strideSize(const IHDRChunk *header) const
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto rs = (header->width() * header->depth() + 7) / 8;
|
||||
// width() and depth() are at most 65535
|
||||
auto rs = (quint32(header->width()) * header->depth() + 7) / 8;
|
||||
|
||||
// No padding bytes are inserted in the data.
|
||||
if (header->model() == IHDRChunk::Rgb888) {
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
#include <ImathBox.h>
|
||||
#include <ImfArray.h>
|
||||
#include <ImfBoxAttribute.h>
|
||||
#include <ImfOpaqueAttribute.h>
|
||||
#include <ImfChannelListAttribute.h>
|
||||
#include <ImfCompressionAttribute.h>
|
||||
#include <ImfConvert.h>
|
||||
@@ -227,25 +228,57 @@ static QImage::Format imageFormat(const Imf::RgbaInputFile &file)
|
||||
|
||||
/*!
|
||||
* \brief viewList
|
||||
* \param header
|
||||
* \param header The image header.
|
||||
* \return The list of available views.
|
||||
* \note This plugin does not support compositing layers which are returned as single images.
|
||||
*/
|
||||
static QStringList viewList(const Imf::Header &h)
|
||||
{
|
||||
QStringList l;
|
||||
if (auto views = h.findTypedAttribute<Imf::StringVectorAttribute>("multiView")) {
|
||||
// Internally OpenEXR first checks if the multiView attribute is present:
|
||||
// if present, I have no other layers.
|
||||
for (auto &&v : views->value()) {
|
||||
l << QString::fromStdString(v);
|
||||
}
|
||||
} else {
|
||||
// Recent versions of Photoshop save images by setting the layer.
|
||||
// Channels are named Layer 1.A, Layer 1.B, etc., so I have to set
|
||||
// the layer or the images will appear black.
|
||||
auto channels = h.channels();
|
||||
for (auto i = channels.begin(); i != channels.end(); ++i) {
|
||||
auto name = QString::fromLatin1(i.name(), -1);
|
||||
auto idx = name.indexOf(QChar(u'.'));
|
||||
if (idx > -1)
|
||||
l << name.left(idx);
|
||||
}
|
||||
l.removeDuplicates();
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
static QString setLayerName(Imf::RgbaInputFile &file, qint32 imageNumber = -1)
|
||||
{
|
||||
// set the image to load
|
||||
QString layerName;
|
||||
auto &&header = file.header();
|
||||
if (imageNumber > -1) {
|
||||
auto views = viewList(header);
|
||||
if (imageNumber < views.count())
|
||||
layerName = views.at(imageNumber);
|
||||
}
|
||||
// set the layer name
|
||||
if (!layerName.isEmpty()) {
|
||||
file.setLayerName(layerName.toStdString());
|
||||
}
|
||||
return layerName;
|
||||
}
|
||||
|
||||
#ifdef QT_DEBUG
|
||||
static void printAttributes(const Imf::Header &h)
|
||||
{
|
||||
for (auto i = h.begin(); i != h.end(); ++i) {
|
||||
qCDebug(LOG_EXRPLUGIN) << i.name();
|
||||
qCDebug(LOG_EXRPLUGIN) << i.name() << i.attribute().typeName();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -340,15 +373,29 @@ static void readColorSpace(const Imf::Header &header, QImage &image)
|
||||
{
|
||||
// final color operations
|
||||
QColorSpace cs;
|
||||
if (auto chroma = header.findTypedAttribute<Imf::ChromaticitiesAttribute>("chromaticities")) {
|
||||
auto &&v = chroma->value();
|
||||
cs = QColorSpace(QPointF(v.white.x, v.white.y),
|
||||
QPointF(v.red.x, v.red.y),
|
||||
QPointF(v.green.x, v.green.y),
|
||||
QPointF(v.blue.x, v.blue.y),
|
||||
QColorSpace::TransferFunction::Linear);
|
||||
|
||||
// Photoshop 2026 allow to save the ICC profile as "iccProfile" attribute
|
||||
if (auto iccProfile = header.findTypedAttribute<Imf::OpaqueAttribute>("iccProfile")) {
|
||||
auto &&v = iccProfile->data();
|
||||
cs = QColorSpace::fromIccProfile(QByteArray::fromRawData(v, v.size()));
|
||||
}
|
||||
|
||||
if (!cs.isValid()) {
|
||||
// Creating the ICC profile from Chromaticities
|
||||
if (auto chroma = header.findTypedAttribute<Imf::ChromaticitiesAttribute>("chromaticities")) {
|
||||
auto &&v = chroma->value();
|
||||
cs = QColorSpace(QPointF(v.white.x, v.white.y),
|
||||
QPointF(v.red.x, v.red.y),
|
||||
QPointF(v.green.x, v.green.y),
|
||||
QPointF(v.blue.x, v.blue.y),
|
||||
QColorSpace::TransferFunction::Linear);
|
||||
if (cs.isValid())
|
||||
cs.setDescription(QStringLiteral("Embedded RGB (linear)"));
|
||||
}
|
||||
}
|
||||
|
||||
if (!cs.isValid()) {
|
||||
// Use a linear profile
|
||||
cs = QColorSpace(QColorSpace::SRgbLinear);
|
||||
}
|
||||
image.setColorSpace(cs);
|
||||
@@ -377,12 +424,7 @@ bool EXRHandler::read(QImage *outImage)
|
||||
auto &&header = file.header();
|
||||
|
||||
// set the image to load
|
||||
if (m_imageNumber > -1) {
|
||||
auto views = viewList(header);
|
||||
if (m_imageNumber < views.count()) {
|
||||
file.setLayerName(views.at(m_imageNumber).toStdString());
|
||||
}
|
||||
}
|
||||
auto layerName = setLayerName(file, m_imageNumber);
|
||||
|
||||
// get image info
|
||||
Imath::Box2i dw = file.dataWindow();
|
||||
@@ -401,6 +443,9 @@ bool EXRHandler::read(QImage *outImage)
|
||||
qCWarning(LOG_EXRPLUGIN) << "Failed to allocate image, invalid size?" << QSize(width, height);
|
||||
return false;
|
||||
}
|
||||
if (!layerName.isEmpty()) {
|
||||
image.setText(QStringLiteral("EXRLayerName"), layerName);
|
||||
}
|
||||
|
||||
Imf::Array2D<Imf::Rgba> pixels;
|
||||
pixels.resizeErase(EXR_LINES_PER_BLOCK, width);
|
||||
@@ -688,12 +733,7 @@ QVariant EXRHandler::option(ImageOption option) const
|
||||
try {
|
||||
K_IStream istr(d);
|
||||
Imf::RgbaInputFile file(istr);
|
||||
if (m_imageNumber > -1) { // set the image to read
|
||||
auto views = viewList(file.header());
|
||||
if (m_imageNumber < views.count()) {
|
||||
file.setLayerName(views.at(m_imageNumber).toStdString());
|
||||
}
|
||||
}
|
||||
setLayerName(file, m_imageNumber);
|
||||
Imath::Box2i dw = file.dataWindow();
|
||||
v = QVariant(QSize(dw.max.x - dw.min.x + 1, dw.max.y - dw.min.y + 1));
|
||||
} catch (const std::exception &) {
|
||||
@@ -713,6 +753,7 @@ QVariant EXRHandler::option(ImageOption option) const
|
||||
try {
|
||||
K_IStream istr(d);
|
||||
Imf::RgbaInputFile file(istr);
|
||||
setLayerName(file, m_imageNumber);
|
||||
v = QVariant::fromValue(imageFormat(file));
|
||||
} catch (const std::exception &) {
|
||||
// broken file or unsupported version
|
||||
@@ -787,12 +828,9 @@ bool EXRHandler::canRead(QIODevice *device)
|
||||
return false;
|
||||
}
|
||||
|
||||
#if OPENEXR_VERSION_MAJOR == 3 && OPENEXR_VERSION_MINOR > 2
|
||||
// openexpr >= 3.3 uses seek and tell extensively
|
||||
if (device->isSequential()) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
const QByteArray head = device->peek(4);
|
||||
|
||||
|
||||
@@ -86,6 +86,8 @@ private:
|
||||
* - 7: lossy 4-by-4 pixel block compression, fields are compressed more
|
||||
* - 8: lossy DCT based compression, in blocks of 32 scanlines. More efficient for partial buffer access.
|
||||
* - 9: lossy DCT based compression, in blocks of 256 scanlines. More efficient space wise and faster to decode full frames than DWAA_COMPRESSION.
|
||||
* - 10: High-Throughput JPEG2000 (HTJ2K), 256 scanlines (requires OpenEXR 3.4+).
|
||||
* - 11: High-Throughput JPEG2000 (HTJ2K), 32 scanlines (requires OpenEXR 3.4+).
|
||||
*/
|
||||
qint32 m_compressionRatio;
|
||||
|
||||
|
||||
@@ -54,10 +54,10 @@ public:
|
||||
{
|
||||
return width() > 0 && height() > 0 && width() <= HDR_MAX_IMAGE_WIDTH && height() <= HDR_MAX_IMAGE_HEIGHT;
|
||||
}
|
||||
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); }
|
||||
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
|
||||
@@ -73,7 +73,7 @@ public:
|
||||
* 0.600 0.150 0.060 0.333 0.333" for red, green, blue
|
||||
* and white, respectively.
|
||||
*/
|
||||
QColorSpace colorSpace() const { return(m_colorSpace); }
|
||||
QColorSpace colorSpace() const { return m_colorSpace; }
|
||||
|
||||
/*!
|
||||
* \brief exposure
|
||||
@@ -247,7 +247,7 @@ static bool Read_Old_Line(uchar *image, int width, QDataStream &s)
|
||||
s >> image[2];
|
||||
s >> image[3];
|
||||
|
||||
if (s.atEnd()) {
|
||||
if (s.status() != QDataStream::Ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -340,20 +340,24 @@ static bool LoadHDR(QDataStream &s, const Header& h, QImage &img)
|
||||
|
||||
// determine scanline type
|
||||
if ((width < MINELEN) || (MAXELEN < width)) {
|
||||
Read_Old_Line(image, width, s);
|
||||
if (!Read_Old_Line(image, width, s)) {
|
||||
return false;
|
||||
}
|
||||
RGBE_To_QRgbLine(image, scanline, h);
|
||||
continue;
|
||||
}
|
||||
|
||||
s >> val;
|
||||
|
||||
if (s.atEnd()) {
|
||||
return true;
|
||||
if (s.status() != QDataStream::Ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (val != 2) {
|
||||
s.device()->ungetChar(val);
|
||||
Read_Old_Line(image, width, s);
|
||||
if (!Read_Old_Line(image, width, s)) {
|
||||
return false;
|
||||
}
|
||||
RGBE_To_QRgbLine(image, scanline, h);
|
||||
continue;
|
||||
}
|
||||
@@ -362,13 +366,15 @@ static bool LoadHDR(QDataStream &s, const Header& h, QImage &img)
|
||||
s >> image[2];
|
||||
s >> image[3];
|
||||
|
||||
if (s.atEnd()) {
|
||||
return true;
|
||||
if (s.status() != QDataStream::Ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((image[1] != 2) || (image[2] & 128)) {
|
||||
image[0] = 2;
|
||||
Read_Old_Line(image + 4, width - 1, s);
|
||||
if (!Read_Old_Line(image + 4, width - 1, s)) {
|
||||
return false;
|
||||
}
|
||||
RGBE_To_QRgbLine(image, scanline, h);
|
||||
continue;
|
||||
}
|
||||
@@ -382,7 +388,7 @@ static bool LoadHDR(QDataStream &s, const Header& h, QImage &img)
|
||||
for (int i = 0, len = int(lineArray.size()); i < 4; i++) {
|
||||
for (int j = 0; j < width;) {
|
||||
s >> code;
|
||||
if (s.atEnd()) {
|
||||
if (s.status() != QDataStream::Ok) {
|
||||
qCDebug(HDRPLUGIN) << "Truncated HDR file";
|
||||
return false;
|
||||
}
|
||||
@@ -510,7 +516,7 @@ bool HDRHandler::canRead(QIODevice *device)
|
||||
}
|
||||
|
||||
// the .pic taken from official test cases does not start with this string but can be loaded.
|
||||
if(device->peek(11) == "#?RADIANCE\n" || device->peek(7) == "#?RGBE\n") {
|
||||
if (device->peek(11) == "#?RADIANCE\n" || device->peek(7) == "#?RGBE\n") {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -474,11 +474,7 @@ bool IFFHandler::readMayaImage(QImage *image)
|
||||
painter.setCompositionMode(QPainter::CompositionMode_Source);
|
||||
painter.drawImage(tp, ti);
|
||||
}
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 9, 0)
|
||||
img.mirror(false, true);
|
||||
#else
|
||||
img.flip(Qt::Orientation::Vertical);
|
||||
#endif
|
||||
addMetadata(img, form);
|
||||
|
||||
*image = img;
|
||||
|
||||
@@ -35,14 +35,19 @@
|
||||
#include <cstring>
|
||||
|
||||
Q_DECLARE_LOGGING_CATEGORY(LOG_JXRPLUGIN)
|
||||
|
||||
#ifdef QT_DEBUG
|
||||
Q_LOGGING_CATEGORY(LOG_JXRPLUGIN, "kf.imageformats.plugins.jxr", QtDebugMsg)
|
||||
#else
|
||||
Q_LOGGING_CATEGORY(LOG_JXRPLUGIN, "kf.imageformats.plugins.jxr", QtWarningMsg)
|
||||
#endif
|
||||
|
||||
/*!
|
||||
* Support for float images
|
||||
*
|
||||
* NOTE: Float images have values greater than 1 so they need an additional in place conversion.
|
||||
*/
|
||||
// #define JXR_DENY_FLOAT_IMAGE
|
||||
// #define JXR_DENY_FLOAT_IMAGE // default commented
|
||||
|
||||
/*!
|
||||
* Remove the needs of additional memory by disabling the conversion between
|
||||
@@ -112,28 +117,35 @@ public:
|
||||
, m_transformations(QImageIOHandler::TransformationNone)
|
||||
{
|
||||
m_tempDir = QSharedPointer<QTemporaryDir>(new QTemporaryDir);
|
||||
if (PKCreateFactory(&pFactory, PK_SDK_VERSION) == WMP_errSuccess) {
|
||||
PKCreateCodecFactory(&pCodecFactory, WMP_SDK_VERSION);
|
||||
}
|
||||
if (pFactory == nullptr || pCodecFactory == nullptr) {
|
||||
qCWarning(LOG_JXRPLUGIN) << "JXRHandlerPrivate::JXRHandlerPrivate() initialization error of JXR library!";
|
||||
if (auto err = PKCreateFactory(&pFactory, PK_SDK_VERSION)) {
|
||||
qCCritical(LOG_JXRPLUGIN) << "JXRHandlerPrivate::JXRHandlerPrivate() error while initializing the JXR factory:" << err;
|
||||
} else if (auto err = PKCreateCodecFactory(&pCodecFactory, WMP_SDK_VERSION)) {
|
||||
qCCritical(LOG_JXRPLUGIN) << "JXRHandlerPrivate::JXRHandlerPrivate() error while initializing the JXR codec factory:" << err;
|
||||
}
|
||||
}
|
||||
JXRHandlerPrivate(const JXRHandlerPrivate &other) = default;
|
||||
|
||||
~JXRHandlerPrivate()
|
||||
{
|
||||
if (pCodecFactory) {
|
||||
PKCreateCodecFactory_Release(&pCodecFactory);
|
||||
}
|
||||
if (pFactory) {
|
||||
PKCreateFactory_Release(&pFactory);
|
||||
}
|
||||
if (pDecoder) {
|
||||
PKImageDecode_Release(&pDecoder);
|
||||
if (auto err = pDecoder->Release(&pDecoder)) {
|
||||
qCWarning(LOG_JXRPLUGIN) << "JXRHandlerPrivate::JXRHandlerPrivate() error while releasing the decoder:" << err;
|
||||
}
|
||||
}
|
||||
if (pEncoder) {
|
||||
PKImageEncode_Release(&pEncoder);
|
||||
if (auto err = pEncoder->Release(&pEncoder)) {
|
||||
qCWarning(LOG_JXRPLUGIN) << "JXRHandlerPrivate::JXRHandlerPrivate() error while releasing the encoder:" << err;
|
||||
}
|
||||
}
|
||||
if (pCodecFactory) {
|
||||
if (auto err = pCodecFactory->Release(&pCodecFactory)) {
|
||||
qCWarning(LOG_JXRPLUGIN) << "JXRHandlerPrivate::JXRHandlerPrivate() error while releasing the codec factory:" << err;
|
||||
}
|
||||
}
|
||||
if (pFactory) {
|
||||
if (auto err = pFactory->Release(&pFactory)) {
|
||||
qCWarning(LOG_JXRPLUGIN) << "JXRHandlerPrivate::JXRHandlerPrivate() error while releasing the factory:" << err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -278,8 +290,12 @@ public:
|
||||
PKPixelFormatGUID jxrFormat() const
|
||||
{
|
||||
PKPixelFormatGUID pixelFormatGUID = GUID_PKPixelFormatUndefined;
|
||||
if (pDecoder) {
|
||||
pDecoder->GetPixelFormat(pDecoder, &pixelFormatGUID);
|
||||
if (pDecoder == nullptr) {
|
||||
return pixelFormatGUID;
|
||||
}
|
||||
if (auto err = pDecoder->GetPixelFormat(pDecoder, &pixelFormatGUID)) {
|
||||
qCCritical(LOG_JXRPLUGIN) << "JXRHandlerPrivate::jxrFormat() error while getting pixel format:" << err;
|
||||
return GUID_PKPixelFormatUndefined;
|
||||
}
|
||||
return pixelFormatGUID;
|
||||
}
|
||||
@@ -303,6 +319,12 @@ public:
|
||||
return qtFormat;
|
||||
}
|
||||
|
||||
// *** MCH could be RGB, CMYK ***
|
||||
qtFormat = multichannelFormat(jxrfmt, colorSpace());
|
||||
if (qtFormat != QImage::Format_Invalid) {
|
||||
return qtFormat;
|
||||
}
|
||||
|
||||
// *** CONVERSION WITH THE SAME DEPTH ***
|
||||
// IMPORTANT: For supported conversions see JXRGluePFC.c
|
||||
|
||||
@@ -393,17 +415,20 @@ public:
|
||||
*/
|
||||
QSize imageSize() const
|
||||
{
|
||||
if (pDecoder) {
|
||||
qint32 w, h;
|
||||
pDecoder->GetSize(pDecoder, &w, &h);
|
||||
if (w > JXR_MAX_IMAGE_WIDTH || h > JXR_MAX_IMAGE_HEIGHT || w < 1 || h < 1) {
|
||||
qCCritical(LOG_JXRPLUGIN) << "JXRHandlerPrivate::imageSize() Maximum image size is limited to" << JXR_MAX_IMAGE_WIDTH << "x"
|
||||
<< JXR_MAX_IMAGE_HEIGHT << "pixels";
|
||||
return {};
|
||||
}
|
||||
return QSize(w, h);
|
||||
if (pDecoder == nullptr) {
|
||||
return {};
|
||||
}
|
||||
return {};
|
||||
qint32 w = 0, h = 0;
|
||||
if (auto err = pDecoder->GetSize(pDecoder, &w, &h)) {
|
||||
qCCritical(LOG_JXRPLUGIN) << "JXRHandlerPrivate::imageSize() error while getting the image size:" << err;
|
||||
return {};
|
||||
}
|
||||
if (w > JXR_MAX_IMAGE_WIDTH || h > JXR_MAX_IMAGE_HEIGHT || w < 1 || h < 1) {
|
||||
qCCritical(LOG_JXRPLUGIN) << "JXRHandlerPrivate::imageSize() Maximum image size is limited to" << JXR_MAX_IMAGE_WIDTH << "x"
|
||||
<< JXR_MAX_IMAGE_HEIGHT << "pixels";
|
||||
return {};
|
||||
}
|
||||
return QSize(w, h);
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -416,8 +441,8 @@ public:
|
||||
if (pDecoder == nullptr) {
|
||||
return cs;
|
||||
}
|
||||
quint32 size;
|
||||
if (!pDecoder->GetColorContext(pDecoder, nullptr, &size) && size) {
|
||||
quint32 size = 0;
|
||||
if (!pDecoder->GetColorContext(pDecoder, nullptr, &size) && size > 0 && size < kMaxQVectorSize) {
|
||||
QByteArray ba(size, 0);
|
||||
if (!pDecoder->GetColorContext(pDecoder, reinterpret_cast<quint8 *>(ba.data()), &size)) {
|
||||
cs = QColorSpace::fromIccProfile(ba);
|
||||
@@ -437,7 +462,7 @@ public:
|
||||
return xmp;
|
||||
}
|
||||
#ifdef JXR_ENABLE_ADVANCED_METADATA
|
||||
quint32 size;
|
||||
quint32 size = 0;
|
||||
if (!PKImageDecode_GetXMPMetadata_WMP(pDecoder, nullptr, &size) && size > 0 && size < JXR_MAX_METADATA_SIZE) {
|
||||
QByteArray ba(size, 0);
|
||||
if (!PKImageDecode_GetXMPMetadata_WMP(pDecoder, reinterpret_cast<quint8 *>(ba.data()), &size)) {
|
||||
@@ -498,7 +523,7 @@ public:
|
||||
}
|
||||
auto host = hostComputer();
|
||||
if (!host.isEmpty()) {
|
||||
image.setText(QStringLiteral(META_KEY_HOSTCOMPUTER), capt);
|
||||
image.setText(QStringLiteral(META_KEY_HOSTCOMPUTER), host);
|
||||
}
|
||||
auto docn = documentName();
|
||||
if (!docn.isEmpty()) {
|
||||
@@ -556,7 +581,11 @@ public:
|
||||
if (device == nullptr || pEncoder == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (auto err = PKImageEncode_Release(&pEncoder)) {
|
||||
if (auto err = pEncoder->Terminate(pEncoder)) {
|
||||
qCWarning(LOG_JXRPLUGIN) << "JXRHandlerPrivate::finalizeWriting() error while terminating the encoder:" << err;
|
||||
return false;
|
||||
}
|
||||
if (auto err = pEncoder->Release(&pEncoder)) {
|
||||
qCWarning(LOG_JXRPLUGIN) << "JXRHandlerPrivate::finalizeWriting() error while releasing the encoder:" << err;
|
||||
return false;
|
||||
}
|
||||
@@ -796,6 +825,47 @@ public:
|
||||
return GUID_PKPixelFormatUndefined;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief multichannelFormat
|
||||
* I can only decide how to interpret multichannels by checking the color profile.
|
||||
* If it's not present, I assume CMYK for 4 channels and RGB for 3 channels (like
|
||||
* Windows does).
|
||||
* \param jxrFormat Format to be converted.
|
||||
* \param cs The color space of the image.
|
||||
* \return A valid Qt format or QImage::Format_Invalid if there is no match
|
||||
*/
|
||||
static QImage::Format multichannelFormat(const PKPixelFormatGUID &jxrFormat, const QColorSpace& cs)
|
||||
{
|
||||
auto model = QColorSpace::ColorModel::Undefined;
|
||||
if (cs.isValid()) {
|
||||
model = cs.colorModel();
|
||||
} else if (!cs.iccProfile().isEmpty()) {
|
||||
model = QColorSpace::ColorModel::Gray; // means invalid
|
||||
}
|
||||
|
||||
if (IsEqualGUID(GUID_PKPixelFormat24bpp3Channels, jxrFormat)) {
|
||||
if (model == QColorSpace::ColorModel::Rgb || model == QColorSpace::ColorModel::Undefined)
|
||||
return QImage::Format_RGB888;
|
||||
}
|
||||
|
||||
if (IsEqualGUID(GUID_PKPixelFormat32bpp4Channels, jxrFormat)) {
|
||||
if (model == QColorSpace::ColorModel::Cmyk || model == QColorSpace::ColorModel::Undefined)
|
||||
return QImage::Format_CMYK8888;
|
||||
}
|
||||
|
||||
if (IsEqualGUID(GUID_PKPixelFormat32bpp3ChannelsAlpha, jxrFormat)) {
|
||||
if (model == QColorSpace::ColorModel::Rgb || model == QColorSpace::ColorModel::Undefined)
|
||||
return QImage::Format_RGBA8888;
|
||||
}
|
||||
|
||||
if (IsEqualGUID(GUID_PKPixelFormat64bpp3ChannelsAlpha, jxrFormat)) {
|
||||
if (model == QColorSpace::ColorModel::Rgb || model == QColorSpace::ColorModel::Undefined)
|
||||
return QImage::Format_RGBA64;
|
||||
}
|
||||
|
||||
return QImage::Format_Invalid;
|
||||
}
|
||||
|
||||
private:
|
||||
static QList<std::pair<QImage::Format, PKPixelFormatGUID>> exactMatchingFormats()
|
||||
{
|
||||
@@ -967,7 +1037,7 @@ bool JXRHandler::read(QImage *outImage)
|
||||
}
|
||||
|
||||
// resolution
|
||||
float hres, vres;
|
||||
float hres = 0, vres = 0;
|
||||
if (auto err = d->pDecoder->GetResolution(d->pDecoder, &hres, &vres)) {
|
||||
qCWarning(LOG_JXRPLUGIN) << "JXRHandler::read() error while reading resolution:" << err;
|
||||
} else {
|
||||
@@ -999,14 +1069,18 @@ bool JXRHandler::read(QImage *outImage)
|
||||
return false;
|
||||
}
|
||||
if (auto err = pConverter->Initialize(pConverter, d->pDecoder, nullptr, convFmt)) {
|
||||
PKFormatConverter_Release(&pConverter);
|
||||
qCWarning(LOG_JXRPLUGIN) << "JXRHandler::read() unable to initialize the converter:" << err;
|
||||
if (auto err = pConverter->Release(&pConverter)) {
|
||||
qCWarning(LOG_JXRPLUGIN) << "JXRHandler::read() error while releasing the converter:" << err;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (d->pDecoder->WMP.wmiI.cBitsPerUnit == size_t(img.depth())) { // in place conversion
|
||||
if (auto err = pConverter->Copy(pConverter, &rect, img.bits(), img.bytesPerLine())) {
|
||||
PKFormatConverter_Release(&pConverter);
|
||||
qCWarning(LOG_JXRPLUGIN) << "JXRHandler::read() unable to copy converted data:" << err;
|
||||
if (auto err = pConverter->Release(&pConverter)) {
|
||||
qCWarning(LOG_JXRPLUGIN) << "JXRHandler::read() error while releasing the converter:" << err;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} else { // additional buffer needed
|
||||
@@ -1015,19 +1089,26 @@ bool JXRHandler::read(QImage *outImage)
|
||||
qint64 limit = QImageReader::allocationLimit();
|
||||
if (limit && (buffSize + img.sizeInBytes()) > limit * 1024 * 1024) {
|
||||
qCWarning(LOG_JXRPLUGIN) << "JXRHandler::read() unable to covert due to allocation limit set:" << limit << "MiB";
|
||||
if (auto err = pConverter->Release(&pConverter)) {
|
||||
qCWarning(LOG_JXRPLUGIN) << "JXRHandler::read() error while releasing the converter:" << err;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
QVector<quint8> ba(buffSize);
|
||||
if (auto err = pConverter->Copy(pConverter, &rect, ba.data(), convStrideSize)) {
|
||||
PKFormatConverter_Release(&pConverter);
|
||||
qCWarning(LOG_JXRPLUGIN) << "JXRHandler::read() unable to copy converted data:" << err;
|
||||
if (auto err = pConverter->Release(&pConverter)) {
|
||||
qCWarning(LOG_JXRPLUGIN) << "JXRHandler::read() error while releasing the converter:" << err;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
for (qint32 y = 0, h = img.height(); y < h; ++y) {
|
||||
std::memcpy(img.scanLine(y), ba.data() + convStrideSize * y, (std::min)(convStrideSize, qint64(img.bytesPerLine())));
|
||||
}
|
||||
}
|
||||
PKFormatConverter_Release(&pConverter);
|
||||
if (auto err = pConverter->Release(&pConverter)) {
|
||||
qCWarning(LOG_JXRPLUGIN) << "JXRHandler::read() error while releasing the converter:" << err;
|
||||
}
|
||||
}
|
||||
|
||||
// Metadata (e.g.: icc profile, description, etc...)
|
||||
|
||||
@@ -36,17 +36,26 @@
|
||||
#define TIFF_VAL_URES_CENTIMETER 3
|
||||
|
||||
// EXIF 3 specs
|
||||
#define EXIF_EXPOSURETIME 0x829A
|
||||
#define EXIF_FNUMBER 0x829D
|
||||
#define EXIF_EXIFIFD 0x8769
|
||||
#define EXIF_EXPOSUREPROGRAM 0x8822
|
||||
#define EXIF_GPSIFD 0x8825
|
||||
#define EXIF_ISOSPEEDRATINGS 0x8827
|
||||
#define EXIF_EXIFVERSION 0x9000
|
||||
#define EXIF_DATETIMEORIGINAL 0x9003
|
||||
#define EXIF_DATETIMEDIGITIZED 0x9004
|
||||
#define EXIF_OFFSETTIME 0x9010
|
||||
#define EXIF_OFFSETTIMEORIGINAL 0x9011
|
||||
#define EXIF_OFFSETTIMEDIGITIZED 0x9012
|
||||
#define EXIF_FLASH 0x9209
|
||||
#define EXIF_FOCALLENGTH 0x920A
|
||||
#define EXIF_COLORSPACE 0xA001
|
||||
#define EXIF_PIXELXDIM 0xA002
|
||||
#define EXIF_PIXELYDIM 0xA003
|
||||
#define EXIF_EXPOSUREMODE 0xA402
|
||||
#define EXIF_WHITEBALANCE 0xA403
|
||||
#define EXIF_DIGITALZOOMRATIO 0xA404
|
||||
#define EXIF_IMAGEUNIQUEID 0xA420
|
||||
#define EXIF_BODYSERIALNUMBER 0xA431
|
||||
#define EXIF_LENSMAKE 0xA433
|
||||
@@ -64,6 +73,8 @@
|
||||
#define GPS_LONGITUDE 4
|
||||
#define GPS_ALTITUDEREF 5
|
||||
#define GPS_ALTITUDE 6
|
||||
#define GPS_SPEEDREF 12
|
||||
#define GPS_SPEED 13
|
||||
#define GPS_IMGDIRECTIONREF 16
|
||||
#define GPS_IMGDIRECTION 17
|
||||
#define EXIF_TAG_VALUE(n, byteSize) (((n) << 6) | ((byteSize) & 0x3F))
|
||||
@@ -123,16 +134,25 @@ static const KnownTags staticTagTypes = {
|
||||
TagInfo(TIFF_ARTIST, ExifTagType::Utf8),
|
||||
TagInfo(TIFF_DATETIME, ExifTagType::Ascii),
|
||||
TagInfo(TIFF_COPYRIGHT, ExifTagType::Utf8),
|
||||
TagInfo(EXIF_EXPOSURETIME, ExifTagType::Rational),
|
||||
TagInfo(EXIF_FNUMBER, ExifTagType::Rational),
|
||||
TagInfo(EXIF_EXIFIFD, ExifTagType::Long),
|
||||
TagInfo(EXIF_EXPOSUREPROGRAM, ExifTagType::Short),
|
||||
TagInfo(EXIF_GPSIFD, ExifTagType::Long),
|
||||
TagInfo(EXIF_ISOSPEEDRATINGS, ExifTagType::Short),
|
||||
TagInfo(EXIF_DATETIMEORIGINAL, ExifTagType::Ascii),
|
||||
TagInfo(EXIF_OFFSETTIMEDIGITIZED, ExifTagType::Ascii),
|
||||
TagInfo(EXIF_DATETIMEDIGITIZED, ExifTagType::Ascii),
|
||||
TagInfo(EXIF_OFFSETTIME, ExifTagType::Ascii),
|
||||
TagInfo(EXIF_OFFSETTIMEORIGINAL, ExifTagType::Ascii),
|
||||
TagInfo(EXIF_OFFSETTIMEDIGITIZED, ExifTagType::Ascii),
|
||||
TagInfo(EXIF_FLASH, ExifTagType::Short),
|
||||
TagInfo(EXIF_FOCALLENGTH, ExifTagType::Rational),
|
||||
TagInfo(EXIF_COLORSPACE, ExifTagType::Short),
|
||||
TagInfo(EXIF_PIXELXDIM, ExifTagType::Long),
|
||||
TagInfo(EXIF_PIXELYDIM, ExifTagType::Long),
|
||||
TagInfo(EXIF_EXPOSUREMODE, ExifTagType::Short),
|
||||
TagInfo(EXIF_WHITEBALANCE, ExifTagType::Short),
|
||||
TagInfo(EXIF_DIGITALZOOMRATIO, ExifTagType::Rational),
|
||||
TagInfo(EXIF_IMAGEUNIQUEID, ExifTagType::Ascii),
|
||||
TagInfo(EXIF_BODYSERIALNUMBER, ExifTagType::Ascii),
|
||||
TagInfo(EXIF_LENSMAKE, ExifTagType::Utf8),
|
||||
@@ -155,6 +175,8 @@ static const KnownTags staticGpsTagTypes = {
|
||||
TagInfo(GPS_LONGITUDE, ExifTagType::Rational),
|
||||
TagInfo(GPS_ALTITUDEREF, ExifTagType::Byte),
|
||||
TagInfo(GPS_ALTITUDE, ExifTagType::Rational),
|
||||
TagInfo(GPS_SPEEDREF, ExifTagType::Ascii),
|
||||
TagInfo(GPS_SPEED, ExifTagType::Rational),
|
||||
TagInfo(GPS_IMGDIRECTIONREF, ExifTagType::Ascii),
|
||||
TagInfo(GPS_IMGDIRECTION, ExifTagType::Rational)
|
||||
};
|
||||
@@ -317,7 +339,7 @@ static void writeList(QDataStream &ds, const QVariant &value)
|
||||
inline qint32 rationalPrecision(double v)
|
||||
{
|
||||
v = qAbs(v);
|
||||
return 8 - qBound(0, v < 1 ? 8 : int(std::log10(v)), 8);
|
||||
return v < 1 ? 8 : 8 - qBound(0, int(std::log10(v)), 8);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
@@ -884,7 +906,7 @@ QDateTime MicroExif::dateTime() const
|
||||
auto ofTag = exifString(EXIF_OFFSETTIME);
|
||||
if (dt.isValid() && !ofTag.isEmpty())
|
||||
dt.setTimeZone(QTimeZone::fromSecondsAheadOfUtc(timeOffset(ofTag) * 60));
|
||||
return(dt);
|
||||
return dt;
|
||||
}
|
||||
|
||||
void MicroExif::setDateTime(const QDateTime &dt)
|
||||
@@ -904,7 +926,7 @@ QDateTime MicroExif::dateTimeOriginal() const
|
||||
auto ofTag = exifString(EXIF_OFFSETTIMEORIGINAL);
|
||||
if (dt.isValid() && !ofTag.isEmpty())
|
||||
dt.setTimeZone(QTimeZone::fromSecondsAheadOfUtc(timeOffset(ofTag) * 60));
|
||||
return(dt);
|
||||
return dt;
|
||||
}
|
||||
|
||||
void MicroExif::setDateTimeOriginal(const QDateTime &dt)
|
||||
@@ -924,7 +946,7 @@ QDateTime MicroExif::dateTimeDigitized() const
|
||||
auto ofTag = exifString(EXIF_OFFSETTIMEDIGITIZED);
|
||||
if (dt.isValid() && !ofTag.isEmpty())
|
||||
dt.setTimeZone(QTimeZone::fromSecondsAheadOfUtc(timeOffset(ofTag) * 60));
|
||||
return(dt);
|
||||
return dt;
|
||||
}
|
||||
|
||||
void MicroExif::setDateTimeDigitized(const QDateTime &dt)
|
||||
@@ -966,6 +988,138 @@ void MicroExif::setUniqueId(const QUuid &uuid)
|
||||
setExifString(EXIF_IMAGEUNIQUEID, uuid.toString(QUuid::WithoutBraces).replace(QStringLiteral("-"), QString()));
|
||||
}
|
||||
|
||||
double MicroExif::digitalZoomRatio() const
|
||||
{
|
||||
if (!m_exifTags.contains(EXIF_DIGITALZOOMRATIO))
|
||||
return qQNaN();
|
||||
return m_exifTags.value(EXIF_DIGITALZOOMRATIO).toDouble();
|
||||
|
||||
}
|
||||
|
||||
void MicroExif::setDigitalZoomRatio(double zoom)
|
||||
{
|
||||
if (qIsNaN(zoom))
|
||||
m_exifTags.remove(EXIF_DIGITALZOOMRATIO);
|
||||
else
|
||||
m_exifTags.insert(EXIF_DIGITALZOOMRATIO, zoom);
|
||||
}
|
||||
|
||||
quint16 MicroExif::isoSpeedRatings() const
|
||||
{
|
||||
return quint16(m_exifTags.value(EXIF_ISOSPEEDRATINGS).toUInt());
|
||||
}
|
||||
|
||||
void MicroExif::setIsoSpeedRatings(quint16 iso)
|
||||
{
|
||||
if (iso == 0)
|
||||
m_exifTags.remove(EXIF_ISOSPEEDRATINGS);
|
||||
else
|
||||
m_exifTags.insert(EXIF_ISOSPEEDRATINGS, iso);
|
||||
}
|
||||
|
||||
ExposureMode MicroExif::exposureMode() const
|
||||
{
|
||||
auto ok = false;
|
||||
auto v = m_exifTags.value(EXIF_EXPOSUREMODE).toUInt(&ok);
|
||||
return ok ? ExposureMode(v) : ExposureMode::NotSet;
|
||||
}
|
||||
|
||||
void MicroExif::setExposureMode(const ExposureMode &em)
|
||||
{
|
||||
if (em == ExposureMode::NotSet)
|
||||
m_exifTags.remove(EXIF_EXPOSUREMODE);
|
||||
else
|
||||
m_exifTags.insert(EXIF_EXPOSUREMODE, quint16(em));
|
||||
}
|
||||
|
||||
ExposureProgram MicroExif::exposureProgram() const
|
||||
{
|
||||
auto ok = false;
|
||||
auto v = m_exifTags.value(EXIF_EXPOSUREPROGRAM).toUInt(&ok);
|
||||
return ok ? ExposureProgram(v) : ExposureProgram::NotSet;
|
||||
}
|
||||
|
||||
void MicroExif::setExposureProgram(const ExposureProgram &ep)
|
||||
{
|
||||
if (ep == ExposureProgram::NotSet)
|
||||
m_exifTags.remove(EXIF_EXPOSUREPROGRAM);
|
||||
else
|
||||
m_exifTags.insert(EXIF_EXPOSUREPROGRAM, quint16(ep));
|
||||
}
|
||||
|
||||
double MicroExif::exposureTime() const
|
||||
{
|
||||
if (!m_exifTags.contains(EXIF_EXPOSURETIME))
|
||||
return qQNaN();
|
||||
return m_exifTags.value(EXIF_EXPOSURETIME).toDouble();
|
||||
}
|
||||
|
||||
void MicroExif::setExposureTime(double et)
|
||||
{
|
||||
if (qIsNaN(et))
|
||||
m_exifTags.remove(EXIF_EXPOSURETIME);
|
||||
else
|
||||
m_exifTags.insert(EXIF_EXPOSURETIME, et);
|
||||
}
|
||||
|
||||
double MicroExif::fNumber() const
|
||||
{
|
||||
if (!m_exifTags.contains(EXIF_FNUMBER))
|
||||
return qQNaN();
|
||||
return m_exifTags.value(EXIF_FNUMBER).toDouble();
|
||||
}
|
||||
|
||||
void MicroExif::setFNumber(double f)
|
||||
{
|
||||
if (qIsNaN(f))
|
||||
m_exifTags.remove(EXIF_FNUMBER);
|
||||
else
|
||||
m_exifTags.insert(EXIF_FNUMBER, f);
|
||||
}
|
||||
|
||||
double MicroExif::focalLength() const
|
||||
{
|
||||
if (!m_exifTags.contains(EXIF_FOCALLENGTH))
|
||||
return qQNaN();
|
||||
return m_exifTags.value(EXIF_FOCALLENGTH).toDouble();
|
||||
}
|
||||
|
||||
void MicroExif::setFocalLength(double fl)
|
||||
{
|
||||
if (qIsNaN(fl))
|
||||
m_exifTags.remove(EXIF_FOCALLENGTH);
|
||||
else
|
||||
m_exifTags.insert(EXIF_FOCALLENGTH, fl);
|
||||
}
|
||||
|
||||
FlashFlags MicroExif::flash() const
|
||||
{
|
||||
return FlashFlags(m_exifTags.value(EXIF_FLASH).toUInt());
|
||||
}
|
||||
|
||||
void MicroExif::setFlash(const FlashFlags &flash)
|
||||
{
|
||||
if (flash == Flash::NotSet)
|
||||
m_exifTags.remove(EXIF_FLASH);
|
||||
else
|
||||
m_exifTags.insert(EXIF_FLASH, quint16(flash));
|
||||
}
|
||||
|
||||
WhiteBalance MicroExif::whiteBalance() const
|
||||
{
|
||||
auto ok = false;
|
||||
auto v = m_exifTags.value(EXIF_WHITEBALANCE).toUInt(&ok);
|
||||
return ok ? WhiteBalance(v) : WhiteBalance::NotSet;
|
||||
}
|
||||
|
||||
void MicroExif::setWhiteBalance(const WhiteBalance &wb)
|
||||
{
|
||||
if (wb == WhiteBalance::NotSet)
|
||||
m_exifTags.remove(EXIF_WHITEBALANCE);
|
||||
else
|
||||
m_exifTags.insert(EXIF_WHITEBALANCE, quint16(wb));
|
||||
}
|
||||
|
||||
double MicroExif::latitude() const
|
||||
{
|
||||
auto ref = gpsString(GPS_LATITUDEREF).toUpper();
|
||||
@@ -1045,6 +1199,30 @@ void MicroExif::setAltitude(double meters)
|
||||
m_gpsTags.insert(GPS_ALTITUDE, meters);
|
||||
}
|
||||
|
||||
double MicroExif::imageSpeed() const
|
||||
{
|
||||
if (!m_gpsTags.contains(GPS_SPEED))
|
||||
return qQNaN();
|
||||
auto ref = gpsString(GPS_SPEEDREF).toUpper();
|
||||
auto speed = m_gpsTags.value(GPS_SPEED).toDouble();
|
||||
if (ref == QStringLiteral("M"))
|
||||
speed *= 1.60934;
|
||||
else if (ref == QStringLiteral("N"))
|
||||
speed *= 1.852;
|
||||
return speed;
|
||||
}
|
||||
|
||||
void MicroExif::setImageSpeed(double kmh)
|
||||
{
|
||||
if (qIsNaN(kmh)) {
|
||||
m_gpsTags.remove(GPS_SPEEDREF);
|
||||
m_gpsTags.remove(GPS_SPEED);
|
||||
return;
|
||||
}
|
||||
m_gpsTags.insert(GPS_SPEEDREF, QStringLiteral("K"));
|
||||
m_gpsTags.insert(GPS_SPEED, kmh);
|
||||
}
|
||||
|
||||
double MicroExif::imageDirection(bool *isMagnetic) const
|
||||
{
|
||||
auto tmp = false;
|
||||
@@ -1191,6 +1369,58 @@ void MicroExif::updateImageMetadata(QImage &targetImage, bool replaceExisting) c
|
||||
if (!qIsNaN(v))
|
||||
targetImage.setText(QStringLiteral(META_KEY_DIRECTION), QStringLiteral("%1").arg(v, 0, 'g', 9));
|
||||
}
|
||||
if (replaceExisting || targetImage.text(QStringLiteral(META_KEY_SPEED)).isEmpty()) {
|
||||
auto v = imageSpeed();
|
||||
if (!qIsNaN(v))
|
||||
targetImage.setText(QStringLiteral(META_KEY_SPEED), QStringLiteral("%1").arg(v, 0, 'g', 9));
|
||||
}
|
||||
|
||||
// shot info
|
||||
if (replaceExisting || targetImage.text(QStringLiteral(META_KEY_DIGITALZOOMRATIO)).isEmpty()) {
|
||||
auto v = digitalZoomRatio();
|
||||
if (!qIsNaN(v))
|
||||
targetImage.setText(QStringLiteral(META_KEY_DIGITALZOOMRATIO), QStringLiteral("%1").arg(v, 0, 'g', 9));
|
||||
}
|
||||
if (replaceExisting || targetImage.text(QStringLiteral(META_KEY_EXPOSUREMODE)).isEmpty()) {
|
||||
auto v = exposureMode();
|
||||
if (v != ExposureMode::NotSet)
|
||||
targetImage.setText(QStringLiteral(META_KEY_EXPOSUREMODE), QStringLiteral("%1").arg(quint16(v)));
|
||||
}
|
||||
if (replaceExisting || targetImage.text(QStringLiteral(META_KEY_EXPOSUREPROGRAM)).isEmpty()) {
|
||||
auto v = exposureProgram();
|
||||
if (v != ExposureProgram::NotSet)
|
||||
targetImage.setText(QStringLiteral(META_KEY_EXPOSUREPROGRAM), QStringLiteral("%1").arg(quint16(v)));
|
||||
}
|
||||
if (replaceExisting || targetImage.text(QStringLiteral(META_KEY_EXPOSURETIME)).isEmpty()) {
|
||||
auto v = exposureTime();
|
||||
if (!qIsNaN(v))
|
||||
targetImage.setText(QStringLiteral(META_KEY_EXPOSURETIME), QStringLiteral("%1").arg(v, 0, 'g', 9));
|
||||
}
|
||||
if (replaceExisting || targetImage.text(QStringLiteral(META_KEY_FLASH)).isEmpty()) {
|
||||
auto v = flash();
|
||||
if (v != Flash::NotSet)
|
||||
targetImage.setText(QStringLiteral(META_KEY_FLASH), QStringLiteral("%1").arg(quint16(v)));
|
||||
}
|
||||
if (replaceExisting || targetImage.text(QStringLiteral(META_KEY_FNUMBER)).isEmpty()) {
|
||||
auto v = fNumber();
|
||||
if (!qIsNaN(v))
|
||||
targetImage.setText(QStringLiteral(META_KEY_FNUMBER), QStringLiteral("%1").arg(v, 0, 'g', 9));
|
||||
}
|
||||
if (replaceExisting || targetImage.text(QStringLiteral(META_KEY_FOCALLENGTH)).isEmpty()) {
|
||||
auto v = focalLength();
|
||||
if (!qIsNaN(v))
|
||||
targetImage.setText(QStringLiteral(META_KEY_FOCALLENGTH), QStringLiteral("%1").arg(v, 0, 'g', 9));
|
||||
}
|
||||
if (replaceExisting || targetImage.text(QStringLiteral(META_KEY_ISOSPEEDRATINGS)).isEmpty()) {
|
||||
auto v = isoSpeedRatings();
|
||||
if (v != 0)
|
||||
targetImage.setText(QStringLiteral(META_KEY_ISOSPEEDRATINGS), QStringLiteral("%1").arg(v));
|
||||
}
|
||||
if (replaceExisting || targetImage.text(QStringLiteral(META_KEY_WHITEBALANCE)).isEmpty()) {
|
||||
auto v = whiteBalance();
|
||||
if (v != WhiteBalance::NotSet)
|
||||
targetImage.setText(QStringLiteral(META_KEY_WHITEBALANCE), QStringLiteral("%1").arg(quint16(v)));
|
||||
}
|
||||
}
|
||||
|
||||
bool MicroExif::updateImageResolution(QImage &targetImage)
|
||||
@@ -1217,7 +1447,7 @@ MicroExif MicroExif::fromByteArray(const QByteArray &ba, bool searchHeader)
|
||||
idx = std::min(idxLE, idxBE);
|
||||
else
|
||||
idx = idxLE > -1 ? idxLE : idxBE;
|
||||
if(idx > 0)
|
||||
if (idx > 0)
|
||||
ba0 = ba0.mid(idx);
|
||||
}
|
||||
QBuffer buf;
|
||||
@@ -1308,7 +1538,7 @@ MicroExif MicroExif::fromImage(const QImage &image)
|
||||
dt = QDateTime::currentDateTime();
|
||||
exif.setDateTimeOriginal(dt);
|
||||
|
||||
// GPS Info
|
||||
// GPS info
|
||||
auto ok = false;
|
||||
auto alt = image.text(QStringLiteral(META_KEY_ALTITUDE)).toDouble(&ok);
|
||||
if (ok)
|
||||
@@ -1322,6 +1552,38 @@ MicroExif MicroExif::fromImage(const QImage &image)
|
||||
auto dir = image.text(QStringLiteral(META_KEY_DIRECTION)).toDouble(&ok);
|
||||
if (ok)
|
||||
exif.setImageDirection(dir);
|
||||
auto spd = image.text(QStringLiteral(META_KEY_SPEED)).toDouble(&ok);
|
||||
if (ok)
|
||||
exif.setImageSpeed(spd);
|
||||
|
||||
// EXIF shot info
|
||||
auto zoom = image.text(QStringLiteral(META_KEY_DIGITALZOOMRATIO)).toDouble(&ok);
|
||||
if (ok)
|
||||
exif.setDigitalZoomRatio(zoom);
|
||||
auto expm = image.text(QStringLiteral(META_KEY_EXPOSUREMODE)).toUShort(&ok);
|
||||
if (ok)
|
||||
exif.setExposureMode(ExposureMode(expm));
|
||||
auto expp = image.text(QStringLiteral(META_KEY_EXPOSUREPROGRAM)).toUShort(&ok);
|
||||
if (ok)
|
||||
exif.setExposureProgram(ExposureProgram(expp));
|
||||
auto expt = image.text(QStringLiteral(META_KEY_EXPOSURETIME)).toDouble(&ok);
|
||||
if (ok)
|
||||
exif.setExposureTime(expt);
|
||||
auto flsh = image.text(QStringLiteral(META_KEY_FLASH)).toUShort(&ok);
|
||||
if (ok)
|
||||
exif.setFlash(FlashFlags(flsh));
|
||||
auto fnum = image.text(QStringLiteral(META_KEY_FNUMBER)).toDouble(&ok);
|
||||
if (ok)
|
||||
exif.setFNumber(fnum);
|
||||
auto flen = image.text(QStringLiteral(META_KEY_FOCALLENGTH)).toDouble(&ok);
|
||||
if (ok)
|
||||
exif.setFocalLength(flen);
|
||||
auto isos = image.text(QStringLiteral(META_KEY_ISOSPEEDRATINGS)).toUShort(&ok);
|
||||
if (ok)
|
||||
exif.setIsoSpeedRatings(isos);
|
||||
auto whtb = image.text(QStringLiteral(META_KEY_WHITEBALANCE)).toUShort(&ok);
|
||||
if (ok)
|
||||
exif.setWhiteBalance(WhiteBalance(whtb));
|
||||
|
||||
return exif;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,85 @@
|
||||
#define EXIF_DEFAULT_BYTEORDER QDataStream::BigEndian
|
||||
#endif
|
||||
|
||||
/*!
|
||||
* \brief The Flash enum
|
||||
*/
|
||||
enum class Flash : quint16 {
|
||||
NotSet = 0,
|
||||
|
||||
// Values for bit 0 indicating whether the flash fired.
|
||||
// 0b = Flash did not fire.
|
||||
// 1b = Flash fired.
|
||||
Fired = 1,
|
||||
|
||||
// Values for bits 1 and 2 indicating the status of returned light.
|
||||
// 00b = No strobe return detection function
|
||||
// 01b = reserved
|
||||
// 10b = Strobe return light not detected.
|
||||
// 11b = Strobe return light detected.
|
||||
ReturnLightNotDetected = 2 << 1,
|
||||
ReturnLightDetected = 3 << 1,
|
||||
|
||||
// Values for bits 3 and 4 indicating the camera's flash mode.
|
||||
// 00b = unknown
|
||||
// 01b = Compulsory flash firing
|
||||
// 10b = Compulsory flash suppression
|
||||
// 11b = Auto mode
|
||||
CompulsoryFiring = 1 << 3,
|
||||
CompulsorySuppression = 2 << 3,
|
||||
AutoMode = 3 << 3,
|
||||
|
||||
// Values for bit 5 indicating the presence of a flash function.
|
||||
// 0b = Flash function present
|
||||
// 1b = No flash function
|
||||
FlashNotAvailable = 1 << 5,
|
||||
|
||||
// Values for bit 6 indicating the camera's red-eye mode.
|
||||
// 0b = No red-eye reduction mode or unknown
|
||||
// 1b = Red-eye reduction supported
|
||||
RedEyeReductionSupported = 1 << 6,
|
||||
};
|
||||
Q_DECLARE_FLAGS(FlashFlags, Flash)
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS(FlashFlags)
|
||||
|
||||
/*!
|
||||
* \brief The ExposureMode enum
|
||||
*/
|
||||
enum class ExposureMode : quint16 {
|
||||
Auto,
|
||||
Manual,
|
||||
AutoBracket,
|
||||
|
||||
NotSet = 65535
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief The ExposureProgram enum
|
||||
*/
|
||||
enum class ExposureProgram : quint16 {
|
||||
NotDefined,
|
||||
Manual,
|
||||
Normal,
|
||||
AperturePriority,
|
||||
ShutterPriority,
|
||||
Creative,
|
||||
Action,
|
||||
PortraitMode,
|
||||
LandscapeMode,
|
||||
|
||||
NotSet = 65535
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief The WhiteBalance enum
|
||||
*/
|
||||
enum class WhiteBalance : quint16 {
|
||||
Auto,
|
||||
Manual,
|
||||
|
||||
NotSet = 65535
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief The MicroExif class
|
||||
* Class to extract / write minimal EXIF data (e.g. resolution, rotation,
|
||||
@@ -236,6 +315,69 @@ public:
|
||||
QUuid uniqueId() const;
|
||||
void setUniqueId(const QUuid &uuid);
|
||||
|
||||
/*!
|
||||
* \brief digitalZoomRatio
|
||||
* \return The digital zoom ratio when the image was shot or NaN if not set.
|
||||
*/
|
||||
double digitalZoomRatio() const;
|
||||
void setDigitalZoomRatio(double zoom);
|
||||
|
||||
/*!
|
||||
* \brief exposureMode
|
||||
* \return The exposure mode set when the image was shot. In auto-bracketing mode, the camera shoots a series of frames of the same scene at different exposure settings.
|
||||
*/
|
||||
ExposureMode exposureMode() const;
|
||||
void setExposureMode(const ExposureMode& em);
|
||||
|
||||
/*!
|
||||
* \brief exposureProgram
|
||||
* \return The class of the program used by the camera to set exposure when the picture is taken.
|
||||
*/
|
||||
ExposureProgram exposureProgram() const;
|
||||
void setExposureProgram(const ExposureProgram& ep);
|
||||
|
||||
/*!
|
||||
* \brief exposureTime
|
||||
* \return Exposure time, given in seconds (sec) or NaN if not set.
|
||||
*/
|
||||
double exposureTime() const;
|
||||
void setExposureTime(double et);
|
||||
|
||||
/*!
|
||||
* \brief fNumber
|
||||
* \return The F number or NaN if not set.
|
||||
*/
|
||||
double fNumber() const;
|
||||
void setFNumber(double f);
|
||||
|
||||
/*!
|
||||
* \brief focalLength
|
||||
* \return The actual focal length of the lens, in mm.
|
||||
*/
|
||||
double focalLength() const;
|
||||
void setFocalLength(double fl);
|
||||
|
||||
/*!
|
||||
* \brief flash
|
||||
* \return The status of flash when the image was shot.
|
||||
*/
|
||||
FlashFlags flash() const;
|
||||
void setFlash(const FlashFlags& flash);
|
||||
|
||||
/*!
|
||||
* \brief isoSpeedRatings
|
||||
* \return The sensitivity of the camera or input device when the image was shot.
|
||||
*/
|
||||
quint16 isoSpeedRatings() const;
|
||||
void setIsoSpeedRatings(quint16 iso);
|
||||
|
||||
/*!
|
||||
* \brief whiteBalance
|
||||
* \return The white balance mode set when the image was shot.
|
||||
*/
|
||||
WhiteBalance whiteBalance() const;
|
||||
void setWhiteBalance(const WhiteBalance& wb);
|
||||
|
||||
/*!
|
||||
* \brief latitude
|
||||
* \return Floating-point number indicating the latitude in degrees north of the equator (e.g. 27.717) or NaN if not set.
|
||||
@@ -258,6 +400,13 @@ public:
|
||||
double altitude() const;
|
||||
void setAltitude(double meters);
|
||||
|
||||
/*!
|
||||
* \brief imageSpeed
|
||||
* \return The speed in Km/h or NaN if not set.
|
||||
*/
|
||||
double imageSpeed() const;
|
||||
void setImageSpeed(double kmh);
|
||||
|
||||
/*!
|
||||
* \brief imageDirection
|
||||
* \param isMagnetic Set to true if the direction is relative to magnetic north, false if it is relative to true north. Leave nullptr if is not of interest.
|
||||
|
||||
398
src/imageformats/tim.cpp
Normal file
398
src/imageformats/tim.cpp
Normal file
@@ -0,0 +1,398 @@
|
||||
/*
|
||||
This file is part of the KDE project
|
||||
SPDX-FileCopyrightText: 2026 Mirco Miranda <mircomir@outlook.com>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "tim_p.h"
|
||||
#include "util_p.h"
|
||||
|
||||
#include <QIODevice>
|
||||
#include <QImage>
|
||||
#include <QLoggingCategory>
|
||||
|
||||
Q_DECLARE_LOGGING_CATEGORY(LOG_TIMPLUGIN)
|
||||
Q_LOGGING_CATEGORY(LOG_TIMPLUGIN, "kf.imageformats.plugins.tim", QtWarningMsg)
|
||||
|
||||
#define TYPE_4BPP 0 // never seen
|
||||
#define TYPE_IDX_4BPP 8
|
||||
#define TYPE_8BPP 1 // never seen
|
||||
#define TYPE_IDX_8BPP 9
|
||||
#define TYPE_16BPP 2
|
||||
#define TYPE_24BPP 3
|
||||
|
||||
#define HEADER_SIZE 20
|
||||
|
||||
class TIMHeader
|
||||
{
|
||||
private:
|
||||
QByteArray m_rawHeader;
|
||||
|
||||
quint16 ui16(quint8 c1, quint8 c2) const {
|
||||
return (quint16(c2) << 8) | quint16(c1);
|
||||
}
|
||||
|
||||
quint32 ui32(quint8 c1, quint8 c2, quint8 c3, quint8 c4) const {
|
||||
return (quint32(c4) << 24) | (quint32(c3) << 16) | (quint32(c2) << 8) | quint32(c1);
|
||||
}
|
||||
|
||||
public:
|
||||
TIMHeader()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
quint32 type() const
|
||||
{
|
||||
if (m_rawHeader.size() < HEADER_SIZE) {
|
||||
return 0;
|
||||
}
|
||||
return ui32(m_rawHeader.at(4), m_rawHeader.at(5), m_rawHeader.at(6), m_rawHeader.at(7)) & 0xF;
|
||||
}
|
||||
|
||||
quint32 offset() const
|
||||
{
|
||||
if (m_rawHeader.size() < HEADER_SIZE) {
|
||||
return 0;
|
||||
}
|
||||
auto o = quint32(HEADER_SIZE);
|
||||
auto t = type();
|
||||
if (t == TYPE_IDX_4BPP || t == TYPE_IDX_8BPP) { // indexed
|
||||
o += ui32(m_rawHeader.at(8), m_rawHeader.at(9), m_rawHeader.at(10), m_rawHeader.at(11));
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
bool isValid(quint32 size = 0) const
|
||||
{
|
||||
if (m_rawHeader.size() < HEADER_SIZE) {
|
||||
return false;
|
||||
}
|
||||
if (size == 0) {
|
||||
size = offset();
|
||||
}
|
||||
if (m_rawHeader.size() < size) {
|
||||
return false;
|
||||
}
|
||||
return (m_rawHeader.startsWith(QByteArray::fromRawData("\x10\x00\x00\x00", 4)));
|
||||
}
|
||||
|
||||
bool isSupported() const
|
||||
{
|
||||
return format() != QImage::Format_Invalid;
|
||||
}
|
||||
|
||||
qint32 width() const
|
||||
{
|
||||
auto strideLen = strideSize();
|
||||
auto t = type();
|
||||
if (t == TYPE_4BPP || t == TYPE_IDX_4BPP) {
|
||||
return strideLen * 2;
|
||||
}
|
||||
if (t == TYPE_8BPP || t == TYPE_IDX_8BPP) {
|
||||
return strideLen;
|
||||
}
|
||||
if (t == TYPE_24BPP) {
|
||||
return strideLen / 3;
|
||||
}
|
||||
return strideLen / 2;
|
||||
}
|
||||
|
||||
qint32 height() const
|
||||
{
|
||||
auto o = offset();
|
||||
if (!isValid(o)) {
|
||||
return 0;
|
||||
}
|
||||
return qint32(ui16(m_rawHeader.at(o - 2), m_rawHeader.at(o - 1)));
|
||||
}
|
||||
|
||||
QSize size() const
|
||||
{
|
||||
return QSize(width(), height());
|
||||
}
|
||||
|
||||
QImage::Format format() const
|
||||
{
|
||||
auto t = type();
|
||||
if (t == TYPE_IDX_4BPP || t == TYPE_IDX_8BPP || t == TYPE_4BPP) {
|
||||
return QImage::Format_Indexed8;
|
||||
}
|
||||
if (t == TYPE_IDX_8BPP) {
|
||||
return QImage::Format_Grayscale8;
|
||||
}
|
||||
if (t == TYPE_16BPP) {
|
||||
return QImage::Format_RGB555;
|
||||
}
|
||||
if (t == TYPE_24BPP) {
|
||||
return QImage::Format_RGB888;
|
||||
}
|
||||
return QImage::Format_Invalid;
|
||||
}
|
||||
|
||||
quint32 strideSize() const
|
||||
{
|
||||
auto o = offset();
|
||||
if (!isValid(o)) {
|
||||
return 0;
|
||||
}
|
||||
return ui16(m_rawHeader.at(o - 4), m_rawHeader.at(o - 3)) * 2;
|
||||
}
|
||||
|
||||
qint32 paletteColors() const
|
||||
{
|
||||
if (this->format() != QImage::Format_Indexed8) {
|
||||
return 0;
|
||||
}
|
||||
return qint32(ui16(m_rawHeader.at(16), m_rawHeader.at(17)));
|
||||
}
|
||||
|
||||
qint32 paletteCount() const
|
||||
{
|
||||
if (this->format() != QImage::Format_Indexed8) {
|
||||
return 0;
|
||||
}
|
||||
return qint32(ui16(m_rawHeader.at(18), m_rawHeader.at(19)));
|
||||
}
|
||||
|
||||
QList<QRgb> palette() const
|
||||
{
|
||||
if (format() != QImage::Format_Indexed8) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// 4bpp without CLUT is treated as indexed
|
||||
if (type() == TYPE_4BPP) {
|
||||
QList<QRgb> pal;
|
||||
for (auto i = 0; i < 16; ++i) {
|
||||
auto v = i * 17;
|
||||
pal << qRgb(v, v, v);
|
||||
}
|
||||
return pal;
|
||||
}
|
||||
|
||||
// read the first paette only
|
||||
auto len = paletteColors();
|
||||
if (!isValid(HEADER_SIZE + len * 2)) {
|
||||
return {};
|
||||
}
|
||||
QList<QRgb> clut;
|
||||
for (auto i = 0; i < len; ++i) {
|
||||
auto v = ui16(m_rawHeader.at(HEADER_SIZE + i * 2), m_rawHeader.at(HEADER_SIZE + i * 2 + 1));
|
||||
// in some specs, the bit 15 is the alpha but with the image sample used, transparencies appear
|
||||
// where there shouldn't be any (so, disabled for now)
|
||||
clut << qRgba((v & 0x1F) * 255 / 31, ((v >> 5) & 0x1F) * 255 / 31, ((v >> 10) & 0x1F) * 255 / 31, 255);
|
||||
}
|
||||
return clut;
|
||||
}
|
||||
|
||||
bool read(QIODevice *d)
|
||||
{
|
||||
m_rawHeader = d->read(HEADER_SIZE);
|
||||
if (m_rawHeader.size() != HEADER_SIZE) {
|
||||
return false;
|
||||
}
|
||||
auto o = offset() - HEADER_SIZE;
|
||||
if (o > kMaxQVectorSize - HEADER_SIZE) {
|
||||
return false;
|
||||
}
|
||||
m_rawHeader.append(d->read(o));
|
||||
return isValid();
|
||||
}
|
||||
|
||||
bool peek(QIODevice *d)
|
||||
{
|
||||
m_rawHeader = d->peek(HEADER_SIZE);
|
||||
if (m_rawHeader.size() != HEADER_SIZE) {
|
||||
return false;
|
||||
}
|
||||
auto o = offset();
|
||||
if (o > kMaxQVectorSize - HEADER_SIZE) {
|
||||
return false;
|
||||
}
|
||||
if (o > m_rawHeader.size()) {
|
||||
m_rawHeader = d->peek(o);
|
||||
}
|
||||
return isValid();
|
||||
}
|
||||
|
||||
bool jumpToImageData(QIODevice *d) const
|
||||
{
|
||||
if (d->isSequential()) {
|
||||
if (auto sz = std::max(offset() - quint32(m_rawHeader.size()), quint32())) {
|
||||
return d->read(sz).size() == sz;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return d->seek(offset());
|
||||
}
|
||||
};
|
||||
|
||||
class TIMHandlerPrivate
|
||||
{
|
||||
public:
|
||||
TIMHandlerPrivate() {}
|
||||
~TIMHandlerPrivate() {}
|
||||
|
||||
TIMHeader m_header;
|
||||
};
|
||||
|
||||
TIMHandler::TIMHandler()
|
||||
: QImageIOHandler()
|
||||
, d(new TIMHandlerPrivate)
|
||||
{
|
||||
}
|
||||
|
||||
bool TIMHandler::canRead() const
|
||||
{
|
||||
if (canRead(device())) {
|
||||
setFormat("tim");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TIMHandler::canRead(QIODevice *device)
|
||||
{
|
||||
if (!device) {
|
||||
qCWarning(LOG_TIMPLUGIN) << "TIMHandler::canRead() called with no device";
|
||||
return false;
|
||||
}
|
||||
|
||||
TIMHeader h;
|
||||
if (!h.peek(device)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return h.isSupported();
|
||||
}
|
||||
|
||||
bool TIMHandler::read(QImage *image)
|
||||
{
|
||||
auto&& header = d->m_header;
|
||||
|
||||
if (!header.read(device())) {
|
||||
qCWarning(LOG_TIMPLUGIN) << "TIMHandler::read() invalid header";
|
||||
return false;
|
||||
}
|
||||
|
||||
auto img = imageAlloc(header.size(), header.format());
|
||||
if (img.isNull()) {
|
||||
qCWarning(LOG_TIMPLUGIN) << "TIMHandler::read() error while allocating the image";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (img.format() == QImage::Format_Indexed8) {
|
||||
auto pal = header.palette();
|
||||
if (pal.isEmpty()) {
|
||||
qCWarning(LOG_TIMPLUGIN) << "TIMHandler::read() error while reading the palette";
|
||||
return false;
|
||||
}
|
||||
img.setColorTable(pal);
|
||||
}
|
||||
|
||||
auto d = device();
|
||||
if (!header.jumpToImageData(d)) {
|
||||
qCWarning(LOG_TIMPLUGIN) << "TIMHandler::read() error while seeking image data";
|
||||
return false;
|
||||
}
|
||||
|
||||
auto size = std::min(img.bytesPerLine(), qsizetype(header.strideSize()));
|
||||
QByteArray tmpBuff;
|
||||
auto conv_4bpp = (header.type() == TYPE_4BPP || header.type() == TYPE_IDX_4BPP);
|
||||
if (conv_4bpp && size * 2 <= img.bytesPerLine()) {
|
||||
tmpBuff.resize(size);
|
||||
}
|
||||
for (auto y = 0, h = img.height(); y < h; ++y) {
|
||||
auto line = reinterpret_cast<char*>(img.scanLine(y));
|
||||
auto tbuf = tmpBuff.isEmpty() ? line : tmpBuff.data();
|
||||
if (d->read(tbuf, size) != size) {
|
||||
qCWarning(LOG_TIMPLUGIN) << "TIMHandler::read() error while reading image scanline";
|
||||
return false;
|
||||
}
|
||||
if (conv_4bpp) {
|
||||
for (auto x = 0, w = qint32(tmpBuff.size()); x < w; ++x) {
|
||||
auto &&v = tmpBuff.at(x);
|
||||
line[x * 2 + 1] = (v >> 4) & 0xF;
|
||||
line[x * 2] = v & 0xF;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (img.format() == QImage::Format_RGB555) {
|
||||
img.rgbSwap();
|
||||
}
|
||||
|
||||
*image = img;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TIMHandler::supportsOption(ImageOption option) const
|
||||
{
|
||||
if (option == QImageIOHandler::Size) {
|
||||
return true;
|
||||
}
|
||||
if (option == QImageIOHandler::ImageFormat) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
QVariant TIMHandler::option(ImageOption option) const
|
||||
{
|
||||
QVariant v;
|
||||
|
||||
if (option == QImageIOHandler::Size) {
|
||||
auto&& h = d->m_header;
|
||||
if (h.isValid()) {
|
||||
v = QVariant::fromValue(h.size());
|
||||
} else if (auto d = device()) {
|
||||
if (h.peek(d)) {
|
||||
v = QVariant::fromValue(h.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (option == QImageIOHandler::ImageFormat) {
|
||||
auto&& h = d->m_header;
|
||||
if (h.isValid()) {
|
||||
v = QVariant::fromValue(h.format());
|
||||
} else if (auto d = device()) {
|
||||
if (h.peek(d)) {
|
||||
v = QVariant::fromValue(h.format());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
QImageIOPlugin::Capabilities TIMPlugin::capabilities(QIODevice *device, const QByteArray &format) const
|
||||
{
|
||||
if (format == "tim") {
|
||||
return Capabilities(CanRead);
|
||||
}
|
||||
if (!format.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
if (!device->isOpen()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
Capabilities cap;
|
||||
if (device->isReadable() && TIMHandler::canRead(device)) {
|
||||
cap |= CanRead;
|
||||
}
|
||||
return cap;
|
||||
}
|
||||
|
||||
QImageIOHandler *TIMPlugin::create(QIODevice *device, const QByteArray &format) const
|
||||
{
|
||||
QImageIOHandler *handler = new TIMHandler;
|
||||
handler->setDevice(device);
|
||||
handler->setFormat(format);
|
||||
return handler;
|
||||
}
|
||||
|
||||
#include "moc_tim_p.cpp"
|
||||
4
src/imageformats/tim.json
Normal file
4
src/imageformats/tim.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"Keys": [ "tim" ],
|
||||
"MimeTypes": [ "image/x-tim" ]
|
||||
}
|
||||
42
src/imageformats/tim_p.h
Normal file
42
src/imageformats/tim_p.h
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
This file is part of the KDE project
|
||||
SPDX-FileCopyrightText: 2026 Mirco Miranda <mircomir@outlook.com>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef KIMG_TIM_P_H
|
||||
#define KIMG_TIM_P_H
|
||||
|
||||
#include <QImageIOPlugin>
|
||||
#include <QScopedPointer>
|
||||
|
||||
class TIMHandlerPrivate;
|
||||
class TIMHandler : public QImageIOHandler
|
||||
{
|
||||
public:
|
||||
TIMHandler();
|
||||
|
||||
bool canRead() const override;
|
||||
bool read(QImage *image) override;
|
||||
|
||||
bool supportsOption(QImageIOHandler::ImageOption option) const override;
|
||||
QVariant option(QImageIOHandler::ImageOption option) const override;
|
||||
|
||||
static bool canRead(QIODevice *device);
|
||||
|
||||
private:
|
||||
const QScopedPointer<TIMHandlerPrivate> d;
|
||||
};
|
||||
|
||||
class TIMPlugin : public QImageIOPlugin
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QImageIOHandlerFactoryInterface" FILE "tim.json")
|
||||
|
||||
public:
|
||||
Capabilities capabilities(QIODevice *device, const QByteArray &format) const override;
|
||||
QImageIOHandler *create(QIODevice *device, const QByteArray &format = QByteArray()) const override;
|
||||
};
|
||||
|
||||
#endif // KIMG_TIM_P_H
|
||||
@@ -34,10 +34,22 @@
|
||||
#define META_KEY_MODIFICATIONDATE "ModificationDate"
|
||||
#define META_KEY_OWNER "Owner"
|
||||
#define META_KEY_SOFTWARE "Software"
|
||||
#define META_KEY_SPEED "Speed"
|
||||
#define META_KEY_TITLE "Title"
|
||||
#define META_KEY_XML_GIMP "XML:org.gimp.xml"
|
||||
#define META_KEY_XMP_ADOBE "XML:com.adobe.xmp"
|
||||
|
||||
// Shot info metadata keys
|
||||
#define META_KEY_DIGITALZOOMRATIO "DigitalZoomRatio"
|
||||
#define META_KEY_EXPOSUREMODE "ExposureMode"
|
||||
#define META_KEY_EXPOSUREPROGRAM "ExposureProgram"
|
||||
#define META_KEY_EXPOSURETIME "ExposureTime"
|
||||
#define META_KEY_FLASH "Flash"
|
||||
#define META_KEY_FNUMBER "FNumber"
|
||||
#define META_KEY_FOCALLENGTH "FocalLength"
|
||||
#define META_KEY_ISOSPEEDRATINGS "ISOSpeedRatings"
|
||||
#define META_KEY_WHITEBALANCE "WhiteBalance"
|
||||
|
||||
// Camera info metadata keys
|
||||
#define META_KEY_MANUFACTURER "Manufacturer"
|
||||
#define META_KEY_MODEL "Model"
|
||||
|
||||
Reference in New Issue
Block a user