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

@@ -7,6 +7,7 @@
#include "chunks_p.h"
#include "packbits_p.h"
#include "util_p.h"
#include <QBuffer>
#include <QColor>
@@ -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<QRgb> &cmapPalette, qint32 height)
{
Q_UNUSED(height)
auto dt = data().mid(20);
if (compression() == PCHGChunk::Compression::Huffman) {
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")) {
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

View File

@@ -32,7 +32,6 @@
#include <QTemporaryDir>
#include <JXRGlue.h>
#include <cfenv>
#include <cstring>
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;
}

View File

@@ -15,8 +15,6 @@
#include <QStringDecoder>
#include <QTimeZone>
#include <cfenv>
// 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<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)
{
const std::optional<int> hdpm = convertToDotsPerMeter(horizontalResolution());
const std::optional<int> 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

View File

@@ -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<quint16>(dppm2dpi(image.dotsPerMeterX()));
header.YDpi = qRoundOrZero_T<quint16>(dppm2dpi(image.dotsPerMeterY()));
header.Reserved = 0;
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.
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;
}

View File

@@ -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);

View File

@@ -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<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.
if (d > std::numeric_limits<int>::max()) {
// checks for undefined behavior
if (qIsNaN(d) || qIsInf(d) || d < SF() || d > SF(std::numeric_limits<TI>::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<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

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_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<int>::max())) {
return false;
}
const float dpmy = xcf_image.y_resolution * INCHESPERMETER;
if (dpmy > float(std::numeric_limits<int>::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;
}