Resolution calculations performed by functions

Added functions for dpi <-> ppm transformations and used in all plugins.
This commit is contained in:
Mirco Miranda
2025-09-17 12:22:44 +02:00
parent 14286a6ab0
commit a4e18734bd
10 changed files with 120 additions and 68 deletions

View File

@@ -4,8 +4,8 @@
"seeAlso" : "https://bugreports.qt.io/browse/QTBUG-120614", "seeAlso" : "https://bugreports.qt.io/browse/QTBUG-120614",
"fuzziness" : 1, "fuzziness" : 1,
"resolution" : { "resolution" : {
"dotsPerMeterX" : 2834, "dotsPerMeterX" : 2835,
"dotsPerMeterY" : 2834 "dotsPerMeterY" : 2835
} }
} }
] ]

View File

@@ -7,6 +7,7 @@
#include "chunks_p.h" #include "chunks_p.h"
#include "packbits_p.h" #include "packbits_p.h"
#include "util_p.h"
#include <QBuffer> #include <QBuffer>
#include <QColor> #include <QColor>
@@ -656,7 +657,7 @@ quint16 DPIChunk::dpiX() const
if (bytes() < 4) { if (bytes() < 4) {
return 0; return 0;
} }
return i16(data().at(1), data().at(0)); return ui16(data().at(1), data().at(0));
} }
quint16 DPIChunk::dpiY() const quint16 DPIChunk::dpiY() const
@@ -664,17 +665,17 @@ quint16 DPIChunk::dpiY() const
if (bytes() < 4) { if (bytes() < 4) {
return 0; return 0;
} }
return i16(data().at(3), data().at(2)); return ui16(data().at(3), data().at(2));
} }
qint32 DPIChunk::dotsPerMeterX() const qint32 DPIChunk::dotsPerMeterX() const
{ {
return qRound(dpiX() / 25.4 * 1000); return dpi2ppm(dpiX());
} }
qint32 DPIChunk::dotsPerMeterY() const qint32 DPIChunk::dotsPerMeterY() const
{ {
return qRound(dpiY() / 25.4 * 1000); return dpi2ppm(dpiY());
} }
bool DPIChunk::innerReadStructure(QIODevice *d) bool DPIChunk::innerReadStructure(QIODevice *d)
@@ -708,7 +709,7 @@ quint16 XBMIChunk::dpiX() const
if (bytes() < 6) { if (bytes() < 6) {
return 0; return 0;
} }
return i16(data().at(3), data().at(2)); return ui16(data().at(3), data().at(2));
} }
quint16 XBMIChunk::dpiY() const quint16 XBMIChunk::dpiY() const
@@ -716,7 +717,7 @@ quint16 XBMIChunk::dpiY() const
if (bytes() < 6) { if (bytes() < 6) {
return 0; return 0;
} }
return i16(data().at(5), data().at(4)); return ui16(data().at(5), data().at(4));
} }
XBMIChunk::PictureType XBMIChunk::pictureType() const XBMIChunk::PictureType XBMIChunk::pictureType() const
@@ -2886,6 +2887,7 @@ static QByteArray pchgFastDecomp(const QByteArray& input, int treeSize, int orig
bool PCHGChunk::initialize(const QList<QRgb> &cmapPalette, qint32 height) bool PCHGChunk::initialize(const QList<QRgb> &cmapPalette, qint32 height)
{ {
Q_UNUSED(height)
auto dt = data().mid(20); auto dt = data().mid(20);
if (compression() == PCHGChunk::Compression::Huffman) { if (compression() == PCHGChunk::Compression::Huffman) {
QDataStream ds(dt); QDataStream ds(dt);

View File

@@ -288,8 +288,14 @@ static void readMetadata(const Imf::Header &header, QImage &image)
if (auto pixelAspectRatio = header.findTypedAttribute<Imf::FloatAttribute>("pixelAspectRatio")) { if (auto pixelAspectRatio = header.findTypedAttribute<Imf::FloatAttribute>("pixelAspectRatio")) {
par = pixelAspectRatio->value(); par = pixelAspectRatio->value();
} }
image.setDotsPerMeterX(qRound(xDensity->value() * 100.0 / 2.54)); auto hres = dpi2ppm(xDensity->value());
image.setDotsPerMeterY(qRound(xDensity->value() * par * 100.0 / 2.54)); if (hres > 0) {
image.setDotsPerMeterX(hres);
}
auto vres = dpi2ppm(xDensity->value() * par);
if (vres > 0) {
image.setDotsPerMeterY(vres);
}
} }
// Non-standard attribute // Non-standard attribute

View File

@@ -32,7 +32,6 @@
#include <QTemporaryDir> #include <QTemporaryDir>
#include <JXRGlue.h> #include <JXRGlue.h>
#include <cfenv>
#include <cstring> #include <cstring>
Q_DECLARE_LOGGING_CATEGORY(LOG_JXRPLUGIN) 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)) { if (auto err = d->pDecoder->GetResolution(d->pDecoder, &hres, &vres)) {
qCWarning(LOG_JXRPLUGIN) << "JXRHandler::read() error while reading resolution:" << err; qCWarning(LOG_JXRPLUGIN) << "JXRHandler::read() error while reading resolution:" << err;
} else { } else {
std::feclearexcept(FE_ALL_EXCEPT); const qint32 hdpm = dpi2ppm(hres);
const int hdpm = std::lround(hres * 1000 / 25.4); if (hdpm > 0) {
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 {
img.setDotsPerMeterX(hdpm); img.setDotsPerMeterX(hdpm);
}
const qint32 vdpm = dpi2ppm(vres);
if (vdpm > 0) {
img.setDotsPerMeterY(vdpm); 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; qCWarning(LOG_JXRPLUGIN) << "JXRHandler::write() error while setting the image size:" << err;
return false; 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; qCWarning(LOG_JXRPLUGIN) << "JXRHandler::write() error while setting the image resolution:" << err;
return false; return false;
} }

View File

@@ -15,8 +15,6 @@
#include <QStringDecoder> #include <QStringDecoder>
#include <QTimeZone> #include <QTimeZone>
#include <cfenv>
// TIFF 6 specs // TIFF 6 specs
#define TIFF_IMAGEWIDTH 0x100 #define TIFF_IMAGEWIDTH 0x100
#define TIFF_IMAGEHEIGHT 0x101 #define TIFF_IMAGEHEIGHT 0x101
@@ -1195,30 +1193,17 @@ void MicroExif::updateImageMetadata(QImage &targetImage, bool replaceExisting) c
} }
} }
static std::optional<int> 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) bool MicroExif::updateImageResolution(QImage &targetImage)
{ {
const std::optional<int> hdpm = convertToDotsPerMeter(horizontalResolution()); const auto hdpm = dpi2ppm(horizontalResolution());
const std::optional<int> vdpm = convertToDotsPerMeter(verticalResolution()); if (hdpm > 0) {
if (hdpm) { targetImage.setDotsPerMeterX(hdpm);
targetImage.setDotsPerMeterX(*hdpm);
} }
if (vdpm) { const auto vdpm = dpi2ppm(verticalResolution());
targetImage.setDotsPerMeterY(*vdpm); if (vdpm > 0) {
targetImage.setDotsPerMeterY(vdpm);
} }
return hdpm || vdpm; return (hdpm > 0) || (vdpm > 0);
} }
MicroExif MicroExif::fromByteArray(const QByteArray &ba, bool searchHeader) MicroExif MicroExif::fromByteArray(const QByteArray &ba, bool searchHeader)
@@ -1288,8 +1273,8 @@ MicroExif MicroExif::fromImage(const QImage &image)
// Image properties // Image properties
exif.setWidth(image.width()); exif.setWidth(image.width());
exif.setHeight(image.height()); exif.setHeight(image.height());
exif.setHorizontalResolution(image.dotsPerMeterX() * 25.4 / 1000); exif.setHorizontalResolution(dppm2dpi(image.dotsPerMeterX()));
exif.setVerticalResolution(image.dotsPerMeterY() * 25.4 / 1000); exif.setVerticalResolution(dppm2dpi(image.dotsPerMeterY()));
exif.setColorSpace(image.colorSpace()); exif.setColorSpace(image.colorSpace());
// TIFF strings // TIFF strings

