blob: c9e3ef3eb3bbc9665e7292b300f64fb4ca1fcd7c [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2017 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the plugins of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** 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.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qsharedimageloader_p.h"
#include <private/qobject_p.h>
#include <private/qimage_p.h>
#include <QSharedMemory>
QT_BEGIN_NAMESPACE
Q_LOGGING_CATEGORY(lcSharedImage, "qt.quick.sharedimage");
struct SharedImageHeader {
quint8 magic;
quint8 version;
quint16 offset;
qint32 width;
qint32 height;
qint32 bpl;
QImage::Format format;
};
Q_STATIC_ASSERT(sizeof(SharedImageHeader) % 4 == 0);
#if QT_CONFIG(sharedmemory)
struct SharedImageInfo {
QString path;
QPointer<QSharedMemory> shmp;
};
void cleanupSharedImage(void *cleanupInfo)
{
if (!cleanupInfo)
return;
SharedImageInfo *sii = static_cast<SharedImageInfo *>(cleanupInfo);
qCDebug(lcSharedImage) << "Cleanup called for" << sii->path;
if (sii->shmp.isNull()) {
qCDebug(lcSharedImage) << "shm is 0 for" << sii->path;
return;
}
QSharedMemory *shm = sii->shmp.data();
sii->shmp.clear();
delete shm; // destructor detaches
delete sii;
}
#else
void cleanupSharedImage(void *) {}
#endif
class QSharedImageLoaderPrivate : public QObjectPrivate
{
Q_DECLARE_PUBLIC(QSharedImageLoader)
public:
QSharedImageLoaderPrivate() {}
QImage load(const QString &path, QSharedImageLoader::ImageParameters *params);
void storeImageToMem(void *data, const QImage &img);
bool verifyMem(const void *data, int size);
QImage createImageFromMem(const void *data, void *cleanupInfo);
};
void QSharedImageLoaderPrivate::storeImageToMem(void *data, const QImage &img)
{
Q_ASSERT(data && !img.isNull());
SharedImageHeader *h = static_cast<SharedImageHeader *>(data);
h->magic = 'Q';
h->version = 1;
h->offset = sizeof(SharedImageHeader);
h->width = img.width();
h->height = img.height();
h->bpl = img.bytesPerLine();
h->format = img.format();
uchar *p = static_cast<uchar *>(data) + sizeof(SharedImageHeader);
memcpy(p, img.constBits(), img.sizeInBytes());
}
bool QSharedImageLoaderPrivate::verifyMem(const void *data, int size)
{
if (!data || size < int(sizeof(SharedImageHeader)))
return false;
const SharedImageHeader *h = static_cast<const SharedImageHeader *>(data);
if ((h->magic != 'Q')
|| (h->version < 1)
|| (h->offset < sizeof(SharedImageHeader))
|| (h->width <= 0)
|| (h->height <= 0)
|| (h->bpl <= 0)
|| (h->format <= QImage::Format_Invalid)
|| (h->format >= QImage::NImageFormats)) {
return false;
}
int availSize = size - h->offset;
if (h->height * h->bpl > availSize)
return false;
if ((qt_depthForFormat(h->format) * h->width * h->height) > (8 * availSize))
return false;
return true;
}
QImage QSharedImageLoaderPrivate::createImageFromMem(const void *data, void *cleanupInfo)
{
const SharedImageHeader *h = static_cast<const SharedImageHeader *>(data);
const uchar *p = static_cast<const uchar *>(data) + h->offset;
QImage img(p, h->width, h->height, h->bpl, h->format, cleanupSharedImage, cleanupInfo);
return img;
}
QImage QSharedImageLoaderPrivate::load(const QString &path, QSharedImageLoader::ImageParameters *params)
{
#if QT_CONFIG(sharedmemory)
Q_Q(QSharedImageLoader);
QImage nil;
if (path.isEmpty())
return nil;
QScopedPointer<QSharedMemory> shm(new QSharedMemory(q->key(path, params)));
bool locked = false;
if (!shm->attach(QSharedMemory::ReadOnly)) {
QImage img = q->loadFile(path, params);
if (img.isNull())
return nil;
size_t size = sizeof(SharedImageHeader) + img.sizeInBytes();
if (size > size_t(std::numeric_limits<int>::max())) {
qCDebug(lcSharedImage) << "Image" << path << "to large to load";
return nil;
} else if (shm->create(int(size))) {
qCDebug(lcSharedImage) << "Created new shm segment of size" << size << "for image" << path;
if (!shm->lock()) {
qCDebug(lcSharedImage) << "Lock1 failed!?" << shm->errorString();
return nil;
}
locked = true;
storeImageToMem(shm->data(), img);
} else if (shm->error() == QSharedMemory::AlreadyExists) {
// race handling: other process may have created the share while
// we loaded the image, so try again to just attach
if (!shm->attach(QSharedMemory::ReadOnly)) {
qCDebug(lcSharedImage) << "Attach to existing failed?" << shm->errorString();
return nil;
}
} else {
qCDebug(lcSharedImage) << "Create failed?" << shm->errorString();
return nil;
}
}
Q_ASSERT(shm->isAttached());
if (!locked) {
if (!shm->lock()) {
qCDebug(lcSharedImage) << "Lock2 failed!?" << shm->errorString();
return nil;
}
locked = true;
}
if (!verifyMem(shm->constData(), shm->size())) {
qCDebug(lcSharedImage) << "Verifymem failed!?";
shm->unlock();
return nil;
}
QSharedMemory *shmp = shm.take();
SharedImageInfo *sii = new SharedImageInfo;
sii->path = path;
sii->shmp = shmp;
QImage shImg = createImageFromMem(shmp->constData(), sii);
if (!shmp->unlock()) {
qCDebug(lcSharedImage) << "UnLock failed!?";
}
return shImg;
#else
Q_UNUSED(path);
Q_UNUSED(params);
return QImage();
#endif
}
QSharedImageLoader::QSharedImageLoader(QObject *parent)
: QObject(*new QSharedImageLoaderPrivate, parent)
{
}
QSharedImageLoader::~QSharedImageLoader()
{
}
QImage QSharedImageLoader::load(const QString &path, ImageParameters *params)
{
Q_D(QSharedImageLoader);
return d->load(path, params);
}
QImage QSharedImageLoader::loadFile(const QString &path, ImageParameters *params)
{
Q_UNUSED(params);
return QImage(path);
}
QString QSharedImageLoader::key(const QString &path, ImageParameters *params)
{
Q_UNUSED(params);
return path;
}
QT_END_NAMESPACE