File indexing completed on 2024-06-16 04:16:54
0001 /* 0002 * SPDX-FileCopyrightText: 2009 Shawn T. Rutledge (shawn.t.rutledge@gmail.com) 0003 * SPDX-FileCopyrightText: 2018 Boudewijn Rempt <boud@valdyas.org> 0004 * 0005 * SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 #include "qgiflibhandler.h" 0008 #include <QDebug> 0009 #include <QVariant> 0010 #include <gif_lib.h> 0011 #include <string.h> // memset 0012 #include <QPainter> 0013 0014 extern int _GifError; 0015 0016 static const int InterlacedOffset[] = { 0, 4, 2, 1 }; /* The way Interlaced image should */ 0017 static const int InterlacedJumps[] = { 8, 8, 4, 2 }; /* be read - offsets and jumps... */ 0018 0019 int doOutput(GifFileType* gif, const GifByteType * data, int i) 0020 { 0021 QIODevice* out = (QIODevice*)gif->UserData; 0022 // qDebug("given %d bytes to write; device is writeable? %d", i, out->isWritable()); 0023 return out->write((const char*)data, i); 0024 } 0025 0026 int doInput(GifFileType* gif, GifByteType* data, int i) 0027 { 0028 QIODevice* in = (QIODevice*)gif->UserData; 0029 return in->read((char*)data, i); 0030 } 0031 0032 QGIFLibHandler::QGIFLibHandler() 0033 : QImageIOHandler() 0034 { 0035 } 0036 0037 bool QGIFLibHandler::canRead () const 0038 { 0039 if (canRead(device())) { 0040 setFormat("gif"); 0041 return true; 0042 } 0043 return false; 0044 } 0045 0046 bool QGIFLibHandler::read ( QImage * image ) 0047 { 0048 // The contents of this function are based on gif2rgb.c, from the giflib source. 0049 // qDebug("QGIFLibHandler::read into image with size %d x %d", image->size().width(), image->size().height()); 0050 0051 int err; 0052 GifFileType* gifFile = DGifOpen(device(), doInput, &err); 0053 if (!gifFile) { 0054 qWarning() << "Received error code" << err; 0055 return false; 0056 } 0057 // qDebug("dimensions %d x %d", gifFile->SWidth, gifFile->SHeight); 0058 0059 *image = QImage(gifFile->SWidth, gifFile->SHeight, QImage::Format_Indexed8); 0060 0061 GifRecordType recordType; 0062 ColorMapObject* ColorMap; 0063 0064 int i, row, imageNum = 0, topRow, width, height; 0065 int transColor = -1; 0066 do 0067 { 0068 DGifGetRecordType(gifFile, &recordType); 0069 switch (recordType) 0070 { 0071 case IMAGE_DESC_RECORD_TYPE: 0072 if (DGifGetImageDesc(gifFile) == GIF_ERROR) 0073 { 0074 qWarning("QGIFLibHandler::read: error %d", gifFile->Error); 0075 return false; 0076 } 0077 topRow = gifFile->Image.Top; /* Image Position relative to Screen. */ 0078 width = gifFile->Image.Width; 0079 height = gifFile->Image.Height; 0080 //qDebug("Image %d at (%d, %d) [%dx%d]", ++imageNum, gifFile->Image.Left, topRow, width, height); 0081 if (gifFile->Image.Left + width > gifFile->SWidth || 0082 gifFile->Image.Top + height > gifFile->SHeight) 0083 { 0084 qWarning("Image %d is not confined to screen dimension, aborted.", imageNum); 0085 return false; 0086 } 0087 0088 // Pre-fill with background color 0089 // qDebug("background color is at index %d", gifFile->SBackGroundColor); 0090 image->fill(gifFile->SBackGroundColor); 0091 0092 // Now read the image data 0093 if (gifFile->Image.Interlace) 0094 { 0095 /* Need to perform 4 passes on the images: */ 0096 for (i = 0; i < 4; i++) 0097 for (row = topRow + InterlacedOffset[i]; row < topRow + height; 0098 row += InterlacedJumps[i]) 0099 { 0100 if (DGifGetLine(gifFile, image->scanLine(row), width) == GIF_ERROR) 0101 { 0102 qWarning("QGIFLibHandler::read: error %d", gifFile->Error); 0103 return false; 0104 } 0105 // else 0106 // qDebug("got row %d: %d %d %d %d %d %d %d %d ...", row, 0107 // image->scanLine(row)[0], image->scanLine(row)[1], image->scanLine(row)[2], image->scanLine(row)[3], 0108 // image->scanLine(row)[4], image->scanLine(row)[5], image->scanLine(row)[6], image->scanLine(row)[7]); 0109 } 0110 } 0111 else 0112 { 0113 for (row = 0; row < height; row++) 0114 { 0115 if (DGifGetLine(gifFile, image->scanLine(row), width) == GIF_ERROR) 0116 { 0117 qWarning("QGIFLibHandler::read: error %d", gifFile->Error); 0118 return false; 0119 } 0120 // else 0121 // qDebug("got row %d: %d %d %d %d %d %d %d %d ...", row, 0122 // image->scanLine(row)[0], image->scanLine(row)[1], image->scanLine(row)[2], image->scanLine(row)[3], 0123 // image->scanLine(row)[4], image->scanLine(row)[5], image->scanLine(row)[6], image->scanLine(row)[7]); 0124 } 0125 } 0126 break; 0127 case EXTENSION_RECORD_TYPE: 0128 { 0129 int extCode; 0130 GifByteType* extData; 0131 /* Skip any extension blocks in file: */ 0132 if (DGifGetExtension(gifFile, &extCode, &extData) == GIF_ERROR) 0133 { 0134 qWarning("QGIFLibHandler::read: error %d", gifFile->Error); 0135 return false; 0136 } 0137 while (extData != NULL) 0138 { 0139 int len = extData[0]; 0140 switch (extCode) 0141 { 0142 case GRAPHICS_EXT_FUNC_CODE: // Graphics control extension 0143 // qDebug("graphics control: %x %x %x %x %x", extData[0], extData[1], extData[2], extData[3], extData[4]); 0144 // Should be block size, packed fields, delay time, 0145 // transparent color, block terminator 0146 // see doc/gif89.txt in libgif source package 0147 // If the trans bit is set in packed fields, 0148 // then set the trans color to the one given 0149 if (extData[1] & 0x01) 0150 { 0151 transColor = extData[3]; 0152 // qDebug("transparent color is at index %d", transColor); 0153 /// @todo is it correct to override default fill color? 0154 // image->fill(transColor); 0155 } 0156 break; 0157 case COMMENT_EXT_FUNC_CODE: 0158 { 0159 QByteArray comment((char*)(extData + 1), len); 0160 // qDebug("comment of len %d: \"%s\"", len, comment.constData()); 0161 image->setText("Description", comment); 0162 } 0163 break; 0164 case PLAINTEXT_EXT_FUNC_CODE: 0165 break; 0166 } 0167 if (DGifGetExtensionNext(gifFile, &extData) == GIF_ERROR) 0168 { 0169 qWarning("QGIFLibHandler::read: error %d", gifFile->Error); 0170 return false; 0171 } 0172 } 0173 } 0174 break; 0175 case TERMINATE_RECORD_TYPE: 0176 break; 0177 default: 0178 break; 0179 } 0180 } 0181 while (recordType != TERMINATE_RECORD_TYPE); 0182 0183 // BackGround = gifFile->SBackGroundColor; 0184 ColorMap = (gifFile->Image.ColorMap 0185 ? gifFile->Image.ColorMap 0186 : gifFile->SColorMap); 0187 if (!ColorMap) 0188 { 0189 qWarning("QGIFLibHandler::read: Image does not have a colormap"); 0190 return false; 0191 } 0192 int ccount = ColorMap->ColorCount; 0193 image->setColorCount(ccount); 0194 for (i = 0; i < ccount; ++i) 0195 { 0196 GifColorType gifColor = ColorMap->Colors[i]; 0197 QRgb color = gifColor.Blue | (gifColor.Green << 8) | (gifColor.Red << 16); 0198 // If this is not the transparent color, 0199 // set the alpha to opaque. 0200 if (i != transColor) 0201 color |= 0xff << 24; 0202 // qDebug("color %d: 0x%X", i, color); 0203 image->setColor(i, color); 0204 } 0205 0206 return true; 0207 } 0208 0209 bool QGIFLibHandler::canRead(QIODevice *device) 0210 { 0211 if (!device) { 0212 qWarning("QGIFLibHandler::canRead() called with no device"); 0213 return false; 0214 } 0215 0216 char head[6]; 0217 if (device->peek(head, sizeof(head)) == sizeof(head)) 0218 return qstrncmp(head, "GIF87a", 6) == 0 0219 || qstrncmp(head, "GIF89a", 6) == 0; 0220 return false; 0221 } 0222 0223 bool QGIFLibHandler::write ( const QImage & image ) 0224 { 0225 QImage toWrite(image); 0226 /// @todo how to specify dithering method 0227 if (toWrite.colorCount() == 0 || toWrite.colorCount() > 256) 0228 toWrite = image.convertToFormat(QImage::Format_Indexed8); 0229 0230 QVector<QRgb> colorTable = toWrite.colorTable(); 0231 ColorMapObject cmap; 0232 // colorCount must be a power of 2 0233 int colorCount = 1 << GifBitSize(toWrite.colorCount()); 0234 cmap.ColorCount = colorCount; 0235 cmap.BitsPerPixel = 8; /// @todo based on colorCount (or not? we did ask for Format_Indexed8, so the data is always 8-bit, right?) 0236 GifColorType* colorValues = (GifColorType*)malloc(cmap.ColorCount * sizeof(GifColorType)); 0237 cmap.Colors = colorValues; 0238 int c = 0; 0239 for(; c < toWrite.colorCount(); ++c) 0240 { 0241 // qDebug("color %d has %02X%02X%02X", c, qRed(colorTable[c]), qGreen(colorTable[c]), qBlue(colorTable[c])); 0242 colorValues[c].Red = qRed(colorTable[c]); 0243 colorValues[c].Green = qGreen(colorTable[c]); 0244 colorValues[c].Blue = qBlue(colorTable[c]); 0245 } 0246 // In case we had an actual number of colors that's not a power of 2, 0247 // fill the rest with something (black perhaps). 0248 for (; c < colorCount; ++c) 0249 { 0250 colorValues[c].Red = 0; 0251 colorValues[c].Green = 0; 0252 colorValues[c].Blue = 0; 0253 } 0254 /// @todo transparent GIFs (use alpha?) 0255 0256 0257 /// @todo write to m_device 0258 int err; 0259 GifFileType *gif = EGifOpen(device(), doOutput, &err); 0260 0261 /// @todo how to specify which version, or decide based on features in use 0262 // Because of this call, libgif is not reentrant 0263 EGifSetGifVersion(gif, true); 0264 0265 /// @todo how to specify background 0266 if (EGifPutScreenDesc(gif, toWrite.width(), toWrite.height(), colorCount, 0, &cmap) == GIF_ERROR) { 0267 qWarning("EGifPutScreenDesc returned error %d", gif->Error); 0268 } 0269 0270 QVariant descText = option(QImageIOHandler::Description); 0271 if (descText.type() == QVariant::String) 0272 { 0273 QString comment = descText.toString(); 0274 // Will be something like "Description: actual text" or just 0275 // ": actual text", so remove everything leading up to and 0276 // including the first colon and the space following it. 0277 int idx = comment.indexOf(": "); 0278 if (idx >= 0) 0279 comment.remove(0, idx + 2); 0280 // qDebug() << "comment:" << comment; 0281 if (!comment.isEmpty()) 0282 EGifPutComment(gif, comment.toUtf8().constData()); 0283 } 0284 // else 0285 // qDebug("description is of qvariant type %d", descText.type()); 0286 0287 /// @todo foreach of multiple images in an animation... 0288 if (EGifPutImageDesc(gif, 0, 0, toWrite.width(), toWrite.height(), 0, &cmap) == GIF_ERROR) 0289 qWarning("EGifPutImageDesc returned error %d", gif->Error); 0290 0291 int lc = toWrite.height(); 0292 0293 // NOTE: we suppose that the pixel size is exactly 1 byte, right now we 0294 // cannot save anything else 0295 int llen = toWrite.width(); 0296 0297 // qDebug("will write %d lines, %d bytes each", lc, llen); 0298 0299 for (int l = 0; l < lc; ++l) 0300 { 0301 uchar* line = toWrite.scanLine(l); 0302 if (EGifPutLine(gif, (GifPixelType*)line, llen) == GIF_ERROR) 0303 { 0304 int i = gif->Error; 0305 qWarning("EGifPutLine returned error %d", i); 0306 } 0307 } 0308 0309 EGifCloseFile(gif, &err); 0310 free(colorValues); 0311 0312 return true; 0313 } 0314 0315 bool QGIFLibHandler::supportsOption ( ImageOption option ) const 0316 { 0317 // qDebug("supportsOption %d", option); 0318 switch (option) 0319 { 0320 // These are relevant only for reading 0321 case QImageIOHandler::ImageFormat: 0322 case QImageIOHandler::Size: 0323 // This is relevant for both reading and writing 0324 case QImageIOHandler::Description: 0325 return true; 0326 break; 0327 default: 0328 return false; 0329 } 0330 } 0331 0332 void QGIFLibHandler::setOption ( ImageOption option, const QVariant & value ) 0333 { 0334 // qDebug("setOption given option %d, variant of type %d", option, value.type()); 0335 if (option == QImageIOHandler::Description) 0336 m_description = value.toString(); 0337 } 0338 0339 QVariant QGIFLibHandler::option( ImageOption option ) const 0340 { 0341 switch (option) 0342 { 0343 case QImageIOHandler::ImageFormat: 0344 return QVariant(); /// @todo 0345 break; 0346 case QImageIOHandler::Size: 0347 return QVariant(); /// @todo 0348 break; 0349 case QImageIOHandler::Description: 0350 return QVariant(m_description); 0351 break; 0352 default: 0353 return QVariant(); 0354 } 0355 }