File indexing completed on 2024-05-12 16:01:48
0001 /* 0002 * SPDX-FileCopyrightText: 2018 Dmitry Kazakov <dimula73@gmail.com> 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 #include "KisFrameDataSerializer.h" 0007 0008 #include <cstring> 0009 0010 #include <QTemporaryDir> 0011 #include <QElapsedTimer> 0012 0013 #include "tiles3/swap/kis_lzf_compression.h" 0014 0015 struct KRITAUI_NO_EXPORT KisFrameDataSerializer::Private 0016 { 0017 Private(const QString &frameCachePath) 0018 : framesDir( 0019 (!frameCachePath.isEmpty() && QTemporaryDir(frameCachePath + "/KritaFrameCacheXXXXXX").isValid() 0020 ? frameCachePath 0021 : QDir::tempPath()) 0022 + "/KritaFrameCacheXXXXXX") 0023 { 0024 framesDirObject = QDir(framesDir.path()); 0025 framesDirObject.makeAbsolute(); 0026 } 0027 0028 QString subfolderNameForFrame(int frameId) 0029 { 0030 const int subfolderIndex = frameId & 0xff00; 0031 return QString::number(subfolderIndex); 0032 } 0033 0034 QString fileNameForFrame(int frameId) { 0035 return QString("frame_%1").arg(frameId); 0036 } 0037 0038 QString filePathForFrame(int frameId) 0039 { 0040 return framesDirObject.filePath( 0041 subfolderNameForFrame(frameId) + '/' + 0042 fileNameForFrame(frameId)); 0043 } 0044 0045 int generateFrameId() { 0046 // TODO: handle wrapping and range compression 0047 return nextFrameId++; 0048 } 0049 0050 quint8* getCompressionBuffer(int size) { 0051 if (compressionBuffer.size() < size) { 0052 compressionBuffer.resize(size); 0053 } 0054 return reinterpret_cast<quint8*>(compressionBuffer.data()); 0055 } 0056 0057 QTemporaryDir framesDir; 0058 QDir framesDirObject; 0059 int nextFrameId = 0; 0060 0061 QByteArray compressionBuffer; 0062 }; 0063 0064 KisFrameDataSerializer::KisFrameDataSerializer() 0065 : KisFrameDataSerializer(QString()) 0066 { 0067 } 0068 0069 KisFrameDataSerializer::KisFrameDataSerializer(const QString &frameCachePath) 0070 : m_d(new Private(frameCachePath)) 0071 { 0072 } 0073 0074 KisFrameDataSerializer::~KisFrameDataSerializer() 0075 { 0076 } 0077 0078 int KisFrameDataSerializer::saveFrame(const KisFrameDataSerializer::Frame &frame) 0079 { 0080 KisLzfCompression compression; 0081 0082 const int frameId = m_d->generateFrameId(); 0083 0084 const QString frameSubfolder = m_d->subfolderNameForFrame(frameId); 0085 0086 if (!m_d->framesDirObject.exists(frameSubfolder)) { 0087 m_d->framesDirObject.mkpath(frameSubfolder); 0088 } 0089 0090 const QString frameRelativePath = frameSubfolder + '/' + m_d->fileNameForFrame(frameId); 0091 0092 if (m_d->framesDirObject.exists(frameRelativePath)) { 0093 qWarning() << "WARNING: overwriting existing frame file!" << frameRelativePath; 0094 forgetFrame(frameId); 0095 } 0096 0097 const QString frameFilePath = m_d->framesDirObject.filePath(frameRelativePath); 0098 0099 QFile file(frameFilePath); 0100 file.open(QFile::WriteOnly); 0101 0102 QDataStream stream(&file); 0103 stream << frameId; 0104 stream << frame.pixelSize; 0105 0106 stream << int(frame.frameTiles.size()); 0107 0108 for (int i = 0; i < int(frame.frameTiles.size()); i++) { 0109 const FrameTile &tile = frame.frameTiles[i]; 0110 0111 stream << tile.col; 0112 stream << tile.row; 0113 stream << tile.rect; 0114 0115 const int frameByteSize = frame.pixelSize * tile.rect.width() * tile.rect.height(); 0116 const int maxBufferSize = compression.outputBufferSize(frameByteSize); 0117 quint8 *buffer = m_d->getCompressionBuffer(maxBufferSize); 0118 0119 const int compressedSize = 0120 compression.compress(tile.data.data(), frameByteSize, buffer, maxBufferSize); 0121 0122 //ENTER_FUNCTION() << ppVar(compressedSize) << ppVar(frameByteSize); 0123 0124 const bool isCompressed = compressedSize < frameByteSize; 0125 stream << isCompressed; 0126 0127 if (isCompressed) { 0128 stream << compressedSize; 0129 stream.writeRawData((char*)buffer, compressedSize); 0130 } else { 0131 stream << frameByteSize; 0132 stream.writeRawData((char*)tile.data.data(), frameByteSize); 0133 } 0134 } 0135 0136 file.close(); 0137 0138 return frameId; 0139 } 0140 0141 KisFrameDataSerializer::Frame KisFrameDataSerializer::loadFrame(int frameId, KisTextureTileInfoPoolSP pool) 0142 { 0143 KisLzfCompression compression; 0144 0145 QElapsedTimer loadingTime; 0146 loadingTime.start(); 0147 0148 int loadedFrameId = -1; 0149 KisFrameDataSerializer::Frame frame; 0150 0151 qint64 compressionTime = 0; 0152 0153 const QString framePath = m_d->filePathForFrame(frameId); 0154 0155 QFile file(framePath); 0156 KIS_SAFE_ASSERT_RECOVER_NOOP(file.exists()); 0157 if (!file.open(QFile::ReadOnly)) return frame; 0158 0159 QDataStream stream(&file); 0160 0161 int numTiles = 0; 0162 0163 stream >> loadedFrameId; 0164 stream >> frame.pixelSize; 0165 stream >> numTiles; 0166 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(loadedFrameId == frameId, KisFrameDataSerializer::Frame()); 0167 0168 0169 0170 for (int i = 0; i < numTiles; i++) { 0171 FrameTile tile(pool); 0172 stream >> tile.col; 0173 stream >> tile.row; 0174 stream >> tile.rect; 0175 0176 const int frameByteSize = frame.pixelSize * tile.rect.width() * tile.rect.height(); 0177 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(frameByteSize <= pool->chunkSize(frame.pixelSize), 0178 KisFrameDataSerializer::Frame()); 0179 0180 bool isCompressed = false; 0181 int inputSize = -1; 0182 0183 stream >> isCompressed; 0184 stream >> inputSize; 0185 0186 if (isCompressed) { 0187 const int maxBufferSize = compression.outputBufferSize(inputSize); 0188 quint8 *buffer = m_d->getCompressionBuffer(maxBufferSize); 0189 stream.readRawData((char*)buffer, inputSize); 0190 0191 tile.data.allocate(frame.pixelSize); 0192 0193 QElapsedTimer compTime; 0194 compTime.start(); 0195 0196 const int decompressedSize = 0197 compression.decompress(buffer, inputSize, tile.data.data(), frameByteSize); 0198 0199 compressionTime += compTime.nsecsElapsed(); 0200 0201 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(frameByteSize == decompressedSize, 0202 KisFrameDataSerializer::Frame()); 0203 0204 } else { 0205 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(frameByteSize == inputSize, 0206 KisFrameDataSerializer::Frame()); 0207 0208 tile.data.allocate(frame.pixelSize); 0209 stream.readRawData((char*)tile.data.data(), inputSize); 0210 } 0211 0212 frame.frameTiles.push_back(std::move(tile)); 0213 } 0214 0215 Q_UNUSED(compressionTime); 0216 0217 file.close(); 0218 0219 return frame; 0220 } 0221 0222 void KisFrameDataSerializer::moveFrame(int srcFrameId, int dstFrameId) 0223 { 0224 const QString srcFramePath = m_d->filePathForFrame(srcFrameId); 0225 const QString dstFramePath = m_d->filePathForFrame(dstFrameId); 0226 KIS_SAFE_ASSERT_RECOVER_RETURN(QFileInfo(srcFramePath).exists()); 0227 0228 KIS_SAFE_ASSERT_RECOVER(!QFileInfo(dstFramePath).exists()) { 0229 QFile::remove(dstFramePath); 0230 } 0231 0232 QFile::rename(srcFramePath, dstFramePath); 0233 } 0234 0235 bool KisFrameDataSerializer::hasFrame(int frameId) const 0236 { 0237 const QString framePath = m_d->filePathForFrame(frameId); 0238 return QFileInfo(framePath).exists(); 0239 } 0240 0241 void KisFrameDataSerializer::forgetFrame(int frameId) 0242 { 0243 const QString framePath = m_d->filePathForFrame(frameId); 0244 QFile::remove(framePath); 0245 } 0246 0247 boost::optional<qreal> KisFrameDataSerializer::estimateFrameUniqueness(const KisFrameDataSerializer::Frame &lhs, const KisFrameDataSerializer::Frame &rhs, qreal portion) 0248 { 0249 if (lhs.pixelSize != rhs.pixelSize) return boost::none; 0250 if (lhs.frameTiles.size() != rhs.frameTiles.size()) return boost::none; 0251 0252 const int pixelSize = lhs.pixelSize; 0253 int numSampledPixels = 0; 0254 int numUniquePixels = 0; 0255 const int sampleStep = portion > 0.0 ? qMax(1, qRound(1.0 / portion)) : 0; 0256 0257 for (int i = 0; i < int(lhs.frameTiles.size()); i++) { 0258 const FrameTile &lhsTile = lhs.frameTiles[i]; 0259 const FrameTile &rhsTile = rhs.frameTiles[i]; 0260 0261 if (lhsTile.col != rhsTile.col || 0262 lhsTile.row != rhsTile.row || 0263 lhsTile.rect != rhsTile.rect) { 0264 0265 return boost::none; 0266 } 0267 0268 if (sampleStep > 0) { 0269 const int numPixels = lhsTile.rect.width() * lhsTile.rect.height(); 0270 for (int j = 0; j < numPixels; j += sampleStep) { 0271 quint8 *lhsDataPtr = lhsTile.data.data() + j * pixelSize; 0272 quint8 *rhsDataPtr = rhsTile.data.data() + j * pixelSize; 0273 0274 if (lhsDataPtr && rhsDataPtr && std::memcmp(lhsDataPtr, rhsDataPtr, pixelSize) != 0) { 0275 numUniquePixels++; 0276 } 0277 numSampledPixels++; 0278 } 0279 } 0280 } 0281 0282 return numSampledPixels > 0 ? qreal(numUniquePixels) / numSampledPixels : 1.0; 0283 } 0284 0285 template <template <typename U> class OpPolicy, typename T> 0286 bool processData(T *dst, const T *src, int numUnits) 0287 { 0288 OpPolicy<T> op; 0289 0290 bool unitsAreSame = true; 0291 0292 for (int j = 0; j < numUnits; j++) { 0293 *dst = op(*dst, *src); 0294 0295 if (*dst != 0) { 0296 unitsAreSame = false; 0297 } 0298 0299 src++; 0300 dst++; 0301 } 0302 return unitsAreSame; 0303 } 0304 0305 0306 template<template <typename U> class OpPolicy> 0307 bool KisFrameDataSerializer::processFrames(KisFrameDataSerializer::Frame &dst, const KisFrameDataSerializer::Frame &src) 0308 { 0309 bool framesAreSame = true; 0310 0311 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(estimateFrameUniqueness(src, dst, 0.0), false); 0312 0313 for (int i = 0; i < int(src.frameTiles.size()); i++) { 0314 const FrameTile &srcTile = src.frameTiles[i]; 0315 FrameTile &dstTile = dst.frameTiles[i]; 0316 0317 const int numBytes = srcTile.rect.width() * srcTile.rect.height() * src.pixelSize; 0318 const int numQWords = numBytes / 8; 0319 0320 const quint64 *srcDataPtr = reinterpret_cast<const quint64*>(srcTile.data.data()); 0321 quint64 *dstDataPtr = reinterpret_cast<quint64*>(dstTile.data.data()); 0322 0323 framesAreSame &= processData<OpPolicy>(dstDataPtr, srcDataPtr, numQWords); 0324 0325 0326 const int tailBytes = numBytes % 8; 0327 const quint8 *srcTailDataPtr = srcTile.data.data() + numBytes - tailBytes; 0328 quint8 *dstTailDataPtr = dstTile.data.data() + numBytes - tailBytes; 0329 0330 framesAreSame &= processData<OpPolicy>(dstTailDataPtr, srcTailDataPtr, tailBytes); 0331 } 0332 0333 return framesAreSame; 0334 } 0335 0336 bool KisFrameDataSerializer::subtractFrames(KisFrameDataSerializer::Frame &dst, const KisFrameDataSerializer::Frame &src) 0337 { 0338 return processFrames<std::minus>(dst, src); 0339 } 0340 0341 void KisFrameDataSerializer::addFrames(KisFrameDataSerializer::Frame &dst, const KisFrameDataSerializer::Frame &src) 0342 { 0343 // TODO: don't spend time on calculation of "framesAreSame" in this case 0344 (void) processFrames<std::plus>(dst, src); 0345 }