简体   繁体   中英

QT QImageReader setScaledSize and setAutoTransform interaction

I would like to read and display thumbnail-sized versions of digital camera pictures. I currently use QImageReader which has the 2 features I need, but they seem to interact non-optimally...:

I want to load and display the image with a width of 100 pixels after rotation based on EXIF properties. However, what happens is this:

code:

QImageReader imageReader(filepath);
auto origSize1 = imageReader.size();
imageReader.setAutoTransform(true);
auto origSize2 = imageReader.size();
auto scaledSize1 = origSize1.scaled(QSize(100, 1000), Qt::KeepAspectRatio);
auto scaledSize2 = origSize2.scaled(QSize(100, 1000), Qt::KeepAspectRatio);
imageReader.setScaledSize(scaledSize2);
auto qimage = imageReader.read();
auto imageSize = qimage.size();
auto qimageScaled = qimage.scaledToWidth(100, Qt::SmoothTransformation);
auto scaledSize3 = qimageScaled.size();

std::cout << "  origSize1 = (" << origSize1.width() << ", " << origSize1.height() << ")" << std::endl;
std::cout << "  origSize2 = (" << origSize2.width() << ", " << origSize2.height() << ")" << std::endl;
std::cout << "scaledSize1 = (" << scaledSize1.width() << ", " << scaledSize1.height() << ")" << std::endl;
std::cout << "scaledSize2 = (" << scaledSize2.width() << ", " << scaledSize2.height() << ")" << std::endl;
std::cout << "  imageSize = (" << imageSize.width() << ", " << imageSize.height() << ")" << std::endl;
std::cout << "scaledSize3 = (" << scaledSize3.width() << ", " << scaledSize3.height() << ")" << std::endl;

output:

  origSize1 = (4896, 3672)
  origSize2 = (4896, 3672)
scaledSize1 = (100, 75)
scaledSize2 = (100, 75)
  imageSize = (75, 100)
scaledSize3 = (100, 134)

So, the image is read with 100 pixels width in landscape mode, then the auto-rotate is applied, resulting in a portrait-mode image of only 75 pixels wide and 100 pixels high. The additional scaledToWidth() call takes care of making the image the correct size, but the quality is very bad due to the x1.34 zooming.

It seems I could call setScaledSize with double (or triple, or ...) the resolution I need, to get enough quality and then relying on the additional scaledToWidth() call to get the correct final width.

A better approach seems to be to use the QImageReader::transformation() info and use that to swap the width/height in the size object passed to setScaledSize:

revised code:

QImageReader imageReader(filepath);
auto origSize1 = imageReader.size();
imageReader.setAutoTransform(true);
auto transformation = imageReader.transformation();
auto swapWH = transformation.testFlag(QImageIOHandler::TransformationRotate90);
auto swappedSize = swapWH ? origSize1.transposed() : origSize1;
auto scaledSwappedSize = swappedSize.scaled(QSize(100, 1000), Qt::KeepAspectRatio);
imageReader.setScaledSize(scaledSwappedSize);
auto qimage = imageReader.read();
auto imageSize = qimage.size();
auto qimageScaled = qimage.scaledToWidth(100, Qt::SmoothTransformation);
auto scaledSize3 = qimageScaled.size();

output with this revised code:

  origSize1 = (4896, 3672)
transformation = 7
swap width/height? = 1
swappedSize = (3672, 4896)
scaledSwapp = (100, 133)
  imageSize = (133, 100)
scaledSize3 = (100, 76)

As you can see, I still end up with a landscape-type image. The content is in portrait mode, but stretched out horizontally (making everyone fat). So, the resolution of 100x133 is OK, but I need to supply 133x100 to setScaledSize() to get "normal" results:

QImageReader imageReader(filepath);
auto origSize1 = imageReader.size();
imageReader.setAutoTransform(true);
auto transformation = imageReader.transformation();
auto swapWH = transformation.testFlag(QImageIOHandler::TransformationRotate90);
auto swappedSize = swapWH ? origSize1.transposed() : origSize1;
auto scaledSwappedSize = swappedSize.scaled(QSize(100, 1000), Qt::KeepAspectRatio);
auto swappedScaledSwappedSize = swapWH ? scaledSwappedSize.transposed() : scaledSwappedSize;
imageReader.setScaledSize(swappedScaledSwappedSize);
auto qimage = imageReader.read();
auto imageSize = qimage.size();
auto qimageScaled = qimage.scaledToWidth(100, Qt::SmoothTransformation);
auto scaledSize3 = qimageScaled.size();

Now I get "correct" results (note that imagesize == scaledSize3) :

  origSize1 = (4896, 3672)
transformation = 7
swap width/height? = 1
swappedSize = (3672, 4896)
scaledSwapp = (100, 133)
swpSclSwapp = (133, 100)
  imageSize = (100, 133)
scaledSize3 = (100, 133)

So, I get this to work, but I feel like I'm doing way too much round-about code. Is this the expected behaviour? Are there simpler ways of getting this result?

AFAIK there is no built-in way to do it (see below why).

The problem I see with your current approach is that it is over complicate when trying to use the built-in scaling feature. Instead, I'd suggest you to use something easier to read, like:

QImageReader imageReader(filepath);
imageReader.setAutoTransform(true);
const auto qimage = imageReader.read();
const auto qscaledImage = qimage.scaledToWidth(100, Qt::SmoothTransformation);

(See at the bottom for one counter-case)


Explanation

Reading the source code of QImageReader::read ( <qt_src_dir>/src/gui/image/qimagereader.cpp ), the method first reads the image, then scales it, and then applies the transformation:

bool QImageReader::read(QImage *image)
{
    // [...]

    // read the image
    if (!d->handler->read(image)) {
        d->imageReaderError = InvalidDataError;
        d->errorString = QImageReader::tr("Unable to read image data");
        return false;
    }

    // provide default implementations for any unsupported image
    // options
    if (d->handler->supportsOption(QImageIOHandler::ClipRect) && !d->clipRect.isNull()) {
        if (d->handler->supportsOption(QImageIOHandler::ScaledSize) && d->scaledSize.isValid()) {

        // [... more lines about scaling ...]

        }
    }

    // [...]
    if (autoTransform())
        qt_imageTransform(*image, transformation());

    return true;
}

Also, reading the documentation of QImageIOHandler::ImageOption::ScaledSize ( QImageIOHandler is used by QImageReader to actually read the image data) you see:

A handler that supports this option is expected to scale the image to the provided size (a QSize), after applying any clip rect transformation (ClipRect). If the handler does not support this option, QImageReader will perform the scaling after the image has been read.

Therefore, the scale is always applied either by the handler or reader before the transformation.


Conclusions

Based on the points mentioned above, you have to either use the EXIF data to provide a correct scaled size, or scale the image after it has been read (which it is easier to read). So, unless you are reading thousand of very large images and the pre-scaling operation speeds up the transformation significatively, I suggest you to keep with a more maintainable code.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM