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 }