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 &&params = 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 &&params = 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"