Please nominate your Qt Champions for 2021! https://forum.qt.io/topic/132134/looking-for-the-2021-qt-champions

How to retrieve original DPI attributes from an image file?



  • 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.



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



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


  • Lifetime Qt Champion

    Hi,

    What changed did you make ?



  • 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
     {
    
    

Log in to reply