1 /*
2  *  Copyright (c) 2010 Dmitry Kazakov <dimula73@gmail.com>
3  *
4  *  This program is free software; you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation; either version 2 of the License, or
7  *  (at your option) any later version.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with this program; if not, write to the Free Software
16  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  */
18 
19 #include "kis_tile_compressor_2.h"
20 #include "kis_lzf_compression.h"
21 #include <QIODevice>
22 #include "kis_paint_device_writer.h"
23 #define TILE_DATA_SIZE(pixelSize) ((pixelSize) * KisTileData::WIDTH * KisTileData::HEIGHT)
24 
25 const QString KisTileCompressor2::m_compressionName = "LZF";
26 
27 
KisTileCompressor2()28 KisTileCompressor2::KisTileCompressor2()
29 {
30     m_compression = new KisLzfCompression();
31 }
32 
~KisTileCompressor2()33 KisTileCompressor2::~KisTileCompressor2()
34 {
35     delete m_compression;
36 }
37 
writeTile(KisTileSP tile,KisPaintDeviceWriter & store)38 bool KisTileCompressor2::writeTile(KisTileSP tile, KisPaintDeviceWriter &store)
39 {
40     const qint32 tileDataSize = TILE_DATA_SIZE(tile->pixelSize());
41     prepareStreamingBuffer(tileDataSize);
42 
43     qint32 bytesWritten;
44 
45     tile->lockForRead();
46     compressTileData(tile->tileData(), (quint8*)m_streamingBuffer.data(),
47                      m_streamingBuffer.size(), bytesWritten);
48     tile->unlockForRead();
49 
50     QString header = getHeader(tile, bytesWritten);
51     bool retval = true;
52     retval = store.write(header.toLatin1());
53     if (!retval) {
54         warnFile << "Failed to write the tile header";
55     }
56     retval = store.write(m_streamingBuffer.data(), bytesWritten);
57     if (!retval) {
58         warnFile << "Failed to write the tile datak";
59     }
60     return retval;
61 }
62 
readTile(QIODevice * stream,KisTiledDataManager * dm)63 bool KisTileCompressor2::readTile(QIODevice *stream, KisTiledDataManager *dm)
64 {
65     const qint32 tileDataSize = TILE_DATA_SIZE(pixelSize(dm));
66     prepareStreamingBuffer(tileDataSize);
67 
68     QByteArray header = stream->readLine(maxHeaderLength());
69 
70     QList<QByteArray> headerItems = header.trimmed().split(',');
71     if (headerItems.size() == 4) {
72         qint32 x = headerItems.takeFirst().toInt();
73         qint32 y = headerItems.takeFirst().toInt();
74         QString compressionName = headerItems.takeFirst();
75         qint32 dataSize = headerItems.takeFirst().toInt();
76 
77         Q_ASSERT(headerItems.isEmpty());
78         Q_ASSERT(compressionName == m_compressionName);
79 
80         qint32 row = yToRow(dm, y);
81         qint32 col = xToCol(dm, x);
82 
83         KisTileSP tile = dm->getTile(col, row, true);
84 
85         stream->read(m_streamingBuffer.data(), dataSize);
86 
87         tile->lockForWrite();
88         bool res = decompressTileData((quint8*)m_streamingBuffer.data(), dataSize, tile->tileData());
89         tile->unlockForWrite();
90         return res;
91     }
92     return false;
93 }
94 
prepareStreamingBuffer(qint32 tileDataSize)95 void KisTileCompressor2::prepareStreamingBuffer(qint32 tileDataSize)
96 {
97     /**
98      * TODO: delete this buffer!
99      * It is better to use one of other two buffers to store streams
100      */
101     m_streamingBuffer.resize(tileDataSize + 1);
102 }
103 
prepareWorkBuffers(qint32 tileDataSize)104 void KisTileCompressor2::prepareWorkBuffers(qint32 tileDataSize)
105 {
106     const qint32 bufferSize = m_compression->outputBufferSize(tileDataSize);
107 
108     m_linearizationBuffer.resize(tileDataSize);
109     m_compressionBuffer.resize(bufferSize);
110 }
111 
compressTileData(KisTileData * tileData,quint8 * buffer,qint32 bufferSize,qint32 & bytesWritten)112 void KisTileCompressor2::compressTileData(KisTileData *tileData,
113                                           quint8 *buffer,
114                                           qint32 bufferSize,
115                                           qint32 &bytesWritten)
116 {
117     const qint32 pixelSize = tileData->pixelSize();
118     const qint32 tileDataSize = TILE_DATA_SIZE(pixelSize);
119     qint32 compressedBytes;
120 
121     Q_UNUSED(bufferSize);
122     Q_ASSERT(bufferSize >= tileDataSize + 1);
123 
124     prepareWorkBuffers(tileDataSize);
125 
126     KisAbstractCompression::linearizeColors(tileData->data(), (quint8*)m_linearizationBuffer.data(),
127                                             tileDataSize, pixelSize);
128 
129     compressedBytes = m_compression->compress((quint8*)m_linearizationBuffer.data(), tileDataSize,
130                                               (quint8*)m_compressionBuffer.data(), m_compressionBuffer.size());
131 
132     if(compressedBytes < tileDataSize) {
133         buffer[0] = COMPRESSED_DATA_FLAG;
134         memcpy(buffer + 1, m_compressionBuffer.data(), compressedBytes);
135         bytesWritten = compressedBytes + 1;
136     }
137     else {
138         buffer[0] = RAW_DATA_FLAG;
139         memcpy(buffer + 1, tileData->data(), tileDataSize);
140         bytesWritten = tileDataSize + 1;
141     }
142 }
143 
decompressTileData(quint8 * buffer,qint32 bufferSize,KisTileData * tileData)144 bool KisTileCompressor2::decompressTileData(quint8 *buffer,
145                                             qint32 bufferSize,
146                                             KisTileData *tileData)
147 {
148     const qint32 pixelSize = tileData->pixelSize();
149     const qint32 tileDataSize = TILE_DATA_SIZE(pixelSize);
150 
151     if(buffer[0] == COMPRESSED_DATA_FLAG) {
152         prepareWorkBuffers(tileDataSize);
153 
154         qint32 bytesWritten;
155         bytesWritten = m_compression->decompress(buffer + 1, bufferSize - 1,
156                                                  (quint8*)m_linearizationBuffer.data(), tileDataSize);
157         if (bytesWritten == tileDataSize) {
158             KisAbstractCompression::delinearizeColors((quint8*)m_linearizationBuffer.data(),
159                                                       tileData->data(),
160                                                       tileDataSize, pixelSize);
161             return true;
162         }
163         return false;
164     }
165     else {
166         memcpy(tileData->data(), buffer + 1, tileDataSize);
167         return true;
168     }
169     return false;
170 
171 }
172 
tileDataBufferSize(KisTileData * tileData)173 qint32 KisTileCompressor2::tileDataBufferSize(KisTileData *tileData)
174 {
175     return TILE_DATA_SIZE(tileData->pixelSize()) + 1;
176 }
177 
maxHeaderLength()178 inline qint32 KisTileCompressor2::maxHeaderLength()
179 {
180     static const qint32 QINT32_LENGTH = 11;
181     static const qint32 COMPRESSION_NAME_LENGTH = 5;
182     static const qint32 SEPARATORS_LENGTH = 4;
183 
184     return 3 * QINT32_LENGTH + COMPRESSION_NAME_LENGTH + SEPARATORS_LENGTH;
185 }
186 
getHeader(KisTileSP tile,qint32 compressedSize)187 inline QString KisTileCompressor2::getHeader(KisTileSP tile,
188                                              qint32 compressedSize)
189 {
190     qint32 x, y;
191     qint32 width, height;
192     tile->extent().getRect(&x, &y, &width, &height);
193 
194     return QString("%1,%2,%3,%4\n").arg(x).arg(y).arg(m_compressionName).arg(compressedSize);
195 }
196