summaryrefslogtreecommitdiffstats
path: root/src/plugins/imageformats/shared/qiiofhelpers.cpp
blob: b5c0ae09d57c18619b24ac89dacfbcac71f5a2a8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the MacJp2 plugin in the Qt ImageFormats module.
**
** $QT_BEGIN_LICENSE:COMM$
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** $QT_END_LICENSE$
**
**
**
**
**
**
**
**
**
**
**
**
**
**
**
**
**
**
**
****************************************************************************/

#include <QGuiApplication>
#include <QBuffer>
#include <QImageIOHandler>
#include <QImage>

#include "qiiofhelpers_p.h"


QT_BEGIN_NAMESPACE

// Callbacks for sequential data provider & consumer:

static size_t cbGetBytes(void *info, void *buffer, size_t count)
{
    QIODevice *dev = static_cast<QIODevice *>(info);
    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);
    if (!dev || count <= 0)
        return 0;
    qint64 res = 0;
    if (!dev->isSequential()) {
        qint64 prevPos = dev->pos();
        dev->seek(prevPos + count);
        res = dev->pos() - prevPos;
    } else {
        char *buf = new char[quint64(count)];
        res = dev->read(buf, count);
        delete[] buf;
    }
    return qMax(qint64(0), res);
}

static void cbRewind(void *)
{
    // 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);
    if (!dev || !buffer)
        return 0;
    qint64 res = dev->write(static_cast<const char *>(buffer), qint64(count));
    return size_t(qMax(qint64(0), res));
}


// QImage <-> CGImage conversion functions from QtGui on darwin
CGImageRef qt_mac_toCGImage(const QImage &qImage);
QImage qt_mac_toQImage(CGImageRef image);

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()
{
    static const CGDataProviderSequentialCallbacks cgCallbacks = { 0, &cbGetBytes, &cbSkipForward, &cbRewind, nullptr };

    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 {
        cgDataProvider = CGDataProviderCreateSequential(q_ptr->device(), &cgCallbacks);
    }

    cgImageSource = CGImageSourceCreateWithDataProvider(cgDataProvider, nullptr);

    if (cgImageSource)
        cfImageDict = CGImageSourceCopyPropertiesAtIndex(cgImageSource, 0, nullptr);

    return (cgImageSource);
}

bool QIIOFHelper::readImage(QImage *out)
{
    if (!out || !initRead())
        return false;

    QCFType<CGImageRef> cgImage = CGImageSourceCreateImageAtIndex(cgImageSource, 0, nullptr);
    if (!cgImage)
        return false;

    *out = qt_mac_toQImage(cgImage);
    if (out->isNull())
        return false;

    int dpi = 0;
    if (getIntProperty(kCGImagePropertyDPIWidth, &dpi))
        out->setDotsPerMeterX(qRound(dpi / 0.0254f));
    if (getIntProperty(kCGImagePropertyDPIHeight, &dpi))
        out->setDotsPerMeterY(qRound(dpi / 0.0254f));

    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);

    CGImageDestinationAddImage(cgImageDest, cgImage, cfProps);
    return CGImageDestinationFinalize(cgImageDest);
}

QT_END_NAMESPACE