File indexing completed on 2024-04-28 03:54:45
0001 /* 0002 RAW support for QImage. 0003 0004 SPDX-FileCopyrightText: 2022 Mirco Miranda <mircomir@outlook.com> 0005 0006 SPDX-License-Identifier: LGPL-2.0-or-later 0007 */ 0008 0009 #include "raw_p.h" 0010 #include "util_p.h" 0011 0012 #include <QColorSpace> 0013 #include <QDateTime> 0014 #include <QDebug> 0015 #include <QImage> 0016 #include <QSet> 0017 #include <QTimeZone> 0018 0019 #if defined(Q_OS_WINDOWS) && !defined(NOMINMAX) 0020 #define NOMINMAX 0021 #endif 0022 0023 #include <libraw/libraw.h> 0024 0025 #ifdef QT_DEBUG 0026 // This should be used to exclude the local QIODevice wrapper of the LibRaw_abstract_datastream interface. 0027 // If the result changes then the problem is in the local wrapper and must be corrected. 0028 // WARNING: using the LibRaw's streams instead of the local wrapper from a Qt program falls in the LOCALE 0029 // bug when the RAW file needs the scanf_one() function (e.g. *.MOS files). See also raw_scanf_one(). 0030 //#define EXCLUDE_LibRaw_QIODevice // Uncomment this code if you think that the problem is LibRaw_QIODevice (default commented) 0031 #endif 0032 0033 namespace // Private. 0034 { 0035 0036 // smart pointer for processed image 0037 using pi_unique_ptr = std::unique_ptr<libraw_processed_image_t, std::function<void(libraw_processed_image_t *)>>; 0038 0039 // clang-format off 0040 // Known formats supported by LibRaw (in alphabetical order and lower case) 0041 const auto supported_formats = QSet<QByteArray>{ 0042 "3fr", 0043 "arw", "arq", 0044 "bay", "bmq", 0045 "cr2", "cr3", "cap", "cine", "cs1", "crw", 0046 "dcs", "dc2", "dcr", "dng", "drf", "dxo", 0047 "eip", "erf", 0048 "fff", 0049 "iiq", 0050 "k25", "kc2", "kdc", 0051 "mdc", "mef", "mfw", "mos", "mrw", 0052 "nef", "nrw", 0053 "obm", "orf", "ori", 0054 "pef", "ptx", "pxn", 0055 "qtk", 0056 "r3d", "raf", "raw", "rdc", "rw2", "rwl", "rwz", 0057 "sr2", "srf", "srw", "sti", 0058 "x3f" 0059 }; 0060 // clang-format on 0061 0062 inline int raw_scanf_one(const QByteArray &ba, const char *fmt, void *val) 0063 { 0064 // WARNING: Here it would be nice to use sscanf like LibRaw does but there 0065 // is a Big Trouble: THE LOCALE! LibRaw is also affected by the 0066 // issue if used in a Qt program: 0067 // If you use sscanf here the conversion is wrong when performed 0068 // with Italian locale (where the decimal separator is the comma). 0069 // The solution should be to use std::locale::global() but it's global! 0070 // I don't want to do it. So, don't use code like that: 0071 // return sscanf(QByteArray(ba).append('\0').data(), fmt, val); 0072 0073 // LibRaw is asking only "%d" and "%f" for now. This code is not affected 0074 // by the LOCALE bug. 0075 auto s = QString::fromLatin1(ba); 0076 if (strcmp(fmt, "%d") == 0) { 0077 auto ok = false; 0078 auto d = QLocale::c().toInt(s, &ok); 0079 if (!ok) { 0080 return EOF; 0081 } 0082 *(static_cast<int *>(val)) = d; 0083 } else { 0084 auto ok = false; 0085 auto f = QLocale::c().toFloat(s, &ok); 0086 if (!ok) { 0087 return EOF; 0088 } 0089 *(static_cast<float *>(val)) = f; 0090 } 0091 return 1; 0092 } 0093 0094 /** 0095 * @brief The LibRaw_QIODevice class 0096 * Implementation of the LibRaw stream interface over a QIODevice. 0097 */ 0098 class LibRaw_QIODevice : public LibRaw_abstract_datastream 0099 { 0100 public: 0101 explicit LibRaw_QIODevice(QIODevice *device) 0102 { 0103 m_device = device; 0104 } 0105 virtual ~LibRaw_QIODevice() override 0106 { 0107 } 0108 virtual int valid() override 0109 { 0110 return m_device != nullptr; 0111 } 0112 virtual int read(void *ptr, size_t sz, size_t nmemb) override 0113 { 0114 qint64 read = 0; 0115 if (sz == 0) { 0116 return 0; 0117 } 0118 auto data = reinterpret_cast<char*>(ptr); 0119 for (qint64 r = 0, size = sz * nmemb; read < size; read += r) { 0120 if (m_device->atEnd()) { 0121 break; 0122 } 0123 r = m_device->read(data + read, size - read); 0124 if (r < 1) { 0125 break; 0126 } 0127 } 0128 return read / sz; 0129 } 0130 virtual int eof() override 0131 { 0132 return m_device->atEnd() ? 1 : 0; 0133 } 0134 virtual int seek(INT64 o, int whence) override 0135 { 0136 auto pos = o; 0137 auto size = m_device->size(); 0138 if (whence == SEEK_CUR) { 0139 pos = m_device->pos() + o; 0140 } 0141 if (whence == SEEK_END) { 0142 pos = size + o; 0143 } 0144 if (pos < 0 || m_device->isSequential()) { 0145 return -1; 0146 } 0147 return m_device->seek(pos) ? 0 : -1; 0148 } 0149 virtual INT64 tell() override 0150 { 0151 return m_device->pos(); 0152 } 0153 virtual INT64 size() override 0154 { 0155 return m_device->size(); 0156 } 0157 virtual char *gets(char *s, int sz) override 0158 { 0159 if (m_device->readLine(s, sz) > 0) { 0160 return s; 0161 } 0162 return nullptr; 0163 } 0164 virtual int scanf_one(const char *fmt, void *val) override 0165 { 0166 QByteArray ba; 0167 for (int xcnt = 0; xcnt < 24 && !m_device->atEnd(); ++xcnt) { 0168 char c; 0169 if (!m_device->getChar(&c)) { 0170 return EOF; 0171 } 0172 if (ba.isEmpty() && (c == ' ' || c == '\t')) { 0173 continue; 0174 } 0175 if (c == '\0' || c == ' ' || c == '\t' || c == '\n') { 0176 break; 0177 } 0178 ba.append(c); 0179 } 0180 return raw_scanf_one(ba, fmt, val); 0181 } 0182 virtual int get_char() override 0183 { 0184 unsigned char c; 0185 if (!m_device->getChar(reinterpret_cast<char *>(&c))) { 0186 return EOF; 0187 } 0188 return int(c); 0189 } 0190 #if (LIBRAW_VERSION < LIBRAW_MAKE_VERSION(0, 21, 0)) || defined(LIBRAW_OLD_VIDEO_SUPPORT) 0191 virtual void *make_jas_stream() override 0192 { 0193 return nullptr; 0194 } 0195 #endif 0196 0197 private: 0198 QIODevice *m_device; 0199 }; 0200 0201 bool addTag(const QString &tag, QStringList &lines) 0202 { 0203 auto ok = !tag.isEmpty(); 0204 if (ok) { 0205 lines << tag; 0206 } 0207 return ok; 0208 } 0209 0210 QString createTag(QString value, const char *tag) 0211 { 0212 if (!value.isEmpty()) { 0213 value = QStringLiteral("<%1>%2</%1>").arg(QString::fromLatin1(tag), value); 0214 } 0215 return value; 0216 } 0217 0218 QString createTag(char *asciiz, const char *tag) 0219 { 0220 auto value = QString::fromUtf8(asciiz); 0221 return createTag(value, tag); 0222 } 0223 0224 QString createTimeTag(time_t time, const char *tag) 0225 { 0226 auto value = QDateTime::fromSecsSinceEpoch(time, QTimeZone::utc()); 0227 if (value.isValid() && time > 0) { 0228 return createTag(value.toString(Qt::ISODate), tag); 0229 } 0230 return QString(); 0231 } 0232 0233 QString createFlashTag(short flash, const char *tag) 0234 { 0235 QStringList l; 0236 auto lc = QLocale::c(); 0237 // EXIF specifications 0238 auto t = QStringLiteral("true"); 0239 auto f = QStringLiteral("false"); 0240 l << QStringLiteral("<exif:Fired>%1</exif:Fired>").arg((flash & 1) ? t : f); 0241 l << QStringLiteral("<exif:Function>%1</exif:Function>").arg((flash & (1 << 5)) ? t : f); 0242 l << QStringLiteral("<exif:RedEyeMode>%1</exif:RedEyeMode>").arg((flash & (1 << 6)) ? t : f); 0243 l << QStringLiteral("<exif:Mode>%1</exif:Mode>").arg(lc.toString((int(flash) >> 3) & 3)); 0244 l << QStringLiteral("<exif:Return>%1</exif:Return>").arg(lc.toString((int(flash) >> 1) & 3)); 0245 return createTag(l.join(QChar()), tag); 0246 } 0247 0248 QString createTag(quint64 n, const char *tag, quint64 invalid = 0) 0249 { 0250 if (n != invalid) { 0251 return createTag(QLocale::c().toString(n), tag); 0252 } 0253 return QString(); 0254 } 0255 0256 QString createTag(qint16 n, const char *tag, qint16 invalid = 0) 0257 { 0258 if (n != invalid) { 0259 return createTag(QLocale::c().toString(n), tag); 0260 } 0261 return QString(); 0262 } 0263 0264 QString createTag(quint16 n, const char *tag, quint16 invalid = 0) 0265 { 0266 if (n != invalid) { 0267 return createTag(QLocale::c().toString(n), tag); 0268 } 0269 return QString(); 0270 } 0271 0272 QString createTag(float f, const char *tag, qint32 mul = 1) 0273 { 0274 if (f != 0) { 0275 if (mul > 1) 0276 return QStringLiteral("<%1>%2/%3</%1>").arg(QString::fromLatin1(tag), QLocale::c().toString(int(f * mul))).arg(mul); 0277 return QStringLiteral("<%1>%2</%1>").arg(QString::fromLatin1(tag), QLocale::c().toString(f)); 0278 } 0279 return QString(); 0280 } 0281 0282 QString createTag(libraw_gps_info_t gps, const char *tag) 0283 { 0284 auto tmp = QString::fromLatin1(tag); 0285 if (tmp.contains(QStringLiteral("Latitude"), Qt::CaseInsensitive)) { 0286 if (gps.latref != '\0') { 0287 auto lc = QLocale::c(); 0288 auto value = QStringLiteral("%1,%2%3") 0289 .arg(lc.toString(gps.latitude[0], 'f', 0)) 0290 .arg(lc.toString(gps.latitude[1] + gps.latitude[2] / 60, 'f', 4)) 0291 .arg(QChar::fromLatin1(gps.latref)); 0292 return createTag(value, tag); 0293 } 0294 } 0295 if (tmp.contains(QStringLiteral("Longitude"), Qt::CaseInsensitive)) { 0296 if (gps.longref != '\0') { 0297 auto lc = QLocale::c(); 0298 auto value = QStringLiteral("%1,%2%3") 0299 .arg(lc.toString(gps.longitude[0], 'f', 0)) 0300 .arg(lc.toString(gps.longitude[1] + gps.longitude[2] / 60, 'f', 4)) 0301 .arg(QChar::fromLatin1(gps.longref)); 0302 return createTag(value, tag); 0303 } 0304 } 0305 if (tmp.contains(QStringLiteral("Altitude"), Qt::CaseInsensitive)) { 0306 if (gps.altitude != 0) { 0307 return createTag(gps.altitude, tag, 1000); 0308 } 0309 } 0310 return QString(); 0311 } 0312 0313 QString createXmpPacket() 0314 { 0315 QStringList lines; 0316 lines << QStringLiteral("<?xpacket begin=\"\" id=\"W5M0MpCehiHzreSzNTczkc9d\"?>"); 0317 lines << QStringLiteral("<x:xmpmeta xmlns:x=\"adobe:ns:meta/\" x:xmptk=\"KIMG RAW Plugin\">"); 0318 lines << QStringLiteral("<rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">"); 0319 lines << QStringLiteral("</rdf:RDF>"); 0320 lines << QStringLiteral("</x:xmpmeta>"); 0321 for (auto i = 30; i > 0; --i) 0322 lines << QString::fromLatin1(QByteArray(80, ' ')); 0323 lines << QStringLiteral("<?xpacket end=\"w\"?>"); 0324 return lines.join(QChar::fromLatin1('\n')); 0325 } 0326 0327 QString updateXmpPacket(const QString &xmpPacket, LibRaw *rawProcessor) 0328 { 0329 auto rdfEnd = xmpPacket.indexOf(QStringLiteral("</rdf:RDF>"), Qt::CaseInsensitive); 0330 if (rdfEnd < 0) { 0331 return updateXmpPacket(createXmpPacket(), rawProcessor); 0332 } 0333 0334 auto lines = QStringList() << xmpPacket.left(rdfEnd); 0335 lines << QStringLiteral("<rdf:Description rdf:about=\"\""); 0336 lines << QStringLiteral(" xmlns:xmp=\"http://ns.adobe.com/xap/1.0/\""); 0337 lines << QStringLiteral(" xmlns:dc=\"http://purl.org/dc/elements/1.1/\""); 0338 lines << QStringLiteral(" xmlns:aux=\"http://ns.adobe.com/exif/1.0/aux/\""); 0339 lines << QStringLiteral(" xmlns:xmpMM=\"http://ns.adobe.com/xap/1.0/mm/\""); 0340 lines << QStringLiteral(" xmlns:stEvt=\"http://ns.adobe.com/xap/1.0/sType/ResourceEvent#\""); 0341 lines << QStringLiteral(" xmlns:stRef=\"http://ns.adobe.com/xap/1.0/sType/ResourceRef#\""); 0342 lines << QStringLiteral(" xmlns:tiff=\"http://ns.adobe.com/tiff/1.0/\""); 0343 lines << QStringLiteral(" xmlns:exif=\"http://ns.adobe.com/exif/1.0/\""); 0344 lines << QStringLiteral(" xmlns:xmpRights=\"http://ns.adobe.com/xap/1.0/rights/\">"); 0345 lines << QStringLiteral("<xmpMM:History>"); 0346 lines << QStringLiteral("<rdf:Seq>"); 0347 lines << QStringLiteral("<rdf:li rdf:parseType=\"Resource\">"); 0348 lines << QStringLiteral("<stEvt:action>converted</stEvt:action>"); 0349 lines << QStringLiteral("<stEvt:parameters>Converted from RAW to Qt Image using KIMG RAW plugin</stEvt:parameters>"); 0350 lines << QStringLiteral("<stEvt:softwareAgent>LibRaw %1</stEvt:softwareAgent>").arg(QString::fromLatin1(LibRaw::version())); 0351 lines << QStringLiteral("<stEvt:when>%1</stEvt:when>").arg(QDateTime::currentDateTimeUtc().toString(Qt::ISODate)); 0352 lines << QStringLiteral("</rdf:li>"); 0353 lines << QStringLiteral("</rdf:Seq>"); 0354 lines << QStringLiteral("</xmpMM:History>"); 0355 0356 auto &&iparams = rawProcessor->imgdata.idata; 0357 addTag(createTag(iparams.normalized_model, "tiff:Model"), lines); 0358 addTag(createTag(iparams.normalized_make, "tiff:Make"), lines); 0359 addTag(createTag(iparams.software, "xmp:CreatorTool"), lines); 0360 0361 auto &&iother = rawProcessor->imgdata.other; 0362 addTag(createTag(createTag(createTag(iother.desc, "rdf:li"), "rdf:Alt"), "dc:description"), lines); 0363 addTag(createTag(createTag(createTag(iother.artist, "rdf:li"), "rdf:Seq"), "dc:creator"), lines); 0364 addTag(createTag(createTag(createTag(iother.iso_speed, "rdf:li"), "rdf:Seq"), "exif:ISOSpeedRatings"), lines); 0365 addTag(createTag(iother.shutter, "exif:ExposureTime", 1000), lines); 0366 addTag(createTag(iother.aperture, "exif:ApertureValue", 1000), lines); 0367 addTag(createTag(iother.focal_len, "exif:FocalLength", 1000), lines); 0368 addTag(createTimeTag(iother.timestamp, "xmp:CreateDate"), lines); 0369 addTag(createTag(iother.parsed_gps, "exif:GPSLatitude"), lines); 0370 addTag(createTag(iother.parsed_gps, "exif:GPSLongitude"), lines); 0371 addTag(createTag(iother.parsed_gps, "exif:GPSAltitude"), lines); 0372 0373 auto &&shotinfo = rawProcessor->imgdata.shootinginfo; 0374 addTag(createTag(shotinfo.ExposureMode, "exif:ExposureMode", short(-1)), lines); 0375 addTag(createTag(shotinfo.MeteringMode, "exif:MeteringMode", short(-1)), lines); 0376 addTag(createTag(shotinfo.BodySerial, "aux:SerialNumber"), lines); 0377 0378 auto &&color = rawProcessor->imgdata.color; 0379 addTag(createFlashTag(color.flash_used, "exif:Flash"), lines); 0380 0381 auto &&lens = rawProcessor->imgdata.lens; 0382 addTag(createTag(lens.FocalLengthIn35mmFormat, "exif:FocalLengthIn35mmFilm"), lines); 0383 addTag(createTag(lens.Lens, "aux:Lens"), lines); 0384 addTag(createTag(lens.LensSerial, "aux:LensSerialNumber"), lines); 0385 addTag(createTag(lens.nikon.LensIDNumber ? quint64(lens.nikon.LensIDNumber) : lens.makernotes.LensID, "aux:LensID"), lines); 0386 0387 auto &&makernotes = rawProcessor->imgdata.makernotes; 0388 addTag(createTag(makernotes.common.firmware, "aux:Firmware"), lines); 0389 0390 lines << QStringLiteral("</rdf:Description>"); 0391 lines << xmpPacket.mid(rdfEnd); 0392 0393 return lines.join(QChar::fromLatin1('\n')); 0394 } 0395 0396 template<class T> 0397 inline void rgbToRgbX(uchar *target, const uchar *source, qint32 targetSize, qint32 sourceSize) 0398 { 0399 auto s = reinterpret_cast<const T *>(source); 0400 auto t = reinterpret_cast<T *>(target); 0401 auto width = std::min(targetSize / 4, sourceSize / 3) / qint32(sizeof(T)); 0402 for (qint32 x = 0; x < width; ++x) { 0403 t[x * 4 + 0] = s[x * 3 + 0]; 0404 t[x * 4 + 1] = s[x * 3 + 1]; 0405 t[x * 4 + 2] = s[x * 3 + 2]; 0406 t[x * 4 + 3] = std::numeric_limits<T>::max(); 0407 } 0408 } 0409 0410 // clang-format off 0411 #define C_IQ(a) (((a) & 0xF) << 4) 0412 #define C_OC(a) (((a) & 0xF) << 8) 0413 #define C_CW(a) (((a) & 0x1) << 12) 0414 #define C_AW(a) (((a) & 0x1) << 13) 0415 #define C_BT(a) (((a) & 0x1) << 14) 0416 #define C_HS(a) (((a) & 0x1) << 15) 0417 #define C_CE(a) (((a) & 0x1) << 16) 0418 #define C_NR(a) (((a) & 0x3) << 17) 0419 #define C_FC(a) (((a) & 0x1) << 19) 0420 #define C_SR(a) (((a) & 0x1) << 20) 0421 #define C_FLAGS(a) (((a) & 0x1) << 31) // flags mode 0422 0423 #define T_IQ(a) (((a) >> 4) & 0xF) 0424 #define T_OC(a) (((a) >> 8) & 0xF) 0425 #define T_CW(a) (((a) >> 12) & 0x1) 0426 #define T_AW(a) (((a) >> 13) & 0x1) 0427 #define T_BT(a) (((a) >> 14) & 0x1) 0428 #define T_HS(a) (((a) >> 15) & 0x1) 0429 #define T_CE(a) (((a) >> 16) & 0x1) 0430 #define T_NR(a) (((a) >> 17) & 0x3) 0431 #define T_FC(a) (((a) >> 19) & 0x1) 0432 #define T_SR(a) (((a) >> 20) & 0x1) 0433 #define T_FLAGS(a) (((a) >> 31) & 0x1) 0434 // clang-format on 0435 0436 #define DEFAULT_QUALITY (C_IQ(3) | C_OC(1) | C_CW(1) | C_AW(1) | C_BT(1) | C_HS(0) | C_FLAGS(1)) 0437 0438 void setParams(QImageIOHandler *handler, LibRaw *rawProcessor) 0439 { 0440 // *** Set raw params 0441 #if (LIBRAW_VERSION < LIBRAW_MAKE_VERSION(0, 21, 0)) 0442 auto &&rawparams = rawProcessor->imgdata.params; 0443 #else 0444 auto &&rawparams = rawProcessor->imgdata.rawparams; 0445 #endif 0446 // Select one raw image from input file (0 - first, ...) 0447 if (handler->currentImageNumber() > -1) { 0448 rawparams.shot_select = handler->currentImageNumber(); 0449 } 0450 0451 // *** Set processing parameters 0452 0453 // NOTE: The default value set below are the ones that gave the best results 0454 // on a large sample of images (https://raw.pixls.us/data/) 0455 0456 /** 0457 * @brief quality 0458 * Plugin quality option. 0459 */ 0460 qint32 quality = -1; 0461 if (handler->supportsOption(QImageIOHandler::Quality)) { 0462 quality = handler->option(QImageIOHandler::Quality).toInt(); 0463 } 0464 if (quality > -1) { 0465 switch (quality / 10) { 0466 case 0: 0467 quality = C_IQ(0) | C_OC(1) | C_CW(1) | C_AW(1) | C_BT(0) | C_HS(1); 0468 break; 0469 case 1: 0470 quality = C_IQ(0) | C_OC(1) | C_CW(1) | C_AW(1) | C_BT(0) | C_HS(0); 0471 break; 0472 case 2: 0473 quality = C_IQ(3) | C_OC(1) | C_CW(1) | C_AW(1) | C_BT(0) | C_HS(0); 0474 break; 0475 case 3: 0476 quality = C_IQ(3) | C_OC(1) | C_CW(1) | C_AW(1) | C_BT(1) | C_HS(0); 0477 break; 0478 case 4: 0479 quality = C_IQ(3) | C_OC(2) | C_CW(1) | C_AW(1) | C_BT(1) | C_HS(0); 0480 break; 0481 case 5: 0482 quality = C_IQ(3) | C_OC(4) | C_CW(1) | C_AW(1) | C_BT(1) | C_HS(0); 0483 break; 0484 case 6: 0485 quality = C_IQ(11) | C_OC(1) | C_CW(1) | C_AW(1) | C_BT(0) | C_HS(0); 0486 break; 0487 case 7: 0488 quality = C_IQ(11) | C_OC(1) | C_CW(1) | C_AW(1) | C_BT(1) | C_HS(0); 0489 break; 0490 case 8: 0491 quality = C_IQ(11) | C_OC(2) | C_CW(1) | C_AW(1) | C_BT(1) | C_HS(0); 0492 break; 0493 default: 0494 quality = C_IQ(11) | C_OC(4) | C_CW(1) | C_AW(1) | C_BT(1) | C_HS(0); 0495 break; 0496 } 0497 quality |= C_FLAGS(1); 0498 } 0499 if (quality == -1) { 0500 quality = DEFAULT_QUALITY; 0501 } 0502 Q_ASSERT(T_FLAGS(quality)); 0503 0504 auto &¶ms = rawProcessor->imgdata.params; 0505 0506 /** 0507 * @brief use_camera_wb 0508 * Use camera white balance, if possible (0 - off, 1 - on) 0509 * 0510 * This should to be set. Alternatively, a calibrated white balance should be set on each camera. 0511 */ 0512 params.use_camera_wb = T_CW(quality); 0513 0514 /*! 0515 * \brief use_auto_wb 0516 * Average the whole image for white balance (0 - off, 1 - on) 0517 * 0518 * This is usefull if no camera white balance is available. 0519 */ 0520 params.use_auto_wb = T_AW(quality); 0521 0522 /** 0523 * @brief output_bps 0524 * Bits per pixel (8 or 16) 0525 * 0526 * Professional cameras (and even some smartphones) generate images at 10 or more bits per sample. 0527 * When using 16-bit images, the highest quality should be maintained. 0528 */ 0529 params.output_bps = T_BT(quality) ? 16 : 8; 0530 0531 /** 0532 * @brief output_color 0533 * Output colorspace (0 - raw, 1 - sRGB, 2 - Adobe, 3 - Wide, 4 - ProPhoto, 5 - XYZ, 6 - ACES, 7 - DCI-P3, 8 - Rec2020) 0534 * 0535 * sRGB allows you to view images correctly on programs that do not support ICC profiles. When most 0536 * Programs will support icc profiles, ProPhoto may be a better choice. 0537 * @note sRgb is the LibRaw default: if grayscale image is loaded, LibRaw switches to 0 (Raw) automatically. 0538 */ 0539 params.output_color = T_OC(quality); 0540 0541 /** 0542 * @brief user_qual 0543 * Interpolation quality (0 - linear, 1 - VNG, 2 - PPG, 3 - AHD, 4 - DCB, 11 - DHT, 12 - AAHD) 0544 * 0545 * DHT seems the best option - See In-Depth Demosaicing Algorithm Analysis (https://www.libraw.org/node/2306) 0546 * but, when used, some FUJI RAF files of my library are poorly rendered (e.g. completely green). This is the 0547 * why I used AHD: a good compromise between quality and performance with no rendering errors. 0548 */ 0549 params.user_qual = T_IQ(quality); 0550 0551 /** 0552 * @brief half_size 0553 * Generate an half-size image (0 - off, 1 - on) 0554 * 0555 * Very fast and useful for generating previews. 0556 */ 0557 params.half_size = T_HS(quality); 0558 0559 /** 0560 * @dcb_enhance_fl 0561 * DCB color enhance filter (0 - off, 1 - on) 0562 */ 0563 params.dcb_enhance_fl = T_CE(quality); 0564 0565 /** 0566 * @fbdd_noiserd 0567 * FBDD noise reduction (0 - off, 1 - light, 2 - full) 0568 */ 0569 params.fbdd_noiserd = std::min(2, T_NR(quality)); 0570 0571 /** 0572 * @four_color_rgb 0573 * Interpolate RGGB as four colors (0 - off, 1 - on) 0574 */ 0575 params.four_color_rgb = T_FC(quality); 0576 0577 /** 0578 * @use_fuji_rotate 0579 * Don't stretch or rotate raw pixels (0 - off, 1 - on) 0580 */ 0581 params.use_fuji_rotate = T_SR(quality) ? 0 : 1; 0582 } 0583 0584 bool LoadRAW(QImageIOHandler *handler, QImage &img) 0585 { 0586 std::unique_ptr<LibRaw> rawProcessor(new LibRaw); 0587 0588 // *** Set parameters 0589 setParams(handler, rawProcessor.get()); 0590 0591 // *** Open the stream 0592 auto device = handler->device(); 0593 #ifndef EXCLUDE_LibRaw_QIODevice 0594 LibRaw_QIODevice stream(device); 0595 if (rawProcessor->open_datastream(&stream) != LIBRAW_SUCCESS) { 0596 return false; 0597 } 0598 #else 0599 auto ba = device->readAll(); 0600 if (rawProcessor->open_buffer(ba.data(), ba.size()) != LIBRAW_SUCCESS) { 0601 return false; 0602 } 0603 #endif 0604 0605 // *** Unpacking selected image 0606 if (rawProcessor->unpack() != LIBRAW_SUCCESS) { 0607 return false; 0608 } 0609 0610 // *** Process selected image 0611 if (rawProcessor->dcraw_process() != LIBRAW_SUCCESS) { 0612 return false; 0613 } 0614 0615 // *** Convert to QImage 0616 pi_unique_ptr processedImage(rawProcessor->dcraw_make_mem_image(), LibRaw::dcraw_clear_mem); 0617 if (processedImage == nullptr) { 0618 return false; 0619 } 0620 0621 // clang-format off 0622 if ((processedImage->type != LIBRAW_IMAGE_BITMAP) || 0623 (processedImage->colors != 1 && processedImage->colors != 3 && processedImage->colors != 4) || 0624 (processedImage->bits != 8 && processedImage->bits != 16)) { 0625 return false; 0626 } 0627 // clang-format on 0628 0629 auto format = QImage::Format_Invalid; 0630 switch (processedImage->colors) { 0631 case 1: // Gray images (tested with image attached on https://bugs.kde.org/show_bug.cgi?id=401371) 0632 format = processedImage->bits == 8 ? QImage::Format_Grayscale8 : QImage::Format_Grayscale16; 0633 break; 0634 case 3: // Images with R G B components 0635 format = processedImage->bits == 8 ? QImage::Format_RGB888 : QImage::Format_RGBX64; 0636 break; 0637 case 4: // Images with R G B components + Alpha (never seen) 0638 format = processedImage->bits == 8 ? QImage::Format_RGBA8888 : QImage::Format_RGBA64; 0639 break; 0640 } 0641 0642 if (format == QImage::Format_Invalid) { 0643 return false; 0644 } 0645 0646 img = imageAlloc(processedImage->width, processedImage->height, format); 0647 if (img.isNull()) { 0648 return false; 0649 } 0650 0651 auto rawBytesPerLine = qint32(processedImage->width * processedImage->bits * processedImage->colors + 7) / 8; 0652 auto lineSize = std::min(qint32(img.bytesPerLine()), rawBytesPerLine); 0653 for (int y = 0, h = img.height(); y < h; ++y) { 0654 auto scanline = img.scanLine(y); 0655 if (format == QImage::Format_RGBX64) 0656 rgbToRgbX<quint16>(scanline, processedImage->data + rawBytesPerLine * y, img.bytesPerLine(), rawBytesPerLine); 0657 else 0658 memcpy(scanline, processedImage->data + rawBytesPerLine * y, lineSize); 0659 } 0660 0661 // *** Set the color space 0662 auto &¶ms = rawProcessor->imgdata.params; 0663 if (params.output_color == 0) { 0664 auto &&color = rawProcessor->imgdata.color; 0665 if (auto profile = reinterpret_cast<char *>(color.profile)) { 0666 img.setColorSpace(QColorSpace::fromIccProfile(QByteArray(profile, color.profile_length))); 0667 } 0668 } 0669 if (processedImage->colors >= 3) { 0670 if (params.output_color == 1) { 0671 img.setColorSpace(QColorSpace(QColorSpace::SRgb)); 0672 } 0673 if (params.output_color == 2) { 0674 img.setColorSpace(QColorSpace(QColorSpace::AdobeRgb)); 0675 } 0676 if (params.output_color == 4) { 0677 img.setColorSpace(QColorSpace(QColorSpace::ProPhotoRgb)); 0678 } 0679 if (params.output_color == 7) { 0680 img.setColorSpace(QColorSpace(QColorSpace::DisplayP3)); 0681 } 0682 } 0683 0684 // *** Set the metadata 0685 auto &&iparams = rawProcessor->imgdata.idata; 0686 0687 auto xmpPacket = QString(); 0688 if (auto xmpdata = iparams.xmpdata) { 0689 if (auto xmplen = iparams.xmplen) 0690 xmpPacket = QString::fromUtf8(xmpdata, xmplen); 0691 } 0692 // Add info from LibRAW structs (e.g. GPS position, info about lens, info about shot and flash, etc...) 0693 img.setText(QStringLiteral("XML:com.adobe.xmp"), updateXmpPacket(xmpPacket, rawProcessor.get())); 0694 0695 auto model = QString::fromUtf8(iparams.normalized_model); 0696 if (!model.isEmpty()) { 0697 img.setText(QStringLiteral("Model"), model); 0698 } 0699 auto manufacturer = QString::fromUtf8(iparams.normalized_make); 0700 if (!manufacturer.isEmpty()) { 0701 img.setText(QStringLiteral("Manufacturer"), manufacturer); 0702 } 0703 auto software = QString::fromUtf8(iparams.software); 0704 if (!software.isEmpty()) { 0705 img.setText(QStringLiteral("Software"), software); 0706 } 0707 0708 auto &&iother = rawProcessor->imgdata.other; 0709 auto description = QString::fromUtf8(iother.desc); 0710 if (!description.isEmpty()) { 0711 img.setText(QStringLiteral("Description"), description); 0712 } 0713 auto artist = QString::fromUtf8(iother.artist); 0714 if (!artist.isEmpty()) { 0715 img.setText(QStringLiteral("Author"), artist); 0716 } 0717 0718 return true; 0719 } 0720 0721 } // Private 0722 0723 RAWHandler::RAWHandler() 0724 : m_imageNumber(0) 0725 , m_imageCount(0) 0726 , m_quality(-1) 0727 , m_startPos(-1) 0728 { 0729 } 0730 0731 bool RAWHandler::canRead() const 0732 { 0733 if (canRead(device())) { 0734 setFormat("raw"); 0735 return true; 0736 } 0737 return false; 0738 } 0739 0740 bool RAWHandler::read(QImage *image) 0741 { 0742 auto dev = device(); 0743 0744 // set the image position after the first run. 0745 if (!dev->isSequential()) { 0746 if (m_startPos < 0) { 0747 m_startPos = dev->pos(); 0748 } else { 0749 dev->seek(m_startPos); 0750 } 0751 } 0752 0753 // Check image file format. 0754 if (dev->atEnd()) { 0755 return false; 0756 } 0757 0758 QImage img; 0759 if (!LoadRAW(this, img)) { 0760 return false; 0761 } 0762 0763 *image = img; 0764 return true; 0765 } 0766 0767 void RAWHandler::setOption(ImageOption option, const QVariant &value) 0768 { 0769 if (option == QImageIOHandler::Quality) { 0770 bool ok = false; 0771 auto q = value.toInt(&ok); 0772 if (ok) { 0773 m_quality = q; 0774 } 0775 } 0776 } 0777 0778 bool RAWHandler::supportsOption(ImageOption option) const 0779 { 0780 #ifndef EXCLUDE_LibRaw_QIODevice 0781 if (option == QImageIOHandler::Size) { 0782 return true; 0783 } 0784 #endif 0785 0786 if (option == QImageIOHandler::Quality) { 0787 return true; 0788 } 0789 0790 return false; 0791 } 0792 0793 QVariant RAWHandler::option(ImageOption option) const 0794 { 0795 QVariant v; 0796 0797 #ifndef EXCLUDE_LibRaw_QIODevice 0798 if (option == QImageIOHandler::Size) { 0799 auto d = device(); 0800 d->startTransaction(); 0801 std::unique_ptr<LibRaw> rawProcessor(new LibRaw); 0802 LibRaw_QIODevice stream(d); 0803 #if (LIBRAW_VERSION < LIBRAW_MAKE_VERSION(0, 21, 0)) 0804 rawProcessor->imgdata.params.shot_select = currentImageNumber(); 0805 #else 0806 rawProcessor->imgdata.rawparams.shot_select = currentImageNumber(); 0807 #endif 0808 if (rawProcessor->open_datastream(&stream) == LIBRAW_SUCCESS) { 0809 if (rawProcessor->unpack() == LIBRAW_SUCCESS) { 0810 auto w = libraw_get_iwidth(&rawProcessor->imgdata); 0811 auto h = libraw_get_iheight(&rawProcessor->imgdata); 0812 // flip & 4: taken from LibRaw code 0813 v = (rawProcessor->imgdata.sizes.flip & 4) ? QSize(h, w) : QSize(w, h); 0814 } 0815 } 0816 d->rollbackTransaction(); 0817 } 0818 #endif 0819 0820 if (option == QImageIOHandler::Quality) { 0821 v = m_quality; 0822 } 0823 0824 return v; 0825 } 0826 0827 bool RAWHandler::jumpToNextImage() 0828 { 0829 return jumpToImage(m_imageNumber + 1); 0830 } 0831 0832 bool RAWHandler::jumpToImage(int imageNumber) 0833 { 0834 if (imageNumber < 0 || imageNumber >= imageCount()) { 0835 return false; 0836 } 0837 m_imageNumber = imageNumber; 0838 return true; 0839 } 0840 0841 int RAWHandler::imageCount() const 0842 { 0843 // NOTE: image count is cached for performance reason 0844 auto &&count = m_imageCount; 0845 if (count > 0) { 0846 return count; 0847 } 0848 0849 count = QImageIOHandler::imageCount(); 0850 0851 #ifndef EXCLUDE_LibRaw_QIODevice 0852 auto d = device(); 0853 d->startTransaction(); 0854 0855 std::unique_ptr<LibRaw> rawProcessor(new LibRaw); 0856 LibRaw_QIODevice stream(d); 0857 if (rawProcessor->open_datastream(&stream) == LIBRAW_SUCCESS) { 0858 count = rawProcessor->imgdata.rawdata.iparams.raw_count; 0859 } 0860 0861 d->rollbackTransaction(); 0862 #endif 0863 0864 return count; 0865 } 0866 0867 int RAWHandler::currentImageNumber() const 0868 { 0869 return m_imageNumber; 0870 } 0871 0872 bool RAWHandler::canRead(QIODevice *device) 0873 { 0874 if (!device) { 0875 qWarning("RAWHandler::canRead() called with no device"); 0876 return false; 0877 } 0878 if (device->isSequential()) { 0879 return false; 0880 } 0881 0882 device->startTransaction(); 0883 0884 std::unique_ptr<LibRaw> rawProcessor(new LibRaw); 0885 0886 #ifndef EXCLUDE_LibRaw_QIODevice 0887 LibRaw_QIODevice stream(device); 0888 auto ok = rawProcessor->open_datastream(&stream) == LIBRAW_SUCCESS; 0889 #else 0890 auto ba = device->readAll(); 0891 auto ok = rawProcessor->open_buffer(ba.data(), ba.size()) == LIBRAW_SUCCESS; 0892 #endif 0893 0894 device->rollbackTransaction(); 0895 return ok; 0896 } 0897 0898 QImageIOPlugin::Capabilities RAWPlugin::capabilities(QIODevice *device, const QByteArray &format) const 0899 { 0900 if (supported_formats.contains(QByteArray(format).toLower())) { 0901 return Capabilities(CanRead); 0902 } 0903 if (!format.isEmpty()) { 0904 return {}; 0905 } 0906 if (!device->isOpen()) { 0907 return {}; 0908 } 0909 0910 Capabilities cap; 0911 if (device->isReadable() && RAWHandler::canRead(device)) { 0912 cap |= CanRead; 0913 } 0914 return cap; 0915 } 0916 0917 QImageIOHandler *RAWPlugin::create(QIODevice *device, const QByteArray &format) const 0918 { 0919 QImageIOHandler *handler = new RAWHandler; 0920 handler->setDevice(device); 0921 handler->setFormat(format); 0922 return handler; 0923 } 0924 0925 #include "moc_raw_p.cpp"