Compare commits

...

13 Commits

Author SHA1 Message Date
Nicolas Fella
84f28e8bc9 Update dependency version to 6.26.0 2026-05-01 13:20:34 +02:00
Mirco Miranda
a936927ec1 JXR: check all library return codes 2026-04-26 06:22:30 +02:00
Mirco Miranda
191e5e6a69 EXIF improvements and bugfixes
- Fixes a writing issue for float values ​​less than 1
- Fixes a missing definition of `EXIF_DATETIMEDIGITIZED` tag
- Adds support for some common camera shot metadata
- Adds missing metadata to writing tests

The following plugins automatically gain support for the new metadata: AVIF, IFF, HEIF, JXL, JXR, PSD and TGA (V2E).

The new metadata added with this patch is usually saved by smartphones (e.g. iPhone or Google Pixel).
2026-04-26 06:08:25 +02:00
Mirco Miranda
51db11eefc JXR: fix Use-of-uninitialized-value 2026-04-23 14:44:45 +02:00
Mirco Miranda
bc398382ac EXR: fix Null-dereference READ in OpenEXR 2026-04-23 11:42:16 +02:00
Mirco Miranda
d5e5012cfb HDR: fix incorrect use of s.atEnd() 2026-04-20 12:49:35 +00:00
Mirco Miranda
1b3f32a332 JXR: minimal support for multichannel 3 and 4 2026-04-20 14:13:10 +02:00
Mirco Miranda
7cf60da031 JXR: fix memory leaks 2026-04-19 20:37:27 +02:00
Mirco Miranda
d160f268e7 Improved documentation for DDS, JP2 and PSD formats 2026-04-18 08:32:32 +00:00
Mirco Miranda
276338199a EXR: fix incorrect loading of EXR files saved by Photoshop 2026 2026-04-16 15:34:33 +02:00
Mirco Miranda
742b5097f6 Fix HOST Computer metadata 2026-04-14 10:47:54 +02:00
Mirco Miranda
2d2ee68cc0 Add more info about unsecure JXR plugin 2026-04-13 12:45:09 +02:00
Nicolas Fella
d15c3c679d Update version to 6.26.0 2026-04-03 19:49:32 +02:00
20 changed files with 818 additions and 99 deletions

View File

@@ -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

View File

@@ -1,11 +1,11 @@
cmake_minimum_required(VERSION 3.29)
set(KF_VERSION "6.25.0") # handled by release scripts
set(KF_DEP_VERSION "6.25.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.25.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)
@@ -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")

View File

@@ -155,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.
@@ -295,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
@@ -324,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
@@ -379,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
@@ -394,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:
@@ -443,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

View File

@@ -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

Binary file not shown.

View 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
}
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -2,7 +2,7 @@
{
"fileName" : "rgb-gimp.png",
"colorSpace" : {
"description" : "",
"description" : "Embedded RGB (linear)",
"primaries" : "Custom",
"transferFunction" : "Linear",
"gamma" : 1

Binary file not shown.

View File

@@ -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"

View File

@@ -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" : {

View File

@@ -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" : {

View File

@@ -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();

View File

@@ -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);

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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...)

View File

@@ -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;
}

View File

@@ -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.

View File

@@ -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"