View File

@@ -888,8 +888,14 @@ bool PCXHandler::read(QImage *outImage)
return false; return false;
} }
img.setDotsPerMeterX(qRound(header.HDpi / 25.4 * 1000)); auto hres = dpi2ppm(header.HDpi);
img.setDotsPerMeterY(qRound(header.YDpi / 25.4 * 1000)); if (hres > 0) {
img.setDotsPerMeterX(hres);
}
auto vres = dpi2ppm(header.YDpi);
if (vres > 0) {
img.setDotsPerMeterY(vres);
}
*outImage = img; *outImage = img;
return true; return true;
} }
@@ -915,8 +921,8 @@ bool PCXHandler::write(const QImage &image)
header.YMin = 0; header.YMin = 0;
header.XMax = w - 1; header.XMax = w - 1;
header.YMax = h - 1; header.YMax = h - 1;
header.HDpi = qRound(image.dotsPerMeterX() * 25.4 / 1000); header.HDpi = qRoundOrZero_T<quint16>(dppm2dpi(image.dotsPerMeterX()));
header.YDpi = qRound(image.dotsPerMeterY() * 25.4 / 1000); header.YDpi = qRoundOrZero_T<quint16>(dppm2dpi(image.dotsPerMeterY()));
header.Reserved = 0; header.Reserved = 0;
header.PaletteInfo = 1; header.PaletteInfo = 1;

View File

