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 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(META_KEY_XMP_ADOBE), updateXmpPacket(xmpPacket, rawProcessor.get()));
694
695 auto model = QString::fromUtf8(iparams.normalized_model);
696 if (!model.isEmpty()) {
697 img.setText(QStringLiteral(META_KEY_MODEL), model);
698 }
699 auto manufacturer = QString::fromUtf8(iparams.normalized_make);
700 if (!manufacturer.isEmpty()) {
701 img.setText(QStringLiteral(META_KEY_MANUFACTURER), manufacturer);
702 }
703 auto software = QString::fromUtf8(iparams.software);
704 if (!software.isEmpty()) {
705 img.setText(QStringLiteral(META_KEY_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(META_KEY_DESCRIPTION), description);
712 }
713 auto artist = QString::fromUtf8(iother.artist);
714 if (!artist.isEmpty()) {
715 img.setText(QStringLiteral(META_KEY_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 auto w = libraw_get_iwidth(&rawProcessor->imgdata);
810 auto h = libraw_get_iheight(&rawProcessor->imgdata);
811 // flip & 4: taken from LibRaw code
812 v = (rawProcessor->imgdata.sizes.flip & 4) ? QSize(h, w) : QSize(w, h);
813 }
814 d->rollbackTransaction();
815 }
816#endif
817
818 if (option == QImageIOHandler::Quality) {
819 v = m_quality;
820 }
821
822 return v;
823}
824
825bool RAWHandler::jumpToNextImage()
826{
827 return jumpToImage(m_imageNumber + 1);
828}
829
830bool RAWHandler::jumpToImage(int imageNumber)
831{
832 if (imageNumber < 0 || imageNumber >= imageCount()) {
833 return false;
834 }
835 m_imageNumber = imageNumber;
836 return true;
837}
838
839int RAWHandler::imageCount() const
840{
841 // NOTE: image count is cached for performance reason
842 auto &&count = m_imageCount;
843 if (count > 0) {
844 return count;
845 }
846
848
849#ifndef EXCLUDE_LibRaw_QIODevice
850 auto d = device();
851 d->startTransaction();
852
853 std::unique_ptr<LibRaw> rawProcessor(new LibRaw);
854 LibRaw_QIODevice stream(d);
855 if (rawProcessor->open_datastream(&stream) == LIBRAW_SUCCESS) {
856 count = rawProcessor->imgdata.rawdata.iparams.raw_count;
857 }
858
859 d->rollbackTransaction();
860#endif
861
862 return count;
863}
864
865int RAWHandler::currentImageNumber() const
866{
867 return m_imageNumber;
868}
869
870bool RAWHandler::canRead(QIODevice *device)
871{
872 if (!device) {
873 qWarning("RAWHandler::canRead() called with no device");
874 return false;
875 }
876 if (device->isSequential()) {
877 return false;
878 }
879
880 device->startTransaction();
881
882 std::unique_ptr<LibRaw> rawProcessor(new LibRaw);
883
884#ifndef EXCLUDE_LibRaw_QIODevice
885 LibRaw_QIODevice stream(device);
886 auto ok = rawProcessor->open_datastream(&stream) == LIBRAW_SUCCESS;
887#else
888 auto ba = device->readAll();
889 auto ok = rawProcessor->open_buffer(ba.data(), ba.size()) == LIBRAW_SUCCESS;
890#endif
891
892 device->rollbackTransaction();
893 return ok;
894}
895
896QImageIOPlugin::Capabilities RAWPlugin::capabilities(QIODevice *device, const QByteArray &format) const
897{
898 if (supported_formats.contains(QByteArray(format).toLower())) {
899 return Capabilities(CanRead);
900 }
901 if (!format.isEmpty()) {
902 return {};
903 }
904 if (!device->isOpen()) {
905 return {};
906 }
907
908 Capabilities cap;
909 if (device->isReadable() && RAWHandler::canRead(device)) {
910 cap |= CanRead;
911 }
912 return cap;
913}
914
915QImageIOHandler *RAWPlugin::create(QIODevice *device, const QByteArray &format) const
916{
917 QImageIOHandler *handler = new RAWHandler;
918 handler->setDevice(device);
919 handler->setFormat(format);
920 return handler;
921}
922
923#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
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 Mon Nov 18 2024 12:07:21 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.