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 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_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_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 LoadRAW(QImageIOHandler *handler, QImage &img)
585{
586 std::unique_ptr<LibRaw> rawProcessor(new LibRaw);
587
588 // *** Set parameters
589 setParams(handler, rawProcessor.get());
590
591 // *** Open the stream
592 auto device = handler->device();
593#ifndef EXCLUDE_LibRaw_QIODevice
594 LibRaw_QIODevice stream(device);
595 if (rawProcessor->open_datastream(&stream) != LIBRAW_SUCCESS) {
596 return false;
597 }
598#else
599 auto ba = device->readAll();
600 if (rawProcessor->open_buffer(ba.data(), ba.size()) != LIBRAW_SUCCESS) {
601 return false;
602 }
603#endif
604
605 // *** Unpacking selected image
606 if (rawProcessor->unpack() != LIBRAW_SUCCESS) {
607 return false;
608 }
609
610 // *** Process selected image
611 if (rawProcessor->dcraw_process() != LIBRAW_SUCCESS) {
612 return false;
613 }
614
615 // *** Convert to QImage
616 pi_unique_ptr processedImage(rawProcessor->dcraw_make_mem_image(), LibRaw::dcraw_clear_mem);
617 if (processedImage == nullptr) {
618 return false;
619 }
620
621 // clang-format off
622 if ((processedImage->type != LIBRAW_IMAGE_BITMAP) ||
623 (processedImage->colors != 1 && processedImage->colors != 3 && processedImage->colors != 4) ||
624 (processedImage->bits != 8 && processedImage->bits != 16)) {
625 return false;
626 }
627 // clang-format on
628
629 auto format = QImage::Format_Invalid;
630 switch (processedImage->colors) {
631 case 1: // Gray images (tested with image attached on https://bugs.kde.org/show_bug.cgi?id=401371)
632 format = processedImage->bits == 8 ? QImage::Format_Grayscale8 : QImage::Format_Grayscale16;
633 break;
634 case 3: // Images with R G B components
635 format = processedImage->bits == 8 ? QImage::Format_RGB888 : QImage::Format_RGBX64;
636 break;
637 case 4: // Images with R G B components + Alpha (never seen)
638 format = processedImage->bits == 8 ? QImage::Format_RGBA8888 : QImage::Format_RGBA64;
639 break;
640 }
641
642 if (format == QImage::Format_Invalid) {
643 return false;
644 }
645
646 img = imageAlloc(processedImage->width, processedImage->height, format);
647 if (img.isNull()) {
648 return false;
649 }
650
651 auto rawBytesPerLine = qint32(processedImage->width * processedImage->bits * processedImage->colors + 7) / 8;
652 auto lineSize = std::min(qint32(img.bytesPerLine()), rawBytesPerLine);
653 for (int y = 0, h = img.height(); y < h; ++y) {
654 auto scanline = img.scanLine(y);
655 if (format == QImage::Format_RGBX64)
656 rgbToRgbX<quint16>(scanline, processedImage->data + rawBytesPerLine * y, img.bytesPerLine(), rawBytesPerLine);
657 else
658 memcpy(scanline, processedImage->data + rawBytesPerLine * y, lineSize);
659 }
660
661 // *** Set the color space
662 auto &&params = rawProcessor->imgdata.params;
663 if (params.output_color == 0) {
664 auto &&color = rawProcessor->imgdata.color;
665 if (auto profile = reinterpret_cast<char *>(color.profile)) {
666 img.setColorSpace(QColorSpace::fromIccProfile(QByteArray(profile, color.profile_length)));
667 }
668 }
669 if (processedImage->colors >= 3) {
670 if (params.output_color == 1) {
672 }
673 if (params.output_color == 2) {
675 }
676 if (params.output_color == 4) {
678 }
679 if (params.output_color == 7) {
681 }
682 }
683
684 // *** Set the metadata
685 auto &&iparams = rawProcessor->imgdata.idata;
686
687 auto xmpPacket = QString();
688 if (auto xmpdata = iparams.xmpdata) {
689 if (auto xmplen = iparams.xmplen)
690 xmpPacket = QString::fromUtf8(xmpdata, xmplen);
691 }
692 // Add info from LibRAW structs (e.g. GPS position, info about lens, info about shot and flash, etc...)
693 img.setText(QStringLiteral("XML:com.adobe.xmp"), updateXmpPacket(xmpPacket, rawProcessor.get()));
694
695 auto model = QString::fromUtf8(iparams.normalized_model);
696 if (!model.isEmpty()) {
697 img.setText(QStringLiteral("Model"), model);
698 }
699 auto manufacturer = QString::fromUtf8(iparams.normalized_make);
700 if (!manufacturer.isEmpty()) {
701 img.setText(QStringLiteral("Manufacturer"), manufacturer);
702 }
703 auto software = QString::fromUtf8(iparams.software);
704 if (!software.isEmpty()) {
705 img.setText(QStringLiteral("Software"), software);
706 }
707
708 auto &&iother = rawProcessor->imgdata.other;
709 auto description = QString::fromUtf8(iother.desc);
710 if (!description.isEmpty()) {
711 img.setText(QStringLiteral("Description"), description);
712 }
713 auto artist = QString::fromUtf8(iother.artist);
714 if (!artist.isEmpty()) {
715 img.setText(QStringLiteral("Author"), artist);
716 }
717
718 return true;
719}
720
721} // Private
722
723RAWHandler::RAWHandler()
724 : m_imageNumber(0)
725 , m_imageCount(0)
726 , m_quality(-1)
727 , m_startPos(-1)
728{
729}
730
731bool RAWHandler::canRead() const
732{
733 if (canRead(device())) {
734 setFormat("raw");
735 return true;
736 }
737 return false;
738}
739
740bool RAWHandler::read(QImage *image)
741{
742 auto dev = device();
743
744 // set the image position after the first run.
745 if (!dev->isSequential()) {
746 if (m_startPos < 0) {
747 m_startPos = dev->pos();
748 } else {
749 dev->seek(m_startPos);
750 }
751 }
752
753 // Check image file format.
754 if (dev->atEnd()) {
755 return false;
756 }
757
758 QImage img;
759 if (!LoadRAW(this, img)) {
760 return false;
761 }
762
763 *image = img;
764 return true;
765}
766
767void RAWHandler::setOption(ImageOption option, const QVariant &value)
768{
769 if (option == QImageIOHandler::Quality) {
770 bool ok = false;
771 auto q = value.toInt(&ok);
772 if (ok) {
773 m_quality = q;
774 }
775 }
776}
777
778bool RAWHandler::supportsOption(ImageOption option) const
779{
780#ifndef EXCLUDE_LibRaw_QIODevice
781 if (option == QImageIOHandler::Size) {
782 return true;
783 }
784#endif
785
786 if (option == QImageIOHandler::Quality) {
787 return true;
788 }
789
790 return false;
791}
792
793QVariant RAWHandler::option(ImageOption option) const
794{
795 QVariant v;
796
797#ifndef EXCLUDE_LibRaw_QIODevice
798 if (option == QImageIOHandler::Size) {
799 auto d = device();
800 d->startTransaction();
801 std::unique_ptr<LibRaw> rawProcessor(new LibRaw);
802 LibRaw_QIODevice stream(d);
803#if (LIBRAW_VERSION < LIBRAW_MAKE_VERSION(0, 21, 0))
804 rawProcessor->imgdata.params.shot_select = currentImageNumber();
805#else
806 rawProcessor->imgdata.rawparams.shot_select = currentImageNumber();
807#endif
808 if (rawProcessor->open_datastream(&stream) == LIBRAW_SUCCESS) {
809 if (rawProcessor->unpack() == LIBRAW_SUCCESS) {
810 auto w = libraw_get_iwidth(&rawProcessor->imgdata);
811 auto h = libraw_get_iheight(&rawProcessor->imgdata);
812 // flip & 4: taken from LibRaw code
813 v = (rawProcessor->imgdata.sizes.flip & 4) ? QSize(h, w) : QSize(w, h);
814 }
815 }
816 d->rollbackTransaction();
817 }
818#endif
819
820 if (option == QImageIOHandler::Quality) {
821 v = m_quality;
822 }
823
824 return v;
825}
826
827bool RAWHandler::jumpToNextImage()
828{
829 return jumpToImage(m_imageNumber + 1);
830}
831
832bool RAWHandler::jumpToImage(int imageNumber)
833{
834 if (imageNumber < 0 || imageNumber >= imageCount()) {
835 return false;
836 }
837 m_imageNumber = imageNumber;
838 return true;
839}
840
841int RAWHandler::imageCount() const
842{
843 // NOTE: image count is cached for performance reason
844 auto &&count = m_imageCount;
845 if (count > 0) {
846 return count;
847 }
848
850
851#ifndef EXCLUDE_LibRaw_QIODevice
852 auto d = device();
853 d->startTransaction();
854
855 std::unique_ptr<LibRaw> rawProcessor(new LibRaw);
856 LibRaw_QIODevice stream(d);
857 if (rawProcessor->open_datastream(&stream) == LIBRAW_SUCCESS) {
858 count = rawProcessor->imgdata.rawdata.iparams.raw_count;
859 }
860
861 d->rollbackTransaction();
862#endif
863
864 return count;
865}
866
867int RAWHandler::currentImageNumber() const
868{
869 return m_imageNumber;
870}
871
872bool RAWHandler::canRead(QIODevice *device)
873{
874 if (!device) {
875 qWarning("RAWHandler::canRead() called with no device");
876 return false;
877 }
878 if (device->isSequential()) {
879 return false;
880 }
881
882 device->startTransaction();
883
884 std::unique_ptr<LibRaw> rawProcessor(new LibRaw);
885
886#ifndef EXCLUDE_LibRaw_QIODevice
887 LibRaw_QIODevice stream(device);
888 auto ok = rawProcessor->open_datastream(&stream) == LIBRAW_SUCCESS;
889#else
890 auto ba = device->readAll();
891 auto ok = rawProcessor->open_buffer(ba.data(), ba.size()) == LIBRAW_SUCCESS;
892#endif
893
894 device->rollbackTransaction();
895 return ok;
896}
897
898QImageIOPlugin::Capabilities RAWPlugin::capabilities(QIODevice *device, const QByteArray &format) const
899{
900 if (supported_formats.contains(QByteArray(format).toLower())) {
901 return Capabilities(CanRead);
902 }
903 if (!format.isEmpty()) {
904 return {};
905 }
906 if (!device->isOpen()) {
907 return {};
908 }
909
910 Capabilities cap;
911 if (device->isReadable() && RAWHandler::canRead(device)) {
912 cap |= CanRead;
913 }
914 return cap;
915}
916
917QImageIOHandler *RAWPlugin::create(QIODevice *device, const QByteArray &format) const
918{
919 QImageIOHandler *handler = new RAWHandler;
920 handler->setDevice(device);
921 handler->setFormat(format);
922 return handler;
923}
924
925#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
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
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-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:12:40 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.