Krita Source Code Documentation
Loading...
Searching...
No Matches
qgiflibhandler.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2009 Shawn T. Rutledge (shawn.t.rutledge@gmail.com)
3 * SPDX-FileCopyrightText: 2018 Boudewijn Rempt <boud@valdyas.org>
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 */
7#include "qgiflibhandler.h"
8#include <QDebug>
9#include <QVariant>
10#include <gif_lib.h>
11#include <string.h> // memset
12#include <QPainter>
13
14extern int _GifError;
15
16static const int InterlacedOffset[] = { 0, 4, 2, 1 }; /* The way Interlaced image should */
17static const int InterlacedJumps[] = { 8, 8, 4, 2 }; /* be read - offsets and jumps... */
18
19int doOutput(GifFileType* gif, const GifByteType * data, int i)
20{
21 QIODevice* out = (QIODevice*)gif->UserData;
22 // qDebug("given %d bytes to write; device is writeable? %d", i, out->isWritable());
23 return out->write((const char*)data, i);
24}
25
26int doInput(GifFileType* gif, GifByteType* data, int i)
27{
28 QIODevice* in = (QIODevice*)gif->UserData;
29 return in->read((char*)data, i);
30}
31
33 : QImageIOHandler()
34{
35}
36
38{
39 if (canRead(device())) {
40 setFormat("gif");
41 return true;
42 }
43 return false;
44}
45
46bool QGIFLibHandler::read ( QImage * image )
47{
48 // The contents of this function are based on gif2rgb.c, from the giflib source.
49// qDebug("QGIFLibHandler::read into image with size %d x %d", image->size().width(), image->size().height());
50
51 int err;
52 GifFileType* gifFile = DGifOpen(device(), doInput, &err);
53 if (!gifFile) {
54 qWarning() << "Received error code" << err;
55 return false;
56 }
57// qDebug("dimensions %d x %d", gifFile->SWidth, gifFile->SHeight);
58
59 *image = QImage(gifFile->SWidth, gifFile->SHeight, QImage::Format_Indexed8);
60
61 GifRecordType recordType;
62 ColorMapObject* ColorMap;
63
64 int i, row, imageNum = 0, topRow, width, height;
65 int transColor = -1;
66 do
67 {
68 DGifGetRecordType(gifFile, &recordType);
69 switch (recordType)
70 {
71 case IMAGE_DESC_RECORD_TYPE:
72 if (DGifGetImageDesc(gifFile) == GIF_ERROR)
73 {
74 qWarning("QGIFLibHandler::read: error %d", gifFile->Error);
75 return false;
76 }
77 topRow = gifFile->Image.Top; /* Image Position relative to Screen. */
78 width = gifFile->Image.Width;
79 height = gifFile->Image.Height;
80 //qDebug("Image %d at (%d, %d) [%dx%d]", ++imageNum, gifFile->Image.Left, topRow, width, height);
81 if (gifFile->Image.Left + width > gifFile->SWidth ||
82 gifFile->Image.Top + height > gifFile->SHeight)
83 {
84 qWarning("Image %d is not confined to screen dimension, aborted.", imageNum);
85 return false;
86 }
87
88 // Pre-fill with background color
89// qDebug("background color is at index %d", gifFile->SBackGroundColor);
90 image->fill(gifFile->SBackGroundColor);
91
92 // Now read the image data
93 if (gifFile->Image.Interlace)
94 {
95 /* Need to perform 4 passes on the images: */
96 for (i = 0; i < 4; i++)
97 for (row = topRow + InterlacedOffset[i]; row < topRow + height;
98 row += InterlacedJumps[i])
99 {
100 if (DGifGetLine(gifFile, image->scanLine(row), width) == GIF_ERROR)
101 {
102 qWarning("QGIFLibHandler::read: error %d", gifFile->Error);
103 return false;
104 }
105 // else
106 // qDebug("got row %d: %d %d %d %d %d %d %d %d ...", row,
107 // image->scanLine(row)[0], image->scanLine(row)[1], image->scanLine(row)[2], image->scanLine(row)[3],
108 // image->scanLine(row)[4], image->scanLine(row)[5], image->scanLine(row)[6], image->scanLine(row)[7]);
109 }
110 }
111 else
112 {
113 for (row = 0; row < height; row++)
114 {
115 if (DGifGetLine(gifFile, image->scanLine(row), width) == GIF_ERROR)
116 {
117 qWarning("QGIFLibHandler::read: error %d", gifFile->Error);
118 return false;
119 }
120 // else
121 // qDebug("got row %d: %d %d %d %d %d %d %d %d ...", row,
122 // image->scanLine(row)[0], image->scanLine(row)[1], image->scanLine(row)[2], image->scanLine(row)[3],
123 // image->scanLine(row)[4], image->scanLine(row)[5], image->scanLine(row)[6], image->scanLine(row)[7]);
124 }
125 }
126 break;
127 case EXTENSION_RECORD_TYPE:
128 {
129 int extCode;
130 GifByteType* extData;
131 /* Skip any extension blocks in file: */
132 if (DGifGetExtension(gifFile, &extCode, &extData) == GIF_ERROR)
133 {
134 qWarning("QGIFLibHandler::read: error %d", gifFile->Error);
135 return false;
136 }
137 while (extData != NULL)
138 {
139 int len = extData[0];
140 switch (extCode)
141 {
142 case GRAPHICS_EXT_FUNC_CODE: // Graphics control extension
143// qDebug("graphics control: %x %x %x %x %x", extData[0], extData[1], extData[2], extData[3], extData[4]);
144 // Should be block size, packed fields, delay time,
145 // transparent color, block terminator
146 // see doc/gif89.txt in libgif source package
147 // If the trans bit is set in packed fields,
148 // then set the trans color to the one given
149 if (extData[1] & 0x01)
150 {
151 transColor = extData[3];
152// qDebug("transparent color is at index %d", transColor);
154 // image->fill(transColor);
155 }
156 break;
157 case COMMENT_EXT_FUNC_CODE:
158 {
159 QByteArray comment((char*)(extData + 1), len);
160 // qDebug("comment of len %d: \"%s\"", len, comment.constData());
161 image->setText("Description", comment);
162 }
163 break;
164 case PLAINTEXT_EXT_FUNC_CODE:
165 break;
166 }
167 if (DGifGetExtensionNext(gifFile, &extData) == GIF_ERROR)
168 {
169 qWarning("QGIFLibHandler::read: error %d", gifFile->Error);
170 return false;
171 }
172 }
173 }
174 break;
175 case TERMINATE_RECORD_TYPE:
176 break;
177 default:
178 break;
179 }
180 }
181 while (recordType != TERMINATE_RECORD_TYPE);
182
183 // BackGround = gifFile->SBackGroundColor;
184 ColorMap = (gifFile->Image.ColorMap
185 ? gifFile->Image.ColorMap
186 : gifFile->SColorMap);
187 if (!ColorMap)
188 {
189 qWarning("QGIFLibHandler::read: Image does not have a colormap");
190 return false;
191 }
192 int ccount = ColorMap->ColorCount;
193 image->setColorCount(ccount);
194 for (i = 0; i < ccount; ++i)
195 {
196 GifColorType gifColor = ColorMap->Colors[i];
197 QRgb color = gifColor.Blue | (gifColor.Green << 8) | (gifColor.Red << 16);
198 // If this is not the transparent color,
199 // set the alpha to opaque.
200 if (i != transColor)
201 color |= 0xff << 24;
202 // qDebug("color %d: 0x%X", i, color);
203 image->setColor(i, color);
204 }
205
206 return true;
207}
208
209bool QGIFLibHandler::canRead(QIODevice *device)
210{
211 if (!device) {
212 qWarning("QGIFLibHandler::canRead() called with no device");
213 return false;
214 }
215
216 char head[6];
217 if (device->peek(head, sizeof(head)) == sizeof(head))
218 return qstrncmp(head, "GIF87a", 6) == 0
219 || qstrncmp(head, "GIF89a", 6) == 0;
220 return false;
221}
222
223bool QGIFLibHandler::write ( const QImage & image )
224{
225 QImage toWrite(image);
227 if (toWrite.colorCount() == 0 || toWrite.colorCount() > 256)
228 toWrite = image.convertToFormat(QImage::Format_Indexed8);
229
230 QVector<QRgb> colorTable = toWrite.colorTable();
231 ColorMapObject cmap;
232 // colorCount must be a power of 2
233 int colorCount = 1 << GifBitSize(toWrite.colorCount());
234 cmap.ColorCount = colorCount;
235 cmap.BitsPerPixel = 8;
236 GifColorType* colorValues = (GifColorType*)malloc(cmap.ColorCount * sizeof(GifColorType));
237 cmap.Colors = colorValues;
238 int c = 0;
239 for(; c < toWrite.colorCount(); ++c)
240 {
241// qDebug("color %d has %02X%02X%02X", c, qRed(colorTable[c]), qGreen(colorTable[c]), qBlue(colorTable[c]));
242 colorValues[c].Red = qRed(colorTable[c]);
243 colorValues[c].Green = qGreen(colorTable[c]);
244 colorValues[c].Blue = qBlue(colorTable[c]);
245 }
246 // In case we had an actual number of colors that's not a power of 2,
247 // fill the rest with something (black perhaps).
248 for (; c < colorCount; ++c)
249 {
250 colorValues[c].Red = 0;
251 colorValues[c].Green = 0;
252 colorValues[c].Blue = 0;
253 }
255
256
258 int err;
259 GifFileType *gif = EGifOpen(device(), doOutput, &err);
260
262 // Because of this call, libgif is not reentrant
263 EGifSetGifVersion(gif, true);
264
266 if (EGifPutScreenDesc(gif, toWrite.width(), toWrite.height(), colorCount, 0, &cmap) == GIF_ERROR) {
267 qWarning("EGifPutScreenDesc returned error %d", gif->Error);
268 }
269
270 QVariant descText = option(QImageIOHandler::Description);
271 if (descText.type() == QVariant::String)
272 {
273 QString comment = descText.toString();
274 // Will be something like "Description: actual text" or just
275 // ": actual text", so remove everything leading up to and
276 // including the first colon and the space following it.
277 int idx = comment.indexOf(": ");
278 if (idx >= 0)
279 comment.remove(0, idx + 2);
280 // qDebug() << "comment:" << comment;
281 if (!comment.isEmpty())
282 EGifPutComment(gif, comment.toUtf8().constData());
283 }
284 // else
285 // qDebug("description is of qvariant type %d", descText.type());
286
288 if (EGifPutImageDesc(gif, 0, 0, toWrite.width(), toWrite.height(), 0, &cmap) == GIF_ERROR)
289 qWarning("EGifPutImageDesc returned error %d", gif->Error);
290
291 int lc = toWrite.height();
292
293 // NOTE: we suppose that the pixel size is exactly 1 byte, right now we
294 // cannot save anything else
295 int llen = toWrite.width();
296
297 // qDebug("will write %d lines, %d bytes each", lc, llen);
298
299 for (int l = 0; l < lc; ++l)
300 {
301 uchar* line = toWrite.scanLine(l);
302 if (EGifPutLine(gif, (GifPixelType*)line, llen) == GIF_ERROR)
303 {
304 int i = gif->Error;
305 qWarning("EGifPutLine returned error %d", i);
306 }
307 }
308
309 EGifCloseFile(gif, &err);
310 free(colorValues);
311
312 return true;
313}
314
315bool QGIFLibHandler::supportsOption ( ImageOption option ) const
316{
317 // qDebug("supportsOption %d", option);
318 switch (option)
319 {
320 // These are relevant only for reading
321 case QImageIOHandler::ImageFormat:
322 case QImageIOHandler::Size:
323 // This is relevant for both reading and writing
324 case QImageIOHandler::Description:
325 return true;
326 break;
327 default:
328 return false;
329 }
330}
331
332void QGIFLibHandler::setOption ( ImageOption option, const QVariant & value )
333{
334 // qDebug("setOption given option %d, variant of type %d", option, value.type());
335 if (option == QImageIOHandler::Description)
336 m_description = value.toString();
337}
338
339QVariant QGIFLibHandler::option( ImageOption option ) const
340{
341 switch (option)
342 {
343 case QImageIOHandler::ImageFormat:
344 return QVariant();
345 break;
346 case QImageIOHandler::Size:
347 return QVariant();
348 break;
349 case QImageIOHandler::Description:
350 return QVariant(m_description);
351 break;
352 default:
353 return QVariant();
354 }
355}
float value(const T *src, size_t ch)
bool read(QImage *image) override
bool supportsOption(ImageOption option) const override
QString m_description
void setOption(ImageOption option, const QVariant &value) override
QVariant option(ImageOption option) const override
bool canRead() const override
bool write(const QImage &image) override
unsigned int QRgb
int doInput(GifFileType *gif, GifByteType *data, int i)
static const int InterlacedOffset[]
int doOutput(GifFileType *gif, const GifByteType *data, int i)
static const int InterlacedJumps[]
int _GifError