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

KDE's Doxygen guidelines are available online.