Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qiiofhelpers.mm
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:critical reason:data-parser
4
5#include <QGuiApplication>
6#include <QBuffer>
7#include <QImageIOHandler>
8#include <QImageReader>
9#include <QImage>
10
11#include <QtCore/qloggingcategory.h>
12#include <QtCore/qnumeric.h>
13
14#include <limits>
15
16#include <QtGui/private/qcoregraphics_p.h>
17
18#include "qiiofhelpers_p.h"
19
20
22
23Q_STATIC_LOGGING_CATEGORY(lcImageIO, "qt.imageformats.imageio", QtCriticalMsg)
24
25namespace NS_IIOF_HELPERS {
26
27// Callbacks for sequential data provider & consumer:
28
29static size_t cbGetBytes(void *info, void *buffer, size_t count)
30{
31 QIODevice *dev = static_cast<QIODevice *>(info);
32 qCDebug(lcImageIO) << "Reading" << count << "bytes from" << dev << "into" << buffer;
33
34 if (!dev || !buffer)
35 return 0;
36 qint64 res = dev->read(static_cast<char *>(buffer), qint64(count));
37 return size_t(qMax(qint64(0), res));
38}
39
40static off_t cbSkipForward(void *info, off_t count)
41{
42 QIODevice *dev = static_cast<QIODevice *>(info);
43 qCDebug(lcImageIO) << "Skipping" << dev << "forward" << count << "bytes" ;
44
45 if (!dev || count <= 0)
46 return 0;
47 qint64 res = 0;
48 if (!dev->isSequential()) {
49 const qint64 prevPos = dev->pos();
50 qint64 newPos;
51 if (!qAddOverflow(prevPos, qint64(count), &newPos))
52 dev->seek(newPos);
53 res = dev->pos() - prevPos;
54 } else {
55 char buf[4096];
56 qint64 remaining = count;
57 while (remaining > 0) {
58 const qint64 chunk = dev->read(buf, qMin(remaining, qint64(sizeof(buf))));
59 if (chunk <= 0)
60 break;
61 res += chunk;
62 remaining -= chunk;
63 }
64 }
65 return qMax(qint64(0), res);
66}
67
68static void cbRewind(void *info)
69{
70 QIODevice *dev = static_cast<QIODevice *>(info);
71 qCDebug(lcImageIO) << "Rewinding" << dev;
72 // Ignore this; we do not want the Qt device to be rewound after reading the image
73}
74
75static size_t cbPutBytes(void *info, const void *buffer, size_t count)
76{
77 QIODevice *dev = static_cast<QIODevice *>(info);
78 qCDebug(lcImageIO) << "Writing" << count << "bytes from" << buffer << "into" << dev;
79
80 if (!dev || !buffer)
81 return 0;
82 qint64 res = dev->write(static_cast<const char *>(buffer), qint64(count));
83 return size_t(qMax(qint64(0), res));
84}
85
86static void cbRelease(void *info)
87{
88 qCDebug(lcImageIO) << "Releasing" << info;
89}
90
91QImageIOPlugin::Capabilities QIIOFHelpers::systemCapabilities(const QString &uti)
92{
93 QImageIOPlugin::Capabilities res;
94 QCFString cfUti(uti);
95
96 QCFType<CFArrayRef> cfSourceTypes = CGImageSourceCopyTypeIdentifiers();
97 CFIndex len = CFArrayGetCount(cfSourceTypes);
98 if (CFArrayContainsValue(cfSourceTypes, CFRangeMake(0, len), cfUti))
99 res |= QImageIOPlugin::CanRead;
100
101 QCFType<CFArrayRef> cfDestTypes = CGImageDestinationCopyTypeIdentifiers();
102 len = CFArrayGetCount(cfDestTypes);
103 if (CFArrayContainsValue(cfDestTypes, CFRangeMake(0, len), cfUti))
104 res |= QImageIOPlugin::CanWrite;
105
106 return res;
107}
108
109bool QIIOFHelpers::readImage(QImageIOHandler *q_ptr, QImage *out)
110{
111 QIIOFHelper h(q_ptr);
112 return h.readImage(out);
113}
114
115bool QIIOFHelpers::writeImage(QImageIOHandler *q_ptr, const QImage &in, const QString &uti)
116{
117 QIIOFHelper h(q_ptr);
118 return h.writeImage(in, uti);
119}
120
121QIIOFHelper::QIIOFHelper(QImageIOHandler *q)
122 : q_ptr(q)
123{
124}
125
127{
128 if (cgImageSource)
129 return true;
130 if (!q_ptr || !q_ptr->device())
131 return false;
132
133 if (QBuffer *b = qobject_cast<QBuffer *>(q_ptr->device())) {
134 // do direct access to avoid data copy
135 const void *rawData = b->data().constData() + b->pos();
136 cgDataProvider = CGDataProviderCreateWithData(nullptr, rawData, size_t(b->data().size() - b->pos()), nullptr);
137 } else {
138 static const CGDataProviderSequentialCallbacks cgCallbacks = {
139 0, // Version, always 0
140 &cbGetBytes, &cbSkipForward, &cbRewind, &cbRelease
141 };
142 cgDataProvider = CGDataProviderCreateSequential(q_ptr->device(), &cgCallbacks);
143 }
144
145 cgImageSource = CGImageSourceCreateWithDataProvider(cgDataProvider, nullptr);
146
147 if (cgImageSource) {
148 auto primaryIndex = CGImageSourceGetPrimaryImageIndex(cgImageSource);
149 cfImageDict = CGImageSourceCopyPropertiesAtIndex(cgImageSource, primaryIndex, nullptr);
150 }
151
152 qCInfo(lcImageIO) << "Initialized source" << cgImageSource
153 << "with" << CGImageSourceGetCount(cgImageSource) << "images"
154 << "and primary index" << CGImageSourceGetPrimaryImageIndex(cgImageSource);
155
156 return (cgImageSource);
157}
158
159bool QIIOFHelper::readImage(QImage *out)
160{
161 if (!out || !initRead())
162 return false;
163
164 // Native reader cannot use QImageIOHandler::allocateImage() so do manual alloc check here
165 if (const int mbLimit = QImageReader::allocationLimit()) {
166 const QSize size = imageProperty(QImageIOHandler::Size).toSize();
167 if (!size.isEmpty()) {
168 qsizetype bpl, total;
169 if (qMulOverflow<qsizetype>(size.width(), 4, &bpl)
170 || qMulOverflow<qsizetype>(bpl, size.height(), &total)
171 || (total >> 20) > qsizetype(mbLimit)) {
172 qCWarning(lcImageIO, "QIIOFHelper: rejecting image (%dx%d) as it exceeds the "
173 "allocation limit of %d MB", size.width(), size.height(), mbLimit);
174 return false;
175 }
176 }
177 }
178
179 auto primaryIndex = CGImageSourceGetPrimaryImageIndex(cgImageSource);
180 QCFType<CGImageRef> cgImage = CGImageSourceCreateImageAtIndex(cgImageSource, primaryIndex, nullptr);
181
182 qCInfo(lcImageIO) << "Read image at index" << primaryIndex
183 << "with properties" << cfImageDict << "and got" << cgImage;
184
185 if (!cgImage)
186 return false;
187
188 *out = qt_mac_toQImage(cgImage);
189 if (out->isNull())
190 return false;
191
192 int dpi = 0;
193 constexpr int maxDpi = int(double(std::numeric_limits<int>::max()) * 0.0254);
194 if (getIntProperty(kCGImagePropertyDPIWidth, &dpi) && dpi > 0 && dpi <= maxDpi)
195 out->setDotsPerMeterX(qRound(dpi / 0.0254));
196 if (getIntProperty(kCGImagePropertyDPIHeight, &dpi) && dpi > 0 && dpi <= maxDpi)
197 out->setDotsPerMeterY(qRound(dpi / 0.0254));
198
199 return true;
200}
201
202bool QIIOFHelper::getIntProperty(CFStringRef property, int *value)
203{
204 if (!cfImageDict)
205 return false;
206
207 CFNumberRef cfNumber = static_cast<CFNumberRef>(CFDictionaryGetValue(cfImageDict, property));
208 if (cfNumber) {
209 int intVal;
210 if (CFNumberGetValue(cfNumber, kCFNumberIntType, &intVal)) {
211 if (value)
212 *value = intVal;
213 return true;
214 }
215 }
216 return false;
217}
218
219static QImageIOHandler::Transformations exif2Qt(int exifOrientation)
220{
221 switch (exifOrientation) {
222 case 1: // normal
223 return QImageIOHandler::TransformationNone;
224 case 2: // mirror horizontal
225 return QImageIOHandler::TransformationMirror;
226 case 3: // rotate 180
227 return QImageIOHandler::TransformationRotate180;
228 case 4: // mirror vertical
229 return QImageIOHandler::TransformationFlip;
230 case 5: // mirror horizontal and rotate 270 CW
231 return QImageIOHandler::TransformationFlipAndRotate90;
232 case 6: // rotate 90 CW
233 return QImageIOHandler::TransformationRotate90;
234 case 7: // mirror horizontal and rotate 90 CW
235 return QImageIOHandler::TransformationMirrorAndRotate90;
236 case 8: // rotate 270 CW
237 return QImageIOHandler::TransformationRotate270;
238 }
239 return QImageIOHandler::TransformationNone;
240}
241
242static int qt2Exif(QImageIOHandler::Transformations transformation)
243{
244 switch (transformation) {
245 case QImageIOHandler::TransformationNone:
246 return 1;
247 case QImageIOHandler::TransformationMirror:
248 return 2;
249 case QImageIOHandler::TransformationRotate180:
250 return 3;
251 case QImageIOHandler::TransformationFlip:
252 return 4;
253 case QImageIOHandler::TransformationFlipAndRotate90:
254 return 5;
255 case QImageIOHandler::TransformationRotate90:
256 return 6;
257 case QImageIOHandler::TransformationMirrorAndRotate90:
258 return 7;
259 case QImageIOHandler::TransformationRotate270:
260 return 8;
261 }
262 qWarning("Invalid Qt image transformation");
263 return 1;
264}
265
266QVariant QIIOFHelper::imageProperty(QImageIOHandler::ImageOption option)
267{
268 if (!initRead())
269 return QVariant();
270
271 switch (option) {
272 case QImageIOHandler::Size: {
273 QSize sz;
274 if (getIntProperty(kCGImagePropertyPixelWidth, &sz.rwidth())
275 && getIntProperty(kCGImagePropertyPixelHeight, &sz.rheight())) {
276 return sz;
277 }
278 break;
279 }
280 case QImageIOHandler::ImageTransformation: {
281 int orient;
282 if (getIntProperty(kCGImagePropertyOrientation, &orient))
283 return int(exif2Qt(orient));
284 break;
285 }
286 default:
287 break;
288 }
289
290 return QVariant();
291}
292
293void QIIOFHelper::setOption(QImageIOHandler::ImageOption option, const QVariant &value)
294{
295 if (writeOptions.size() < option + 1)
296 writeOptions.resize(option + 1);
297 writeOptions[option] = value;
298}
299
300bool QIIOFHelper::writeImage(const QImage &in, const QString &uti)
301{
302 static const CGDataConsumerCallbacks cgCallbacks = { &cbPutBytes, nullptr };
303
304 if (!q_ptr || !q_ptr->device() || in.isNull())
305 return false;
306
307 QCFType<CGImageRef> cgImage = qt_mac_toCGImage(in);
308 QCFType<CGDataConsumerRef> cgDataConsumer = CGDataConsumerCreate(q_ptr->device(), &cgCallbacks);
309 QCFType<CFStringRef> cfUti = uti.toCFString();
310 QCFType<CGImageDestinationRef> cgImageDest = CGImageDestinationCreateWithDataConsumer(cgDataConsumer, cfUti, 1, nullptr);
311 if (!cgImageDest || !cgImage)
312 return false;
313
314 QCFType<CFNumberRef> cfQuality = nullptr;
315 QCFType<CFNumberRef> cfOrientation = nullptr;
316 const void *dictKeys[2];
317 const void *dictVals[2];
318 int dictSize = 0;
319
320 if (q_ptr->supportsOption(QImageIOHandler::Quality)) {
321 bool ok = false;
322 int writeQuality = writeOptions.value(QImageIOHandler::Quality).toInt(&ok);
323 // If quality is unset, default to 75%
324 float quality = (ok && writeQuality >= 0 ? (qMin(writeQuality, 100)) : 75) / 100.0f;
325 cfQuality = CFNumberCreate(nullptr, kCFNumberFloatType, &quality);
326 dictKeys[dictSize] = static_cast<const void *>(kCGImageDestinationLossyCompressionQuality);
327 dictVals[dictSize] = static_cast<const void *>(cfQuality);
328 dictSize++;
329 }
330 if (q_ptr->supportsOption(QImageIOHandler::ImageTransformation)) {
331 int orient = qt2Exif(static_cast<QImageIOHandler::Transformation>(writeOptions.value(QImageIOHandler::ImageTransformation).toInt()));
332 cfOrientation = CFNumberCreate(nullptr, kCFNumberIntType, &orient);
333 dictKeys[dictSize] = static_cast<const void *>(kCGImagePropertyOrientation);
334 dictVals[dictSize] = static_cast<const void *>(cfOrientation);
335 dictSize++;
336 }
337
338 QCFType<CFDictionaryRef> cfProps = nullptr;
339 if (dictSize)
340 cfProps = CFDictionaryCreate(nullptr, dictKeys, dictVals, dictSize,
341 &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
342
343 qCInfo(lcImageIO) << "Writing" << cgImage << "\nwith properties" << cfProps;
344
345 CGImageDestinationAddImage(cgImageDest, cgImage, cfProps);
346 return CGImageDestinationFinalize(cgImageDest);
347}
348
349}
350
351QT_END_NAMESPACE
QIIOFHelper(QImageIOHandler *q)
bool getIntProperty(CFStringRef property, int *value)
QVariant imageProperty(QImageIOHandler::ImageOption option)
static void cbRelease(void *info)
static size_t cbPutBytes(void *info, const void *buffer, size_t count)
static size_t cbGetBytes(void *info, void *buffer, size_t count)
static int qt2Exif(QImageIOHandler::Transformations transformation)
static off_t cbSkipForward(void *info, off_t count)
static QImageIOHandler::Transformations exif2Qt(int exifOrientation)
static void cbRewind(void *info)
Combined button and popup list for selecting options.