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 }