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?
Forum Updated to NodeBB v4.3 + New Features

How to retrieve original DPI attributes from an image file?

Scheduled Pinned Locked Moved Solved General and Desktop
4 Posts 2 Posters 715 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