mirror of
https://invent.kde.org/frameworks/kimageformats.git
synced 2025-06-03 17:08:08 -04:00
Camera RAW images plugin
Plugin to read RAW camera images based on LibRAW. - Supersedes MR !86 - Support to LibRaw 0.20 and 0.21-Beta - Support to multi-shot images: use imageCount(), jumpToImage() to select the wanted shot - By default generates 16-bits sRGB images using camera white balance and interpolation AHD - Should fix CCBUG: 454208: on my Debian with KF5.96 and the pulgin installed, I see the preview of all my RAW files (ARW included) in Dolphin News compared to V1 (MR !86) - Fix possible stack overflow due to the huge size of LibRaw class - Fix image allocation with Qt 6 (make use of QImageIOHandler::allocateImage()) - Support to XMP metapacket - Support to quality option. For e.g. you can focus on quality (q = 10) or speed (q = 1) - oss-fuzz available [here](https://github.com/mircomir/oss-fuzz/tree/raw_fuzz/projects/kimageformats)
This commit is contained in:
parent
84941b7690
commit
65a20b43fc
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
autotests/read/raw/RAW_KODAK_C330_FORMAT_NONE_YRGB.raw binary
|
@ -8,7 +8,7 @@ set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Mod
|
|||||||
feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES)
|
feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES)
|
||||||
|
|
||||||
|
|
||||||
set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH})
|
set(CMAKE_MODULE_PATH ${KImageFormats_SOURCE_DIR}/cmake/find-modules ${ECM_MODULE_PATH})
|
||||||
|
|
||||||
include(KDEInstallDirs)
|
include(KDEInstallDirs)
|
||||||
include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE)
|
include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE)
|
||||||
@ -70,6 +70,13 @@ if(KIMAGEFORMATS_JXL)
|
|||||||
endif()
|
endif()
|
||||||
add_feature_info(LibJXL LibJXL_FOUND "required for the QImage plugin for JPEG XL images")
|
add_feature_info(LibJXL LibJXL_FOUND "required for the QImage plugin for JPEG XL images")
|
||||||
|
|
||||||
|
# note: module FindLibRaw missing from https://invent.kde.org/frameworks/extra-cmake-modules
|
||||||
|
find_package(LibRaw 0.20.2)
|
||||||
|
set_package_properties(LibRaw PROPERTIES
|
||||||
|
TYPE OPTIONAL
|
||||||
|
PURPOSE "Required for the QImage plugin for RAW images"
|
||||||
|
)
|
||||||
|
|
||||||
ecm_set_disabled_deprecation_versions(
|
ecm_set_disabled_deprecation_versions(
|
||||||
QT 5.15.2
|
QT 5.15.2
|
||||||
KF 5.95
|
KF 5.95
|
||||||
|
@ -18,6 +18,7 @@ The following image formats have read-only support:
|
|||||||
- OpenEXR (exr)
|
- OpenEXR (exr)
|
||||||
- Photoshop documents (psd, psb, pdd, psdt)
|
- Photoshop documents (psd, psb, pdd, psdt)
|
||||||
- Sun Raster (ras)
|
- Sun Raster (ras)
|
||||||
|
- Camera RAW images (arw, cr2, cr3, dcs, dng, ...)
|
||||||
|
|
||||||
The following image formats have read and write support:
|
The following image formats have read and write support:
|
||||||
|
|
||||||
|
@ -139,6 +139,12 @@ if (OpenEXR_FOUND)
|
|||||||
# FIXME: OpenEXR tests
|
# FIXME: OpenEXR tests
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if (LibRaw_FOUND)
|
||||||
|
kimageformats_read_tests(
|
||||||
|
raw
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
find_package(Qt${QT_MAJOR_VERSION}Test ${REQUIRED_QT_VERSION} CONFIG QUIET)
|
find_package(Qt${QT_MAJOR_VERSION}Test ${REQUIRED_QT_VERSION} CONFIG QUIET)
|
||||||
|
|
||||||
if(NOT TARGET Qt${QT_MAJOR_VERSION}::Test)
|
if(NOT TARGET Qt${QT_MAJOR_VERSION}::Test)
|
||||||
|
BIN
autotests/read/raw/RAW_KODAK_C330_FORMAT_NONE_YRGB.png
Normal file
BIN
autotests/read/raw/RAW_KODAK_C330_FORMAT_NONE_YRGB.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.2 MiB |
901
autotests/read/raw/RAW_KODAK_C330_FORMAT_NONE_YRGB.raw
Normal file
901
autotests/read/raw/RAW_KODAK_C330_FORMAT_NONE_YRGB.raw
Normal file
File diff suppressed because one or more lines are too long
@ -94,7 +94,6 @@ int main(int argc, char **argv)
|
|||||||
QByteArray format = suffix.toLatin1();
|
QByteArray format = suffix.toLatin1();
|
||||||
|
|
||||||
QDir imgdir(QLatin1String(IMAGEDIR "/") + suffix);
|
QDir imgdir(QLatin1String(IMAGEDIR "/") + suffix);
|
||||||
imgdir.setNameFilters(QStringList(QLatin1String("*.") + suffix));
|
|
||||||
imgdir.setFilter(QDir::Files);
|
imgdir.setFilter(QDir::Files);
|
||||||
|
|
||||||
int passed = 0;
|
int passed = 0;
|
||||||
@ -113,6 +112,9 @@ int main(int argc, char **argv)
|
|||||||
|
|
||||||
const QFileInfoList lstImgDir = imgdir.entryInfoList();
|
const QFileInfoList lstImgDir = imgdir.entryInfoList();
|
||||||
for (const QFileInfo &fi : lstImgDir) {
|
for (const QFileInfo &fi : lstImgDir) {
|
||||||
|
if (!fi.suffix().compare("png", Qt::CaseInsensitive)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
int suffixPos = fi.filePath().count() - suffix.count();
|
int suffixPos = fi.filePath().count() - suffix.count();
|
||||||
QString inputfile = fi.filePath();
|
QString inputfile = fi.filePath();
|
||||||
QString expfile = fi.filePath().replace(suffixPos, suffix.count(), QStringLiteral("png"));
|
QString expfile = fi.filePath().replace(suffixPos, suffix.count(), QStringLiteral("png"));
|
||||||
|
87
cmake/find-modules/FindLibRaw.cmake
Normal file
87
cmake/find-modules/FindLibRaw.cmake
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
# - Find LibRaw
|
||||||
|
# Find the LibRaw library <https://www.libraw.org>
|
||||||
|
# This module defines
|
||||||
|
# LibRaw_VERSION, the version string of LibRaw
|
||||||
|
# LibRaw_INCLUDE_DIR, where to find libraw.h
|
||||||
|
# LibRaw_LIBRARIES, the libraries needed to use LibRaw (non-thread-safe)
|
||||||
|
# LibRaw_r_LIBRARIES, the libraries needed to use LibRaw (thread-safe)
|
||||||
|
# LibRaw_DEFINITIONS, the definitions needed to use LibRaw (non-thread-safe)
|
||||||
|
# LibRaw_r_DEFINITIONS, the definitions needed to use LibRaw (thread-safe)
|
||||||
|
#
|
||||||
|
# SPDX-FileCopyrightText: 2013 Pino Toscano <pino at kde dot org>
|
||||||
|
# SPDX-FileCopyrightText: 2013 Gilles Caulier <caulier dot gilles at gmail dot com>
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
FIND_PACKAGE(PkgConfig)
|
||||||
|
|
||||||
|
IF(PKG_CONFIG_FOUND)
|
||||||
|
PKG_CHECK_MODULES(PC_LIBRAW libraw)
|
||||||
|
SET(LibRaw_DEFINITIONS ${PC_LIBRAW_CFLAGS_OTHER})
|
||||||
|
|
||||||
|
PKG_CHECK_MODULES(PC_LIBRAW_R libraw_r)
|
||||||
|
SET(LibRaw_r_DEFINITIONS ${PC_LIBRAW_R_CFLAGS_OTHER})
|
||||||
|
ENDIF()
|
||||||
|
|
||||||
|
FIND_PATH(LibRaw_INCLUDE_DIR libraw.h
|
||||||
|
HINTS
|
||||||
|
${PC_LIBRAW_INCLUDEDIR}
|
||||||
|
${PC_LibRaw_INCLUDE_DIRS}
|
||||||
|
PATH_SUFFIXES libraw
|
||||||
|
)
|
||||||
|
|
||||||
|
FIND_LIBRARY(LibRaw_LIBRARIES NAMES raw
|
||||||
|
HINTS
|
||||||
|
${PC_LIBRAW_LIBDIR}
|
||||||
|
${PC_LIBRAW_LIBRARY_DIRS}
|
||||||
|
)
|
||||||
|
|
||||||
|
FIND_LIBRARY(LibRaw_r_LIBRARIES NAMES raw_r
|
||||||
|
HINTS
|
||||||
|
${PC_LIBRAW_R_LIBDIR}
|
||||||
|
${PC_LIBRAW_R_LIBRARY_DIRS}
|
||||||
|
)
|
||||||
|
|
||||||
|
IF(LibRaw_INCLUDE_DIR)
|
||||||
|
FILE(READ ${LibRaw_INCLUDE_DIR}/libraw_version.h _libraw_version_content)
|
||||||
|
|
||||||
|
STRING(REGEX MATCH "#define LIBRAW_MAJOR_VERSION[ \t]*([0-9]*)\n" _version_major_match ${_libraw_version_content})
|
||||||
|
SET(_libraw_version_major "${CMAKE_MATCH_1}")
|
||||||
|
|
||||||
|
STRING(REGEX MATCH "#define LIBRAW_MINOR_VERSION[ \t]*([0-9]*)\n" _version_minor_match ${_libraw_version_content})
|
||||||
|
SET(_libraw_version_minor "${CMAKE_MATCH_1}")
|
||||||
|
|
||||||
|
STRING(REGEX MATCH "#define LIBRAW_PATCH_VERSION[ \t]*([0-9]*)\n" _version_patch_match ${_libraw_version_content})
|
||||||
|
SET(_libraw_version_patch "${CMAKE_MATCH_1}")
|
||||||
|
|
||||||
|
IF(_version_major_match AND _version_minor_match AND _version_patch_match)
|
||||||
|
SET(LibRaw_VERSION "${_libraw_version_major}.${_libraw_version_minor}.${_libraw_version_patch}")
|
||||||
|
ELSE()
|
||||||
|
IF(NOT LibRaw_FIND_QUIETLY)
|
||||||
|
MESSAGE(STATUS "Failed to get version information from ${LibRaw_INCLUDE_DIR}/libraw_version.h")
|
||||||
|
ENDIF()
|
||||||
|
ENDIF()
|
||||||
|
ENDIF()
|
||||||
|
|
||||||
|
INCLUDE(FindPackageHandleStandardArgs)
|
||||||
|
FIND_PACKAGE_HANDLE_STANDARD_ARGS(LibRaw
|
||||||
|
REQUIRED_VARS LibRaw_LIBRARIES LibRaw_INCLUDE_DIR
|
||||||
|
VERSION_VAR LibRaw_VERSION
|
||||||
|
)
|
||||||
|
|
||||||
|
MARK_AS_ADVANCED(LibRaw_VERSION
|
||||||
|
LibRaw_INCLUDE_DIR
|
||||||
|
LibRaw_LIBRARIES
|
||||||
|
LibRaw_r_LIBRARIES
|
||||||
|
LibRaw_DEFINITIONS
|
||||||
|
LibRaw_r_DEFINITIONS
|
||||||
|
)
|
||||||
|
|
||||||
|
if(LibRaw_FOUND AND NOT TARGET LibRaw::LibRaw)
|
||||||
|
add_library(LibRaw::LibRaw UNKNOWN IMPORTED)
|
||||||
|
set_target_properties(LibRaw::LibRaw PROPERTIES
|
||||||
|
IMPORTED_LOCATION "${LibRaw_LIBRARIES}"
|
||||||
|
INTERFACE_COMPILE_OPTIONS "${LibRaw_DEFINITIONS}"
|
||||||
|
INTERFACE_INCLUDE_DIRECTORIES "${LibRaw_INCLUDE_DIR}"
|
||||||
|
)
|
||||||
|
endif()
|
@ -130,6 +130,15 @@ install(FILES xcf.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugin
|
|||||||
|
|
||||||
##################################
|
##################################
|
||||||
|
|
||||||
|
if (LibRaw_FOUND)
|
||||||
|
kimageformats_add_plugin(kimg_raw SOURCES raw.cpp)
|
||||||
|
kde_enable_exceptions()
|
||||||
|
target_link_libraries(kimg_raw LibRaw::LibRaw)
|
||||||
|
install(FILES raw.desktop DESTINATION ${KDE_INSTALL_KSERVICESDIR}/qimageioplugins/)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
##################################
|
||||||
|
|
||||||
if (KF5Archive_FOUND)
|
if (KF5Archive_FOUND)
|
||||||
|
|
||||||
kimageformats_add_plugin(kimg_kra SOURCES kra.cpp)
|
kimageformats_add_plugin(kimg_kra SOURCES kra.cpp)
|
||||||
|
895
src/imageformats/raw.cpp
Normal file
895
src/imageformats/raw.cpp
Normal file
@ -0,0 +1,895 @@
|
|||||||
|
/*
|
||||||
|
RAW support for QImage.
|
||||||
|
|
||||||
|
SPDX-FileCopyrightText: 2022 Mirco Miranda <mircomir@outlook.com>
|
||||||
|
|
||||||
|
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "raw_p.h"
|
||||||
|
#include "util_p.h"
|
||||||
|
|
||||||
|
#include <QColorSpace>
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QImage>
|
||||||
|
#include <QSet>
|
||||||
|
|
||||||
|
#if defined(Q_OS_WINDOWS) && !defined(NOMINMAX)
|
||||||
|
#define NOMINMAX
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(Q_OS_WINDOWS)
|
||||||
|
#include <libraw.h>
|
||||||
|
#else
|
||||||
|
#include <libraw/libraw.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef QT_DEBUG
|
||||||
|
// This should be used to exclude the local QIODevice wrapper of the LibRaw_abstract_datastream interface.
|
||||||
|
// If the result changes then the problem is in the local wrapper and must be corrected.
|
||||||
|
// WARNING: using the LibRaw's streams instead of the local wrapper from a Qt program falls in the LOCALE
|
||||||
|
// bug when the RAW file needs the scanf_one() function (e.g. *.MOS files). See also raw_scanf_one().
|
||||||
|
//#define EXCLUDE_LibRaw_QIODevice // Uncomment this code if you think that the problem is LibRaw_QIODevice (default commented)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace // Private.
|
||||||
|
{
|
||||||
|
|
||||||
|
// smart pointer for processed image
|
||||||
|
using pi_unique_ptr = std::unique_ptr<libraw_processed_image_t, std::function<void(libraw_processed_image_t *)>>;
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
// Known formats supported by LibRaw (in alphabetical order and lower case)
|
||||||
|
const auto supported_formats = QSet<QByteArray>{
|
||||||
|
"3fr",
|
||||||
|
"arw", "arq",
|
||||||
|
"bay", "bmq",
|
||||||
|
"cr2", "cr3", "cap", "cine", "cs1", "crw",
|
||||||
|
"dcs", "dc2", "dcr", "dng", "drf", "dxo",
|
||||||
|
"eip", "erf",
|
||||||
|
"fff",
|
||||||
|
"hdr",
|
||||||
|
"iiq",
|
||||||
|
"k25", "kc2", "kdc",
|
||||||
|
"mdc", "mef", "mfw", "mos", "mrw",
|
||||||
|
"nef", "nrw",
|
||||||
|
"obm", "orf", "ori",
|
||||||
|
"pef", "ptx", "pxn",
|
||||||
|
"qtk",
|
||||||
|
"r3d", "raf", "raw", "rdc", "rw2", "rwl", "rwz",
|
||||||
|
"sr2", "srf", "srw", "sti",
|
||||||
|
"x3f"
|
||||||
|
};
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
inline int raw_scanf_one(const QByteArray &ba, const char *fmt, void *val)
|
||||||
|
{
|
||||||
|
// WARNING: Here it would be nice to use sscanf like LibRaw does but there
|
||||||
|
// is a Big Trouble: THE LOCALE! LibRaw is also affected by the
|
||||||
|
// issue if used in a Qt program:
|
||||||
|
// If you use sscanf here the conversion is wrong when performed
|
||||||
|
// with Italian locale (where the decimal separator is the comma).
|
||||||
|
// The solution should be to use std::locale::global() but it's global!
|
||||||
|
// I don't want to do it. So, don't use code like that:
|
||||||
|
// return sscanf(QByteArray(ba).append('\0').data(), fmt, val);
|
||||||
|
|
||||||
|
// LibRaw is asking only "%d" and "%f" for now. This code is not affected
|
||||||
|
// by the LOCALE bug.
|
||||||
|
auto s = QString::fromLatin1(ba);
|
||||||
|
if (strcmp(fmt, "%d") == 0) {
|
||||||
|
auto ok = false;
|
||||||
|
auto d = QLocale::c().toInt(s, &ok);
|
||||||
|
if (!ok) {
|
||||||
|
return EOF;
|
||||||
|
}
|
||||||
|
*(static_cast<int *>(val)) = d;
|
||||||
|
} else {
|
||||||
|
auto ok = false;
|
||||||
|
auto f = QLocale::c().toFloat(s, &ok);
|
||||||
|
if (!ok) {
|
||||||
|
return EOF;
|
||||||
|
}
|
||||||
|
*(static_cast<float *>(val)) = f;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The LibRaw_QIODevice class
|
||||||
|
* Implementation of the LibRaw stream interface over a QIODevice.
|
||||||
|
*/
|
||||||
|
class LibRaw_QIODevice : public LibRaw_abstract_datastream
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit LibRaw_QIODevice(QIODevice *device)
|
||||||
|
{
|
||||||
|
m_device = device;
|
||||||
|
}
|
||||||
|
virtual ~LibRaw_QIODevice() override
|
||||||
|
{
|
||||||
|
}
|
||||||
|
virtual int valid() override
|
||||||
|
{
|
||||||
|
return m_device != nullptr;
|
||||||
|
}
|
||||||
|
virtual int read(void *ptr, size_t sz, size_t nmemb) override
|
||||||
|
{
|
||||||
|
auto read = m_device->read(reinterpret_cast<char *>(ptr), sz * nmemb);
|
||||||
|
if (read < 1) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (auto o = read % sz) {
|
||||||
|
seek(-(sz - o), SEEK_CUR);
|
||||||
|
}
|
||||||
|
return read / sz;
|
||||||
|
}
|
||||||
|
virtual int eof() override
|
||||||
|
{
|
||||||
|
return m_device->atEnd() ? 1 : 0;
|
||||||
|
}
|
||||||
|
virtual int seek(INT64 o, int whence) override
|
||||||
|
{
|
||||||
|
auto pos = o;
|
||||||
|
if (whence == SEEK_CUR) {
|
||||||
|
pos = m_device->pos() + o;
|
||||||
|
}
|
||||||
|
if (whence == SEEK_END) {
|
||||||
|
pos = m_device->size() + o;
|
||||||
|
}
|
||||||
|
return m_device->seek(pos) ? 0 : -1;
|
||||||
|
}
|
||||||
|
virtual INT64 tell() override
|
||||||
|
{
|
||||||
|
return m_device->pos();
|
||||||
|
}
|
||||||
|
virtual INT64 size() override
|
||||||
|
{
|
||||||
|
return m_device->size();
|
||||||
|
}
|
||||||
|
virtual char *gets(char *s, int sz) override
|
||||||
|
{
|
||||||
|
if (m_device->readLine(s, sz) > 0) {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
virtual int scanf_one(const char *fmt, void *val) override
|
||||||
|
{
|
||||||
|
QByteArray ba;
|
||||||
|
for (int xcnt = 0; xcnt < 24 && !m_device->atEnd(); ++xcnt) {
|
||||||
|
char c;
|
||||||
|
if (!m_device->getChar(&c)) {
|
||||||
|
return EOF;
|
||||||
|
}
|
||||||
|
if (ba.isEmpty() && (c == ' ' || c == '\t')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (c == '\0' || c == ' ' || c == '\t' || c == '\n') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ba.append(c);
|
||||||
|
}
|
||||||
|
return raw_scanf_one(ba, fmt, val);
|
||||||
|
}
|
||||||
|
virtual int get_char() override
|
||||||
|
{
|
||||||
|
unsigned char c;
|
||||||
|
if (!m_device->getChar(reinterpret_cast<char *>(&c))) {
|
||||||
|
return EOF;
|
||||||
|
}
|
||||||
|
return int(c);
|
||||||
|
}
|
||||||
|
#if (LIBRAW_VERSION < LIBRAW_MAKE_VERSION(0, 21, 0)) || defined(LIBRAW_OLD_VIDEO_SUPPORT)
|
||||||
|
virtual void *make_jas_stream() override
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
private:
|
||||||
|
QIODevice *m_device;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool addTag(const QString &tag, QStringList &lines)
|
||||||
|
{
|
||||||
|
auto ok = !tag.isEmpty();
|
||||||
|
if (ok) {
|
||||||
|
lines << tag;
|
||||||
|
}
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString createTag(QString value, const char *tag)
|
||||||
|
{
|
||||||
|
if (!value.isEmpty()) {
|
||||||
|
value = QStringLiteral("<%1>%2</%1>").arg(QString::fromLatin1(tag), value);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString createTag(char *asciiz, const char *tag)
|
||||||
|
{
|
||||||
|
auto value = QString::fromUtf8(asciiz);
|
||||||
|
return createTag(value, tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString createTimeTag(time_t time, const char *tag)
|
||||||
|
{
|
||||||
|
auto value = QDateTime::fromSecsSinceEpoch(time, Qt::UTC);
|
||||||
|
if (value.isValid() && time > 0) {
|
||||||
|
return createTag(value.toString(Qt::ISODate), tag);
|
||||||
|
}
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString createFlashTag(short flash, const char *tag)
|
||||||
|
{
|
||||||
|
QStringList l;
|
||||||
|
auto lc = QLocale::c();
|
||||||
|
// EXIF specifications
|
||||||
|
auto t = QStringLiteral("true");
|
||||||
|
auto f = QStringLiteral("false");
|
||||||
|
l << QStringLiteral("<exif:Fired>%1</exif:Fired>").arg((flash & 1) ? t : f);
|
||||||
|
l << QStringLiteral("<exif:Function>%1</exif:Function>").arg((flash & (1 << 5)) ? t : f);
|
||||||
|
l << QStringLiteral("<exif:RedEyeMode>%1</exif:RedEyeMode>").arg((flash & (1 << 6)) ? t : f);
|
||||||
|
l << QStringLiteral("<exif:Mode>%1</exif:Mode>").arg(lc.toString((int(flash) >> 3) & 3));
|
||||||
|
l << QStringLiteral("<exif:Return>%1</exif:Return>").arg(lc.toString((int(flash) >> 1) & 3));
|
||||||
|
return createTag(l.join(QChar()), tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString createTag(quint64 n, const char *tag, quint64 invalid = 0)
|
||||||
|
{
|
||||||
|
if (n != invalid) {
|
||||||
|
return createTag(QLocale::c().toString(n), tag);
|
||||||
|
}
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString createTag(qint16 n, const char *tag, qint16 invalid = 0)
|
||||||
|
{
|
||||||
|
if (n != invalid) {
|
||||||
|
return createTag(QLocale::c().toString(n), tag);
|
||||||
|
}
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString createTag(quint16 n, const char *tag, quint16 invalid = 0)
|
||||||
|
{
|
||||||
|
if (n != invalid) {
|
||||||
|
return createTag(QLocale::c().toString(n), tag);
|
||||||
|
}
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString createTag(float f, const char *tag, qint32 mul = 1)
|
||||||
|
{
|
||||||
|
if (f != 0) {
|
||||||
|
if (mul > 1)
|
||||||
|
return QStringLiteral("<%1>%2/%3</%1>").arg(QString::fromLatin1(tag), QLocale::c().toString(int(f * mul))).arg(mul);
|
||||||
|
return QStringLiteral("<%1>%2</%1>").arg(QString::fromLatin1(tag), QLocale::c().toString(f));
|
||||||
|
}
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString createTag(libraw_gps_info_t gps, const char *tag)
|
||||||
|
{
|
||||||
|
auto tmp = QString::fromLatin1(tag);
|
||||||
|
if (tmp.contains(QStringLiteral("Latitude"), Qt::CaseInsensitive)) {
|
||||||
|
if (gps.latref != '\0') {
|
||||||
|
auto lc = QLocale::c();
|
||||||
|
auto value = QStringLiteral("%1,%2%3")
|
||||||
|
.arg(lc.toString(gps.latitude[0], 'f', 0))
|
||||||
|
.arg(lc.toString(gps.latitude[1] + gps.latitude[2] / 60, 'f', 4))
|
||||||
|
.arg(QChar::fromLatin1(gps.latref));
|
||||||
|
return createTag(value, tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (tmp.contains(QStringLiteral("Longitude"), Qt::CaseInsensitive)) {
|
||||||
|
if (gps.longref != '\0') {
|
||||||
|
auto lc = QLocale::c();
|
||||||
|
auto value = QStringLiteral("%1,%2%3")
|
||||||
|
.arg(lc.toString(gps.longitude[0], 'f', 0))
|
||||||
|
.arg(lc.toString(gps.longitude[1] + gps.longitude[2] / 60, 'f', 4))
|
||||||
|
.arg(QChar::fromLatin1(gps.longref));
|
||||||
|
return createTag(value, tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (tmp.contains(QStringLiteral("Altitude"), Qt::CaseInsensitive)) {
|
||||||
|
if (gps.altitude != 0) {
|
||||||
|
return createTag(gps.altitude, tag, 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString createXmpPacket()
|
||||||
|
{
|
||||||
|
QStringList lines;
|
||||||
|
lines << QStringLiteral("<?xpacket begin=\"\" id=\"W5M0MpCehiHzreSzNTczkc9d\"?>");
|
||||||
|
lines << QStringLiteral("<x:xmpmeta xmlns:x=\"adobe:ns:meta/\" x:xmptk=\"KIMG RAW Plugin\">");
|
||||||
|
lines << QStringLiteral("<rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">");
|
||||||
|
lines << QStringLiteral("</rdf:RDF>");
|
||||||
|
lines << QStringLiteral("</x:xmpmeta>");
|
||||||
|
for (auto i = 30; i > 0; --i)
|
||||||
|
lines << QString::fromLatin1(QByteArray(80, ' '));
|
||||||
|
lines << QStringLiteral("<?xpacket end=\"w\"?>");
|
||||||
|
return lines.join(QChar::fromLatin1('\n'));
|
||||||
|
}
|
||||||
|
|
||||||
|
QString updateXmpPacket(const QString &xmpPacket, LibRaw *rawProcessor)
|
||||||
|
{
|
||||||
|
auto rdfEnd = xmpPacket.indexOf(QStringLiteral("</rdf:RDF>"), Qt::CaseInsensitive);
|
||||||
|
if (rdfEnd < 0) {
|
||||||
|
return updateXmpPacket(createXmpPacket(), rawProcessor);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto lines = QStringList() << xmpPacket.left(rdfEnd);
|
||||||
|
lines << QStringLiteral("<rdf:Description rdf:about=\"\"");
|
||||||
|
lines << QStringLiteral(" xmlns:xmp=\"http://ns.adobe.com/xap/1.0/\"");
|
||||||
|
lines << QStringLiteral(" xmlns:dc=\"http://purl.org/dc/elements/1.1/\"");
|
||||||
|
lines << QStringLiteral(" xmlns:aux=\"http://ns.adobe.com/exif/1.0/aux/\"");
|
||||||
|
lines << QStringLiteral(" xmlns:xmpMM=\"http://ns.adobe.com/xap/1.0/mm/\"");
|
||||||
|
lines << QStringLiteral(" xmlns:stEvt=\"http://ns.adobe.com/xap/1.0/sType/ResourceEvent#\"");
|
||||||
|
lines << QStringLiteral(" xmlns:stRef=\"http://ns.adobe.com/xap/1.0/sType/ResourceRef#\"");
|
||||||
|
lines << QStringLiteral(" xmlns:tiff=\"http://ns.adobe.com/tiff/1.0/\"");
|
||||||
|
lines << QStringLiteral(" xmlns:exif=\"http://ns.adobe.com/exif/1.0/\"");
|
||||||
|
lines << QStringLiteral(" xmlns:xmpRights=\"http://ns.adobe.com/xap/1.0/rights/\">");
|
||||||
|
lines << QStringLiteral("<xmpMM:History>");
|
||||||
|
lines << QStringLiteral("<rdf:Seq>");
|
||||||
|
lines << QStringLiteral("<rdf:li rdf:parseType=\"Resource\">");
|
||||||
|
lines << QStringLiteral("<stEvt:action>converted</stEvt:action>");
|
||||||
|
lines << QStringLiteral("<stEvt:parameters>Converted from RAW to Qt Image using KIMG RAW plugin</stEvt:parameters>");
|
||||||
|
lines << QStringLiteral("<stEvt:softwareAgent>LibRaw %1</stEvt:softwareAgent>").arg(QString::fromLatin1(LibRaw::version()));
|
||||||
|
lines << QStringLiteral("<stEvt:when>%1</stEvt:when>").arg(QDateTime::currentDateTimeUtc().toString(Qt::ISODate));
|
||||||
|
lines << QStringLiteral("</rdf:li>");
|
||||||
|
lines << QStringLiteral("</rdf:Seq>");
|
||||||
|
lines << QStringLiteral("</xmpMM:History>");
|
||||||
|
|
||||||
|
auto &&iparams = rawProcessor->imgdata.idata;
|
||||||
|
addTag(createTag(iparams.normalized_model, "tiff:Model"), lines);
|
||||||
|
addTag(createTag(iparams.normalized_make, "tiff:Make"), lines);
|
||||||
|
addTag(createTag(iparams.software, "xmp:CreatorTool"), lines);
|
||||||
|
|
||||||
|
auto &&iother = rawProcessor->imgdata.other;
|
||||||
|
addTag(createTag(createTag(createTag(iother.desc, "rdf:li"), "rdf:Alt"), "dc:description"), lines);
|
||||||
|
addTag(createTag(createTag(createTag(iother.artist, "rdf:li"), "rdf:Seq"), "dc:creator"), lines);
|
||||||
|
addTag(createTag(createTag(createTag(iother.iso_speed, "rdf:li"), "rdf:Seq"), "exif:ISOSpeedRatings"), lines);
|
||||||
|
addTag(createTag(iother.shutter, "exif:ExposureTime", 1000), lines);
|
||||||
|
addTag(createTag(iother.aperture, "exif:ApertureValue", 1000), lines);
|
||||||
|
addTag(createTag(iother.focal_len, "exif:FocalLength", 1000), lines);
|
||||||
|
addTag(createTimeTag(iother.timestamp, "xmp:CreateDate"), lines);
|
||||||
|
addTag(createTag(iother.parsed_gps, "exif:GPSLatitude"), lines);
|
||||||
|
addTag(createTag(iother.parsed_gps, "exif:GPSLongitude"), lines);
|
||||||
|
addTag(createTag(iother.parsed_gps, "exif:GPSAltitude"), lines);
|
||||||
|
|
||||||
|
auto &&shotinfo = rawProcessor->imgdata.shootinginfo;
|
||||||
|
addTag(createTag(shotinfo.ExposureMode, "exif:ExposureMode", short(-1)), lines);
|
||||||
|
addTag(createTag(shotinfo.MeteringMode, "exif:MeteringMode", short(-1)), lines);
|
||||||
|
addTag(createTag(shotinfo.BodySerial, "aux:SerialNumber"), lines);
|
||||||
|
|
||||||
|
auto &&color = rawProcessor->imgdata.color;
|
||||||
|
addTag(createFlashTag(color.flash_used, "exif:Flash"), lines);
|
||||||
|
|
||||||
|
auto &&lens = rawProcessor->imgdata.lens;
|
||||||
|
addTag(createTag(lens.FocalLengthIn35mmFormat, "exif:FocalLengthIn35mmFilm"), lines);
|
||||||
|
addTag(createTag(lens.Lens, "aux:Lens"), lines);
|
||||||
|
addTag(createTag(lens.LensSerial, "aux:LensSerialNumber"), lines);
|
||||||
|
addTag(createTag(lens.nikon.LensIDNumber ? quint64(lens.nikon.LensIDNumber) : lens.makernotes.LensID, "aux:LensID"), lines);
|
||||||
|
|
||||||
|
auto &&makernotes = rawProcessor->imgdata.makernotes;
|
||||||
|
addTag(createTag(makernotes.common.firmware, "aux:Firmware"), lines);
|
||||||
|
|
||||||
|
lines << QStringLiteral("</rdf:Description>");
|
||||||
|
lines << xmpPacket.mid(rdfEnd);
|
||||||
|
|
||||||
|
return lines.join(QChar::fromLatin1('\n'));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
inline void rgbToRgbX(uchar *target, const uchar *source, qint32 targetSize, qint32 sourceSize)
|
||||||
|
{
|
||||||
|
auto s = reinterpret_cast<const T *>(source);
|
||||||
|
auto t = reinterpret_cast<T *>(target);
|
||||||
|
auto width = std::min(targetSize / 4, sourceSize / 3) / qint32(sizeof(T));
|
||||||
|
for (qint32 x = 0; x < width; ++x) {
|
||||||
|
t[x * 4 + 0] = s[x * 3 + 0];
|
||||||
|
t[x * 4 + 1] = s[x * 3 + 1];
|
||||||
|
t[x * 4 + 2] = s[x * 3 + 2];
|
||||||
|
t[x * 4 + 3] = std::numeric_limits<T>::max();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
#define C_IQ(a) (((a) & 0xF) << 4)
|
||||||
|
#define C_OC(a) (((a) & 0xF) << 8)
|
||||||
|
#define C_CW(a) (((a) & 0x1) << 12)
|
||||||
|
#define C_AW(a) (((a) & 0x1) << 13)
|
||||||
|
#define C_BT(a) (((a) & 0x1) << 14)
|
||||||
|
#define C_HS(a) (((a) & 0x1) << 15)
|
||||||
|
#define C_CE(a) (((a) & 0x1) << 16)
|
||||||
|
#define C_NR(a) (((a) & 0x3) << 17)
|
||||||
|
#define C_FC(a) (((a) & 0x1) << 19)
|
||||||
|
#define C_SR(a) (((a) & 0x1) << 20)
|
||||||
|
#define C_PRESET(a) ((a) & 0xF)
|
||||||
|
|
||||||
|
#define T_IQ(a) (((a) >> 4) & 0xF)
|
||||||
|
#define T_OC(a) (((a) >> 8) & 0xF)
|
||||||
|
#define T_CW(a) (((a) >> 12) & 0x1)
|
||||||
|
#define T_AW(a) (((a) >> 13) & 0x1)
|
||||||
|
#define T_BT(a) (((a) >> 14) & 0x1)
|
||||||
|
#define T_HS(a) (((a) >> 15) & 0x1)
|
||||||
|
#define T_CE(a) (((a) >> 16) & 0x1)
|
||||||
|
#define T_NR(a) (((a) >> 17) & 0x3)
|
||||||
|
#define T_FC(a) (((a) >> 19) & 0x1)
|
||||||
|
#define T_SR(a) (((a) >> 20) & 0x1)
|
||||||
|
#define T_PRESET(a) ((a) & 0xF)
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
#define DEFAULT_QUALITY (C_IQ(3) | C_OC(1) | C_CW(1) | C_AW(1) | C_BT(1) | C_HS(0))
|
||||||
|
|
||||||
|
void setParams(QImageIOHandler *handler, LibRaw *rawProcessor)
|
||||||
|
{
|
||||||
|
// *** Set raw params
|
||||||
|
#if (LIBRAW_VERSION < LIBRAW_MAKE_VERSION(0, 21, 0))
|
||||||
|
auto &&rawparams = rawProcessor->imgdata.params;
|
||||||
|
#else
|
||||||
|
auto &&rawparams = rawProcessor->imgdata.rawparams;
|
||||||
|
#endif
|
||||||
|
// Select one raw image from input file (0 - first, ...)
|
||||||
|
rawparams.shot_select = handler->currentImageNumber();
|
||||||
|
|
||||||
|
// *** Set processing parameters
|
||||||
|
|
||||||
|
// NOTE: The default value set below are the ones that gave the best results
|
||||||
|
// on a large sample of images (https://raw.pixls.us/data/)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief quality
|
||||||
|
* Plugin quality option.
|
||||||
|
*/
|
||||||
|
qint32 quality = -1;
|
||||||
|
if (handler->supportsOption(QImageIOHandler::Quality)) {
|
||||||
|
quality = handler->option(QImageIOHandler::Quality).toInt();
|
||||||
|
}
|
||||||
|
if (quality < 0) {
|
||||||
|
quality = DEFAULT_QUALITY;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (T_PRESET(quality)) {
|
||||||
|
case 0:
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
quality = C_IQ(0) | C_OC(1) | C_CW(1) | C_AW(1) | C_BT(0) | C_HS(1);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
quality = C_IQ(0) | C_OC(1) | C_CW(1) | C_AW(1) | C_BT(0) | C_HS(0);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
quality = C_IQ(3) | C_OC(1) | C_CW(1) | C_AW(1) | C_BT(0) | C_HS(0);
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
quality = C_IQ(3) | C_OC(1) | C_CW(1) | C_AW(1) | C_BT(1) | C_HS(0);
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
quality = C_IQ(3) | C_OC(2) | C_CW(1) | C_AW(1) | C_BT(1) | C_HS(0);
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
quality = C_IQ(3) | C_OC(4) | C_CW(1) | C_AW(1) | C_BT(1) | C_HS(0);
|
||||||
|
break;
|
||||||
|
case 7:
|
||||||
|
quality = C_IQ(11) | C_OC(1) | C_CW(1) | C_AW(1) | C_BT(0) | C_HS(0);
|
||||||
|
break;
|
||||||
|
case 8:
|
||||||
|
quality = C_IQ(11) | C_OC(1) | C_CW(1) | C_AW(1) | C_BT(1) | C_HS(0);
|
||||||
|
break;
|
||||||
|
case 9:
|
||||||
|
quality = C_IQ(11) | C_OC(2) | C_CW(1) | C_AW(1) | C_BT(1) | C_HS(0);
|
||||||
|
break;
|
||||||
|
case 10:
|
||||||
|
quality = C_IQ(11) | C_OC(4) | C_CW(1) | C_AW(1) | C_BT(1) | C_HS(0);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
quality = DEFAULT_QUALITY;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto &¶ms = rawProcessor->imgdata.params;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief use_camera_wb
|
||||||
|
* Use camera white balance, if possible (0 - off, 1 - on)
|
||||||
|
*
|
||||||
|
* This should to be set. Alternatively, a calibrated white balance should be set on each camera.
|
||||||
|
*/
|
||||||
|
params.use_camera_wb = T_CW(quality);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief use_auto_wb
|
||||||
|
* Average the whole image for white balance (0 - off, 1 - on)
|
||||||
|
*
|
||||||
|
* This is usefull if no camera white balance is available.
|
||||||
|
*/
|
||||||
|
params.use_auto_wb = T_AW(quality);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief output_bps
|
||||||
|
* Bits per pixel (8 or 16)
|
||||||
|
*
|
||||||
|
* Professional cameras (and even some smartphones) generate images at 10 or more bits per sample.
|
||||||
|
* When using 16-bit images, the highest quality should be maintained.
|
||||||
|
*/
|
||||||
|
params.output_bps = T_BT(quality) ? 16 : 8;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief output_color
|
||||||
|
* Output colorspace (0 - raw, 1 - sRGB, 2 - Adobe, 3 - Wide, 4 - ProPhoto, 5 - XYZ, 6 - ACES, 7 - DCI-P3, 8 - Rec2020)
|
||||||
|
*
|
||||||
|
* sRGB allows you to view images correctly on programs that do not support ICC profiles. When most
|
||||||
|
* Programs will support icc profiles, ProPhoto may be a better choice.
|
||||||
|
* @note sRgb is the LibRaw default: if grayscale image is loaded, LibRaw switches to 0 (Raw) automatically.
|
||||||
|
*/
|
||||||
|
params.output_color = T_OC(quality);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief user_qual
|
||||||
|
* Interpolation quality (0 - linear, 1 - VNG, 2 - PPG, 3 - AHD, 4 - DCB, 11 - DHT, 12 - AAHD)
|
||||||
|
*
|
||||||
|
* DHT seems the best option - See In-Depth Demosaicing Algorithm Analysis (https://www.libraw.org/node/2306)
|
||||||
|
* but, when used, some FUJI RAF files of my library are poorly rendered (e.g. completely green). This is the
|
||||||
|
* why I used AHD: a good compromise between quality and performance with no rendering errors.
|
||||||
|
*/
|
||||||
|
params.user_qual = T_IQ(quality);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief half_size
|
||||||
|
* Generate an half-size image (0 - off, 1 - on)
|
||||||
|
*
|
||||||
|
* Very fast and useful for generating previews.
|
||||||
|
*/
|
||||||
|
params.half_size = T_HS(quality);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dcb_enhance_fl
|
||||||
|
* DCB color enhance filter (0 - off, 1 - on)
|
||||||
|
*/
|
||||||
|
params.dcb_enhance_fl = T_CE(quality);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @fbdd_noiserd
|
||||||
|
* FBDD noise reduction (0 - off, 1 - light, 2 - full)
|
||||||
|
*/
|
||||||
|
params.fbdd_noiserd = std::min(2, T_NR(quality));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @four_color_rgb
|
||||||
|
* Interpolate RGGB as four colors (0 - off, 1 - on)
|
||||||
|
*/
|
||||||
|
params.four_color_rgb = T_FC(quality);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @use_fuji_rotate
|
||||||
|
* Don't stretch or rotate raw pixels (0 - off, 1 - on)
|
||||||
|
*/
|
||||||
|
params.use_fuji_rotate = T_SR(quality) ? 0 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LoadRAW(QImageIOHandler *handler, QImage &img)
|
||||||
|
{
|
||||||
|
std::unique_ptr<LibRaw> rawProcessor(new LibRaw);
|
||||||
|
|
||||||
|
// *** Set parameters
|
||||||
|
setParams(handler, rawProcessor.get());
|
||||||
|
|
||||||
|
// *** Open the stream
|
||||||
|
auto device = handler->device();
|
||||||
|
#ifndef EXCLUDE_LibRaw_QIODevice
|
||||||
|
LibRaw_QIODevice stream(device);
|
||||||
|
if (rawProcessor->open_datastream(&stream) != LIBRAW_SUCCESS) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
auto ba = device->readAll();
|
||||||
|
if (rawProcessor->open_buffer(ba.data(), ba.size()) != LIBRAW_SUCCESS) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// *** Unpacking selected image
|
||||||
|
if (rawProcessor->unpack() != LIBRAW_SUCCESS) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// *** Process selected image
|
||||||
|
if (rawProcessor->dcraw_process() != LIBRAW_SUCCESS) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// *** Convert to QImage
|
||||||
|
pi_unique_ptr processedImage(rawProcessor->dcraw_make_mem_image(), LibRaw::dcraw_clear_mem);
|
||||||
|
if (processedImage == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
if ((processedImage->type != LIBRAW_IMAGE_BITMAP) ||
|
||||||
|
(processedImage->colors != 1 && processedImage->colors != 3 && processedImage->colors != 4) ||
|
||||||
|
(processedImage->bits != 8 && processedImage->bits != 16)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
auto format = QImage::Format_Invalid;
|
||||||
|
switch (processedImage->colors) {
|
||||||
|
case 1: // Gray images (tested with image attached on https://bugs.kde.org/show_bug.cgi?id=401371)
|
||||||
|
format = processedImage->bits == 8 ? QImage::Format_Grayscale8 : QImage::Format_Grayscale16;
|
||||||
|
break;
|
||||||
|
case 3: // Images with R G B components
|
||||||
|
format = processedImage->bits == 8 ? QImage::Format_RGB888 : QImage::Format_RGBX64;
|
||||||
|
break;
|
||||||
|
case 4: // Images with R G B components + Alpha (never seen)
|
||||||
|
format = processedImage->bits == 8 ? QImage::Format_RGBA8888 : QImage::Format_RGBA64;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (format == QImage::Format_Invalid) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
img = imageAlloc(processedImage->width, processedImage->height, format);
|
||||||
|
if (img.isNull()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto rawBytesPerLine = qint32(processedImage->width * processedImage->bits * processedImage->colors + 7) / 8;
|
||||||
|
auto lineSize = std::min(qint32(img.bytesPerLine()), rawBytesPerLine);
|
||||||
|
for (int y = 0, h = img.height(); y < h; ++y) {
|
||||||
|
auto scanline = img.scanLine(y);
|
||||||
|
if (format == QImage::Format_RGBX64)
|
||||||
|
rgbToRgbX<quint16>(scanline, processedImage->data + rawBytesPerLine * y, img.bytesPerLine(), rawBytesPerLine);
|
||||||
|
else
|
||||||
|
memcpy(scanline, processedImage->data + rawBytesPerLine * y, lineSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
// *** Set the color space
|
||||||
|
auto &¶ms = rawProcessor->imgdata.params;
|
||||||
|
if (params.output_color == 0) {
|
||||||
|
auto &&color = rawProcessor->imgdata.color;
|
||||||
|
if (auto profile = reinterpret_cast<char *>(color.profile)) {
|
||||||
|
img.setColorSpace(QColorSpace::fromIccProfile(QByteArray(profile, color.profile_length)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (processedImage->colors >= 3) {
|
||||||
|
if (params.output_color == 1) {
|
||||||
|
img.setColorSpace(QColorSpace(QColorSpace::SRgb));
|
||||||
|
}
|
||||||
|
if (params.output_color == 2) {
|
||||||
|
img.setColorSpace(QColorSpace(QColorSpace::AdobeRgb));
|
||||||
|
}
|
||||||
|
if (params.output_color == 4) {
|
||||||
|
img.setColorSpace(QColorSpace(QColorSpace::ProPhotoRgb));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// *** Set the metadata
|
||||||
|
auto &&iparams = rawProcessor->imgdata.idata;
|
||||||
|
|
||||||
|
auto xmpPacket = QString();
|
||||||
|
if (auto xmpdata = iparams.xmpdata) {
|
||||||
|
if (auto xmplen = iparams.xmplen)
|
||||||
|
xmpPacket = QString::fromUtf8(xmpdata, xmplen);
|
||||||
|
}
|
||||||
|
// Add info from LibRAW structs (e.g. GPS position, info about lens, info about shot and flash, etc...)
|
||||||
|
img.setText(QStringLiteral("XML:com.adobe.xmp"), updateXmpPacket(xmpPacket, rawProcessor.get()));
|
||||||
|
|
||||||
|
auto model = QString::fromUtf8(iparams.normalized_model);
|
||||||
|
if (!model.isEmpty()) {
|
||||||
|
img.setText(QStringLiteral("Model"), model);
|
||||||
|
}
|
||||||
|
auto manufacturer = QString::fromUtf8(iparams.normalized_make);
|
||||||
|
if (!manufacturer.isEmpty()) {
|
||||||
|
img.setText(QStringLiteral("Manufacturer"), manufacturer);
|
||||||
|
}
|
||||||
|
auto software = QString::fromUtf8(iparams.software);
|
||||||
|
if (!software.isEmpty()) {
|
||||||
|
img.setText(QStringLiteral("Software"), software);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto &&iother = rawProcessor->imgdata.other;
|
||||||
|
auto description = QString::fromUtf8(iother.desc);
|
||||||
|
if (!description.isEmpty()) {
|
||||||
|
img.setText(QStringLiteral("Description"), description);
|
||||||
|
}
|
||||||
|
auto artist = QString::fromUtf8(iother.artist);
|
||||||
|
if (!artist.isEmpty()) {
|
||||||
|
img.setText(QStringLiteral("Author"), artist);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // Private
|
||||||
|
|
||||||
|
RAWHandler::RAWHandler()
|
||||||
|
: m_imageNumber(0)
|
||||||
|
, m_imageCount(0)
|
||||||
|
, m_quality(-1)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RAWHandler::canRead() const
|
||||||
|
{
|
||||||
|
if (canRead(device())) {
|
||||||
|
setFormat("raw");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RAWHandler::read(QImage *image)
|
||||||
|
{
|
||||||
|
auto dev = device();
|
||||||
|
|
||||||
|
// Check image file format.
|
||||||
|
if (dev->atEnd()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QImage img;
|
||||||
|
if (!LoadRAW(this, img)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
*image = img;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RAWHandler::setOption(ImageOption option, const QVariant &value)
|
||||||
|
{
|
||||||
|
if (option == QImageIOHandler::Quality) {
|
||||||
|
bool ok = false;
|
||||||
|
auto q = value.toInt(&ok);
|
||||||
|
if (ok) {
|
||||||
|
m_quality = q;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RAWHandler::supportsOption(ImageOption option) const
|
||||||
|
{
|
||||||
|
#ifndef EXCLUDE_LibRaw_QIODevice
|
||||||
|
if (option == QImageIOHandler::Size) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (option == QImageIOHandler::Quality) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant RAWHandler::option(ImageOption option) const
|
||||||
|
{
|
||||||
|
QVariant v;
|
||||||
|
|
||||||
|
#ifndef EXCLUDE_LibRaw_QIODevice
|
||||||
|
if (option == QImageIOHandler::Size) {
|
||||||
|
auto d = device();
|
||||||
|
d->startTransaction();
|
||||||
|
std::unique_ptr<LibRaw> rawProcessor(new LibRaw);
|
||||||
|
LibRaw_QIODevice stream(d);
|
||||||
|
#if (LIBRAW_VERSION < LIBRAW_MAKE_VERSION(0, 21, 0))
|
||||||
|
rawProcessor->imgdata.params.shot_select = currentImageNumber();
|
||||||
|
#else
|
||||||
|
rawProcessor->imgdata.rawparams.shot_select = currentImageNumber();
|
||||||
|
#endif
|
||||||
|
if (rawProcessor->open_datastream(&stream) == LIBRAW_SUCCESS) {
|
||||||
|
if (rawProcessor->unpack() == LIBRAW_SUCCESS) {
|
||||||
|
auto w = libraw_get_iwidth(&rawProcessor->imgdata);
|
||||||
|
auto h = libraw_get_iheight(&rawProcessor->imgdata);
|
||||||
|
// flip & 4: taken from LibRaw code
|
||||||
|
v = (rawProcessor->imgdata.sizes.flip & 4) ? QSize(h, w) : QSize(w, h);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d->rollbackTransaction();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (option == QImageIOHandler::Quality) {
|
||||||
|
v = m_quality;
|
||||||
|
}
|
||||||
|
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RAWHandler::jumpToNextImage()
|
||||||
|
{
|
||||||
|
return jumpToImage(m_imageNumber + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RAWHandler::jumpToImage(int imageNumber)
|
||||||
|
{
|
||||||
|
if (imageNumber >= imageCount()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
m_imageNumber = imageNumber;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int RAWHandler::imageCount() const
|
||||||
|
{
|
||||||
|
// NOTE: image count is cached for performance reason
|
||||||
|
auto &&count = m_imageCount;
|
||||||
|
if (count > 0) {
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
count = QImageIOHandler::imageCount();
|
||||||
|
|
||||||
|
#ifndef EXCLUDE_LibRaw_QIODevice
|
||||||
|
auto d = device();
|
||||||
|
d->startTransaction();
|
||||||
|
|
||||||
|
std::unique_ptr<LibRaw> rawProcessor(new LibRaw);
|
||||||
|
LibRaw_QIODevice stream(d);
|
||||||
|
if (rawProcessor->open_datastream(&stream) == LIBRAW_SUCCESS) {
|
||||||
|
count = rawProcessor->imgdata.rawdata.iparams.raw_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
d->rollbackTransaction();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
int RAWHandler::currentImageNumber() const
|
||||||
|
{
|
||||||
|
return m_imageNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RAWHandler::canRead(QIODevice *device)
|
||||||
|
{
|
||||||
|
device->startTransaction();
|
||||||
|
|
||||||
|
std::unique_ptr<LibRaw> rawProcessor(new LibRaw);
|
||||||
|
|
||||||
|
#ifndef EXCLUDE_LibRaw_QIODevice
|
||||||
|
LibRaw_QIODevice stream(device);
|
||||||
|
auto ok = rawProcessor->open_datastream(&stream) == LIBRAW_SUCCESS;
|
||||||
|
#else
|
||||||
|
auto ba = device->readAll();
|
||||||
|
auto ok = rawProcessor->open_buffer(ba.data(), ba.size()) == LIBRAW_SUCCESS;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
device->rollbackTransaction();
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
QImageIOPlugin::Capabilities RAWPlugin::capabilities(QIODevice *device, const QByteArray &format) const
|
||||||
|
{
|
||||||
|
if (supported_formats.contains(QByteArray(format).toLower())) {
|
||||||
|
return Capabilities(CanRead);
|
||||||
|
}
|
||||||
|
if (!format.isEmpty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
if (!device->isOpen()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
Capabilities cap;
|
||||||
|
if (device->isReadable() && RAWHandler::canRead(device)) {
|
||||||
|
cap |= CanRead;
|
||||||
|
}
|
||||||
|
return cap;
|
||||||
|
}
|
||||||
|
|
||||||
|
QImageIOHandler *RAWPlugin::create(QIODevice *device, const QByteArray &format) const
|
||||||
|
{
|
||||||
|
QImageIOHandler *handler = new RAWHandler;
|
||||||
|
handler->setDevice(device);
|
||||||
|
handler->setFormat(format);
|
||||||
|
return handler;
|
||||||
|
}
|
7
src/imageformats/raw.desktop
Normal file
7
src/imageformats/raw.desktop
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Type=Service
|
||||||
|
X-KDE-ServiceTypes=QImageIOPlugins
|
||||||
|
X-KDE-ImageFormat=3fr,arw,arq,bay,bmq,crw,cr2,cr3,cap,cine,cs1,dcs,dc2,dcr,dng,drf,dxo,eip,erf,fff,hdr,iiq,k25,kdc,kc2,mdc,mef,mfw,mos,mrw,nef,nrw,obm,orf,ori,pef,ptx,pxn,qtk,r3d,raf,raw,rdc,rwl,rw2,rwz,sr2,srf,srw,sti,x3f
|
||||||
|
X-KDE-MimeType=image/x-hasselblad-3fr,image/x-sony-arw,image/x-arq,image/x-bay,image/x-bmq,image/x-canon-crw,image/x-canon-cr2,image/x-canon-cr3,image/x-cap,image/x-cine,image/x-cs1,image/x-kodak-dcs,image/x-dc2,image/x-kodak-dcr,image/x-adobe-dng,image/x-drf,image/x-dxo,image/x-epson-eip,image/x-epson-erf,image/x-fff,image/x-hdr,image/x-iiq,image/x-kodak-k25,image/x-kodak-kdc,image/x-kodak-kc2,image/x-minolta-mdc,image/x-mamiya-mef,image/x-mfw,image/x-aptus-mos,image/x-minolta-mrw,image/x-nikon-nef,image/x-nikon-nrw,image/x-obm,image/x-olympus-orf,image/x-ori,image/x-pentax-pef,image/x-ptx,image/x-pxn,image/x-qtk,image/x-r3d,image/x-fuji-raf,image/x-raw,image/x-rdc,image/x-rwl,image/x-panasonic-rw2,image/x-rwz,image/x-sony-sr2,image/x-sony-srf,image/x-samsung-srw,image/x-sti,image/x-sigma-x3f
|
||||||
|
X-KDE-Read=true
|
||||||
|
X-KDE-Write=false
|
42
src/imageformats/raw.json
Normal file
42
src/imageformats/raw.json
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
"Keys": [
|
||||||
|
"3fr",
|
||||||
|
"arw", "arq",
|
||||||
|
"bay", "bmq",
|
||||||
|
"crw", "cr2", "cr3", "cap", "cine", "cs1",
|
||||||
|
"dcs", "dc2", "dcr", "dng", "drf", "dxo",
|
||||||
|
"eip", "erf",
|
||||||
|
"fff",
|
||||||
|
"hdr",
|
||||||
|
"iiq",
|
||||||
|
"k25", "kdc", "kc2",
|
||||||
|
"mdc", "mef", "mfw", "mos", "mrw",
|
||||||
|
"nef", "nrw",
|
||||||
|
"obm", "orf", "ori",
|
||||||
|
"pef", "ptx", "pxn",
|
||||||
|
"qtk",
|
||||||
|
"r3d", "raf", "raw", "rdc", "rwl", "rw2", "rwz",
|
||||||
|
"sr2", "srf", "srw", "sti",
|
||||||
|
"x3f"
|
||||||
|
],
|
||||||
|
"MimeTypes": [
|
||||||
|
"image/x-hasselblad-3fr",
|
||||||
|
"image/x-sony-arw", "image/x-arq",
|
||||||
|
"image/x-bay", "image/x-bmq",
|
||||||
|
"image/x-canon-crw", "image/x-canon-cr2", "image/x-canon-cr3", "image/x-cap", "image/x-cine", "image/x-cs1",
|
||||||
|
"image/x-kodak-dcs", "image/x-dc2", "image/x-kodak-dcr", "image/x-adobe-dng", "image/x-drf", "image/x-dxo",
|
||||||
|
"image/x-epson-eip", "image/x-epson-erf",
|
||||||
|
"image/x-fff",
|
||||||
|
"image/x-hdr",
|
||||||
|
"image/x-iiq",
|
||||||
|
"image/x-kodak-k25", "image/x-kodak-kdc", "image/x-kodak-kc2",
|
||||||
|
"image/x-minolta-mdc", "image/x-mamiya-mef", "image/x-mfw", "image/x-aptus-mos", "image/x-minolta-mrw",
|
||||||
|
"image/x-nikon-nef", "image/x-nikon-nrw",
|
||||||
|
"image/x-obm", "image/x-olympus-orf", "image/x-ori",
|
||||||
|
"image/x-pentax-pef", "image/x-ptx", "image/x-pxn",
|
||||||
|
"image/x-qtk",
|
||||||
|
"image/x-r3d", "image/x-fuji-raf", "image/x-raw", "image/x-rdc", "image/x-rwl", "image/x-panasonic-rw2", "image/x-rwz",
|
||||||
|
"image/x-sony-sr2", "image/x-sony-srf", "image/x-samsung-srw", "image/x-sti",
|
||||||
|
"image/x-sigma-x3f"
|
||||||
|
]
|
||||||
|
}
|
93
src/imageformats/raw_p.h
Normal file
93
src/imageformats/raw_p.h
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
/*
|
||||||
|
This file is part of the KDE project
|
||||||
|
|
||||||
|
SPDX-FileCopyrightText: 2022 Mirco Miranda <mircomir@outlook.com>
|
||||||
|
|
||||||
|
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef KIMG_RAW_P_H
|
||||||
|
#define KIMG_RAW_P_H
|
||||||
|
|
||||||
|
#include <QImageIOPlugin>
|
||||||
|
|
||||||
|
class RAWHandler : public QImageIOHandler
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
RAWHandler();
|
||||||
|
|
||||||
|
bool canRead() const override;
|
||||||
|
bool read(QImage *image) override;
|
||||||
|
|
||||||
|
void setOption(ImageOption option, const QVariant &value) override;
|
||||||
|
bool supportsOption(QImageIOHandler::ImageOption option) const override;
|
||||||
|
QVariant option(QImageIOHandler::ImageOption option) const override;
|
||||||
|
|
||||||
|
bool jumpToNextImage() override;
|
||||||
|
bool jumpToImage(int imageNumber) override;
|
||||||
|
int imageCount() const override;
|
||||||
|
int currentImageNumber() const override;
|
||||||
|
|
||||||
|
static bool canRead(QIODevice *device);
|
||||||
|
|
||||||
|
private:
|
||||||
|
qint32 m_imageNumber;
|
||||||
|
|
||||||
|
mutable qint32 m_imageCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief m_quality
|
||||||
|
* Change the quality of the conversion. If -1, default quality is used.
|
||||||
|
* @note Verify that the quality change support has been compiled with supportsOption()
|
||||||
|
*
|
||||||
|
* 3 2 1 0
|
||||||
|
* 1 0 9 8 7 6 5 4 3 2 1 0 9 87 6 5 4 3 2 1098 7654 3210
|
||||||
|
* _ _ _ _ _ _ _ _ _ _ _ S F NN E H B A W CCCC IIII PPPP
|
||||||
|
*
|
||||||
|
* Where:
|
||||||
|
*
|
||||||
|
* _: reserved
|
||||||
|
* P: preset values: *** if set, other flags are ignored! ***
|
||||||
|
* - 0: Use other flags (no preset)
|
||||||
|
* - 1: I = 0, C = 1, B = 0, W = 1, A = 1, H = 1 (Linear, sRGB, 8-bits, Camera White, Auto White, Half-size)
|
||||||
|
* - 2: I = 0, C = 1, B = 0, W = 1, A = 1, H = 0 (Linear, sRGB, 8-bits, Camera White, Auto White)
|
||||||
|
* - 3: I = 3, C = 1, B = 0, W = 1, A = 1, H = 0 (AHD, sRGB, 8-bits, Camera White, Auto White)
|
||||||
|
* - 4: I = 3, C = 1, B = 1, W = 1, A = 1, H = 0 (AHD, sRGB, 16-bits, Camera White, Auto White)
|
||||||
|
* - 5: I = 3, C = 2, B = 1, W = 1, A = 1, H = 0 (AHD, Adobe, 16-bits, Camera White, Auto White)
|
||||||
|
* - 6: I = 3, C = 4, B = 1, W = 1, A = 1, H = 0 (AHD, ProPhoto, 16-bits, Camera White, Auto White)
|
||||||
|
* - 7: I = 11, C = 1, B = 0, W = 1, A = 1, H = 0 (DHT, sRGB, 8-bits, Camera White, Auto White)
|
||||||
|
* - 8: I = 11, C = 1, B = 1, W = 1, A = 1, H = 0 (DHT, sRGB, 16-bits, Camera White, Auto White)
|
||||||
|
* - 9: I = 11, C = 2, B = 1, W = 1, A = 1, H = 0 (DHT, Adobe, 16-bits, Camera White, Auto White)
|
||||||
|
* - 10: I = 11, C = 4, B = 1, W = 1, A = 1, H = 0 (DHT, ProPhoto, 16-bits, Camera White, Auto White)
|
||||||
|
* - 11: reserved
|
||||||
|
* - 12: reserved
|
||||||
|
* - 13: reserved
|
||||||
|
* - 14: reserved
|
||||||
|
* - 15: reserved
|
||||||
|
* I: interpolation quality (0 - linear, 1 - VNG, 2 - PPG, 3 - AHD, 4 - DCB, 11 - DHT, 12 - AAHD)
|
||||||
|
* C: output colorspace (0 - raw, 1 - sRGB, 2 - Adobe, 3 - Wide, 4 - ProPhoto, 5 - XYZ, 6 - ACES, 7 - DCI-P3, 8 - Rec2020)
|
||||||
|
* W: use camera white balace (0 - off, 1 - on)
|
||||||
|
* A: use auto white balance (0 - off, 1 - on)
|
||||||
|
* B: output bit per sample (0 - 8-bits, 1 - 16-bits)
|
||||||
|
* H: half size image (0 - off, 1 - on)
|
||||||
|
* E: DCB color enhance (0 - off, 1 - on)
|
||||||
|
* N: FBDD noise reduction (0 - off, 1 - light, 2 - full)
|
||||||
|
* F: Interpolate RGGB as four colors (0 - off, 1 - on)
|
||||||
|
* S: Don't stretch or rotate raw pixels (0 - rotate and stretch, 1 - don't rotate and stretch)
|
||||||
|
*
|
||||||
|
* @note It is safe to set both W and A: W is used if camera white balance is found, otherwise A is used.
|
||||||
|
*/
|
||||||
|
qint32 m_quality;
|
||||||
|
};
|
||||||
|
|
||||||
|
class RAWPlugin : public QImageIOPlugin
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QImageIOHandlerFactoryInterface" FILE "raw.json")
|
||||||
|
|
||||||
|
public:
|
||||||
|
Capabilities capabilities(QIODevice *device, const QByteArray &format) const override;
|
||||||
|
QImageIOHandler *create(QIODevice *device, const QByteArray &format = QByteArray()) const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // KIMG_RAW_P_H
|
Loading…
x
Reference in New Issue
Block a user