Krita Source Code Documentation
Loading...
Searching...
No Matches
KisFrameDataSerializer.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2018 Dmitry Kazakov <dimula73@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
7
8#include <cstring>
9
10#include <QTemporaryDir>
11#include <QElapsedTimer>
12
14
15struct KRITAUI_NO_EXPORT KisFrameDataSerializer::Private
16{
17 Private(const QString &frameCachePath)
18 : framesDir(
19 (!frameCachePath.isEmpty() && QTemporaryDir(frameCachePath + "/KritaFrameCacheXXXXXX").isValid()
20 ? frameCachePath
21 : QDir::tempPath())
22 + "/KritaFrameCacheXXXXXX")
23 {
24 framesDirObject = QDir(framesDir.path());
25 framesDirObject.makeAbsolute();
26 }
27
28 QString subfolderNameForFrame(int frameId)
29 {
30 const int subfolderIndex = frameId & 0xff00;
31 return QString::number(subfolderIndex);
32 }
33
34 QString fileNameForFrame(int frameId) {
35 return QString("frame_%1").arg(frameId);
36 }
37
38 QString filePathForFrame(int frameId)
39 {
40 return framesDirObject.filePath(
41 subfolderNameForFrame(frameId) + '/' +
42 fileNameForFrame(frameId));
43 }
44
46 // TODO: handle wrapping and range compression
47 return nextFrameId++;
48 }
49
50 quint8* getCompressionBuffer(int size) {
51 if (compressionBuffer.size() < size) {
52 compressionBuffer.resize(size);
53 }
54 return reinterpret_cast<quint8*>(compressionBuffer.data());
55 }
56
57 QTemporaryDir framesDir;
59 int nextFrameId = 0;
60
62};
63
68
70 : m_d(new Private(frameCachePath))
71{
72}
73
77
79{
80 KisLzfCompression compression;
81
82 const int frameId = m_d->generateFrameId();
83
84 const QString frameSubfolder = m_d->subfolderNameForFrame(frameId);
85
86 if (!m_d->framesDirObject.exists(frameSubfolder)) {
87 m_d->framesDirObject.mkpath(frameSubfolder);
88 }
89
90 const QString frameRelativePath = frameSubfolder + '/' + m_d->fileNameForFrame(frameId);
91
92 if (m_d->framesDirObject.exists(frameRelativePath)) {
93 qWarning() << "WARNING: overwriting existing frame file!" << frameRelativePath;
94 forgetFrame(frameId);
95 }
96
97 const QString frameFilePath = m_d->framesDirObject.filePath(frameRelativePath);
98
99 QFile file(frameFilePath);
100 if (!file.open(QFile::WriteOnly)) {
101 return frameId;
102 }
103
104 QDataStream stream(&file);
105 stream << frameId;
106 stream << frame.pixelSize;
107
108 stream << int(frame.frameTiles.size());
109
110 for (int i = 0; i < int(frame.frameTiles.size()); i++) {
111 const FrameTile &tile = frame.frameTiles[i];
112
113 stream << tile.col;
114 stream << tile.row;
115 stream << tile.rect;
116
117 const int frameByteSize = frame.pixelSize * tile.rect.width() * tile.rect.height();
118 const int maxBufferSize = compression.outputBufferSize(frameByteSize);
119 quint8 *buffer = m_d->getCompressionBuffer(maxBufferSize);
120
121 const int compressedSize =
122 compression.compress(tile.data.data(), frameByteSize, buffer, maxBufferSize);
123
124 //ENTER_FUNCTION() << ppVar(compressedSize) << ppVar(frameByteSize);
125
126 const bool isCompressed = compressedSize < frameByteSize;
127 stream << isCompressed;
128
129 if (isCompressed) {
130 stream << compressedSize;
131 stream.writeRawData((char*)buffer, compressedSize);
132 } else {
133 stream << frameByteSize;
134 stream.writeRawData((char*)tile.data.data(), frameByteSize);
135 }
136 }
137
138 file.close();
139
140 return frameId;
141}
142
144{
145 KisLzfCompression compression;
146
147 QElapsedTimer loadingTime;
148 loadingTime.start();
149
150 int loadedFrameId = -1;
152
153 qint64 compressionTime = 0;
154
155 const QString framePath = m_d->filePathForFrame(frameId);
156
157 QFile file(framePath);
158 KIS_SAFE_ASSERT_RECOVER_NOOP(file.exists());
159 if (!file.open(QFile::ReadOnly)) return frame;
160
161 QDataStream stream(&file);
162
163 int numTiles = 0;
164
165 stream >> loadedFrameId;
166 stream >> frame.pixelSize;
167 stream >> numTiles;
169
170
171
172 for (int i = 0; i < numTiles; i++) {
173 FrameTile tile(pool);
174 stream >> tile.col;
175 stream >> tile.row;
176 stream >> tile.rect;
177
178 const int frameByteSize = frame.pixelSize * tile.rect.width() * tile.rect.height();
179 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(frameByteSize <= pool->chunkSize(frame.pixelSize),
181
182 bool isCompressed = false;
183 int inputSize = -1;
184
185 stream >> isCompressed;
186 stream >> inputSize;
187
188 if (isCompressed) {
189 const int maxBufferSize = compression.outputBufferSize(inputSize);
190 quint8 *buffer = m_d->getCompressionBuffer(maxBufferSize);
191 stream.readRawData((char*)buffer, inputSize);
192
193 tile.data.allocate(frame.pixelSize);
194
195 QElapsedTimer compTime;
196 compTime.start();
197
198 const int decompressedSize =
199 compression.decompress(buffer, inputSize, tile.data.data(), frameByteSize);
200
201 compressionTime += compTime.nsecsElapsed();
202
203 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(frameByteSize == decompressedSize,
205
206 } else {
207 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(frameByteSize == inputSize,
209
210 tile.data.allocate(frame.pixelSize);
211 stream.readRawData((char*)tile.data.data(), inputSize);
212 }
213
214 frame.frameTiles.push_back(std::move(tile));
215 }
216
217 Q_UNUSED(compressionTime);
218
219 file.close();
220
221 return frame;
222}
223
224void KisFrameDataSerializer::moveFrame(int srcFrameId, int dstFrameId)
225{
226 const QString srcFramePath = m_d->filePathForFrame(srcFrameId);
227 const QString dstFramePath = m_d->filePathForFrame(dstFrameId);
228 KIS_SAFE_ASSERT_RECOVER_RETURN(QFileInfo(srcFramePath).exists());
229
230 KIS_SAFE_ASSERT_RECOVER(!QFileInfo(dstFramePath).exists()) {
231 QFile::remove(dstFramePath);
232 }
233
234 QFile::rename(srcFramePath, dstFramePath);
235}
236
237bool KisFrameDataSerializer::hasFrame(int frameId) const
238{
239 const QString framePath = m_d->filePathForFrame(frameId);
240 return QFileInfo(framePath).exists();
241}
242
244{
245 const QString framePath = m_d->filePathForFrame(frameId);
246 QFile::remove(framePath);
247}
248
250{
251 if (lhs.pixelSize != rhs.pixelSize) return boost::none;
252 if (lhs.frameTiles.size() != rhs.frameTiles.size()) return boost::none;
253
254 const int pixelSize = lhs.pixelSize;
255 int numSampledPixels = 0;
256 int numUniquePixels = 0;
257 const int sampleStep = portion > 0.0 ? qMax(1, qRound(1.0 / portion)) : 0;
258
259 for (int i = 0; i < int(lhs.frameTiles.size()); i++) {
260 const FrameTile &lhsTile = lhs.frameTiles[i];
261 const FrameTile &rhsTile = rhs.frameTiles[i];
262
263 if (lhsTile.col != rhsTile.col ||
264 lhsTile.row != rhsTile.row ||
265 lhsTile.rect != rhsTile.rect) {
266
267 return boost::none;
268 }
269
270 if (sampleStep > 0) {
271 const int numPixels = lhsTile.rect.width() * lhsTile.rect.height();
272
273 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(lhsTile.data.data(), boost::none);
274 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(rhsTile.data.data(), boost::none);
275
276 for (int j = 0; j < numPixels; j += sampleStep) {
277 quint8 *lhsDataPtr = lhsTile.data.data() + j * pixelSize;
278 quint8 *rhsDataPtr = rhsTile.data.data() + j * pixelSize;
279
280 if (std::memcmp(lhsDataPtr, rhsDataPtr, pixelSize) != 0) {
281 numUniquePixels++;
282 }
283 numSampledPixels++;
284 }
285 }
286 }
287
288 return numSampledPixels > 0 ? qreal(numUniquePixels) / numSampledPixels : 1.0;
289}
290
291template <template <typename U> class OpPolicy, typename T>
292bool processData(T *dst, const T *src, int numUnits)
293{
294 OpPolicy<T> op;
295
296 bool unitsAreSame = true;
297
298 for (int j = 0; j < numUnits; j++) {
299 *dst = op(*dst, *src);
300
301 if (*dst != 0) {
302 unitsAreSame = false;
303 }
304
305 src++;
306 dst++;
307 }
308 return unitsAreSame;
309}
310
311
312template<template <typename U> class OpPolicy>
314{
315 bool framesAreSame = true;
316
318
319 for (int i = 0; i < int(src.frameTiles.size()); i++) {
320 const FrameTile &srcTile = src.frameTiles[i];
321 FrameTile &dstTile = dst.frameTiles[i];
322
323 const int numBytes = srcTile.rect.width() * srcTile.rect.height() * src.pixelSize;
324 const int numQWords = numBytes / 8;
325
326 const quint64 *srcDataPtr = reinterpret_cast<const quint64*>(srcTile.data.data());
327 quint64 *dstDataPtr = reinterpret_cast<quint64*>(dstTile.data.data());
328
329 framesAreSame &= processData<OpPolicy>(dstDataPtr, srcDataPtr, numQWords);
330
331
332 const int tailBytes = numBytes % 8;
333 const quint8 *srcTailDataPtr = srcTile.data.data() + numBytes - tailBytes;
334 quint8 *dstTailDataPtr = dstTile.data.data() + numBytes - tailBytes;
335
336 framesAreSame &= processData<OpPolicy>(dstTailDataPtr, srcTailDataPtr, tailBytes);
337 }
338
339 return framesAreSame;
340}
341
343{
344 return processFrames<std::minus>(dst, src);
345}
346
348{
349 // TODO: don't spend time on calculation of "framesAreSame" in this case
350 (void) processFrames<std::plus>(dst, src);
351}
bool processData(T *dst, const T *src, int numUnits)
void allocate(int pixelSize)
qint32 compress(const quint8 *input, qint32 inputLength, quint8 *output, qint32 outputLength) override
qint32 decompress(const quint8 *input, qint32 inputLength, quint8 *output, qint32 outputLength) override
qint32 outputBufferSize(qint32 dataSize) override
#define KIS_SAFE_ASSERT_RECOVER(cond)
Definition kis_assert.h:126
#define KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(cond, val)
Definition kis_assert.h:129
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128
#define KIS_SAFE_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:130
typedef void(QOPENGLF_APIENTRYP PFNGLINVALIDATEBUFFERDATAPROC)(GLuint buffer)
std::vector< FrameTile > frameTiles
bool hasFrame(int frameId) const
quint8 * getCompressionBuffer(int size)
static boost::optional< qreal > estimateFrameUniqueness(const Frame &lhs, const Frame &rhs, qreal portion)
static void addFrames(Frame &dst, const Frame &src)
const QScopedPointer< Private > m_d
static bool subtractFrames(Frame &dst, const Frame &src)
QString subfolderNameForFrame(int frameId)
Frame loadFrame(int frameId, KisTextureTileInfoPoolSP pool)
void moveFrame(int srcFrameId, int dstFrameId)
QString filePathForFrame(int frameId)
Private(const QString &frameCachePath)
int saveFrame(const Frame &frame)
static bool processFrames(KisFrameDataSerializer::Frame &dst, const KisFrameDataSerializer::Frame &src)
QString fileNameForFrame(int frameId)