KImageFormats

raw.cpp
1/*
2 RAW support for QImage.
3
4 SPDX-FileCopyrightText: 2022 Mirco Miranda <mircomir@outlook.com>
5
6 SPDX-License-Identifier: LGPL-2.0-or-later
7*/
8
9#include "raw_p.h"
10#include "util_p.h"
11
12#include <QColorSpace>
13#include <QDateTime>
14#include <QDebug>
15#include <QImage>
16#include <QSet>
17#include <QTimeZone>
18
19#if defined(Q_OS_WINDOWS) && !defined(NOMINMAX)
20#define NOMINMAX
21#endif
22
23#include <libraw/libraw.h>
24
25#ifdef QT_DEBUG
26// This should be used to exclude the local QIODevice wrapper of the LibRaw_abstract_datastream interface.
27// If the result changes then the problem is in the local wrapper and must be corrected.
28// WARNING: using the LibRaw's streams instead of the local wrapper from a Qt program falls in the LOCALE
29// bug when the RAW file needs the scanf_one() function (e.g. *.MOS files). See also raw_scanf_one().
30//#define EXCLUDE_LibRaw_QIODevice // Uncomment this code if you think that the problem is LibRaw_QIODevice (default commented)
31#endif
32
33namespace // Private.
34{
35
36// smart pointer for processed image
37using pi_unique_ptr = std::unique_ptr<libraw_processed_image_t, std::function<void(libraw_processed_image_t *)>>;
38
39// clang-format off
40// Known formats supported by LibRaw (in alphabetical order and lower case)
41const auto supported_formats = QSet<QByteArray>{
42 "3fr",
43 "arw", "arq",
44 "bay", "bmq",
45 "cr2", "cr3", "cap", "cine", "cs1", "crw",
46 "dcs", "dc2", "dcr", "dng", "drf", "dxo",
47 "eip", "erf",
48 "fff",
49 "iiq",
50 "k25", "kc2", "kdc",
51 "mdc", "mef", "mfw", "mos", "mrw",
52 "nef", "nrw",
53 "obm", "orf", "ori",
54 "pef", "ptx", "pxn",
55 "qtk",
56 "r3d", "raf", "raw", "rdc", "rw2", "rwl", "rwz",
57 "sr2", "srf", "srw", "sti",
58 "x3f"
59};
60// clang-format on
61
62inline int raw_scanf_one(const QByteArray &ba, const char *fmt, void *val)
63{
64 // WARNING: Here it would be nice to use sscanf like LibRaw does but there
65 // is a Big Trouble: THE LOCALE! LibRaw is also affected by the
66 // issue if used in a Qt program:
67 // If you use sscanf here the conversion is wrong when performed
68 // with Italian locale (where the decimal separator is the comma).
69 // The solution should be to use std::locale::global() but it's global!
70 // I don't want to do it. So, don't use code like that:
71 // return sscanf(QByteArray(ba).append('\0').data(), fmt, val);
72
73 // LibRaw is asking only "%d" and "%f" for now. This code is not affected
74 // by the LOCALE bug.
75 auto s = QString::fromLatin1(ba);
76 if (strcmp(fmt, "%d") == 0) {
77 auto ok = false;
78 auto d = QLocale::c().toInt(s, &ok);
79 if (!ok) {
80 return EOF;
81 }
82 *(static_cast<int *>(val)) = d;
83 } else {
84 auto ok = false;
85 auto f = QLocale::c().toFloat(s, &ok);
86 if (!ok) {
87 return EOF;
88 }
89 *(static_cast<float *>(val)) = f;
90 }
91 return 1;
92}
93
94/**
95 * @brief The LibRaw_QIODevice class
96 * Implementation of the LibRaw stream interface over a QIODevice.
97 */
98class LibRaw_QIODevice : public LibRaw_abstract_datastream
99{
100public:
101 explicit LibRaw_QIODevice(QIODevice *device)
102 {
103 m_device = device;
104 }
105 virtual ~LibRaw_QIODevice() override
106 {
107 }
108 virtual int valid() override
109 {
110 return m_device != nullptr;
111 }
112 virtual int read(void *ptr, size_t sz, size_t nmemb) override
113 {
114 qint64 read = 0;
115 if (sz == 0) {
116 return 0;
117 }
118 auto data = reinterpret_cast<char*>(ptr);
119 for (qint64 r = 0, size = sz * nmemb; read < size; read += r) {
120 if (m_device->atEnd()) {
121 break;
122 }
123 r = m_device->read(data + read, size - read);
124 if (r < 1) {
125 break;
126 }
127 }
128 return int(read / sz);
129 }
130 virtual int eof() override
131 {
132 return m_device->atEnd() ? 1 : 0;
133 }
134 virtual int seek(INT64 o, int whence) override
135 {
136 auto pos = o;
137 auto size = m_device->size();
138 if (whence == SEEK_CUR) {
139 pos = m_device->pos() + o;
140 }
141 if (whence == SEEK_END) {
142 pos = size + o;
143 }
144 if (pos < 0 || m_device->isSequential()) {
145 return -1;
146 }
147 return m_device->seek(pos) ? 0 : -1;
148 }
149 virtual INT64 tell() override
150 {
151 return m_device->pos();
152 }
153 virtual INT64 size() override
154 {
155 return m_device->size();
156 }
157 virtual char *gets(char *s, int sz) override
158 {
159 if (m_device->readLine(s, sz) > 0) {
160 return s;
161 }
162 return nullptr;
163 }
164 virtual int scanf_one(const char *fmt, void *val) override
165 {
166 QByteArray ba;
167 for (int xcnt = 0; xcnt < 24 && !m_device->atEnd(); ++xcnt) {
168 char c;
169 if (!m_device->getChar(&c)) {
170 return EOF;
171 }
172 if (ba.isEmpty() && (c == ' ' || c == '\t')) {
173 continue;
174 }
175 if (c == '\0' || c == ' ' || c == '\t' || c == '\n') {
176 break;
177 }
178 ba.append(c);
179 }
180 return raw_scanf_one(ba, fmt, val);
181 }
182 virtual int get_char() override
183 {
184 unsigned char c;
185 if (!m_device->getChar(reinterpret_cast<char *>(&c))) {
186 return EOF;
187 }
188 return int(c);
189 }
190#if (LIBRAW_VERSION < LIBRAW_MAKE_VERSION(0, 21, 0)) || defined(LIBRAW_OLD_VIDEO_SUPPORT)
191 virtual void *make_jas_stream() override
192 {
193 return nullptr;
194 }
195#endif
196
197private:
198 QIODevice *m_device;
199};
200
201bool addTag(const QString &tag, QStringList &lines)
202{
203 auto ok = !tag.isEmpty();
204 if (ok) {
205 lines << tag;
206 }
207 return ok;
208}
209
210QString createTag(QString value, const char *tag)
211{
212 if (!value.isEmpty()) {
213 value = QStringLiteral("<%1>%2</%1>").arg(QString::fromLatin1(tag), value);
214 }
215 return value;
216}
217
218QString createTag(char *asciiz, const char *tag)
219{
220 auto value = QString::fromUtf8(asciiz);
221 return createTag(value, tag);
222}
223
224QString createTimeTag(time_t time, const char *tag)
225{
226 auto value = QDateTime::fromSecsSinceEpoch(time, QTimeZone::utc());
227 if (value.isValid() && time > 0) {
228 return createTag(value.toString(Qt::ISODate), tag);
229 }
230 return QString();
231}
232
233QString createFlashTag(short flash, const char *tag)
234{
235 QStringList l;
236 auto lc = QLocale::c();
237 // EXIF specifications
238 auto t = QStringLiteral("true");
239 auto f = QStringLiteral("false");
240 l << QStringLiteral("<exif:Fired>%1</exif:Fired>").arg((flash & 1) ? t : f);
241 l << QStringLiteral("<exif:Function>%1</exif:Function>").arg((flash & (1 << 5)) ? t : f);
242 l << QStringLiteral("<exif:RedEyeMode>%1</exif:RedEyeMode>").arg((flash & (1 << 6)) ? t : f);
243 l << QStringLiteral("<exif:Mode>%1</exif:Mode>").arg(lc.toString((int(flash) >> 3) & 3));
244 l << QStringLiteral("<exif:Return>%1</exif:Return>").arg(lc.toString((int(flash) >> 1) & 3));
245 return createTag(l.join(QChar()), tag);
246}
247
248QString createTag(quint64 n, const char *tag, quint64 invalid = 0)
249{
250 if (n != invalid) {
251 return createTag(QLocale::c().toString(n), tag);
252 }
253 return QString();
254}
255
256QString createTag(qint16 n, const char *tag, qint16 invalid = 0)
257{
258 if (n != invalid) {
259 return createTag(QLocale::c().toString(n), tag);
260 }
261 return QString();
262}
263
264QString createTag(quint16 n, const char *tag, quint16 invalid = 0)
265{
266 if (n != invalid) {
267 return createTag(QLocale::c().toString(n), tag);
268 }
269 return QString();
270}
271
272QString createTag(float f, const char *tag, qint32 mul = 1)
273{
274 if (f != 0) {
275 if (mul > 1)
276 return QStringLiteral("<%1>%2/%3</%1>").arg(QString::fromLatin1(tag), QLocale::c().toString(int(f * mul))).arg(mul);
277 return QStringLiteral("<%1>%2</%1>").arg(QString::fromLatin1(tag), QLocale::c().toString(f));
278 }
279 return QString();
280}
281
282QString createTag(libraw_gps_info_t gps, const char *tag)
283{
284 auto tmp = QString::fromLatin1(tag);
285 if (tmp.contains(QStringLiteral("Latitude"), Qt::CaseInsensitive)) {
286 if (gps.latref != '\0') {
287 auto lc = QLocale::c();
288 auto value = QStringLiteral("%1,%2%3")
289 .arg(lc.toString(gps.latitude[0], 'f', 0))
290 .arg(lc.toString(gps.latitude[1] + gps.latitude[2] / 60, 'f', 4))
291 .arg(QChar::fromLatin1(gps.latref));
292 return createTag(value, tag);
293 }
294 }
295 if (tmp.contains(QStringLiteral("Longitude"), Qt::CaseInsensitive)) {
296 if (gps.longref != '\0') {
297 auto lc = QLocale::c();
298 auto value = QStringLiteral("%1,%2%3")
299 .arg(lc.toString(gps.longitude[0], 'f', 0))
300 .arg(lc.toString(gps.longitude[1] + gps.longitude[2] / 60, 'f', 4))
301 .arg(QChar::fromLatin1(gps.longref));
302 return createTag(value, tag);
303 }
304 }
305 if (tmp.contains(QStringLiteral("Altitude"), Qt::CaseInsensitive)) {
306 if (gps.altitude != 0) {
307 return createTag(gps.altitude, tag, 1000);
308 }
309 }
310 return QString();
311}
312
313QString createXmpPacket()
314{
315 QStringList lines;
316 lines << QStringLiteral("<?xpacket begin=\"\" id=\"W5M0MpCehiHzreSzNTczkc9d\"?>");
317 lines << QStringLiteral("<x:xmpmeta xmlns:x=\"adobe:ns:meta/\" x:xmptk=\"KIMG RAW Plugin\">");
318 lines << QStringLiteral("<rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">");
319 lines << QStringLiteral("</rdf:RDF>");
320 lines << QStringLiteral("</x:xmpmeta>");
321 for (auto i = 30; i > 0; --i)
322 lines << QString::fromLatin1(QByteArray(80, ' '));
323 lines << QStringLiteral("<?xpacket end=\"w\"?>");
324 return lines.join(QChar::fromLatin1('\n'));
325}
326
327QString updateXmpPacket(const QString &xmpPacket, LibRaw *rawProcessor)
328{
329 auto rdfEnd = xmpPacket.indexOf(QStringLiteral("</rdf:RDF>"), Qt::CaseInsensitive);
330 if (rdfEnd < 0) {
331 return updateXmpPacket(createXmpPacket(), rawProcessor);
332 }
333
334 auto lines = QStringList() << xmpPacket.left(rdfEnd);
335 lines << QStringLiteral("<rdf:Description rdf:about=\"\"");
336 lines << QStringLiteral(" xmlns:xmp=\"http://ns.adobe.com/xap/1.0/\"");
337 lines << QStringLiteral(" xmlns:dc=\"http://purl.org/dc/elements/1.1/\"");
338 lines << QStringLiteral(" xmlns:aux=\"http://ns.adobe.com/exif/1.0/aux/\"");
339 lines << QStringLiteral(" xmlns:xmpMM=\"http://ns.adobe.com/xap/1.0/mm/\"");
340 lines << QStringLiteral(" xmlns:stEvt=\"http://ns.adobe.com/xap/1.0/sType/ResourceEvent#\"");
341 lines << QStringLiteral(" xmlns:stRef=\"http://ns.adobe.com/xap/1.0/sType/ResourceRef#\"");
342 lines << QStringLiteral(" xmlns:tiff=\"http://ns.adobe.com/tiff/1.0/\"");
343 lines << QStringLiteral(" xmlns:exif=\"http://ns.adobe.com/exif/1.0/\"");
344 lines << QStringLiteral(" xmlns:xmpRights=\"http://ns.adobe.com/xap/1.0/rights/\">");
345 lines << QStringLiteral("<xmpMM:History>");
346 lines << QStringLiteral("<rdf:Seq>");
347 lines << QStringLiteral("<rdf:li rdf:parseType=\"Resource\">");
348 lines << QStringLiteral("<stEvt:action>converted</stEvt:action>");
349 lines << QStringLiteral("<stEvt:parameters>Converted from RAW to Qt Image using KIMG RAW plugin</stEvt:parameters>");
350 lines << QStringLiteral("<stEvt:softwareAgent>LibRaw %1</stEvt:softwareAgent>").arg(QString::fromLatin1(LibRaw::version()));
351 lines << QStringLiteral("<stEvt:when>%1</stEvt:when>").arg(QDateTime::currentDateTimeUtc().toString(Qt::ISODate));
352 lines << QStringLiteral("</rdf:li>");
353 lines << QStringLiteral("</rdf:Seq>");
354 lines << QStringLiteral("</xmpMM:History>");
355
356 auto &&iparams = rawProcessor->imgdata.idata;
357 addTag(createTag(iparams.normalized_model, "tiff:Model"), lines);
358 addTag(createTag(iparams.normalized_make, "tiff:Make"), lines);
359 addTag(createTag(iparams.software, "xmp:CreatorTool"), lines);
360
361 auto &&iother = rawProcessor->imgdata.other;
362 addTag(createTag(createTag(createTag(iother.desc, "rdf:li"), "rdf:Alt"), "dc:description"), lines);
363 addTag(createTag(createTag(createTag(iother.artist, "rdf:li"), "rdf:Seq"), "dc:creator"), lines);
364 addTag(createTag(createTag(createTag(iother.iso_speed, "rdf:li"), "rdf:Seq"), "exif:ISOSpeedRatings"), lines);
365 addTag(createTag(iother.shutter, "exif:ExposureTime", 1000), lines);
366 addTag(createTag(iother.aperture, "exif:ApertureValue", 1000), lines);
367 addTag(createTag(iother.focal_len, "exif:FocalLength", 1000), lines);
368 addTag(createTimeTag(iother.timestamp, "xmp:CreateDate"), lines);
369 addTag(createTag(iother.parsed_gps, "exif:GPSLatitude"), lines);
370 addTag(createTag(iother.parsed_gps, "exif:GPSLongitude"), lines);
371 addTag(createTag(iother.parsed_gps, "exif:GPSAltitude"), lines);
372
373 auto &&shotinfo = rawProcessor->imgdata.shootinginfo;
374 addTag(createTag(shotinfo.ExposureMode, "exif:ExposureMode", short(-1)), lines);
375 addTag(createTag(shotinfo.MeteringMode, "exif:MeteringMode", short(-1)), lines);
376 addTag(createTag(shotinfo.BodySerial, "aux:SerialNumber"), lines);
377
378 auto &&color = rawProcessor->imgdata.color;
379 addTag(createFlashTag(color.flash_used, "exif:Flash"), lines);
380
381 auto &&lens = rawProcessor->imgdata.lens;
382 addTag(createTag(lens.FocalLengthIn35mmFormat, "exif:FocalLengthIn35mmFilm"), lines);
383 addTag(createTag(lens.Lens, "aux:Lens"), lines);
384 addTag(createTag(lens.LensSerial, "aux:LensSerialNumber"), lines);
385 addTag(createTag(lens.nikon.LensIDNumber ? quint64(lens.nikon.LensIDNumber) : lens.makernotes.LensID, "aux:LensID"), lines);
386
387 auto &&makernotes = rawProcessor->imgdata.makernotes;
388 addTag(createTag(makernotes.common.firmware, "aux:Firmware"), lines);
389
390 lines << QStringLiteral("</rdf:Description>");
391 lines << xmpPacket.mid(rdfEnd);
392
393 return lines.join(QChar::fromLatin1('\n'));
394}
395
396template<class T>
397inline void rgbToRgbX(uchar *target, const uchar *source, qint32 targetSize, qint32 sourceSize)
398{
399 auto s = reinterpret_cast<const T *>(source);
400 auto t = reinterpret_cast<T *>(target);
401 auto width = std::min(targetSize / 4, sourceSize / 3) / qint32(sizeof(T));
402 for (qint32 x = 0; x < width; ++x) {
403 t[x * 4 + 0] = s[x * 3 + 0];
404 t[x * 4 + 1] = s[x * 3 + 1];
405 t[x * 4 + 2] = s[x * 3 + 2];
406 t[x * 4 + 3] = std::numeric_limits<T>::max();
407 }
408}
409
410// clang-format off
411#define C_IQ(a) (((a) & 0xF) << 4)
412#define C_OC(a) (((a) & 0xF) << 8)
413#define C_CW(a) (((a) & 0x1) << 12)
414#define C_AW(a) (((a) & 0x1) << 13)
415#define C_BT(a) (((a) & 0x1) << 14)
416#define C_HS(a) (((a) & 0x1) << 15)
417#define C_CE(a) (((a) & 0x1) << 16)
418#define C_NR(a) (((a) & 0x3) << 17)
419#define C_FC(a) (((a) & 0x1) << 19)
420#define C_SR(a) (((a) & 0x1) << 20)
421#define C_FLAGS(a) (((a) & 0x1) << 31) // flags mode
422
423#define T_IQ(a) (((a) >> 4) & 0xF)
424#define T_OC(a) (((a) >> 8) & 0xF)
425#define T_CW(a) (((a) >> 12) & 0x1)
426#define T_AW(a) (((a) >> 13) & 0x1)
427#define T_BT(a) (((a) >> 14) & 0x1)
428#define T_HS(a) (((a) >> 15) & 0x1)
429#define T_CE(a) (((a) >> 16) & 0x1)
430#define T_NR(a) (((a) >> 17) & 0x3)
431#define T_FC(a) (((a) >> 19) & 0x1)
432#define T_SR(a) (((a) >> 20) & 0x1)
433#define T_FLAGS(a) (((a) >> 31) & 0x1)
434// clang-format on
435
436#define DEFAULT_IMAGE_QUALITY (C_IQ(3) | C_OC(1) | C_CW(1) | C_AW(1) | C_BT(1) | C_HS(0) | C_FLAGS(1))
437
438void setParams(QImageIOHandler *handler, LibRaw *rawProcessor)
439{
440 // *** Set raw params
441#if (LIBRAW_VERSION < LIBRAW_MAKE_VERSION(0, 21, 0))
442 auto &&rawparams = rawProcessor->imgdata.params;
443#else
444 auto &&rawparams = rawProcessor->imgdata.rawparams;
445#endif
446 // Select one raw image from input file (0 - first, ...)
447 if (handler->currentImageNumber() > -1) {
448 rawparams.shot_select = handler->currentImageNumber();
449 }
450
451 // *** Set processing parameters
452
453 // NOTE: The default value set below are the ones that gave the best results
454 // on a large sample of images (https://raw.pixls.us/data/)
455
456 /**
457 * @brief quality
458 * Plugin quality option.
459 */
460 qint32 quality = -1;
462 quality = handler->option(QImageIOHandler::Quality).toInt();
463 }
464 if (quality > -1) {
465 switch (quality / 10) {
466 case 0:
467 quality = C_IQ(0) | C_OC(1) | C_CW(1) | C_AW(1) | C_BT(0) | C_HS(1);
468 break;
469 case 1:
470 quality = C_IQ(0) | C_OC(1) | C_CW(1) | C_AW(1) | C_BT(0) | C_HS(0);
471 break;
472 case 2:
473 quality = C_IQ(3) | C_OC(1) | C_CW(1) | C_AW(1) | C_BT(0) | C_HS(0);
474 break;
475 case 3:
476 quality = C_IQ(3) | C_OC(1) | C_CW(1) | C_AW(1) | C_BT(1) | C_HS(0);
477 break;
478 case 4:
479 quality = C_IQ(3) | C_OC(2) | C_CW(1) | C_AW(1) | C_BT(1) | C_HS(0);
480 break;
481 case 5:
482 quality = C_IQ(3) | C_OC(4) | C_CW(1) | C_AW(1) | C_BT(1) | C_HS(0);
483 break;
484 case 6:
485 quality = C_IQ(11) | C_OC(1) | C_CW(1) | C_AW(1) | C_BT(0) | C_HS(0);
486 break;
487 case 7:
488 quality = C_IQ(11) | C_OC(1) | C_CW(1) | C_AW(1) | C_BT(1) | C_HS(0);
489 break;
490 case 8:
491 quality = C_IQ(11) | C_OC(2) | C_CW(1) | C_AW(1) | C_BT(1) | C_HS(0);
492 break;
493 default:
494 quality = C_IQ(11) | C_OC(4) | C_CW(1) | C_AW(1) | C_BT(1) | C_HS(0);
495 break;
496 }
497 quality |= C_FLAGS(1);
498 }
499 if (quality == -1) {
500 quality = DEFAULT_IMAGE_QUALITY;
501 }
502 Q_ASSERT(T_FLAGS(quality));
503
504 auto &&params = rawProcessor->imgdata.params;
505
506 /**
507 * @brief use_camera_wb
508 * Use camera white balance, if possible (0 - off, 1 - on)
509 *
510 * This should to be set. Alternatively, a calibrated white balance should be set on each camera.
511 */
512 params.use_camera_wb = T_CW(quality);
513
514 /*!
515 * \brief use_auto_wb
516 * Average the whole image for white balance (0 - off, 1 - on)
517 *
518 * This is usefull if no camera white balance is available.
519 */
520 params.use_auto_wb = T_AW(quality);
521
522 /**
523 * @brief output_bps
524 * Bits per pixel (8 or 16)
525 *
526 * Professional cameras (and even some smartphones) generate images at 10 or more bits per sample.
527 * When using 16-bit images, the highest quality should be maintained.
528 */
529 params.output_bps = T_BT(quality) ? 16 : 8;
530
531 /**
532 * @brief output_color
533 * Output colorspace (0 - raw, 1 - sRGB, 2 - Adobe, 3 - Wide, 4 - ProPhoto, 5 - XYZ, 6 - ACES, 7 - DCI-P3, 8 - Rec2020)
534 *
535 * sRGB allows you to view images correctly on programs that do not support ICC profiles. When most
536 * Programs will support icc profiles, ProPhoto may be a better choice.
537 * @note sRgb is the LibRaw default: if grayscale image is loaded, LibRaw switches to 0 (Raw) automatically.
538 */
539 params.output_color = T_OC(quality);
540
541 /**
542 * @brief user_qual
543 * Interpolation quality (0 - linear, 1 - VNG, 2 - PPG, 3 - AHD, 4 - DCB, 11 - DHT, 12 - AAHD)
544 *
545 * DHT seems the best option - See In-Depth Demosaicing Algorithm Analysis (https://www.libraw.org/node/2306)
546 * but, when used, some FUJI RAF files of my library are poorly rendered (e.g. completely green). This is the
547 * why I used AHD: a good compromise between quality and performance with no rendering errors.
548 */
549 params.user_qual = T_IQ(quality);
550
551 /**
552 * @brief half_size
553 * Generate an half-size image (0 - off, 1 - on)
554 *
555 * Very fast and useful for generating previews.
556 */
557 params.half_size = T_HS(quality);
558
559 /**
560 * @dcb_enhance_fl
561 * DCB color enhance filter (0 - off, 1 - on)
562 */
563 params.dcb_enhance_fl = T_CE(quality);
564
565 /**
566 * @fbdd_noiserd
567 * FBDD noise reduction (0 - off, 1 - light, 2 - full)
568 */
569 params.fbdd_noiserd = std::min(2, T_NR(quality));
570
571 /**
572 * @four_color_rgb
573 * Interpolate RGGB as four colors (0 - off, 1 - on)
574 */
575 params.four_color_rgb = T_FC(quality);
576
577 /**
578 * @use_fuji_rotate
579 * Don't stretch or rotate raw pixels (0 - off, 1 - on)
580 */
581 params.use_fuji_rotate = T_SR(quality) ? 0 : 1;
582}
583
584bool LoadTHUMB(QImageIOHandler *handler, QImage &img)
585{
586 std::unique_ptr<LibRaw> rawProcessor(new LibRaw);
587
588 // *** Open the stream
589 auto device = handler->device();
590#ifndef EXCLUDE_LibRaw_QIODevice
591 LibRaw_QIODevice stream(device);
592 if (rawProcessor->open_datastream(&stream) != LIBRAW_SUCCESS) {
593 return false;
594 }
595#else
596 auto ba = device->readAll();
597 if (rawProcessor->open_buffer(ba.data(), ba.size()) != LIBRAW_SUCCESS) {
598 return false;
599 }
600#endif
601
602#if (LIBRAW_VERSION < LIBRAW_MAKE_VERSION(0, 21, 0))
603 // *** Unpacking selected thumbnail
604 if (rawProcessor->unpack_thumb() != LIBRAW_SUCCESS) {
605 return false;
606 }
607#else
608 // *** Search for the bigger thumbnail
609 auto &&tlist = rawProcessor->imgdata.thumbs_list;
610 auto idx = 0;
611 for (auto n = 1; n < std::min(tlist.thumbcount, LIBRAW_THUMBNAIL_MAXCOUNT); ++n) {
612 if (tlist.thumblist[n].twidth > tlist.thumblist[idx].twidth)
613 idx = n;
614 }
615
616 // *** Unpacking selected thumbnail
617 if (rawProcessor->unpack_thumb_ex(idx) != LIBRAW_SUCCESS) {
618 return false;
619 }
620#endif
621
622 // *** Convert to QImage
623 pi_unique_ptr processedImage(rawProcessor->dcraw_make_mem_thumb(), LibRaw::dcraw_clear_mem);
624 if (processedImage == nullptr) {
625 return false;
626 }
627 auto ba = QByteArray(reinterpret_cast<const char *>(processedImage->data), qsizetype(processedImage->data_size));
628 if (ba.isEmpty()) {
629 return false;
630 }
631 if (processedImage->type == LIBRAW_IMAGE_BITMAP) {
632 // clang-format off
633 auto header = QString::fromUtf8("P%1\n%2 %3\n%4\n") // taken from KDcraw
634 .arg(processedImage->colors == 3 ? QLatin1String("6") : QLatin1String("5"))
635 .arg(processedImage->width)
636 .arg(processedImage->height)
637 .arg((1 << processedImage->bits)-1);
638 // clang-format on
639 ba.prepend(header.toLatin1());
640 }
641 return img.loadFromData(ba);
642}
643
644bool LoadRAW(QImageIOHandler *handler, QImage &img)
645{
646 std::unique_ptr<LibRaw> rawProcessor(new LibRaw);
647
648 // *** Set parameters
649 setParams(handler, rawProcessor.get());
650
651 // *** Open the stream
652 auto device = handler->device();
653#ifndef EXCLUDE_LibRaw_QIODevice
654 LibRaw_QIODevice stream(device);
655 if (rawProcessor->open_datastream(&stream) != LIBRAW_SUCCESS) {
656 return false;
657 }
658#else
659 auto ba = device->readAll();
660 if (rawProcessor->open_buffer(ba.data(), ba.size()) != LIBRAW_SUCCESS) {
661 return false;
662 }
663#endif
664
665 // *** Unpacking selected image
666 if (rawProcessor->unpack() != LIBRAW_SUCCESS) {
667 return false;
668 }
669
670 // *** Process selected image
671 if (rawProcessor->dcraw_process() != LIBRAW_SUCCESS) {
672 return false;
673 }
674
675 // *** Convert to QImage
676 pi_unique_ptr processedImage(rawProcessor->dcraw_make_mem_image(), LibRaw::dcraw_clear_mem);
677 if (processedImage == nullptr) {
678 return false;
679 }
680
681 // clang-format off
682 if ((processedImage->type != LIBRAW_IMAGE_BITMAP) ||
683 (processedImage->colors != 1 && processedImage->colors != 3 && processedImage->colors != 4) ||
684 (processedImage->bits != 8 && processedImage->bits != 16)) {
685 return false;
686 }
687 // clang-format on
688
689 auto format = QImage::Format_Invalid;
690 switch (processedImage->colors) {
691 case 1: // Gray images (tested with image attached on https://bugs.kde.org/show_bug.cgi?id=401371)
692 format = processedImage->bits == 8 ? QImage::Format_Grayscale8 : QImage::Format_Grayscale16;
693 break;
694 case 3: // Images with R G B components
695 format = processedImage->bits == 8 ? QImage::Format_RGB888 : QImage::Format_RGBX64;
696 break;
697 case 4: // Images with R G B components + Alpha (never seen)
698 format = processedImage->bits == 8 ? QImage::Format_RGBA8888 : QImage::Format_RGBA64;
699 break;
700 }
701
702 if (format == QImage::Format_Invalid) {
703 return false;
704 }
705
706 img = imageAlloc(processedImage->width, processedImage->height, format);
707 if (img.isNull()) {
708 return false;
709 }
710
711 auto rawBytesPerLine = qint32(processedImage->width * processedImage->bits * processedImage->colors + 7) / 8;
712 auto lineSize = std::min(qint32(img.bytesPerLine()), rawBytesPerLine);
713 for (int y = 0, h = img.height(); y < h; ++y) {
714 auto scanline = img.scanLine(y);
715 if (format == QImage::Format_RGBX64)
716 rgbToRgbX<quint16>(scanline, processedImage->data + rawBytesPerLine * y, img.bytesPerLine(), rawBytesPerLine);
717 else
718 memcpy(scanline, processedImage->data + rawBytesPerLine * y, lineSize);
719 }
720
721 // *** Set the color space
722 auto &&params = rawProcessor->imgdata.params;
723 if (params.output_color == 0) {
724 auto &&color = rawProcessor->imgdata.color;
725 if (auto profile = reinterpret_cast<char *>(color.profile)) {
726 img.setColorSpace(QColorSpace::fromIccProfile(QByteArray(profile, color.profile_length)));
727 }
728 }
729 if (processedImage->colors >= 3) {
730 if (params.output_color == 1) {
732 }
733 if (params.output_color == 2) {
735 }
736 if (params.output_color == 4) {
738 }
739 if (params.output_color == 7) {
741 }
742#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
743 if (params.output_color == 8) {
744 img.setColorSpace(QColorSpace(QColorSpace::Bt2020));
745 }
746#endif
747 }
748
749 // *** Set the metadata
750 auto &&iparams = rawProcessor->imgdata.idata;
751
752 auto xmpPacket = QString();
753 if (auto xmpdata = iparams.xmpdata) {
754 if (auto xmplen = iparams.xmplen)
755 xmpPacket = QString::fromUtf8(xmpdata, xmplen);
756 }
757 // Add info from LibRAW structs (e.g. GPS position, info about lens, info about shot and flash, etc...)
758 img.setText(QStringLiteral(META_KEY_XMP_ADOBE), updateXmpPacket(xmpPacket, rawProcessor.get()));
759
760 auto model = QString::fromUtf8(iparams.normalized_model);
761 if (!model.isEmpty()) {
762 img.setText(QStringLiteral(META_KEY_MODEL), model);
763 }
764 auto manufacturer = QString::fromUtf8(iparams.normalized_make);
765 if (!manufacturer.isEmpty()) {
766 img.setText(QStringLiteral(META_KEY_MANUFACTURER), manufacturer);
767 }
768 auto software = QString::fromUtf8(iparams.software);
769 if (!software.isEmpty()) {
770 img.setText(QStringLiteral(META_KEY_SOFTWARE), software);
771 }
772
773 auto &&iother = rawProcessor->imgdata.other;
774 auto description = QString::fromUtf8(iother.desc);
775 if (!description.isEmpty()) {
776 img.setText(QStringLiteral(META_KEY_DESCRIPTION), description);
777 }
778 auto artist = QString::fromUtf8(iother.artist);
779 if (!artist.isEmpty()) {
780 img.setText(QStringLiteral(META_KEY_AUTHOR), artist);
781 }
782
783 return true;
784}
785
786} // Private
787
788RAWHandler::RAWHandler()
789 : m_imageNumber(0)
790 , m_imageCount(0)
791 , m_quality(-1)
792 , m_startPos(-1)
793{
794}
795
796bool RAWHandler::canRead() const
797{
798 if (canRead(device())) {
799 setFormat("raw");
800 return true;
801 }
802 return false;
803}
804
805bool RAWHandler::read(QImage *image)
806{
807 auto dev = device();
808
809 // set the image position after the first run.
810 if (!dev->isSequential()) {
811 if (m_startPos < 0) {
812 m_startPos = dev->pos();
813 } else {
814 dev->seek(m_startPos);
815 }
816 }
817
818 // Check image file format.
819 if (dev->atEnd()) {
820 return false;
821 }
822
823 QImage img;
824 auto ok = false;
825 if (m_quality == 0) {
826 ok = LoadTHUMB(this, img);
827 if (!ok && !dev->isSequential())
828 dev->seek(m_startPos);
829 }
830 if (!ok) {
831 ok = LoadRAW(this, img);
832 }
833 if (!ok) {
834 return false;
835 }
836
837 *image = img;
838 return true;
839}
840
841void RAWHandler::setOption(ImageOption option, const QVariant &value)
842{
843 if (option == QImageIOHandler::Quality) {
844 bool ok = false;
845 auto q = value.toInt(&ok);
846 if (ok) {
847 m_quality = q;
848 }
849 }
850}
851
852bool RAWHandler::supportsOption(ImageOption option) const
853{
854#ifndef EXCLUDE_LibRaw_QIODevice
855 if (option == QImageIOHandler::Size) {
856 return true;
857 }
858#endif
859
860 if (option == QImageIOHandler::Quality) {
861 return true;
862 }
863
864 return false;
865}
866
867QVariant RAWHandler::option(ImageOption option) const
868{
869 QVariant v;
870
871#ifndef EXCLUDE_LibRaw_QIODevice
872 if (option == QImageIOHandler::Size) {
873 auto d = device();
874 d->startTransaction();
875 std::unique_ptr<LibRaw> rawProcessor(new LibRaw);
876 LibRaw_QIODevice stream(d);
877#if (LIBRAW_VERSION < LIBRAW_MAKE_VERSION(0, 21, 0))
878 rawProcessor->imgdata.params.shot_select = currentImageNumber();
879#else
880 rawProcessor->imgdata.rawparams.shot_select = currentImageNumber();
881#endif
882 if (rawProcessor->open_datastream(&stream) == LIBRAW_SUCCESS) {
883 auto w = libraw_get_iwidth(&rawProcessor->imgdata);
884 auto h = libraw_get_iheight(&rawProcessor->imgdata);
885 // flip & 4: taken from LibRaw code
886 v = (rawProcessor->imgdata.sizes.flip & 4) ? QSize(h, w) : QSize(w, h);
887 }
888 d->rollbackTransaction();
889 }
890#endif
891
892 if (option == QImageIOHandler::Quality) {
893 v = m_quality;
894 }
895
896 return v;
897}
898
899bool RAWHandler::jumpToNextImage()
900{
901 return jumpToImage(m_imageNumber + 1);
902}
903
904bool RAWHandler::jumpToImage(int imageNumber)
905{
906 if (imageNumber < 0 || imageNumber >= imageCount()) {
907 return false;
908 }
909 m_imageNumber = imageNumber;
910 return true;
911}
912
913int RAWHandler::imageCount() const
914{
915 // NOTE: image count is cached for performance reason
916 auto &&count = m_imageCount;
917 if (count > 0) {
918 return count;
919 }
920
922
923#ifndef EXCLUDE_LibRaw_QIODevice
924 auto d = device();
925 d->startTransaction();
926
927 std::unique_ptr<LibRaw> rawProcessor(new LibRaw);
928 LibRaw_QIODevice stream(d);
929 if (rawProcessor->open_datastream(&stream) == LIBRAW_SUCCESS) {
930 count = rawProcessor->imgdata.rawdata.iparams.raw_count;
931 }
932
933 d->rollbackTransaction();
934#endif
935
936 return count;
937}
938
939int RAWHandler::currentImageNumber() const
940{
941 return m_imageNumber;
942}
943
944bool RAWHandler::canRead(QIODevice *device)
945{
946 if (!device) {
947 qWarning("RAWHandler::canRead() called with no device");
948 return false;
949 }
950 if (device->isSequential()) {
951 return false;
952 }
953
954 device->startTransaction();
955
956 std::unique_ptr<LibRaw> rawProcessor(new LibRaw);
957
958#ifndef EXCLUDE_LibRaw_QIODevice
959 LibRaw_QIODevice stream(device);
960 auto ok = rawProcessor->open_datastream(&stream) == LIBRAW_SUCCESS;
961#else
962 auto ba = device->readAll();
963 auto ok = rawProcessor->open_buffer(ba.data(), ba.size()) == LIBRAW_SUCCESS;
964#endif
965
966 device->rollbackTransaction();
967 return ok;
968}
969
970QImageIOPlugin::Capabilities RAWPlugin::capabilities(QIODevice *device, const QByteArray &format) const
971{
972 if (supported_formats.contains(QByteArray(format).toLower())) {
973 return Capabilities(CanRead);
974 }
975 if (!format.isEmpty()) {
976 return {};
977 }
978 if (!device->isOpen()) {
979 return {};
980 }
981
982 Capabilities cap;
983 if (device->isReadable() && RAWHandler::canRead(device)) {
984 cap |= CanRead;
985 }
986 return cap;
987}
988
989QImageIOHandler *RAWPlugin::create(QIODevice *device, const QByteArray &format) const
990{
991 QImageIOHandler *handler = new RAWHandler;
992 handler->setDevice(device);
993 handler->setFormat(format);
994 return handler;
995}
996
997#include "moc_raw_p.cpp"
char * toString(const EngineQuery &query)
QFlags< Capability > Capabilities
QVariant read(const QByteArray &data, int versionOverride=0)
QByteArray & append(QByteArrayView data)
char * data()
bool isEmpty() const const
QByteArray & prepend(QByteArrayView ba)
qsizetype size() const const
QChar fromLatin1(char c)
QColorSpace fromIccProfile(const QByteArray &iccProfile)
QDateTime currentDateTimeUtc()
QDateTime fromSecsSinceEpoch(qint64 secs)
qsizetype bytesPerLine() const const
int height() const const
bool isNull() const const
bool loadFromData(QByteArrayView data, const char *format)
uchar * scanLine(int i)
void setColorSpace(const QColorSpace &colorSpace)
void setText(const QString &key, const QString &text)
virtual int currentImageNumber() const const
QIODevice * device() const const
virtual int imageCount() const const
virtual QVariant option(ImageOption option) const const
void setDevice(QIODevice *device)
void setFormat(const QByteArray &format)
virtual bool supportsOption(ImageOption option) const const
typedef Capabilities
bool isOpen() const const
bool isReadable() const const
virtual bool isSequential() const const
QByteArray readAll()
void rollbackTransaction()
void startTransaction()
QLocale c()
float toFloat(QStringView s, bool *ok) const const
int toInt(QStringView s, bool *ok) const const
QString arg(Args &&... args) const const
QString fromLatin1(QByteArrayView str)
QString fromUtf8(QByteArrayView str)
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
QString left(qsizetype n) const const
QString mid(qsizetype position, qsizetype n) const const
QString join(QChar separator) const const
CaseInsensitive
QTimeZone utc()
int toInt(bool *ok) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Jan 24 2025 11:53:42 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.