diff --git a/libs/ui/KisFrameDataSerializer.cpp b/libs/ui/KisFrameDataSerializer.cpp index 7abb6fb156..5894face65 100644 --- a/libs/ui/KisFrameDataSerializer.cpp +++ b/libs/ui/KisFrameDataSerializer.cpp @@ -1,357 +1,354 @@ /* * Copyright (c) 2018 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KisFrameDataSerializer.h" #include #include #include #include "tiles3/swap/kis_lzf_compression.h" struct KRITAUI_NO_EXPORT KisFrameDataSerializer::Private { Private(const QString &frameCachePath) : framesDir( (!frameCachePath.isEmpty() ? frameCachePath : QDir::tempPath()) + QDir::separator() + "KritaFrameCacheXXXXXX") { KIS_SAFE_ASSERT_RECOVER_NOOP(framesDir.isValid()); framesDirObject = QDir(framesDir.path()); framesDirObject.makeAbsolute(); } QString subfolderNameForFrame(int frameId) { const int subfolderIndex = frameId & 0xff00; return QString::number(subfolderIndex); } QString fileNameForFrame(int frameId) { return QString("frame_%1").arg(frameId); } QString filePathForFrame(int frameId) { return framesDirObject.filePath( subfolderNameForFrame(frameId) + QDir::separator() + fileNameForFrame(frameId)); } int generateFrameId() { // TODO: handle wrapping and range compression return nextFrameId++; } quint8* getCompressionBuffer(int size) { if (compressionBuffer.size() < size) { compressionBuffer.resize(size); } return reinterpret_cast(compressionBuffer.data()); } QTemporaryDir framesDir; QDir framesDirObject; int nextFrameId = 0; QByteArray compressionBuffer; }; KisFrameDataSerializer::KisFrameDataSerializer() : KisFrameDataSerializer(QString()) { } KisFrameDataSerializer::KisFrameDataSerializer(const QString &frameCachePath) : m_d(new Private(frameCachePath)) { } KisFrameDataSerializer::~KisFrameDataSerializer() { } int KisFrameDataSerializer::saveFrame(const KisFrameDataSerializer::Frame &frame) { KisLzfCompression compression; const int frameId = m_d->generateFrameId(); const QString frameSubfolder = m_d->subfolderNameForFrame(frameId); if (!m_d->framesDirObject.exists(frameSubfolder)) { m_d->framesDirObject.mkpath(frameSubfolder); } const QString frameRelativePath = frameSubfolder + QDir::separator() + m_d->fileNameForFrame(frameId); if (m_d->framesDirObject.exists(frameRelativePath)) { qWarning() << "WARNING: overwriting existing frame file!" << frameRelativePath; forgetFrame(frameId); } const QString frameFilePath = m_d->framesDirObject.filePath(frameRelativePath); QFile file(frameFilePath); file.open(QFile::WriteOnly); QDataStream stream(&file); stream << frameId; stream << frame.pixelSize; stream << int(frame.frameTiles.size()); for (int i = 0; i < int(frame.frameTiles.size()); i++) { const FrameTile &tile = frame.frameTiles[i]; stream << tile.col; stream << tile.row; stream << tile.rect; const int frameByteSize = frame.pixelSize * tile.rect.width() * tile.rect.height(); const int maxBufferSize = compression.outputBufferSize(frameByteSize); quint8 *buffer = m_d->getCompressionBuffer(maxBufferSize); const int compressedSize = compression.compress(tile.data.data(), frameByteSize, buffer, maxBufferSize); ENTER_FUNCTION() << ppVar(compressedSize) << ppVar(frameByteSize); const bool isCompressed = compressedSize < frameByteSize; stream << isCompressed; if (isCompressed) { stream << compressedSize; stream.writeRawData((char*)buffer, compressedSize); } else { stream << frameByteSize; stream.writeRawData((char*)tile.data.data(), frameByteSize); } } file.close(); return frameId; } KisFrameDataSerializer::Frame KisFrameDataSerializer::loadFrame(int frameId, KisTextureTileInfoPoolSP pool) { KisLzfCompression compression; QElapsedTimer loadingTime; loadingTime.start(); int loadedFrameId = -1; KisFrameDataSerializer::Frame frame; qint64 compressionTime = 0; const QString framePath = m_d->filePathForFrame(frameId); QFile file(framePath); KIS_SAFE_ASSERT_RECOVER_NOOP(file.exists()); if (!file.open(QFile::ReadOnly)) return frame; QDataStream stream(&file); int numTiles = 0; stream >> loadedFrameId; stream >> frame.pixelSize; stream >> numTiles; KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(loadedFrameId == frameId, KisFrameDataSerializer::Frame()); for (int i = 0; i < numTiles; i++) { FrameTile tile(pool); stream >> tile.col; stream >> tile.row; stream >> tile.rect; const int frameByteSize = frame.pixelSize * tile.rect.width() * tile.rect.height(); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(frameByteSize <= pool->chunkSize(frame.pixelSize), KisFrameDataSerializer::Frame()); bool isCompressed = false; int inputSize = -1; stream >> isCompressed; stream >> inputSize; if (isCompressed) { const int maxBufferSize = compression.outputBufferSize(inputSize); quint8 *buffer = m_d->getCompressionBuffer(maxBufferSize); stream.readRawData((char*)buffer, inputSize); tile.data.allocate(frame.pixelSize); QElapsedTimer compTime; compTime.start(); const int decompressedSize = compression.decompress(buffer, inputSize, tile.data.data(), frameByteSize); compressionTime += compTime.nsecsElapsed(); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(frameByteSize == decompressedSize, KisFrameDataSerializer::Frame()); } else { KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(frameByteSize == inputSize, KisFrameDataSerializer::Frame()); tile.data.allocate(frame.pixelSize); stream.readRawData((char*)tile.data.data(), inputSize); } frame.frameTiles.push_back(std::move(tile)); } file.close(); - CC.addVal(compressionTime / 1000); - CC.addTotal(loadingTime.nsecsElapsed() / 1000); - return frame; } void KisFrameDataSerializer::moveFrame(int srcFrameId, int dstFrameId) { const QString srcFramePath = m_d->filePathForFrame(srcFrameId); const QString dstFramePath = m_d->filePathForFrame(dstFrameId); KIS_SAFE_ASSERT_RECOVER_RETURN(QFileInfo(srcFramePath).exists()); KIS_SAFE_ASSERT_RECOVER(!QFileInfo(dstFramePath).exists()) { QFile::remove(dstFramePath); } QFile::rename(srcFramePath, dstFramePath); } bool KisFrameDataSerializer::hasFrame(int frameId) const { const QString framePath = m_d->filePathForFrame(frameId); return QFileInfo(framePath).exists(); } void KisFrameDataSerializer::forgetFrame(int frameId) { const QString framePath = m_d->filePathForFrame(frameId); QFile::remove(framePath); } boost::optional KisFrameDataSerializer::estimateFrameUniqueness(const KisFrameDataSerializer::Frame &lhs, const KisFrameDataSerializer::Frame &rhs, qreal portion) { if (lhs.pixelSize != rhs.pixelSize) return boost::none; if (lhs.frameTiles.size() != rhs.frameTiles.size()) return boost::none; const int pixelSize = lhs.pixelSize; int numSampledPixels = 0; int numUniquePixels = 0; const int sampleStep = portion > 0.0 ? qMax(1, qRound(1.0 / portion)) : 0; for (int i = 0; i < int(lhs.frameTiles.size()); i++) { const FrameTile &lhsTile = lhs.frameTiles[i]; const FrameTile &rhsTile = rhs.frameTiles[i]; if (lhsTile.col != rhsTile.col || lhsTile.row != rhsTile.row || lhsTile.rect != rhsTile.rect) { return boost::none; } if (sampleStep > 0) { const int numPixels = lhsTile.rect.width() * lhsTile.rect.height(); for (int j = 0; j < numPixels; j += sampleStep) { quint8 *lhsDataPtr = lhsTile.data.data() + j * pixelSize; quint8 *rhsDataPtr = rhsTile.data.data() + j * pixelSize; if (std::memcmp(lhsDataPtr, rhsDataPtr, pixelSize) != 0) { numUniquePixels++; } numSampledPixels++; } } } return numSampledPixels > 0 ? qreal(numUniquePixels) / numSampledPixels : 1.0; } template