diff --git a/autotests/read/xcf/fruktpilot16fplin_icc.xcf.json b/autotests/read/xcf/fruktpilot16fplin_icc.xcf.json index 462f6d5..c15a2f7 100644 --- a/autotests/read/xcf/fruktpilot16fplin_icc.xcf.json +++ b/autotests/read/xcf/fruktpilot16fplin_icc.xcf.json @@ -4,8 +4,8 @@ "seeAlso" : "https://bugreports.qt.io/browse/QTBUG-120614", "fuzziness" : 1, "resolution" : { - "dotsPerMeterX" : 2834, - "dotsPerMeterY" : 2834 + "dotsPerMeterX" : 2835, + "dotsPerMeterY" : 2835 } } ] diff --git a/src/imageformats/chunks.cpp b/src/imageformats/chunks.cpp index c3d7f44..13a3cc1 100644 --- a/src/imageformats/chunks.cpp +++ b/src/imageformats/chunks.cpp @@ -7,6 +7,7 @@ #include "chunks_p.h" #include "packbits_p.h" +#include "util_p.h" #include #include @@ -656,7 +657,7 @@ quint16 DPIChunk::dpiX() const if (bytes() < 4) { return 0; } - return i16(data().at(1), data().at(0)); + return ui16(data().at(1), data().at(0)); } quint16 DPIChunk::dpiY() const @@ -664,17 +665,17 @@ quint16 DPIChunk::dpiY() const if (bytes() < 4) { return 0; } - return i16(data().at(3), data().at(2)); + return ui16(data().at(3), data().at(2)); } qint32 DPIChunk::dotsPerMeterX() const { - return qRound(dpiX() / 25.4 * 1000); + return dpi2ppm(dpiX()); } qint32 DPIChunk::dotsPerMeterY() const { - return qRound(dpiY() / 25.4 * 1000); + return dpi2ppm(dpiY()); } bool DPIChunk::innerReadStructure(QIODevice *d) @@ -708,7 +709,7 @@ quint16 XBMIChunk::dpiX() const if (bytes() < 6) { return 0; } - return i16(data().at(3), data().at(2)); + return ui16(data().at(3), data().at(2)); } quint16 XBMIChunk::dpiY() const @@ -716,7 +717,7 @@ quint16 XBMIChunk::dpiY() const if (bytes() < 6) { return 0; } - return i16(data().at(5), data().at(4)); + return ui16(data().at(5), data().at(4)); } XBMIChunk::PictureType XBMIChunk::pictureType() const @@ -2886,6 +2887,7 @@ static QByteArray pchgFastDecomp(const QByteArray& input, int treeSize, int orig bool PCHGChunk::initialize(const QList &cmapPalette, qint32 height) { + Q_UNUSED(height) auto dt = data().mid(20); if (compression() == PCHGChunk::Compression::Huffman) { QDataStream ds(dt); diff --git a/src/imageformats/exr.cpp b/src/imageformats/exr.cpp index ca0d793..eac97e7 100644 --- a/src/imageformats/exr.cpp +++ b/src/imageformats/exr.cpp @@ -288,8 +288,14 @@ static void readMetadata(const Imf::Header &header, QImage &image) if (auto pixelAspectRatio = header.findTypedAttribute("pixelAspectRatio")) { par = pixelAspectRatio->value(); } - image.setDotsPerMeterX(qRound(xDensity->value() * 100.0 / 2.54)); - image.setDotsPerMeterY(qRound(xDensity->value() * par * 100.0 / 2.54)); + auto hres = dpi2ppm(xDensity->value()); + if (hres > 0) { + image.setDotsPerMeterX(hres); + } + auto vres = dpi2ppm(xDensity->value() * par); + if (vres > 0) { + image.setDotsPerMeterY(vres); + } } // Non-standard attribute diff --git a/src/imageformats/jxr.cpp b/src/imageformats/jxr.cpp index 7df9ffe..a1020d3 100644 --- a/src/imageformats/jxr.cpp +++ b/src/imageformats/jxr.cpp @@ -32,7 +32,6 @@ #include #include -#include #include Q_DECLARE_LOGGING_CATEGORY(LOG_JXRPLUGIN) @@ -978,13 +977,12 @@ bool JXRHandler::read(QImage *outImage) if (auto err = d->pDecoder->GetResolution(d->pDecoder, &hres, &vres)) { qCWarning(LOG_JXRPLUGIN) << "JXRHandler::read() error while reading resolution:" << err; } else { - std::feclearexcept(FE_ALL_EXCEPT); - const int hdpm = std::lround(hres * 1000 / 25.4); - const int vdpm = std::lround(vres * 1000 / 25.4); - if (std::fetestexcept(FE_INVALID)) { - qCWarning(LOG_JXRPLUGIN) << "JXRHandler::read() resolution is out of range:" << hres << vres; - } else { + const qint32 hdpm = dpi2ppm(hres); + if (hdpm > 0) { img.setDotsPerMeterX(hdpm); + } + const qint32 vdpm = dpi2ppm(vres); + if (vdpm > 0) { img.setDotsPerMeterY(vdpm); } } @@ -1132,7 +1130,7 @@ bool JXRHandler::write(const QImage &image) qCWarning(LOG_JXRPLUGIN) << "JXRHandler::write() error while setting the image size:" << err; return false; } - if (auto err = d->pEncoder->SetResolution(d->pEncoder, qi.dotsPerMeterX() * 25.4 / 1000, qi.dotsPerMeterY() * 25.4 / 1000)) { + if (auto err = d->pEncoder->SetResolution(d->pEncoder, dppm2dpi(qi.dotsPerMeterX()), dppm2dpi(qi.dotsPerMeterY()))) { qCWarning(LOG_JXRPLUGIN) << "JXRHandler::write() error while setting the image resolution:" << err; return false; } diff --git a/src/imageformats/microexif.cpp b/src/imageformats/microexif.cpp index 110c982..9eae917 100644 --- a/src/imageformats/microexif.cpp +++ b/src/imageformats/microexif.cpp @@ -15,8 +15,6 @@ #include #include -#include - // TIFF 6 specs #define TIFF_IMAGEWIDTH 0x100 #define TIFF_IMAGEHEIGHT 0x101 @@ -1195,30 +1193,17 @@ void MicroExif::updateImageMetadata(QImage &targetImage, bool replaceExisting) c } } -static std::optional convertToDotsPerMeter(const double value) -{ - if (value <= 0) { - return {}; - } - std::feclearexcept(FE_ALL_EXCEPT); - const int rounded = std::lround(value / 25.4 * 1000); - if (std::fetestexcept(FE_INVALID)) { - return {}; - } - return rounded; -} - bool MicroExif::updateImageResolution(QImage &targetImage) { - const std::optional hdpm = convertToDotsPerMeter(horizontalResolution()); - const std::optional vdpm = convertToDotsPerMeter(verticalResolution()); - if (hdpm) { - targetImage.setDotsPerMeterX(*hdpm); + const auto hdpm = dpi2ppm(horizontalResolution()); + if (hdpm > 0) { + targetImage.setDotsPerMeterX(hdpm); } - if (vdpm) { - targetImage.setDotsPerMeterY(*vdpm); + const auto vdpm = dpi2ppm(verticalResolution()); + if (vdpm > 0) { + targetImage.setDotsPerMeterY(vdpm); } - return hdpm || vdpm; + return (hdpm > 0) || (vdpm > 0); } MicroExif MicroExif::fromByteArray(const QByteArray &ba, bool searchHeader) @@ -1288,8 +1273,8 @@ MicroExif MicroExif::fromImage(const QImage &image) // Image properties exif.setWidth(image.width()); exif.setHeight(image.height()); - exif.setHorizontalResolution(image.dotsPerMeterX() * 25.4 / 1000); - exif.setVerticalResolution(image.dotsPerMeterY() * 25.4 / 1000); + exif.setHorizontalResolution(dppm2dpi(image.dotsPerMeterX())); + exif.setVerticalResolution(dppm2dpi(image.dotsPerMeterY())); exif.setColorSpace(image.colorSpace()); // TIFF strings diff --git a/src/imageformats/pcx.cpp b/src/imageformats/pcx.cpp index 5a9d8de..5901a0a 100644 --- a/src/imageformats/pcx.cpp +++ b/src/imageformats/pcx.cpp @@ -888,8 +888,14 @@ bool PCXHandler::read(QImage *outImage) return false; } - img.setDotsPerMeterX(qRound(header.HDpi / 25.4 * 1000)); - img.setDotsPerMeterY(qRound(header.YDpi / 25.4 * 1000)); + auto hres = dpi2ppm(header.HDpi); + if (hres > 0) { + img.setDotsPerMeterX(hres); + } + auto vres = dpi2ppm(header.YDpi); + if (vres > 0) { + img.setDotsPerMeterY(vres); + } *outImage = img; return true; } @@ -915,8 +921,8 @@ bool PCXHandler::write(const QImage &image) header.YMin = 0; header.XMax = w - 1; header.YMax = h - 1; - header.HDpi = qRound(image.dotsPerMeterX() * 25.4 / 1000); - header.YDpi = qRound(image.dotsPerMeterY() * 25.4 / 1000); + header.HDpi = qRoundOrZero_T(dppm2dpi(image.dotsPerMeterX())); + header.YDpi = qRoundOrZero_T(dppm2dpi(image.dotsPerMeterY())); header.Reserved = 0; header.PaletteInfo = 1; diff --git a/src/imageformats/psd.cpp b/src/imageformats/psd.cpp index 444b9d8..56fc451 100644 --- a/src/imageformats/psd.cpp +++ b/src/imageformats/psd.cpp @@ -596,17 +596,21 @@ static bool setResolution(QImage &img, const PSDImageResourceSection &irs) s >> i32; // Horizontal resolution in pixels per inch. if (i32 <= 0) return false; - auto hres = fixedPointToDouble(i32); + auto hres = dpi2ppm(fixedPointToDouble(i32)); s.skipRawData(4); // Display data (not used here) s >> i32; // Vertial resolution in pixels per inch. if (i32 <= 0) return false; - auto vres = fixedPointToDouble(i32); + auto vres = dpi2ppm(fixedPointToDouble(i32)); - img.setDotsPerMeterX(hres * 1000 / 25.4); - img.setDotsPerMeterY(vres * 1000 / 25.4); + if (hres > 0) { + img.setDotsPerMeterX(hres); + } + if (vres > 0) { + img.setDotsPerMeterY(vres); + } return true; } diff --git a/src/imageformats/sct.cpp b/src/imageformats/sct.cpp index 7da6357..a7765f3 100644 --- a/src/imageformats/sct.cpp +++ b/src/imageformats/sct.cpp @@ -216,7 +216,7 @@ public: auto v = QString::fromLatin1(pchar_t(res.data()), res.size()).toDouble(&ok); if (ok && v > 0) { if (m_pb._unitsOfMeasurement) { // Inches - return qRoundOrZero(width() / v / 25.4 * 1000); + return dpi2ppm(width() / v); } // Millimeters return qRoundOrZero(width() / v * 1000); @@ -230,7 +230,7 @@ public: auto v = QString::fromLatin1(pchar_t(res.data()), res.size()).toDouble(&ok); if (ok && v > 0) { if (m_pb._unitsOfMeasurement) { // Inches - return qRoundOrZero(height() / v / 25.4 * 1000); + return dpi2ppm(height() / v); } // Millimeters return qRoundOrZero(height() / v * 1000); diff --git a/src/imageformats/util_p.h b/src/imageformats/util_p.h index 1fbc775..2a6d6b3 100644 --- a/src/imageformats/util_p.h +++ b/src/imageformats/util_p.h @@ -66,13 +66,70 @@ inline QImage imageAlloc(qint32 width, qint32 height, const QImage::Format &form return imageAlloc(QSize(width, height), format); } -inline double qRoundOrZero(double d) +template // SF = source FP, TI = target INT +TI qRoundOrZero_T(SF d, bool *ok = nullptr) { - // If the value d is outside the range of int, the behavior is undefined. - if (d > std::numeric_limits::max()) { + // 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); +} + #endif // UTIL_P_H diff --git a/src/imageformats/xcf.cpp b/src/imageformats/xcf.cpp index f6c6ea3..69b1548 100644 --- a/src/imageformats/xcf.cpp +++ b/src/imageformats/xcf.cpp @@ -55,8 +55,6 @@ Q_LOGGING_CATEGORY(XCFPLUGIN, "kf.imageformats.plugins.xcf", QtWarningMsg) #define DISABLE_TILE_PROFILE_CONV // default uncommented (comment to use the conversion as intended by Martin) #define DISABLE_IMAGE_PROFILE_CONV // default uncommented (comment to use the conversion as intended by Martin) -const float INCHESPERMETER = (100.0f / 2.54f); - namespace { struct RandomTable { @@ -2722,17 +2720,13 @@ bool XCFImageFormat::initializeImage(XCFImage &xcf_image) } #endif - if (xcf_image.x_resolution > 0 && xcf_image.y_resolution > 0) { - const float dpmx = xcf_image.x_resolution * INCHESPERMETER; - if (dpmx > float(std::numeric_limits::max())) { - return false; - } - const float dpmy = xcf_image.y_resolution * INCHESPERMETER; - if (dpmy > float(std::numeric_limits::max())) { - return false; - } - image.setDotsPerMeterX((int)dpmx); - image.setDotsPerMeterY((int)dpmy); + const qint32 dpmx = dpi2ppm(xcf_image.x_resolution); + if (dpmx > 0) { + image.setDotsPerMeterX(dpmx); + } + const qint32 dpmy = dpi2ppm(xcf_image.y_resolution); + if (dpmy > 0) { + image.setDotsPerMeterY(dpmy); } return true; }