1// Copyright (C) 2016 Klaralvdalens Datakonsult AB (KDAB).
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 "qtextureatlas_p.h"
5#include "qtextureatlas_p_p.h"
6#include <Qt3DRender/qtexturedata.h>
7#include <Qt3DRender/qabstracttextureimage.h>
8
9QT_BEGIN_NAMESPACE
10
11
12namespace Qt3DExtras {
13
14using namespace Qt3DCore;
15
16QTextureAtlasData::QTextureAtlasData(int w, int h, QImage::Format fmt)
17 : m_image(w, h, fmt)
18{
19 m_image.fill(pixel: 0);
20}
21
22QTextureAtlasData::~QTextureAtlasData()
23{
24}
25
26void QTextureAtlasData::addImage(const AtlasTexture &texture, const QImage &image)
27{
28 QMutexLocker lock(&m_mutex);
29
30 Update update;
31 update.textureInfo = texture;
32 update.image = image;
33 m_updates << update;
34}
35
36QByteArray QTextureAtlasData::createUpdatedImageData()
37{
38 m_mutex.lock();
39 const QList<Update> updates = std::move(m_updates);
40 m_mutex.unlock();
41
42 // copy sub-images into the actual texture image
43 for (const Update &update : updates) {
44 const QImage &image = update.image;
45
46 const int padding = update.textureInfo.padding;
47 const QRect imgRect = update.textureInfo.position;
48 const QRect alloc = imgRect.adjusted(xp1: -padding, yp1: -padding, xp2: padding, yp2: padding);
49
50 // bytes per pixel
51 if (image.depth() != m_image.depth()) {
52 qWarning() << "[QTextureAtlas] Image depth does not match. Original =" << m_image.depth() << ", Sub-Image =" << image.depth();
53 continue;
54 }
55 int bpp = image.depth() / 8;
56
57 // copy image contents into texture image
58 // use image border pixels to fill the padding region
59 for (int y = alloc.top(); y <= alloc.bottom(); y++) {
60 uchar *dstLine = m_image.scanLine(y);
61
62 uchar *dstPadL = &dstLine[bpp * alloc.left()];
63 uchar *dstPadR = &dstLine[bpp * imgRect.right()];
64 uchar *dstImg = &dstLine[bpp * imgRect.left()];
65
66 // do padding with 0 in the upper/lower padding parts around the actual image
67 if (y < imgRect.top() || y > imgRect.bottom()) {
68 memset(s: dstPadL, c: 0, n: bpp * (imgRect.width() + 2 * padding));
69 continue;
70 }
71
72 // copy left and right padding pixels
73 memset(s: dstPadL, c: 0, n: bpp * padding);
74 memset(s: dstPadR, c: 0, n: bpp * padding);
75
76 // copy image scanline
77 const int ySrc = qBound(min: 0, val: y - imgRect.top(), max: image.height()-1);
78 memcpy(dest: dstImg, src: image.scanLine(ySrc), n: bpp * imgRect.width());
79 }
80 }
81
82 return QByteArray(reinterpret_cast<const char*>(m_image.constBits()), m_image.sizeInBytes());
83}
84
85QTextureAtlasPrivate::QTextureAtlasPrivate()
86 : Qt3DRender::QAbstractTexturePrivate()
87{
88 m_target = Qt3DRender::QAbstractTexture::TargetAutomatic;
89 m_format = Qt3DRender::QAbstractTexture::RGBA8_UNorm;
90 m_width = 256;
91 m_height = 256;
92 m_depth = 1;
93}
94
95QTextureAtlasPrivate::~QTextureAtlasPrivate()
96{
97}
98
99QTextureAtlasGenerator::QTextureAtlasGenerator(const QTextureAtlasPrivate *texAtlas)
100 : m_data(texAtlas->m_data)
101 , m_format(texAtlas->m_format)
102 , m_pixelFormat(texAtlas->m_pixelFormat)
103 , m_generation(texAtlas->m_currGen)
104 , m_atlasId(texAtlas->m_id)
105{
106}
107
108QTextureAtlasGenerator::~QTextureAtlasGenerator()
109{
110}
111
112Qt3DRender::QTextureDataPtr QTextureAtlasGenerator::operator()()
113{
114 Qt3DRender::QTextureImageDataPtr texImage = Qt3DRender::QTextureImageDataPtr::create();
115 texImage->setTarget(QOpenGLTexture::Target2D);
116 texImage->setWidth(m_data->width());
117 texImage->setHeight(m_data->height());
118 texImage->setDepth(1);
119 texImage->setFaces(1);
120 texImage->setLayers(1);
121 texImage->setMipLevels(1);
122 texImage->setFormat(static_cast<QOpenGLTexture::TextureFormat>(m_format));
123 texImage->setPixelFormat(m_pixelFormat);
124 texImage->setPixelType(QOpenGLTexture::UInt8);
125
126 const QByteArray bytes = m_data->createUpdatedImageData();
127 texImage->setData(data: bytes, blockSize: 1);
128
129 Qt3DRender::QTextureDataPtr generatedData = Qt3DRender::QTextureDataPtr::create();
130 generatedData->setTarget(Qt3DRender::QAbstractTexture::Target2D);
131 generatedData->setFormat(m_format);
132 generatedData->setWidth(m_data->width());
133 generatedData->setHeight(m_data->height());
134 generatedData->setDepth(1);
135 generatedData->setLayers(1);
136 generatedData->addImageData(imageData: texImage);
137
138 return generatedData;
139}
140
141bool QTextureAtlasGenerator::operator==(const QTextureGenerator &other) const
142{
143 const QTextureAtlasGenerator *otherFunctor = Qt3DCore::functor_cast<QTextureAtlasGenerator>(other: &other);
144 return (otherFunctor != nullptr
145 && otherFunctor->m_data == m_data
146 && otherFunctor->m_atlasId == m_atlasId
147 && otherFunctor->m_generation == m_generation);
148}
149
150QTextureAtlas::QTextureAtlas(Qt3DCore::QNode *parent)
151 : QAbstractTexture(*new QTextureAtlasPrivate(), parent)
152{
153}
154
155QOpenGLTexture::PixelFormat QTextureAtlas::pixelFormat() const
156{
157 Q_D(const QTextureAtlas);
158 return d->m_pixelFormat;
159}
160
161void QTextureAtlas::setPixelFormat(QOpenGLTexture::PixelFormat fmt)
162{
163 Q_D(QTextureAtlas);
164 d->m_pixelFormat = fmt;
165}
166
167QTextureAtlas::~QTextureAtlas()
168{
169}
170
171QTextureAtlas::TextureId QTextureAtlas::addImage(const QImage &image, int padding)
172{
173 Q_D(QTextureAtlas);
174
175 // lazily create image and allocator to allow setWidth/setHeight after object construction
176 if (!d->m_allocator) {
177 Q_ASSERT(d->m_data.isNull());
178
179 d->m_allocator.reset(other: new AreaAllocator(QSize(width(), height())));
180 d->m_data = QTextureAtlasDataPtr::create(arguments: width(), arguments: height(), arguments: image.format());
181 }
182
183 const QSize allocSz = image.size() + QSize(2 * padding, 2 * padding);
184
185 // try to allocate space within image space
186 const QRect alloc = d->m_allocator->allocate(size: allocSz);
187 if (alloc.isEmpty())
188 return InvalidTexture;
189
190 const QRect imgRect = alloc.adjusted(xp1: padding, yp1: padding, xp2: -padding, yp2: -padding);
191 AtlasTexture tex;
192 tex.position = imgRect;
193 tex.padding = padding;
194
195 // store texture
196 TextureId id = d->m_currId++;
197 d->m_textures[id] = tex;
198 d->m_data->addImage(texture: tex, image);
199
200 // update data functor
201 d->m_currGen++;
202 d->setDataFunctor(QTextureAtlasGeneratorPtr::create(arguments: d));
203
204 return id;
205}
206
207void QTextureAtlas::removeImage(TextureId id)
208{
209 Q_D(QTextureAtlas);
210 auto it = d->m_textures.find(key: id);
211 if (it != d->m_textures.end()) {
212 QRect imgRect = it->position;
213 imgRect.adjust(dx1: -it->padding, dy1: -it->padding, dx2: 2*it->padding, dy2: 2*it->padding);
214
215 if (d->m_allocator)
216 d->m_allocator->deallocate(rect: imgRect);
217 d->m_textures.erase(it);
218 }
219}
220
221bool QTextureAtlas::hasImage(TextureId id) const
222{
223 Q_D(const QTextureAtlas);
224 return d->m_textures.contains(key: id);
225}
226
227qsizetype QTextureAtlas::imageCount() const
228{
229 Q_D(const QTextureAtlas);
230 return d->m_textures.size();
231}
232
233QRect QTextureAtlas::imagePosition(TextureId id) const
234{
235 Q_D(const QTextureAtlas);
236 const auto it = d->m_textures.find(key: id);
237 return (it != d->m_textures.cend()) ? it->position : QRect();
238}
239
240QRectF QTextureAtlas::imageTexCoords(TextureId id) const
241{
242 Q_D(const QTextureAtlas);
243 const auto it = d->m_textures.find(key: id);
244 if (it != d->m_textures.cend()) {
245 const float w = d->m_data->width();
246 const float h = d->m_data->height();
247 return QRectF(static_cast<float>(it->position.x()) / w,
248 static_cast<float>(it->position.y()) / h,
249 static_cast<float>(it->position.width()) / w,
250 static_cast<float>(it->position.height()) / h);
251 }
252 return QRectF();
253}
254
255int QTextureAtlas::imagePadding(TextureId id) const
256{
257 Q_D(const QTextureAtlas);
258 const auto it = d->m_textures.find(key: id);
259 return (it != d->m_textures.cend()) ? it->padding : -1;
260}
261
262} // namespace Qt3DExtras
263
264QT_END_NAMESPACE
265
266#include "moc_qtextureatlas_p.cpp"
267

source code of qt3d/src/extras/text/qtextureatlas.cpp