File indexing completed on 2024-12-22 04:10:30

0001 /*
0002  *  SPDX-FileCopyrightText: 2010 Dmitry Kazakov <dimula73@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "kis_tile_compressor_2.h"
0008 #include "kis_lzf_compression.h"
0009 #include <QIODevice>
0010 #include "kis_paint_device_writer.h"
0011 #define TILE_DATA_SIZE(pixelSize) ((pixelSize) * KisTileData::WIDTH * KisTileData::HEIGHT)
0012 
0013 const QString KisTileCompressor2::m_compressionName = "LZF";
0014 
0015 
0016 KisTileCompressor2::KisTileCompressor2()
0017 {
0018     m_compression = new KisLzfCompression();
0019 }
0020 
0021 KisTileCompressor2::~KisTileCompressor2()
0022 {
0023     delete m_compression;
0024 }
0025 
0026 bool KisTileCompressor2::writeTile(KisTileSP tile, KisPaintDeviceWriter &store)
0027 {
0028     const qint32 tileDataSize = TILE_DATA_SIZE(tile->pixelSize());
0029     prepareStreamingBuffer(tileDataSize);
0030 
0031     qint32 bytesWritten;
0032 
0033     tile->lockForRead();
0034     compressTileData(tile->tileData(), (quint8*)m_streamingBuffer.data(),
0035                      m_streamingBuffer.size(), bytesWritten);
0036     tile->unlockForRead();
0037 
0038     QString header = getHeader(tile, bytesWritten);
0039     bool retval = true;
0040     retval = store.write(header.toLatin1());
0041     if (!retval) {
0042         warnFile << "Failed to write the tile header";
0043     }
0044     retval = store.write(m_streamingBuffer.data(), bytesWritten);
0045     if (!retval) {
0046         warnFile << "Failed to write the tile data";
0047     }
0048     return retval;
0049 }
0050 
0051 bool KisTileCompressor2::readTile(QIODevice *stream, KisTiledDataManager *dm)
0052 {
0053     const qint32 tileDataSize = TILE_DATA_SIZE(pixelSize(dm));
0054     prepareStreamingBuffer(tileDataSize);
0055 
0056     QByteArray header = stream->readLine(maxHeaderLength());
0057 
0058     QList<QByteArray> headerItems = header.trimmed().split(',');
0059     if (headerItems.size() == 4) {
0060         qint32 x = headerItems.takeFirst().toInt();
0061         qint32 y = headerItems.takeFirst().toInt();
0062         QString compressionName = headerItems.takeFirst();
0063         qint32 dataSize = headerItems.takeFirst().toInt();
0064 
0065         Q_ASSERT(headerItems.isEmpty());
0066         Q_ASSERT(compressionName == m_compressionName);
0067 
0068         qint32 row = yToRow(dm, y);
0069         qint32 col = xToCol(dm, x);
0070 
0071         KisTileSP tile = dm->getTile(col, row, true);
0072 
0073         stream->read(m_streamingBuffer.data(), dataSize);
0074 
0075         tile->lockForWrite();
0076         bool res = decompressTileData((quint8*)m_streamingBuffer.data(), dataSize, tile->tileData());
0077         tile->unlockForWrite();
0078         return res;
0079     }
0080     return false;
0081 }
0082 
0083 void KisTileCompressor2::prepareStreamingBuffer(qint32 tileDataSize)
0084 {
0085     /**
0086      * TODO: delete this buffer!
0087      * It is better to use one of other two buffers to store streams
0088      */
0089     m_streamingBuffer.resize(tileDataSize + 1);
0090 }
0091 
0092 void KisTileCompressor2::prepareWorkBuffers(qint32 tileDataSize)
0093 {
0094     const qint32 bufferSize = m_compression->outputBufferSize(tileDataSize);
0095 
0096     if (m_linearizationBuffer.size() < tileDataSize) {
0097         m_linearizationBuffer.resize(tileDataSize);
0098     }
0099 
0100     if (m_compressionBuffer.size() < bufferSize) {
0101         m_compressionBuffer.resize(bufferSize);
0102     }
0103 }
0104 
0105 void KisTileCompressor2::compressTileData(KisTileData *tileData,
0106                                           quint8 *buffer,
0107                                           qint32 bufferSize,
0108                                           qint32 &bytesWritten)
0109 {
0110     const qint32 pixelSize = tileData->pixelSize();
0111     const qint32 tileDataSize = TILE_DATA_SIZE(pixelSize);
0112     qint32 compressedBytes;
0113 
0114     Q_UNUSED(bufferSize);
0115     Q_ASSERT(bufferSize >= tileDataSize + 1);
0116 
0117     prepareWorkBuffers(tileDataSize);
0118 
0119     KisAbstractCompression::linearizeColors(tileData->data(), (quint8*)m_linearizationBuffer.data(),
0120                                             tileDataSize, pixelSize);
0121 
0122     compressedBytes = m_compression->compress((quint8*)m_linearizationBuffer.data(), tileDataSize,
0123                                               (quint8*)m_compressionBuffer.data(), m_compressionBuffer.size());
0124 
0125     if(compressedBytes < tileDataSize) {
0126         buffer[0] = COMPRESSED_DATA_FLAG;
0127         memcpy(buffer + 1, m_compressionBuffer.data(), compressedBytes);
0128         bytesWritten = compressedBytes + 1;
0129     }
0130     else {
0131         buffer[0] = RAW_DATA_FLAG;
0132         memcpy(buffer + 1, tileData->data(), tileDataSize);
0133         bytesWritten = tileDataSize + 1;
0134     }
0135 }
0136 
0137 bool KisTileCompressor2::decompressTileData(quint8 *buffer,
0138                                             qint32 bufferSize,
0139                                             KisTileData *tileData)
0140 {
0141     const qint32 pixelSize = tileData->pixelSize();
0142     const qint32 tileDataSize = TILE_DATA_SIZE(pixelSize);
0143 
0144     if(buffer[0] == COMPRESSED_DATA_FLAG) {
0145         prepareWorkBuffers(tileDataSize);
0146 
0147         qint32 bytesWritten;
0148         bytesWritten = m_compression->decompress(buffer + 1, bufferSize - 1,
0149                                                  (quint8*)m_linearizationBuffer.data(), tileDataSize);
0150         if (bytesWritten == tileDataSize) {
0151             KisAbstractCompression::delinearizeColors((quint8*)m_linearizationBuffer.data(),
0152                                                       tileData->data(),
0153                                                       tileDataSize, pixelSize);
0154             return true;
0155         }
0156         return false;
0157     }
0158     else {
0159         memcpy(tileData->data(), buffer + 1, tileDataSize);
0160         return true;
0161     }
0162     return false;
0163 
0164 }
0165 
0166 qint32 KisTileCompressor2::tileDataBufferSize(KisTileData *tileData)
0167 {
0168     return TILE_DATA_SIZE(tileData->pixelSize()) + 1;
0169 }
0170 
0171 inline qint32 KisTileCompressor2::maxHeaderLength()
0172 {
0173     static const qint32 QINT32_LENGTH = 11;
0174     static const qint32 COMPRESSION_NAME_LENGTH = 5;
0175     static const qint32 SEPARATORS_LENGTH = 4;
0176 
0177     return 3 * QINT32_LENGTH + COMPRESSION_NAME_LENGTH + SEPARATORS_LENGTH;
0178 }
0179 
0180 inline QString KisTileCompressor2::getHeader(KisTileSP tile,
0181                                              qint32 compressedSize)
0182 {
0183     qint32 x, y;
0184     qint32 width, height;
0185     tile->extent().getRect(&x, &y, &width, &height);
0186 
0187     return QString("%1,%2,%3,%4\n").arg(x).arg(y).arg(m_compressionName).arg(compressedSize);
0188 }