@@ -596,17 +596,21 @@ static bool setResolution(QImage &img, const PSDImageResourceSection &irs)
s >> i32; // Horizontal resolution in pixels per inch. s >> i32; // Horizontal resolution in pixels per inch.
if (i32 <= 0) if (i32 <= 0)
return false; return false;
auto hres = fixedPointToDouble(i32); auto hres = dpi2ppm(fixedPointToDouble(i32));
s.skipRawData(4); // Display data (not used here) s.skipRawData(4); // Display data (not used here)
s >> i32; // Vertial resolution in pixels per inch. s >> i32; // Vertial resolution in pixels per inch.
if (i32 <= 0) if (i32 <= 0)
return false; return false;
auto vres = fixedPointToDouble(i32); auto vres = dpi2ppm(fixedPointToDouble(i32));
img.setDotsPerMeterX(hres * 1000 / 25.4); if (hres > 0) {
img.setDotsPerMeterY(vres * 1000 / 25.4); img.setDotsPerMeterX(hres);
}
if (vres > 0) {
img.setDotsPerMeterY(vres);
}
return true; return true;
} }

View File

@@ -216,7 +216,7 @@ public:
auto v = QString::fromLatin1(pchar_t(res.data()), res.size()).toDouble(&ok); auto v = QString::fromLatin1(pchar_t(res.data()), res.size()).toDouble(&ok);
if (ok && v > 0) { if (ok && v > 0) {
if (m_pb._unitsOfMeasurement) { // Inches if (m_pb._unitsOfMeasurement) { // Inches
return qRoundOrZero(width() / v / 25.4 * 1000); return dpi2ppm(width() / v);
} }
// Millimeters // Millimeters
return qRoundOrZero(width() / v * 1000); return qRoundOrZero(width() / v * 1000);
@@ -230,7 +230,7 @@ public:
auto v = QString::fromLatin1(pchar_t(res.data()), res.size()).toDouble(&ok); auto v = QString::fromLatin1(pchar_t(res.data()), res.size()).toDouble(&ok);
if (ok && v > 0) { if (ok && v > 0) {
if (m_pb._unitsOfMeasurement) { // Inches if (m_pb._unitsOfMeasurement) { // Inches
return qRoundOrZero(height() / v / 25.4 * 1000); return dpi2ppm(height() / v);
} }
// Millimeters // Millimeters
return qRoundOrZero(height() / v * 1000); return qRoundOrZero(height() / v * 1000);

View File

@@ -66,13 +66,70 @@ inline QImage imageAlloc(qint32 width, qint32 height, const QImage::Format &form
return imageAlloc(QSize(width, height), format); return imageAlloc(QSize(width, height), format);
} }
inline double qRoundOrZero(double d) template<class TI, class SF> // 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. // checks for undefined behavior
if (d > std::numeric_limits<int>::max()) { if (qIsNaN(d) || qIsInf(d) || d < SF() || d > SF(std::numeric_limits<TI>::max())) {
if (ok) {
*ok = false;
}
return 0; return 0;
} }
if (ok) {
*ok = true;
}
return qRound(d); return qRound(d);
} }
inline qint32 qRoundOrZero(double d, bool *ok = nullptr)
{
return qRoundOrZero_T<qint32>(d, ok);
}
inline qint32 qRoundOrZero(float d, bool *ok = nullptr)
{
return qRoundOrZero_T<qint32>(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<class TF, class SI> // 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<double>(ppm, ok);
}
inline float fppm2dpi(qint32 ppm, bool *ok = nullptr)
{
return ppm2dpi_T<float>(ppm, ok);
}
#endif // UTIL_P_H #endif // UTIL_P_H

View File

@@ -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_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) #define DISABLE_IMAGE_PROFILE_CONV // default uncommented (comment to use the conversion as intended by Martin)
const float INCHESPERMETER = (100.0f / 2.54f);
namespace namespace
{ {
struct RandomTable { struct RandomTable {
@@ -2722,17 +2720,13 @@ bool XCFImageFormat::initializeImage(XCFImage &xcf_image)
} }
#endif #endif
if (xcf_image.x_resolution > 0 && xcf_image.y_resolution > 0) { const qint32 dpmx = dpi2ppm(xcf_image.x_resolution);
const float dpmx = xcf_image.x_resolution * INCHESPERMETER; if (dpmx > 0) {
if (dpmx > float(std::numeric_limits<int>::max())) { image.setDotsPerMeterX(dpmx);
return false; }
} const qint32 dpmy = dpi2ppm(xcf_image.y_resolution);
const float dpmy = xcf_image.y_resolution * INCHESPERMETER; if (dpmy > 0) {
if (dpmy > float(std::numeric_limits<int>::max())) { image.setDotsPerMeterY(dpmy);
return false;
}
image.setDotsPerMeterX((int)dpmx);
image.setDotsPerMeterY((int)dpmy);
} }
return true; return true;
} }