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