1 | // Copyright (C) 2017 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 | |
4 | #include "qsharedimageloader_p.h" |
5 | #include <private/qobject_p.h> |
6 | #include <private/qimage_p.h> |
7 | |
8 | #include <QtCore/qpointer.h> |
9 | #include <QSharedMemory> |
10 | |
11 | #include <memory> |
12 | |
13 | QT_BEGIN_NAMESPACE |
14 | |
15 | Q_LOGGING_CATEGORY(lcSharedImage, "qt.quick.sharedimage" ); |
16 | |
17 | struct { |
18 | quint8 ; |
19 | quint8 ; |
20 | quint16 ; |
21 | qint32 ; |
22 | qint32 ; |
23 | qint32 ; |
24 | QImage::Format ; |
25 | }; |
26 | Q_STATIC_ASSERT(sizeof(SharedImageHeader) % 4 == 0); |
27 | |
28 | #if QT_CONFIG(sharedmemory) |
29 | struct SharedImageInfo { |
30 | QString path; |
31 | QPointer<QSharedMemory> shmp; |
32 | }; |
33 | |
34 | void cleanupSharedImage(void *cleanupInfo) |
35 | { |
36 | if (!cleanupInfo) |
37 | return; |
38 | SharedImageInfo *sii = static_cast<SharedImageInfo *>(cleanupInfo); |
39 | qCDebug(lcSharedImage) << "Cleanup called for" << sii->path; |
40 | if (sii->shmp.isNull()) { |
41 | qCDebug(lcSharedImage) << "shm is 0 for" << sii->path; |
42 | return; |
43 | } |
44 | QSharedMemory *shm = sii->shmp.data(); |
45 | sii->shmp.clear(); |
46 | delete shm; // destructor detaches |
47 | delete sii; |
48 | } |
49 | #else |
50 | void cleanupSharedImage(void *) {} |
51 | #endif |
52 | |
53 | class QSharedImageLoaderPrivate : public QObjectPrivate |
54 | { |
55 | Q_DECLARE_PUBLIC(QSharedImageLoader) |
56 | |
57 | public: |
58 | QSharedImageLoaderPrivate() {} |
59 | |
60 | QImage load(const QString &path, QSharedImageLoader::ImageParameters *params); |
61 | |
62 | void storeImageToMem(void *data, const QImage &img); |
63 | |
64 | bool verifyMem(const void *data, int size); |
65 | |
66 | QImage createImageFromMem(const void *data, void *cleanupInfo); |
67 | |
68 | }; |
69 | |
70 | |
71 | void QSharedImageLoaderPrivate::storeImageToMem(void *data, const QImage &img) |
72 | { |
73 | Q_ASSERT(data && !img.isNull()); |
74 | |
75 | SharedImageHeader *h = static_cast<SharedImageHeader *>(data); |
76 | h->magic = 'Q'; |
77 | h->version = 1; |
78 | h->offset = sizeof(SharedImageHeader); |
79 | h->width = img.width(); |
80 | h->height = img.height(); |
81 | h->bpl = img.bytesPerLine(); |
82 | h->format = img.format(); |
83 | |
84 | uchar *p = static_cast<uchar *>(data) + sizeof(SharedImageHeader); |
85 | memcpy(dest: p, src: img.constBits(), n: img.sizeInBytes()); |
86 | } |
87 | |
88 | |
89 | bool QSharedImageLoaderPrivate::verifyMem(const void *data, int size) |
90 | { |
91 | if (!data || size < int(sizeof(SharedImageHeader))) |
92 | return false; |
93 | |
94 | const SharedImageHeader *h = static_cast<const SharedImageHeader *>(data); |
95 | if ((h->magic != 'Q') |
96 | || (h->version < 1) |
97 | || (h->offset < sizeof(SharedImageHeader)) |
98 | || (h->width <= 0) |
99 | || (h->height <= 0) |
100 | || (h->bpl <= 0) |
101 | || (h->format <= QImage::Format_Invalid) |
102 | || (h->format >= QImage::NImageFormats)) { |
103 | return false; |
104 | } |
105 | |
106 | int availSize = size - h->offset; |
107 | if (h->height * h->bpl > availSize) |
108 | return false; |
109 | if ((qt_depthForFormat(format: h->format) * h->width * h->height) > (8 * availSize)) |
110 | return false; |
111 | |
112 | return true; |
113 | } |
114 | |
115 | |
116 | QImage QSharedImageLoaderPrivate::createImageFromMem(const void *data, void *cleanupInfo) |
117 | { |
118 | const SharedImageHeader *h = static_cast<const SharedImageHeader *>(data); |
119 | const uchar *p = static_cast<const uchar *>(data) + h->offset; |
120 | |
121 | QImage img(p, h->width, h->height, h->bpl, h->format, cleanupSharedImage, cleanupInfo); |
122 | return img; |
123 | } |
124 | |
125 | |
126 | QImage QSharedImageLoaderPrivate::load(const QString &path, QSharedImageLoader::ImageParameters *params) |
127 | { |
128 | #if QT_CONFIG(sharedmemory) |
129 | Q_Q(QSharedImageLoader); |
130 | |
131 | QImage nil; |
132 | if (path.isEmpty()) |
133 | return nil; |
134 | |
135 | auto shm = std::make_unique<QSharedMemory>(args: QSharedMemory::legacyNativeKey(key: q->key(path, params))); |
136 | bool locked = false; |
137 | |
138 | if (!shm->attach(mode: QSharedMemory::ReadOnly)) { |
139 | QImage img = q->loadFile(path, params); |
140 | if (img.isNull()) |
141 | return nil; |
142 | size_t size = sizeof(SharedImageHeader) + img.sizeInBytes(); |
143 | if (size > size_t(std::numeric_limits<int>::max())) { |
144 | qCDebug(lcSharedImage) << "Image" << path << "to large to load" ; |
145 | return nil; |
146 | } else if (shm->create(size: int(size))) { |
147 | qCDebug(lcSharedImage) << "Created new shm segment of size" << size << "for image" << path; |
148 | if (!shm->lock()) { |
149 | qCDebug(lcSharedImage) << "Lock1 failed!?" << shm->errorString(); |
150 | return nil; |
151 | } |
152 | locked = true; |
153 | storeImageToMem(data: shm->data(), img); |
154 | } else if (shm->error() == QSharedMemory::AlreadyExists) { |
155 | // race handling: other process may have created the share while |
156 | // we loaded the image, so try again to just attach |
157 | if (!shm->attach(mode: QSharedMemory::ReadOnly)) { |
158 | qCDebug(lcSharedImage) << "Attach to existing failed?" << shm->errorString(); |
159 | return nil; |
160 | } |
161 | } else { |
162 | qCDebug(lcSharedImage) << "Create failed?" << shm->errorString(); |
163 | return nil; |
164 | } |
165 | } |
166 | |
167 | Q_ASSERT(shm->isAttached()); |
168 | |
169 | if (!locked) { |
170 | if (!shm->lock()) { |
171 | qCDebug(lcSharedImage) << "Lock2 failed!?" << shm->errorString(); |
172 | return nil; |
173 | } |
174 | locked = true; |
175 | } |
176 | |
177 | if (!verifyMem(data: shm->constData(), size: shm->size())) { |
178 | qCDebug(lcSharedImage) << "Verifymem failed!?" ; |
179 | shm->unlock(); |
180 | return nil; |
181 | } |
182 | |
183 | QSharedMemory *shmp = shm.release(); |
184 | SharedImageInfo *sii = new SharedImageInfo; |
185 | sii->path = path; |
186 | sii->shmp = shmp; |
187 | QImage shImg = createImageFromMem(data: shmp->constData(), cleanupInfo: sii); |
188 | |
189 | if (!shmp->unlock()) { |
190 | qCDebug(lcSharedImage) << "UnLock failed!?" ; |
191 | } |
192 | |
193 | return shImg; |
194 | #else |
195 | Q_UNUSED(path); |
196 | Q_UNUSED(params); |
197 | return QImage(); |
198 | #endif |
199 | } |
200 | |
201 | |
202 | QSharedImageLoader::QSharedImageLoader(QObject *parent) |
203 | : QObject(*new QSharedImageLoaderPrivate, parent) |
204 | { |
205 | } |
206 | |
207 | QSharedImageLoader::~QSharedImageLoader() |
208 | { |
209 | } |
210 | |
211 | QImage QSharedImageLoader::load(const QString &path, ImageParameters *params) |
212 | { |
213 | Q_D(QSharedImageLoader); |
214 | |
215 | return d->load(path, params); |
216 | } |
217 | |
218 | QImage QSharedImageLoader::loadFile(const QString &path, ImageParameters *params) |
219 | { |
220 | Q_UNUSED(params); |
221 | |
222 | return QImage(path); |
223 | } |
224 | |
225 | QString QSharedImageLoader::key(const QString &path, ImageParameters *params) |
226 | { |
227 | Q_UNUSED(params); |
228 | |
229 | return path; |
230 | } |
231 | |
232 | |
233 | QT_END_NAMESPACE |
234 | |
235 | #include "moc_qsharedimageloader_p.cpp" |
236 | |