/* SPDX-FileCopyrightText: 2022 Albert Astals Cid SPDX-FileCopyrightText: 2022 Mirco Miranda SPDX-License-Identifier: LGPL-2.0-or-later */ #ifndef UTIL_P_H #define UTIL_P_H #include #include #include #include #include #include // Default maximum number of channels (do not exceed 256). #ifndef KIF_MAX_IMAGE_CHANNELS #define KIF_MAX_IMAGE_CHANNELS 60 #endif // Default maximum width and height for the large image plugins. #ifndef KIF_LARGE_IMAGE_PIXEL_LIMIT #define KIF_LARGE_IMAGE_PIXEL_LIMIT 300000 #endif // Maximum size for legacy image formats. #define KIF_64K_IMAGE_PIXEL_LIMIT 65536 #if KIF_LARGE_IMAGE_PIXEL_LIMIT < KIF_64K_IMAGE_PIXEL_LIMIT #undef KIF_LARGE_IMAGE_PIXEL_LIMIT #define KIF_LARGE_IMAGE_PIXEL_LIMIT KIF_64K_IMAGE_PIXEL_LIMIT #endif // Image metadata keys to use in plugins (so they are consistent) #define META_KEY_ALTITUDE "Altitude" #define META_KEY_AUTHOR "Author" #define META_KEY_COMMENT "Comment" #define META_KEY_COPYRIGHT "Copyright" #define META_KEY_CREATIONDATE "CreationDate" #define META_KEY_DESCRIPTION "Description" #define META_KEY_DIRECTION "Direction" #define META_KEY_DOCUMENTNAME "DocumentName" #define META_KEY_HOSTCOMPUTER "HostComputer" #define META_KEY_LATITUDE "Latitude" #define META_KEY_LONGITUDE "Longitude" #define META_KEY_MODIFICATIONDATE "ModificationDate" #define META_KEY_OWNER "Owner" #define META_KEY_SOFTWARE "Software" #define META_KEY_SPEED "Speed" #define META_KEY_TITLE "Title" #define META_KEY_XML_GIMP "XML:org.gimp.xml" #define META_KEY_XMP_ADOBE "XML:com.adobe.xmp" // Shot info metadata keys #define META_KEY_DIGITALZOOMRATIO "DigitalZoomRatio" #define META_KEY_EXPOSUREMODE "ExposureMode" #define META_KEY_EXPOSUREPROGRAM "ExposureProgram" #define META_KEY_EXPOSURETIME "ExposureTime" #define META_KEY_FLASH "Flash" #define META_KEY_FNUMBER "FNumber" #define META_KEY_FOCALLENGTH "FocalLength" #define META_KEY_ISOSPEEDRATINGS "ISOSpeedRatings" #define META_KEY_WHITEBALANCE "WhiteBalance" // Camera info metadata keys #define META_KEY_MANUFACTURER "Manufacturer" #define META_KEY_MODEL "Model" #define META_KEY_SERIALNUMBER "SerialNumber" // Lens info metadata keys #define META_KEY_LENS_MANUFACTURER "LensManufacturer" #define META_KEY_LENS_MODEL "LensModel" #define META_KEY_LENS_SERIALNUMBER "LensSerialNumber" // QList uses some extra space for stuff, hence the 32 here suggested by Thiago Macieira static constexpr int kMaxQVectorSize = std::numeric_limits::max() - 32; /*! * \brief The ImageInitToZero enum */ enum class ImageInitToZero { None, /**< Do not initialize the image. */ All, /**< Initialize all images. */ PremulOnly, /**< Initialize only images with premultiplied alpha. */ FPOnly, /**< Initialize only images with floating point format. */ FPAndPremul /**< Initialize floating point and premultiplied formats. */ }; /*! * \brief imageAlloc * Helper function to initialize framework images. * \param size The image size. * \param format The image format. * \param init Whether and which images should be initialized to zero. * \return The allocated image or a null image on error. */ inline QImage imageAlloc(const QSize &size, const QImage::Format &format, const ImageInitToZero& init = ImageInitToZero::None) { QImage img; if (!QImageIOHandler::allocateImage(size, format, &img)) { img = QImage(); // paranoia } if (init != ImageInitToZero::None && !img.isNull()) { auto pixelFormat = img.pixelFormat(); auto isFloat = pixelFormat.typeInterpretation() == QPixelFormat::FloatingPoint; auto isPremul = pixelFormat.premultiplied(); if (init == ImageInitToZero::All) { img.fill(Qt::black); } else if (isFloat && (init == ImageInitToZero::FPOnly || init == ImageInitToZero::FPAndPremul)) { img.fill(Qt::black); } else if (isPremul && (init == ImageInitToZero::PremulOnly || init == ImageInitToZero::FPAndPremul)) { img.fill(Qt::black); } } return img; } /*! * \brief imageAlloc * Helper function to initialize framework images. * \param width The image width. * \param height The image height. * \param format The image format. * \param init Whether and which images should be initialized to zero. * \return The allocated image or a null image on error. */ inline QImage imageAlloc(qint32 width, qint32 height, const QImage::Format &format, const ImageInitToZero& init = ImageInitToZero::None) { return imageAlloc(QSize(width, height), format, init); } /*! * \brief checkImageSize * Helper function to make sure the image size does not exceed the limit set in Qt. * \param width The image width. * \param height The image height. * \param bytesPerPixel The number of bytes for each pixel of the image. * \return True if the limit is respected, false otherwise. */ inline bool checkImageSize(qint32 width, qint32 height, qint32 bytesPerPixel) { size_t maxBytes = size_t(QImageReader::allocationLimit()) * 1024 * 1024; if (maxBytes == 0) { return true; } size_t bytes = size_t(width) * height * bytesPerPixel; return bytes <= maxBytes; } /*! * \brief checkImageSize * Helper function to make sure the image size does not exceed the limit set in Qt. * \param size The image size. * \param bytesPerPixel The number of bytes for each pixel of the image. * \return True if the limit is respected, false otherwise. */ inline bool checkImageSize(const QSize& size, qint32 bytesPerPixel) { return checkImageSize(size.width(), size.height(), bytesPerPixel); } template // SF = source FP, TI = target INT TI qRoundOrZero_T(SF d, bool *ok = nullptr) { // checks for undefined behavior if (qIsNaN(d) || qIsInf(d) || d < SF() || d > SF(std::numeric_limits::max())) { if (ok) { *ok = false; } return 0; } if (ok) { *ok = true; } return qRound(d); } inline qint32 qRoundOrZero(double d, bool *ok = nullptr) { return qRoundOrZero_T(d, ok); } inline qint32 qRoundOrZero(float d, bool *ok = nullptr) { return qRoundOrZero_T(d, ok); } /*! * \brief dpi2ppm * Converts a value from DPI to PPM. * \return \a dpi converted to pixel per meter. */ inline qint32 dpi2ppm(double dpi, bool *ok = nullptr) { return qRoundOrZero(dpi / double(25.4) * double(1000), ok); } inline qint32 dpi2ppm(float dpi, bool *ok = nullptr) { return qRoundOrZero(dpi / float(25.4) * float(1000), ok); } inline qint32 dpi2ppm(quint16 dpi, bool *ok = nullptr) { return qRoundOrZero(dpi / double(25.4) * double(1000), ok); } /*! * \brief ppm2dpi * Converts a value from PPM to DPI. * \return \a ppm converted to dot per inch. */ template // SI = source INT, TF = target FP TF ppm2dpi_T(SI ppm, bool *ok = nullptr) { if (ok) { *ok = ppm > 0; } return ppm > 0 ? ppm * TF(25.4) / TF(1000) : TF(); } inline double dppm2dpi(qint32 ppm, bool *ok = nullptr) { return ppm2dpi_T(ppm, ok); } inline float fppm2dpi(qint32 ppm, bool *ok = nullptr) { return ppm2dpi_T(ppm, ok); } /*! * \brief deviceRead * A function for reading from devices. * * Similar to QIODevice::read(qint64) but limits the initial memory allocation. Useful for reading corrupted streams. * \param d The device. * \param maxSize The maximum size to read. * \return The byte array read. */ static QByteArray deviceRead(QIODevice *d, qint64 maxSize) { if (d == nullptr) { return{}; } const qint64 blockSize = 1024 * 1024; auto devSize = d->isSequential() ? qint64() : d->size(); if (devSize > 0) { // random access device maxSize = std::min(maxSize, devSize - d->pos()); return d->read(maxSize); } else if (maxSize < blockSize) { // small read return d->read(maxSize); } // sequential device QByteArray ba; while (ba.size() < maxSize) { auto toRead = std::min(blockSize, maxSize - ba.size()); if (toRead + ba.size() > QByteArray::maxSize()) { break; } auto tmp = d->read(toRead); if (tmp.isEmpty()) { break; } ba.append(tmp); } return ba; } #endif // UTIL_P_H