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