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