Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. How to retrieve original DPI attributes from an image file?
Qt 6.11 is out! See what's new in the release blog

How to retrieve original DPI attributes from an image file?

Scheduled Pinned Locked Moved Solved General and Desktop
4 Posts 2 Posters 1.7k Views 2 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • N Offline
    N Offline
    nen777w
    wrote on last edited by nen777w
    #1

    I can edit image file attributes in Photoshop or for example in FastStone Image Viewer and set any DPI value.

    But when I load the image file to QImage.

    QImage img;
    img.load(<path to image file>);
    

    How to get this data?

    Functions like QImage::devicePixelRatioX/Y() is not help.

    1 Reply Last reply
    0
    • N Offline
      N Offline
      nen777w
      wrote on last edited by nen777w
      #2

      I solved the problem (made patch in Qt library witch is extend Qt-features).
      If anybody needs the same, let me know.

      1 Reply Last reply
      0
      • SGaistS Offline
        SGaistS Offline
        SGaist
        Lifetime Qt Champion
        wrote on last edited by
        #3

        Hi,

        What changed did you make ?

        Interested in AI ? www.idiap.ch
        Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

        1 Reply Last reply
        0
        • N Offline
          N Offline
          nen777w
          wrote on last edited by nen777w
          #4

          Hi

          This patches is not pretend on a complete solution, but...
          I made some investigation regarding the problem described above, and found that for some image formats Qt really using values from the image metadata/Exif but for the some of that not.
          This is why I add new essences (QImage::get/setOriginalDPIX/Y ()) because it's confusing, and I do not want to break some compatibilities (if it exists).
          So.. here are the patches:

          qjpeghandler.cpp

          --- qtbase/src/plugins/imageformats/jpeg/qjpeghandler.cpp	2019-12-07 06:27:08.000000000 +0200
          +++ qtbase/src/plugins/imageformats/jpeg/qjpeghandler.cpp	2020-02-16 09:11:31.533186500 +0200
          @@ -46,20 +46,21 @@
           #include <qvariant.h>
           #include <qvector.h>
           #include <qbuffer.h>
           #include <qmath.h>
           #include <private/qicc_p.h>
           #include <private/qsimd_p.h>
           #include <private/qimage_p.h>   // for qt_getImageText
           
           #include <stdio.h>      // jpeglib needs this to be pre-included
           #include <setjmp.h>
          +#include <optional>
           
           #ifdef FAR
           #undef FAR
           #endif
           
           // including jpeglib.h seems to be a little messy
           extern "C" {
           // jpeglib.h->jmorecfg.h tries to typedef int boolean; but this conflicts with
           // some Windows headers that may or may not have been included
           #ifdef HAVE_BOOLEAN
          @@ -721,39 +722,40 @@
           {
           public:
               enum State {
                   Ready,
                   ReadHeader,
                   ReadingEnd,
                   Error
               };
           
               QJpegHandlerPrivate(QJpegHandler *qq)
          -        : quality(75), transformation(QImageIOHandler::TransformationNone), iod_src(0),
          +        : quality(75), transformation(QImageIOHandler::TransformationNone), DPIX(96.), DPIY(96.), iod_src(0),
                     rgb888ToRgb32ConverterPtr(qt_convert_rgb888_to_rgb32), state(Ready), optimize(false), progressive(false), q(qq)
               {}
           
               ~QJpegHandlerPrivate()
               {
                   if(iod_src)
                   {
                       jpeg_destroy_decompress(&info);
                       delete iod_src;
                       iod_src = 0;
                   }
               }
           
               bool readJpegHeader(QIODevice*);
               bool read(QImage *image);
           
               int quality;
               QImageIOHandler::Transformations transformation;
          +    qreal DPIX, DPIY;
               QVariant size;
               QImage::Format format;
               QSize scaledSize;
               QRect scaledClipRect;
               QRect clipRect;
               QString description;
               QStringList readTexts;
               QByteArray iccProfile;
           
               struct jpeg_decompress_struct info;
          @@ -772,112 +774,183 @@
           
           static bool readExifHeader(QDataStream &stream)
           {
               char prefix[6];
               if (stream.readRawData(prefix, sizeof(prefix)) != sizeof(prefix))
                   return false;
               static const char exifMagic[6] = {'E', 'x', 'i', 'f', 0, 0};
               return memcmp(prefix, exifMagic, 6) == 0;
           }
           
          +const quint16 exif_tag_Orientattion = 0x0112;
          +const quint16 exif_tag_XResolution = 0x011A;
          +const quint16 exif_tag_YResolution = 0x011B;
          +
          +struct pair_quint32
          +{
          +    quint32 nominator, denominator;
          +};
          +
          +union exif_values {
          +    quint16 uint16;
          +    pair_quint32 pair_uint32;
          +};
          +
          +/*
          +    Validate Exif tag
          +*/
          +static bool validateTag(quint16 req_tag, quint32 req_components, exif_values value)
          +{
          +    switch (req_tag) {
          +    case exif_tag_Orientattion: {
          +        if (req_components != 1)
          +            return false;
          +        if (value.uint16 < 1 || value.uint16 > 8) // check for valid range
          +            return false;
          +
          +        return true;
          +    } break;
          +    case exif_tag_XResolution:
          +    case exif_tag_YResolution: {
          +        if (req_components != 1)
          +            return false;
          +
          +        return true;
          +    } break;
          +    }
          +
          +    return false;
          +}
          +
           /*
            * Returns -1 on error
            * Returns 0 if no Exif orientation was found
            * Returns 1 orientation is horizontal (normal)
            * Returns 2 mirror horizontal
            * Returns 3 rotate 180
            * Returns 4 mirror vertical
            * Returns 5 mirror horizontal and rotate 270 CCW
            * Returns 6 rotate 90 CW
            * Returns 7 mirror horizontal and rotate 90 CW
            * Returns 8 rotate 270 CW
            */
          -static int getExifOrientation(QByteArray &exifData)
          +static std::optional<exif_values> getExifTag(quint16 req_tag, QByteArray &exifData)
           {
               // Current EXIF version (2.3) says there can be at most 5 IFDs,
               // byte we allow for 10 so we're able to deal with future extensions.
               const int maxIfdCount = 10;
           
               QDataStream stream(&exifData, QIODevice::ReadOnly);
           
               if (!readExifHeader(stream))
          -        return -1;
          +        return std::nullopt;
           
               quint16 val;
               quint32 offset;
          -    const qint64 headerStart = 6;   // the EXIF header has a constant size
          +    const qint64 headerStart = 6; // the EXIF header has a constant size
               Q_ASSERT(headerStart == stream.device()->pos());
           
               // read byte order marker
               stream >> val;
               if (val == 0x4949) // 'II' == Intel
                   stream.setByteOrder(QDataStream::LittleEndian);
               else if (val == 0x4d4d) // 'MM' == Motorola
                   stream.setByteOrder(QDataStream::BigEndian);
               else
          -        return -1; // unknown byte order
          +        return std::nullopt; // unknown byte order
           
               // confirm byte order
               stream >> val;
               if (val != 0x2a)
          -        return -1;
          +        return std::nullopt;
           
               stream >> offset;
           
               // read IFD
               for (int n = 0; n < maxIfdCount; ++n) {
                   quint16 numEntries;
           
                   const qint64 bytesToSkip = offset - (stream.device()->pos() - headerStart);
                   if (bytesToSkip < 0 || (offset + headerStart >= exifData.size())) {
                       // disallow going backwards, though it's permitted in the spec
          -            return -1;
          +            return std::nullopt;
                   } else if (bytesToSkip != 0) {
                       // seek to the IFD
                       if (!stream.device()->seek(offset + headerStart))
          -                return -1;
          +                return std::nullopt;
                   }
           
                   stream >> numEntries;
           
          +        enum { exif_record_skip = sizeof(quint16) * 3 + sizeof(quint32) };
          +
                   for (; numEntries > 0 && stream.status() == QDataStream::Ok; --numEntries) {
                       quint16 tag;
          -            quint16 type;
          -            quint32 components;
          -            quint16 value;
          -            quint16 dummy;
          -
          -            stream >> tag >> type >> components >> value >> dummy;
          -            if (tag == 0x0112) { // Tag Exif.Image.Orientation
          -                if (components != 1)
          -                    return -1;
          -                if (type != 3) // we are expecting it to be an unsigned short
          -                    return -1;
          -                if (value < 1 || value > 8) // check for valid range
          -                    return -1;
          -
          -                // It is possible to include the orientation multiple times.
          -                // Right now the first value is returned.
          -                return value;
          -            }
          -        }
          +
          +            // read tag
          +            stream >> tag;
          +            if (req_tag != tag) {
          +                stream.skipRawData(exif_record_skip);
          +            } else {
          +                quint16 type;
          +                // read type
          +                stream >> type;
          +
          +                // currently support( type 3(quint16) ,5(quint32, quint32))
          +                switch (type) {
          +                // quint16
          +                case 3: {
          +                    quint32 components;
          +                    exif_values value;
          +                    quint16 dummy;
          +
          +                    stream >> components >> value.uint16 >> dummy;
          +
          +                    if (!validateTag(req_tag, components, value))
          +                        return std::nullopt;
          +
          +                    return std::make_optional<exif_values>(value);
          +                } break;
          +                // (quint32, quint32)
          +                case 5: {
          +                    quint32 components;
          +                    quint32 offset;
          +
          +                    stream >> components >> offset;
          +
          +                    exif_values value;
          +                    //+6 = Exif\0\0
          +                    stream.skipRawData(offset - stream.device()->pos() + 6);
          +                    stream >> value.pair_uint32.nominator >> value.pair_uint32.denominator;
          +
          +                    if (!validateTag(req_tag, components, value))
          +                        return std::nullopt;
          +
          +                    return std::make_optional<exif_values>(value);
          +                } break;
          +                default: {
          +                    stream.skipRawData(exif_record_skip - sizeof(quint16));
          +                } break;
          +                } // switch
          +            } // else
          +        } // for
           
                   // read offset to next IFD
                   stream >> offset;
                   if (stream.status() != QDataStream::Ok)
          -            return -1;
          +            return std::nullopt;
                   if (offset == 0) // this is the last IFD
          -            return 0;   // No Exif orientation was found
          +            return std::nullopt; // No Exif orientation was found
               }
           
               // too many IFDs
          -    return -1;
          +    return std::nullopt;
           }
           
           static QImageIOHandler::Transformations exif2Qt(int exifOrientation)
           {
               switch (exifOrientation) {
               case 1: // normal
                   return QImageIOHandler::TransformationNone;
               case 2: // mirror horizontal
                   return QImageIOHandler::TransformationMirror;
               case 3: // rotate 180
          @@ -951,24 +1024,53 @@
                           } else if (marker->marker == JPEG_APP0 + 1) {
                               exifData.append((const char*)marker->data, marker->data_length);
                           } else if (marker->marker == JPEG_APP0 + 2) {
                               if (marker->data_length > 128 + 4 + 14 && strcmp((const char *)marker->data, "ICC_PROFILE") == 0) {
                                   iccProfile.append((const char*)marker->data + 14, marker->data_length - 14);
                               }
                           }
                       }
           
                       if (!exifData.isEmpty()) {
          -                // Exif data present
          -                int exifOrientation = getExifOrientation(exifData);
          -                if (exifOrientation > 0)
          -                    transformation = exif2Qt(exifOrientation);
          +                /*
          +                 * Returns -1 on error
          +                 * Returns 0 if no Exif orientation was found
          +                 * Returns 1 orientation is horizontal (normal)
          +                 * Returns 2 mirror horizontal
          +                 * Returns 3 rotate 180
          +                 * Returns 4 mirror vertical
          +                 * Returns 5 mirror horizontal and rotate 270 CCW
          +                 * Returns 6 rotate 90 CW
          +                 * Returns 7 mirror horizontal and rotate 90 CW
          +                 * Returns 8 rotate 270 CW
          +                 */
          +                std::optional<exif_values> exifOrientation =
          +                        getExifTag(exif_tag_Orientattion, exifData);
          +                if (exifOrientation)
          +                    transformation = exif2Qt(exifOrientation->uint16);
          +
          +                std::optional<exif_values> exifXResolution =
          +                        getExifTag(exif_tag_XResolution, exifData);
          +                std::optional<exif_values> exifYResolution =
          +                        getExifTag(exif_tag_YResolution, exifData);
          +
          +                if (exifXResolution && exifYResolution) {
          +                    DPIX = exifXResolution->pair_uint32.nominator
          +                            / (qreal)exifXResolution->pair_uint32.denominator;
          +                    DPIY = exifYResolution->pair_uint32.nominator
          +                            / (qreal)exifYResolution->pair_uint32.denominator;
          +                }
          +            } 
          +            else 
          +            {
          +                DPIX = info.X_density;
          +                DPIY = info.Y_density;
                       }
           
                       state = ReadHeader;
                       return true;
                   }
                   else
                   {
                       return false;
                   }
               }
          @@ -985,20 +1087,23 @@
               if(state == ReadHeader)
               {
                   bool success = read_jpeg_image(image, scaledSize, scaledClipRect, clipRect, quality, rgb888ToRgb32ConverterPtr, &info, &err);
                   if (success) {
                       for (int i = 0; i < readTexts.size()-1; i+=2)
                           image->setText(readTexts.at(i), readTexts.at(i+1));
           
                       if (!iccProfile.isEmpty())
                           image->setColorSpace(QColorSpace::fromIccProfile(iccProfile));
           
          +            image->setOriginalDPIX(DPIX);
          +            image->setOriginalDPIY(DPIY);
          +
                       state = ReadingEnd;
                       return true;
                   }
           
                   state = Error;
               }
           
               return false;
           }
          
          

          qbmphandler.cpp

          --- qtbase/src/gui/image/qbmphandler.cpp	2019-12-07 06:27:08.000000000 +0200
          +++ qtbase/src/gui/image/qbmphandler.cpp	2020-02-12 19:47:59.316533900 +0200
          @@ -740,20 +740,23 @@
               // Intel byte order
               s.setByteOrder(QDataStream::LittleEndian);
           
               // read image
               const bool readSuccess = m_format == BmpFormat ?
                   read_dib_body(s, infoHeader, fileHeader.bfOffBits, startpos, *image) :
                   read_dib_body(s, infoHeader, -1, startpos - BMP_FILEHDR_SIZE, *image);
               if (!readSuccess)
                   return false;
           
          +    image->setOriginalDPIX(infoHeader.biXPelsPerMeter * 0.0254);
          +    image->setOriginalDPIY(infoHeader.biYPelsPerMeter * 0.0254);
          +
               state = Ready;
               return true;
           }
           
           bool QBmpHandler::write(const QImage &img)
           {
               QImage image;
               switch (img.format()) {
               case QImage::Format_Mono:
               case QImage::Format_Indexed8:
          
          

          qpnghandler.cpp

          --- qtbase/src/gui/image/qpnghandler.cpp	2019-12-07 06:27:08.000000000 +0200
          +++ qtbase/src/gui/image/qpnghandler.cpp	2020-02-12 21:33:13.707239900 +0200
          @@ -713,20 +713,22 @@
                   amp.row_pointers = new png_bytep[height];
           
                   for (uint y = 0; y < height; y++)
                       amp.row_pointers[y] = data + y * bpl;
           
                   png_read_image(png_ptr, amp.row_pointers);
                   amp.deallocate();
           
                   outImage->setDotsPerMeterX(png_get_x_pixels_per_meter(png_ptr,info_ptr));
                   outImage->setDotsPerMeterY(png_get_y_pixels_per_meter(png_ptr,info_ptr));
          +        outImage->setOriginalDPIX(png_get_x_pixels_per_meter(png_ptr, info_ptr) * 0.0254);
          +        outImage->setOriginalDPIY(png_get_y_pixels_per_meter(png_ptr, info_ptr) * 0.0254);
           
                   if (unit_type == PNG_OFFSET_PIXEL)
                       outImage->setOffset(QPoint(offset_x, offset_y));
           
                   // sanity check palette entries
                   if (color_type == PNG_COLOR_TYPE_PALETTE && outImage->format() == QImage::Format_Indexed8) {
                       int color_table_size = outImage->colorCount();
                       for (int y=0; y<(int)height; ++y) {
                           uchar *p = FAST_SCAN_LINE(data, bpl, y);
                           uchar *end = p + width;
          
          

          qtiffhandler.cpp

          --- qtimageformats/src/plugins/imageformats/tiff/qtiffhandler.cpp	2019-11-19 14:47:50.000000000 +0200
          +++ qtimageformats/src/plugins/imageformats/tiff/qtiffhandler.cpp	2020-02-12 23:08:13.882787800 +0200
          @@ -470,24 +470,28 @@
               if (!TIFFGetField(tiff, TIFFTAG_RESOLUTIONUNIT, &resUnit))
                   resUnit = RESUNIT_INCH;
           
               if (TIFFGetField(tiff, TIFFTAG_XRESOLUTION, &resX)
                   && TIFFGetField(tiff, TIFFTAG_YRESOLUTION, &resY)) {
           
                   switch(resUnit) {
                   case RESUNIT_CENTIMETER:
                       image->setDotsPerMeterX(qRound(resX * 100));
                       image->setDotsPerMeterY(qRound(resY * 100));
          +            image->setOriginalDPIX((resX * 100) * 0.254);
          +            image->setOriginalDPIY((resY * 100) * 0.254);
                       break;
                   case RESUNIT_INCH:
                       image->setDotsPerMeterX(qRound(resX * (100 / 2.54)));
                       image->setDotsPerMeterY(qRound(resY * (100 / 2.54)));
          +            image->setOriginalDPIX(resX);
          +            image->setOriginalDPIY(resY);
                       break;
                   default:
                       // do nothing as defaults have already
                       // been set within the QImage class
                       break;
                   }
               }
           
               uint32 count;
               void *profile;
          
          

          qimage_p.h

          --- qtbase/src/gui/image/qimage_p.h	2019-12-07 06:27:08.000000000 +0200
          +++ qtbase/src/gui/image/qimage_p.h	2020-02-12 15:44:52.016150000 +0200
          @@ -78,20 +78,22 @@
               qreal devicePixelRatio;
               QVector<QRgb> colortable;
               uchar *data;
               QImage::Format format;
               qsizetype bytes_per_line;
               int ser_no;               // serial number
               int detach_no;
           
               qreal  dpmx;                // dots per meter X (or 0)
               qreal  dpmy;                // dots per meter Y (or 0)
          +    qreal  odpix;
          +    qreal  odpiy;
               QPoint  offset;           // offset in pixels
           
               uint own_data : 1;
               uint ro_data : 1;
               uint has_alpha_clut : 1;
               uint is_cached : 1;
               uint is_locked : 1;
           
               QImageCleanupFunction cleanupFunction;
               void* cleanupInfo;
          
          

          qimage.h

          --- qtbase/src/gui/image/qimage.h	2019-12-07 06:27:08.000000000 +0200
          +++ qtbase/src/gui/image/qimage.h	2020-02-12 23:08:13.886784700 +0200
          @@ -326,20 +326,22 @@
           
               QPaintEngine *paintEngine() const override;
           
               // Auxiliary data
               int dotsPerMeterX() const;
               int dotsPerMeterY() const;
               void setDotsPerMeterX(int);
               void setDotsPerMeterY(int);
               QPoint offset() const;
               void setOffset(const QPoint&);
          +    qreal originalDPIX() const;
          +    qreal originalDPIY() const;
           
               QStringList textKeys() const;
               QString text(const QString &key = QString()) const;
               void setText(const QString &key, const QString &value);
           
               QPixelFormat pixelFormat() const noexcept;
               static QPixelFormat toPixelFormat(QImage::Format format) noexcept;
               static QImage::Format toImageFormat(QPixelFormat format) noexcept;
           
               // Platform specific conversion functions
          @@ -365,20 +367,29 @@
               virtual int metric(PaintDeviceMetric metric) const override;
               QImage mirrored_helper(bool horizontal, bool vertical) const;
               QImage rgbSwapped_helper() const;
               void mirrored_inplace(bool horizontal, bool vertical);
               void rgbSwapped_inplace();
               QImage convertToFormat_helper(Format format, Qt::ImageConversionFlags flags) const;
               bool convertToFormat_inplace(Format format, Qt::ImageConversionFlags flags);
               QImage smoothScaled(int w, int h) const;
           
           private:
          +    friend class QJpegHandlerPrivate;
          +    friend class QBmpHandler;
          +    friend class QPngHandlerPrivate;
          +    friend class QTiffHandler;
          +
          +    void setOriginalDPIX(qreal);
          +    void setOriginalDPIY(qreal);
          +
          +private:
               friend class QWSOnScreenSurface;
               QImageData *d;
           
               friend class QRasterPlatformPixmap;
               friend class QBlittablePlatformPixmap;
               friend class QPixmapCacheEntry;
           
           public:
               typedef QImageData * DataPtr;
               inline DataPtr &data_ptr() { return d; }
          
          

          qimage.cpp

          --- qtbase/src/gui/image/qimage.cpp	2019-12-07 06:27:08.000000000 +0200
          +++ qtbase/src/gui/image/qimage.cpp	2020-02-12 15:49:10.283828100 +0200
          @@ -98,20 +98,22 @@
               return 1 + serial.fetchAndAddRelaxed(1);
           }
           
           QImageData::QImageData()
               : ref(0), width(0), height(0), depth(0), nbytes(0), devicePixelRatio(1.0), data(0),
                 format(QImage::Format_ARGB32), bytes_per_line(0),
                 ser_no(next_qimage_serial_number()),
                 detach_no(0),
                 dpmx(qt_defaultDpiX() * 100 / qreal(2.54)),
                 dpmy(qt_defaultDpiY() * 100 / qreal(2.54)),
          +      odpix(-1),
          +      odpiy(-1),
                 offset(0, 0), own_data(true), ro_data(false), has_alpha_clut(false),
                 is_cached(false), is_locked(false), cleanupFunction(0), cleanupInfo(0),
                 paintEngine(0)
           {
           }
           
           /*! \fn QImageData * QImageData::create(const QSize &size, QImage::Format format)
           
               \internal
           
          @@ -3944,20 +3946,42 @@
           void QImage::setDotsPerMeterY(int y)
           {
               if (!d || !y)
                   return;
               detach();
           
               if (d)
                   d->dpmy = y;
           }
           
          +qreal QImage::originalDPIX() const
          +{
          +    return d->odpix;
          +}
          +
          +qreal QImage::originalDPIY() const
          +{
          +    return d->odpiy;
          +}
          +
          +void QImage::setOriginalDPIX(qreal x)
          +{
          +    if (d)
          +        d->odpix = x;
          +}
          +
          +void QImage::setOriginalDPIY(qreal y)
          +{
          +    if (d)
          +        d->odpiy = y;
          +}
          +
           /*!
               \fn QPoint QImage::offset() const
           
               Returns the number of pixels by which the image is intended to be
               offset by when positioning relative to other images.
           
               \sa setOffset(), {QImage#Image Information}{Image Information}
           */
           QPoint QImage::offset() const
           {
          
          
          1 Reply Last reply
          0

          • Login

          • Login or register to search.
          • First post
            Last post
          0
          • Categories
          • Recent
          • Tags
          • Popular
          • Users
          • Groups
          • Search
          • Get Qt Extensions
          • Unsolved