Revision control

Copy as Markdown

// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:critical reason:data-parser
#include <QGuiApplication>
#include <QBuffer>
#include <QImageIOHandler>
#include <QImageReader>
#include <QImage>
#include <QtCore/qloggingcategory.h>
#include <QtCore/qnumeric.h>
#include <limits>
#include <QtGui/private/qcoregraphics_p.h>
#include "qiiofhelpers_p.h"
QT_BEGIN_NAMESPACE
Q_STATIC_LOGGING_CATEGORY(lcImageIO, "qt.imageformats.imageio", QtCriticalMsg)
namespace NS_IIOF_HELPERS {
// Callbacks for sequential data provider & consumer:
static size_t cbGetBytes(void *info, void *buffer, size_t count)
{
QIODevice *dev = static_cast<QIODevice *>(info);
qCDebug(lcImageIO) << "Reading" << count << "bytes from" << dev << "into" << buffer;
if (!dev || !buffer)
return 0;
qint64 res = dev->read(static_cast<char *>(buffer), qint64(count));
return size_t(qMax(qint64(0), res));
}
static off_t cbSkipForward(void *info, off_t count)
{
QIODevice *dev = static_cast<QIODevice *>(info);
qCDebug(lcImageIO) << "Skipping" << dev << "forward" << count << "bytes" ;
if (!dev || count <= 0)
return 0;
qint64 res = 0;
if (!dev->isSequential()) {
const qint64 prevPos = dev->pos();
qint64 newPos;
if (!qAddOverflow(prevPos, qint64(count), &newPos))
dev->seek(newPos);
res = dev->pos() - prevPos;
} else {
char buf[4096];
qint64 remaining = count;
while (remaining > 0) {
const qint64 chunk = dev->read(buf, qMin(remaining, qint64(sizeof(buf))));
if (chunk <= 0)
break;
res += chunk;
remaining -= chunk;
}
}
return qMax(qint64(0), res);
}
static void cbRewind(void *info)
{
QIODevice *dev = static_cast<QIODevice *>(info);
qCDebug(lcImageIO) << "Rewinding" << dev;
// Ignore this; we do not want the Qt device to be rewound after reading the image
}
static size_t cbPutBytes(void *info, const void *buffer, size_t count)
{
QIODevice *dev = static_cast<QIODevice *>(info);
qCDebug(lcImageIO) << "Writing" << count << "bytes from" << buffer << "into" << dev;
if (!dev || !buffer)
return 0;
qint64 res = dev->write(static_cast<const char *>(buffer), qint64(count));
return size_t(qMax(qint64(0), res));
}
static void cbRelease(void *info)
{
qCDebug(lcImageIO) << "Releasing" << info;
}
QImageIOPlugin::Capabilities QIIOFHelpers::systemCapabilities(const QString &uti)
{
QImageIOPlugin::Capabilities res;
QCFString cfUti(uti);
QCFType<CFArrayRef> cfSourceTypes = CGImageSourceCopyTypeIdentifiers();
CFIndex len = CFArrayGetCount(cfSourceTypes);
if (CFArrayContainsValue(cfSourceTypes, CFRangeMake(0, len), cfUti))
res |= QImageIOPlugin::CanRead;
QCFType<CFArrayRef> cfDestTypes = CGImageDestinationCopyTypeIdentifiers();
len = CFArrayGetCount(cfDestTypes);
if (CFArrayContainsValue(cfDestTypes, CFRangeMake(0, len), cfUti))
res |= QImageIOPlugin::CanWrite;
return res;
}
bool QIIOFHelpers::readImage(QImageIOHandler *q_ptr, QImage *out)
{
QIIOFHelper h(q_ptr);
return h.readImage(out);
}
bool QIIOFHelpers::writeImage(QImageIOHandler *q_ptr, const QImage &in, const QString &uti)
{
QIIOFHelper h(q_ptr);
return h.writeImage(in, uti);
}
QIIOFHelper::QIIOFHelper(QImageIOHandler *q)
: q_ptr(q)
{
}
bool QIIOFHelper::initRead()
{
if (cgImageSource)
return true;
if (!q_ptr || !q_ptr->device())
return false;
if (QBuffer *b = qobject_cast<QBuffer *>(q_ptr->device())) {
// do direct access to avoid data copy
const void *rawData = b->data().constData() + b->pos();
cgDataProvider = CGDataProviderCreateWithData(nullptr, rawData, size_t(b->data().size() - b->pos()), nullptr);
} else {
static const CGDataProviderSequentialCallbacks cgCallbacks = {
0, // Version, always 0
&cbGetBytes, &cbSkipForward, &cbRewind, &cbRelease
};
cgDataProvider = CGDataProviderCreateSequential(q_ptr->device(), &cgCallbacks);
}
cgImageSource = CGImageSourceCreateWithDataProvider(cgDataProvider, nullptr);
if (cgImageSource) {
auto primaryIndex = CGImageSourceGetPrimaryImageIndex(cgImageSource);
cfImageDict = CGImageSourceCopyPropertiesAtIndex(cgImageSource, primaryIndex, nullptr);
}
qCInfo(lcImageIO) << "Initialized source" << cgImageSource
<< "with" << CGImageSourceGetCount(cgImageSource) << "images"
<< "and primary index" << CGImageSourceGetPrimaryImageIndex(cgImageSource);
return (cgImageSource);
}
bool QIIOFHelper::readImage(QImage *out)
{
if (!out || !initRead())
return false;
// Native reader cannot use QImageIOHandler::allocateImage() so do manual alloc check here
if (const int mbLimit = QImageReader::allocationLimit()) {
const QSize size = imageProperty(QImageIOHandler::Size).toSize();
if (!size.isEmpty()) {
qsizetype bpl, total;
if (qMulOverflow<qsizetype>(size.width(), 4, &bpl)
|| qMulOverflow<qsizetype>(bpl, size.height(), &total)
|| (total >> 20) > qsizetype(mbLimit)) {
qCWarning(lcImageIO, "QIIOFHelper: rejecting image (%dx%d) as it exceeds the "
"allocation limit of %d MB", size.width(), size.height(), mbLimit);
return false;
}
}
}
auto primaryIndex = CGImageSourceGetPrimaryImageIndex(cgImageSource);
QCFType<CGImageRef> cgImage = CGImageSourceCreateImageAtIndex(cgImageSource, primaryIndex, nullptr);
qCInfo(lcImageIO) << "Read image at index" << primaryIndex
<< "with properties" << cfImageDict << "and got" << cgImage;
if (!cgImage)
return false;
*out = qt_mac_toQImage(cgImage);
if (out->isNull())
return false;
int dpi = 0;
constexpr int maxDpi = int(double(std::numeric_limits<int>::max()) * 0.0254);
if (getIntProperty(kCGImagePropertyDPIWidth, &dpi) && dpi > 0 && dpi <= maxDpi)
out->setDotsPerMeterX(qRound(dpi / 0.0254));
if (getIntProperty(kCGImagePropertyDPIHeight, &dpi) && dpi > 0 && dpi <= maxDpi)
out->setDotsPerMeterY(qRound(dpi / 0.0254));
return true;
}
bool QIIOFHelper::getIntProperty(CFStringRef property, int *value)
{
if (!cfImageDict)
return false;
CFNumberRef cfNumber = static_cast<CFNumberRef>(CFDictionaryGetValue(cfImageDict, property));
if (cfNumber) {
int intVal;
if (CFNumberGetValue(cfNumber, kCFNumberIntType, &intVal)) {
if (value)
*value = intVal;
return true;
}
}
return false;
}
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
return QImageIOHandler::TransformationRotate180;
case 4: // mirror vertical
return QImageIOHandler::TransformationFlip;
case 5: // mirror horizontal and rotate 270 CW
return QImageIOHandler::TransformationFlipAndRotate90;
case 6: // rotate 90 CW
return QImageIOHandler::TransformationRotate90;
case 7: // mirror horizontal and rotate 90 CW
return QImageIOHandler::TransformationMirrorAndRotate90;
case 8: // rotate 270 CW
return QImageIOHandler::TransformationRotate270;
}
return QImageIOHandler::TransformationNone;
}
static int qt2Exif(QImageIOHandler::Transformations transformation)
{
switch (transformation) {
case QImageIOHandler::TransformationNone:
return 1;
case QImageIOHandler::TransformationMirror:
return 2;
case QImageIOHandler::TransformationRotate180:
return 3;
case QImageIOHandler::TransformationFlip:
return 4;
case QImageIOHandler::TransformationFlipAndRotate90:
return 5;
case QImageIOHandler::TransformationRotate90:
return 6;
case QImageIOHandler::TransformationMirrorAndRotate90:
return 7;
case QImageIOHandler::TransformationRotate270:
return 8;
}
qWarning("Invalid Qt image transformation");
return 1;
}
QVariant QIIOFHelper::imageProperty(QImageIOHandler::ImageOption option)
{
if (!initRead())
return QVariant();
switch (option) {
case QImageIOHandler::Size: {
QSize sz;
if (getIntProperty(kCGImagePropertyPixelWidth, &sz.rwidth())
&& getIntProperty(kCGImagePropertyPixelHeight, &sz.rheight())) {
return sz;
}
break;
}
case QImageIOHandler::ImageTransformation: {
int orient;
if (getIntProperty(kCGImagePropertyOrientation, &orient))
return int(exif2Qt(orient));
break;
}
default:
break;
}
return QVariant();
}
void QIIOFHelper::setOption(QImageIOHandler::ImageOption option, const QVariant &value)
{
if (writeOptions.size() < option + 1)
writeOptions.resize(option + 1);
writeOptions[option] = value;
}
bool QIIOFHelper::writeImage(const QImage &in, const QString &uti)
{
static const CGDataConsumerCallbacks cgCallbacks = { &cbPutBytes, nullptr };
if (!q_ptr || !q_ptr->device() || in.isNull())
return false;
QCFType<CGImageRef> cgImage = qt_mac_toCGImage(in);
QCFType<CGDataConsumerRef> cgDataConsumer = CGDataConsumerCreate(q_ptr->device(), &cgCallbacks);
QCFType<CFStringRef> cfUti = uti.toCFString();
QCFType<CGImageDestinationRef> cgImageDest = CGImageDestinationCreateWithDataConsumer(cgDataConsumer, cfUti, 1, nullptr);
if (!cgImageDest || !cgImage)
return false;
QCFType<CFNumberRef> cfQuality = nullptr;
QCFType<CFNumberRef> cfOrientation = nullptr;
const void *dictKeys[2];
const void *dictVals[2];
int dictSize = 0;
if (q_ptr->supportsOption(QImageIOHandler::Quality)) {
bool ok = false;
int writeQuality = writeOptions.value(QImageIOHandler::Quality).toInt(&ok);
// If quality is unset, default to 75%
float quality = (ok && writeQuality >= 0 ? (qMin(writeQuality, 100)) : 75) / 100.0f;
cfQuality = CFNumberCreate(nullptr, kCFNumberFloatType, &quality);
dictKeys[dictSize] = static_cast<const void *>(kCGImageDestinationLossyCompressionQuality);
dictVals[dictSize] = static_cast<const void *>(cfQuality);
dictSize++;
}
if (q_ptr->supportsOption(QImageIOHandler::ImageTransformation)) {
int orient = qt2Exif(static_cast<QImageIOHandler::Transformation>(writeOptions.value(QImageIOHandler::ImageTransformation).toInt()));
cfOrientation = CFNumberCreate(nullptr, kCFNumberIntType, &orient);
dictKeys[dictSize] = static_cast<const void *>(kCGImagePropertyOrientation);
dictVals[dictSize] = static_cast<const void *>(cfOrientation);
dictSize++;
}
QCFType<CFDictionaryRef> cfProps = nullptr;
if (dictSize)
cfProps = CFDictionaryCreate(nullptr, dictKeys, dictVals, dictSize,
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
qCInfo(lcImageIO) << "Writing" << cgImage << "\nwith properties" << cfProps;
CGImageDestinationAddImage(cgImageDest, cgImage, cfProps);
return CGImageDestinationFinalize(cgImageDest);
}
}
QT_END_NAMESPACE