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 file.open(QFile::WriteOnly);
101
102 QDataStream stream(&file);
103 stream << frameId;
104 stream << frame.pixelSize;
105
106 stream << int(frame.frameTiles.size());
107
108 for (int i = 0; i < int(frame.frameTiles.size()); i++) {
109 const FrameTile &tile = frame.frameTiles[i];
110
111 stream << tile.col;
112 stream << tile.row;
113 stream << tile.rect;
114
115 const int frameByteSize = frame.pixelSize * tile.rect.width() * tile.rect.height();
116 const int maxBufferSize = compression.outputBufferSize(frameByteSize);
117 quint8 *buffer = m_d->getCompressionBuffer(maxBufferSize);
118
119 const int compressedSize =
120 compression.compress(tile.data.data(), frameByteSize, buffer, maxBufferSize);
121
122 //ENTER_FUNCTION() << ppVar(compressedSize) << ppVar(frameByteSize);
123
124 const bool isCompressed = compressedSize < frameByteSize;
125 stream << isCompressed;
126
127 if (isCompressed) {
128 stream << compressedSize;
129 stream.writeRawData((char*)buffer, compressedSize);
130 } else {
131 stream << frameByteSize;
132 stream.writeRawData((char*)tile.data.data(), frameByteSize);
133 }
134 }
135
136 file.close();
137
138 return frameId;
139}
140
142{
143 KisLzfCompression compression;
144
145 QElapsedTimer loadingTime;
146 loadingTime.start();
147
148 int loadedFrameId = -1;
150
151 qint64 compressionTime = 0;
152
153 const QString framePath = m_d->filePathForFrame(frameId);
154
155 QFile file(framePath);
156 KIS_SAFE_ASSERT_RECOVER_NOOP(file.exists());
157 if (!file.open(QFile::ReadOnly)) return frame;
158
159 QDataStream stream(&file);
160
161 int numTiles = 0;
162
163 stream >> loadedFrameId;
164 stream >> frame.pixelSize;
165 stream >> numTiles;
167
168
169
170 for (int i = 0; i < numTiles; i++) {
171 FrameTile tile(pool);
172 stream >> tile.col;
173 stream >> tile.row;
174 stream >> tile.rect;
175
176 const int frameByteSize = frame.pixelSize * tile.rect.width() * tile.rect.height();
177 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(frameByteSize <= pool->chunkSize(frame.pixelSize),
179
180 bool isCompressed = false;
181 int inputSize = -1;
182
183 stream >> isCompressed;
184 stream >> inputSize;
185
186 if (isCompressed) {
187 const int maxBufferSize = compression.outputBufferSize(inputSize);
188 quint8 *buffer = m_d->getCompressionBuffer(maxBufferSize);
189 stream.readRawData((char*)buffer, inputSize);
190
191 tile.data.allocate(frame.pixelSize);
192
193 QElapsedTimer compTime;
194 compTime.start();
195
196 const int decompressedSize =
197 compression.decompress(buffer, inputSize, tile.data.data(), frameByteSize);
198
199 compressionTime += compTime.nsecsElapsed();
200
201 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(frameByteSize == decompressedSize,
203
204 } else {
205 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(frameByteSize == inputSize,
207
208 tile.data.allocate(frame.pixelSize);
209 stream.readRawData((char*)tile.data.data(), inputSize);
210 }
211
212 frame.frameTiles.push_back(std::move(tile));
213 }
214
215 Q_UNUSED(compressionTime);
216
217 file.close();
218
219 return frame;
220}
221
222void KisFrameDataSerializer::moveFrame(int srcFrameId, int dstFrameId)
223{
224 const QString srcFramePath = m_d->filePathForFrame(srcFrameId);
225 const QString dstFramePath = m_d->filePathForFrame(dstFrameId);
226 KIS_SAFE_ASSERT_RECOVER_RETURN(QFileInfo(srcFramePath).exists());
227
228 KIS_SAFE_ASSERT_RECOVER(!QFileInfo(dstFramePath).exists()) {
229 QFile::remove(dstFramePath);
230 }
231
232 QFile::rename(srcFramePath, dstFramePath);
233}
234
235bool KisFrameDataSerializer::hasFrame(int frameId) const
236{
237 const QString framePath = m_d->filePathForFrame(frameId);
238 return QFileInfo(framePath).exists();
239}
240
242{
243 const QString framePath = m_d->filePathForFrame(frameId);
244 QFile::remove(framePath);
245}
246
248{
249 if (lhs.pixelSize != rhs.pixelSize) return boost::none;
250 if (lhs.frameTiles.size() != rhs.frameTiles.size()) return boost::none;
251
252 const int pixelSize = lhs.pixelSize;
253 int numSampledPixels = 0;
254 int numUniquePixels = 0;
255 const int sampleStep = portion > 0.0 ? qMax(1, qRound(1.0 / portion)) : 0;
256
257 for (int i = 0; i < int(lhs.frameTiles.size()); i++) {
258 const FrameTile &lhsTile = lhs.frameTiles[i];
259 const FrameTile &rhsTile = rhs.frameTiles[i];
260
261 if (lhsTile.col != rhsTile.col ||
262 lhsTile.row != rhsTile.row ||
263 lhsTile.rect != rhsTile.rect) {
264
265 return boost::none;
266 }
267
268 if (sampleStep > 0) {
269 const int numPixels = lhsTile.rect.width() * lhsTile.rect.height();
270
271 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(lhsTile.data.data(), boost::none);
272 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(rhsTile.data.data(), boost::none);
273
274 for (int j = 0; j < numPixels; j += sampleStep) {
275 quint8 *lhsDataPtr = lhsTile.data.data() + j * pixelSize;
276 quint8 *rhsDataPtr = rhsTile.data.data() + j * pixelSize;
277
278 if (std::memcmp(lhsDataPtr, rhsDataPtr, pixelSize) != 0) {
279 numUniquePixels++;
280 }
281 numSampledPixels++;
282 }
283 }
284 }
285
286 return numSampledPixels > 0 ? qreal(numUniquePixels) / numSampledPixels : 1.0;
287}
288
289template <template <typename U> class OpPolicy, typename T>
290bool processData(T *dst, const T *src, int numUnits)
291{
292 OpPolicy<T> op;
293
294 bool unitsAreSame = true;
295
296 for (int j = 0; j < numUnits; j++) {
297 *dst = op(*dst, *src);
298
299 if (*dst != 0) {
300 unitsAreSame = false;
301 }
302
303 src++;
304 dst++;
305 }
306 return unitsAreSame;
307}
308
309
310template<template <typename U> class OpPolicy>
312{
313 bool framesAreSame = true;
314
316
317 for (int i = 0; i < int(src.frameTiles.size()); i++) {
318 const FrameTile &srcTile = src.frameTiles[i];
319 FrameTile &dstTile = dst.frameTiles[i];
320
321 const int numBytes = srcTile.rect.width() * srcTile.rect.height() * src.pixelSize;
322 const int numQWords = numBytes / 8;
323
324 const quint64 *srcDataPtr = reinterpret_cast<const quint64*>(srcTile.data.data());
325 quint64 *dstDataPtr = reinterpret_cast<quint64*>(dstTile.data.data());
326
327 framesAreSame &= processData<OpPolicy>(dstDataPtr, srcDataPtr, numQWords);
328
329
330 const int tailBytes = numBytes % 8;
331 const quint8 *srcTailDataPtr = srcTile.data.data() + numBytes - tailBytes;
332 quint8 *dstTailDataPtr = dstTile.data.data() + numBytes - tailBytes;
333
334 framesAreSame &= processData<OpPolicy>(dstTailDataPtr, srcTailDataPtr, tailBytes);
335 }
336
337 return framesAreSame;
338}
339
341{
342 return processFrames<std::minus>(dst, src);
343}
344
346{
347 // TODO: don't spend time on calculation of "framesAreSame" in this case
348 (void) processFrames<std::plus>(dst, src);
349}
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)