├── qtgif.pro ├── test ├── main.cpp ├── test.pro ├── mainwindow.h ├── mainwindow.ui └── mainwindow.cpp ├── TODO ├── qgiflibhandler.h ├── README.md ├── main.cpp ├── BUGS └── qgiflibhandler.cpp /qtgif.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = lib 2 | HEADERS += qgiflibhandler.h 3 | SOURCES += qgiflibhandler.cpp \ 4 | main.cpp 5 | LIBS += -lgif 6 | -------------------------------------------------------------------------------- /test/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "mainwindow.h" 3 | 4 | int main(int argc, char *argv[]) 5 | { 6 | QApplication a(argc, argv); 7 | // a.addLibraryPath(".."); 8 | MainWindow w; 9 | if (argc > 1) 10 | w.open(argv[1]); 11 | w.show(); 12 | return a.exec(); 13 | } 14 | -------------------------------------------------------------------------------- /test/test.pro: -------------------------------------------------------------------------------- 1 | #------------------------------------------------- 2 | # 3 | # Project created by QtCreator 2009-06-30T22:47:22 4 | # 5 | #------------------------------------------------- 6 | 7 | TARGET = giftest 8 | TEMPLATE = app 9 | #CONFIG += debug 10 | 11 | SOURCES += main.cpp\ 12 | mainwindow.cpp 13 | 14 | HEADERS += mainwindow.h 15 | 16 | FORMS += mainwindow.ui 17 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | show checkerboard pattern as background to see transparency 2 | show on status bar the color index, and visual representation of the color (including checkerboard for transparency) 3 | read plaintext as text? (don't see any need to support rendering at this point, unless libgif "just does it") 4 | 5 | done 6 | ---- 7 | allow editing the comment field before saving 8 | read comments 9 | 10 | -------------------------------------------------------------------------------- /qgiflibhandler.h: -------------------------------------------------------------------------------- 1 | #ifndef QGIFLIBHANDLER_H 2 | #define QGIFLIBHANDLER_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | class QGIFLibHandler : public QImageIOHandler 9 | { 10 | public: 11 | QGIFLibHandler(); 12 | bool canRead () const; 13 | bool read ( QImage * image ); 14 | bool write ( const QImage & image ); 15 | static bool canRead(QIODevice *device); 16 | bool supportsOption ( ImageOption option ) const; 17 | void setOption ( ImageOption option, const QVariant & value ); 18 | QVariant option( ImageOption option ) const; 19 | 20 | private: 21 | // QVariant m_description; 22 | QString m_description; 23 | }; 24 | 25 | #endif // QGIFLIBHANDLER_H 26 | -------------------------------------------------------------------------------- /test/mainwindow.h: -------------------------------------------------------------------------------- 1 | #ifndef MAINWINDOW_H 2 | #define MAINWINDOW_H 3 | 4 | #include 5 | #include 6 | 7 | namespace Ui 8 | { 9 | class MainWindow; 10 | } 11 | 12 | class MainWindow : public QMainWindow 13 | { 14 | Q_OBJECT 15 | 16 | public: 17 | MainWindow(QWidget *parent = 0); 18 | ~MainWindow(); 19 | void open(char* filename); 20 | 21 | private: 22 | void open(QString filename); 23 | void save(QString filename); 24 | 25 | private slots: 26 | void on_actionSave_triggered(); 27 | void on_actionComment_triggered(); 28 | void on_actionExport_GIF_triggered(); 29 | void on_actionOpen_triggered(); 30 | 31 | private: 32 | Ui::MainWindow *ui; 33 | QImage image; 34 | QPlainTextEdit commentText; 35 | QString lastFileName; 36 | }; 37 | 38 | #endif // MAINWINDOW_H 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # qt-gif-plugin 2 | Alternative GIF imageformat plugin which uses giflib, supports reading and 3 | writing, and GIF comments. 4 | 5 | Qt 4.5.x already has a GIF imageformat plugin, implemented in a 6 | self-contained way; however it can only read GIFs. This one is an 7 | alternative which can both read and write GIFs. It is simply a wrapper for 8 | giflib. It also supports GIF comments for both reading and writing, via 9 | QImage::text, setText or QImageWriter::setText. 10 | 11 | The usual reason for not implementing GIF writing capabilities was because 12 | of the annoying old Unisys-owned patents, but those have now expired. Nobody 13 | much cares for GIF anymore, but it's occasionally useful to prepare GIF 14 | images for old software which does not support PNG. Futhermore the Qt PNG 15 | and JPEG plugins do not yet support preserving comments as metadata within 16 | the image, so it's an improvement to have available a format which does. 17 | 18 | The original motivation for this plugin is to enable GIF export from PDFs, 19 | while embedding the OCR'd text as a GIF comment. 20 | 21 | See the related project for which this has been used: 22 | https://sourceforge.net/projects/taborca/ 23 | 24 | Originally hosted at https://gitorious.org/qt-gif-plugin since 2009/09/07 25 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2009 Shawn T. Rutledge (shawn.t.rutledge@gmail.com) 4 | ** 5 | ** This file is derived from the Qt gif plugin which shipped with 6 | ** Qt 4.5.1 and therefore the same license terms apply. 7 | ** 8 | ****************************************************************************/ 9 | 10 | #include 11 | #include 12 | 13 | #ifndef QT_NO_IMAGEFORMATPLUGIN 14 | 15 | #ifdef QT_NO_IMAGEFORMAT_GIF 16 | #undef QT_NO_IMAGEFORMAT_GIF 17 | #endif 18 | #include "qgiflibhandler.h" 19 | 20 | QT_BEGIN_NAMESPACE 21 | 22 | /** 23 | This GIF image format differs from the Qt 4.5.1 version 24 | in the following ways: 25 | 1) It can both read and write GIF files 26 | 2) It handles GIF comments 27 | 3) It is linked with giflib rather than being self-contained 28 | */ 29 | class QGIFLibPlugin : public QImageIOPlugin 30 | { 31 | public: 32 | QGIFLibPlugin(); 33 | ~QGIFLibPlugin(); 34 | 35 | QStringList keys() const; 36 | Capabilities capabilities(QIODevice *device, const QByteArray &format) const; 37 | QImageIOHandler *create(QIODevice *device, const QByteArray &format = QByteArray()) const; 38 | }; 39 | 40 | QGIFLibPlugin::QGIFLibPlugin() 41 | { 42 | } 43 | 44 | QGIFLibPlugin::~QGIFLibPlugin() 45 | { 46 | } 47 | 48 | QStringList QGIFLibPlugin::keys() const 49 | { 50 | return QStringList() << QLatin1String("gif"); 51 | } 52 | 53 | QImageIOPlugin::Capabilities QGIFLibPlugin::capabilities(QIODevice *device, const QByteArray &format) const 54 | { 55 | if (format == "gif") 56 | return Capabilities(CanRead | CanWrite); 57 | if (!format.isEmpty()) 58 | return 0; 59 | if (!device) 60 | return 0; 61 | if (!device->isOpen()) 62 | return 0; 63 | Capabilities ret; 64 | if (device->isReadable() && QGIFLibHandler::canRead(device)) 65 | ret |= CanRead; 66 | if (device->isWritable()) 67 | ret |= CanWrite; 68 | return ret; 69 | } 70 | 71 | QImageIOHandler *QGIFLibPlugin::create(QIODevice *device, const QByteArray &format) const 72 | { 73 | QImageIOHandler *handler = new QGIFLibHandler(); 74 | handler->setDevice(device); 75 | handler->setFormat(format); 76 | return handler; 77 | } 78 | 79 | Q_EXPORT_STATIC_PLUGIN(QGIFLibPlugin) 80 | Q_EXPORT_PLUGIN2(giflib, QGIFLibPlugin) 81 | 82 | #endif // QT_NO_IMAGEFORMATPLUGIN 83 | 84 | QT_END_NAMESPACE 85 | -------------------------------------------------------------------------------- /test/mainwindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 148 10 | 170 11 | 12 | 13 | 14 | GIFTest 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 0 23 | 0 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 0 37 | 0 38 | 148 39 | 26 40 | 41 | 42 | 43 | 44 | File 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | View 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | Open... 64 | 65 | 66 | 67 | 68 | Export GIF... 69 | 70 | 71 | 72 | 73 | Comment... 74 | 75 | 76 | 77 | 78 | Save 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /test/mainwindow.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | #include "ui_mainwindow.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | MainWindow::MainWindow(QWidget *parent) 10 | : QMainWindow(parent), ui(new Ui::MainWindow) 11 | { 12 | ui->setupUi(this); 13 | qDebug("libpaths:"); 14 | foreach (QString libpath, QCoreApplication::libraryPaths ()) 15 | qDebug(" %s", libpath.toAscii().constData()); 16 | qDebug("plugin path:\n %s", QLibraryInfo::location(QLibraryInfo::PluginsPath).toAscii().constData()); 17 | qDebug("supported image formats for reading:"); 18 | foreach(QByteArray f, QImageReader::supportedImageFormats()) 19 | qDebug(" %s", f.constData()); 20 | qDebug("supported image formats for writing:"); 21 | foreach(QByteArray f, QImageWriter::supportedImageFormats()) 22 | qDebug(" %s", f.constData()); 23 | commentText.setWindowFlags(Qt::Dialog); 24 | } 25 | 26 | MainWindow::~MainWindow() 27 | { 28 | delete ui; 29 | } 30 | 31 | void MainWindow::open(char* filename) 32 | { 33 | open(QString(filename)); 34 | } 35 | 36 | void MainWindow::open(QString filename) 37 | { 38 | lastFileName = filename; 39 | image = QImage(filename); 40 | ui->label->setPixmap(QPixmap::fromImage(image)); 41 | commentText.setPlainText(image.text()); 42 | QFileInfo fi(filename); 43 | ui->actionSave->setEnabled( 44 | QString::compare(fi.suffix(), "gif", Qt::CaseInsensitive) == 0); 45 | setWindowTitle(QString("GIFTest %1").arg(filename)); 46 | } 47 | 48 | void MainWindow::on_actionOpen_triggered() 49 | { 50 | QString fileName = QFileDialog::getOpenFileName(this, 51 | tr("Open Image"), "", tr("Image Files (*.png *.jpg *.bmp *.gif)")); 52 | open(fileName); 53 | // image = QImage(fileName); 54 | // ui->label->setPixmap(QPixmap::fromImage(image)); 55 | } 56 | 57 | void MainWindow::on_actionExport_GIF_triggered() 58 | { 59 | QString fileName = QFileDialog::getSaveFileName(this, 60 | tr("Export GIF Image"), "", tr("GIF Files (*.gif)")); 61 | save(fileName); 62 | } 63 | 64 | void MainWindow::save(QString fileName) 65 | { 66 | QImageWriter wr(fileName, "gif"); 67 | qDebug("supports description? %d", wr.supportsOption(QImageIOHandler::Description)); 68 | QString comment = commentText.toPlainText(); 69 | if (!comment.isEmpty()) 70 | { 71 | int colonIdx = comment.indexOf(":"); 72 | if (colonIdx >= 0) 73 | { 74 | // qDebug() << "setText" << comment.left(colonIdx) << comment.mid(colonIdx + 2); 75 | wr.setText(comment.left(colonIdx), comment.mid(colonIdx + 2)); 76 | } 77 | else 78 | wr.setText("", comment); 79 | } 80 | if (!wr.write(image)) 81 | qCritical() << "error" << wr.error() << ": " << wr.errorString(); 82 | } 83 | 84 | void MainWindow::on_actionComment_triggered() 85 | { 86 | commentText.setVisible(true); 87 | } 88 | 89 | void MainWindow::on_actionSave_triggered() 90 | { 91 | save(lastFileName); 92 | } 93 | -------------------------------------------------------------------------------- /BUGS: -------------------------------------------------------------------------------- 1 | giftext does a hex dump of the comment, and it does not display the last line if 2 | it's less than 16 bytes. So short comments (less than 16 bytes) aren't 3 | displayed. 4 | 5 | But, giftext DOES handle long comments successfully (352 characters tested). 6 | 7 | [sony][10:40:37 PM] giftext /tmp/out.gif 8 | 9 | /tmp/out.gif: 10 | 11 | Screen Size - Width = 96, Height = 96. 12 | ColorResolution = 8, BitsPerPixel = 8, BackGround = 0. 13 | Has Global Color Map. 14 | 15 | 16 | GIF89 comment (Ext Code = 254 [ ]): 17 | 18 | 00000: 41 20 72 65 61 6c 6c 79 20 72 69 64 69 63 75 6c A really ridicul 19 | 00010: 6f 75 73 6c 79 20 76 65 72 62 6f 73 65 20 6c 6f ously verbose lo 20 | 00020: 6e 67 20 63 6f 6d 6d 65 6e 74 20 77 69 74 68 20 ng comment with 21 | 00030: 6c 6f 74 73 20 6f 66 20 72 61 6d 62 6c 69 6e 67 lots of rambling 22 | 00040: 20 6f 6e 20 61 6e 64 20 6f 6e 20 61 6e 64 20 6f on and on and o 23 | 00050: 6e 2e 2e 2e 20 61 63 74 75 61 6c 6c 79 2c 20 74 n... actually, t 24 | 00060: 68 69 73 20 69 73 20 74 68 65 20 63 6f 6d 6d 65 his is the comme 25 | 00070: 6e 74 20 74 68 61 74 20 6e 65 76 65 72 20 65 6e nt that never en 26 | 00080: 64 73 2c 20 69 74 20 6a 75 73 74 20 67 6f 65 73 ds, it just goes 27 | 00090: 20 6f 6e 20 61 6e 64 20 6f 6e 2c 20 6d 79 20 66 on and on, my f 28 | 000a0: 72 69 65 6e 64 73 2e 20 20 50 65 6f 70 6c 65 20 riends. People 29 | 000b0: 73 74 61 72 74 20 72 65 61 64 69 6e 67 20 69 74 start reading it 30 | 000c0: 2c 20 6e 6f 74 20 6b 6e 6f 77 69 6e 67 20 77 68 , not knowing wh 31 | 000d0: 61 74 20 69 74 20 69 73 2c 20 62 75 74 20 74 68 at it is, but th 32 | 000e0: 65 6e 20 61 74 20 74 68 65 20 62 6f 74 74 6f 6d en at the bottom 33 | 000f0: 20 69 74 20 73 61 79 73 20 27 72 65 61 64 20 61 it says 'read a 34 | 00100: 67 61 69 6e 27 2c 20 61 6e 64 20 73 6f 20 79 6f gain', and so yo 35 | 00110: 75 20 73 65 65 2c 20 74 68 65 20 62 6f 74 74 6f u see, the botto 36 | 00120: 6d 20 69 73 6e 27 74 20 72 65 61 6c 6c 79 20 74 m isn't really t 37 | 00130: 68 65 20 65 6e 64 2c 20 62 65 63 61 75 73 65 20 he end, because 38 | 00140: 79 6f 75 20 61 72 65 20 6f 62 6c 69 67 65 64 20 you are obliged 39 | 00150: 74 6f 20 72 65 61 64 20 61 67 61 69 6e 2e 2e 2e to read again... 40 | Image #1: 41 | 42 | Image Size - Left = 0, Top = 0, Width = 96, Height = 96. 43 | Image is Non Interlaced, BitsPerPixel = 8. 44 | Image Has Color Map. 45 | 46 | Gif file terminated normally. 47 | 48 | 49 | gifsicle displays short comments successfully but long comments are wrapped. 50 | 51 | [sony][10:40:42 PM] gifsicle --info /tmp/out.gif * /tmp/out.gif 1 image logical 52 | screen 96x96 global color table [256] background 0 + image #0 96x96 comment 53 | again', and so you see, the bottom isn't really the end, because you are obliged 54 | to read again...is is the comment that never ends, it just goes on and on, my 55 | friends. People start reading it, not knowing what it is, but then at the 56 | bottom it says 'read 57 | \000\000\000\000\000!\37777777647\001\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000 58 | local color table [256] 59 | 60 | gimp has the same problem: reads short comments but long ones get wrapped. 61 | 62 | 63 | To be tested: gifs with various palette sizes. Seems that if memory serves, 64 | small non-power-of-2 palette sizes are problematic. 65 | 66 | bool QImageWriter::write(const QImage &image) 67 | { 68 | if (!canWrite()) 69 | return false; 70 | 71 | if (d->handler->supportsOption(QImageIOHandler::Quality)) 72 | d->handler->setOption(QImageIOHandler::Quality, d->quality); 73 | if (d->handler->supportsOption(QImageIOHandler::CompressionRatio)) 74 | d->handler->setOption(QImageIOHandler::CompressionRatio, d->compression); 75 | if (d->handler->supportsOption(QImageIOHandler::Gamma)) 76 | d->handler->setOption(QImageIOHandler::Gamma, d->gamma); 77 | if (!d->description.isEmpty() && d->handler->supportsOption(QImageIOHandler::Description)) 78 | d->handler->setOption(QImageIOHandler::Description, d->descript 79 | 80 | but no call to supportsOption? 81 | 82 | 83 | Transparency needs work. Often the transparent color index is 0 and 84 | the background color is 255. But ImageMagick will display such images 85 | with the transparent color instead of anything with idx 255. Why? 86 | libgif doesn't do it that way, and also doesn't pay attention 87 | to the "Do we have global color map?" flag in packed bits in the 88 | logical screen desc when deciding whether to pay attention to the 89 | background color. Wonder if that's a bug... 90 | 91 | already fixed: 92 | -------------- 93 | If we save a GIF with no comment, xv doesn't like it 94 | (but ImageMagick and the GIF plugin itself are OK). 95 | Gimp says 96 | GIF: bogus character 0x00, ignoring. 97 | 98 | -------------------------------------------------------------------------------- /qgiflibhandler.cpp: -------------------------------------------------------------------------------- 1 | #include "qgiflibhandler.h" 2 | #include 3 | #include 4 | #include 5 | #include // memset 6 | #include 7 | 8 | extern int _GifError; 9 | 10 | static const int InterlacedOffset[] = { 0, 4, 2, 1 }; /* The way Interlaced image should */ 11 | static const int InterlacedJumps[] = { 8, 8, 4, 2 }; /* be read - offsets and jumps... */ 12 | 13 | int doOutput(GifFileType* gif, const GifByteType * data, int i) 14 | { 15 | QIODevice* out = (QIODevice*)gif->UserData; 16 | // qDebug("given %d bytes to write; device is writeable? %d", i, out->isWritable()); 17 | return out->write((const char*)data, i); 18 | } 19 | 20 | int doInput(GifFileType* gif, GifByteType* data, int i) 21 | { 22 | QIODevice* in = (QIODevice*)gif->UserData; 23 | return in->read((char*)data, i); 24 | } 25 | 26 | QGIFLibHandler::QGIFLibHandler() : 27 | QImageIOHandler() 28 | { 29 | } 30 | 31 | bool QGIFLibHandler::canRead () const 32 | { 33 | if (canRead(device())) { 34 | setFormat("gif"); 35 | return true; 36 | } 37 | return false; 38 | } 39 | 40 | bool QGIFLibHandler::read ( QImage * image ) 41 | { 42 | // The contents of this function are based on gif2rgb.c, from the giflib source. 43 | // qDebug("QGIFLibHandler::read into image with size %d x %d", 44 | // image->size().width(), image->size().height()); 45 | GifFileType* gifFile = DGifOpen(device(), doInput); 46 | if (!gifFile) 47 | return false; 48 | qDebug("dimensions %d x %d", gifFile->SWidth, gifFile->SHeight); 49 | 50 | *image = QImage(gifFile->SWidth, gifFile->SHeight, QImage::Format_Indexed8); 51 | // memset(image->bits(), 0, image->numBytes()); 52 | 53 | 54 | GifRecordType recordType; 55 | ColorMapObject* ColorMap; 56 | 57 | int i, row, imageNum = 0, topRow, leftCol, width, height; 58 | int transColor = -1; 59 | do 60 | { 61 | DGifGetRecordType(gifFile, &recordType); 62 | switch (recordType) 63 | { 64 | case IMAGE_DESC_RECORD_TYPE: 65 | if (DGifGetImageDesc(gifFile) == GIF_ERROR) 66 | { 67 | qWarning("QGIFLibHandler::read: error %d", _GifError); 68 | return false; 69 | } 70 | topRow = gifFile->Image.Top; /* Image Position relative to Screen. */ 71 | leftCol = gifFile->Image.Left; 72 | width = gifFile->Image.Width; 73 | height = gifFile->Image.Height; 74 | qDebug("Image %d at (%d, %d) [%dx%d]", 75 | ++imageNum, leftCol, topRow, width, height); 76 | if (gifFile->Image.Left + width > gifFile->SWidth || 77 | gifFile->Image.Top + height > gifFile->SHeight) 78 | { 79 | qCritical("Image %d is not confined to screen dimension, aborted.", imageNum); 80 | return false; 81 | } 82 | 83 | // Pre-fill with background color 84 | qDebug("background color is at index %d", gifFile->SBackGroundColor); 85 | image->fill(gifFile->SBackGroundColor); 86 | 87 | // Now read the image data 88 | if (gifFile->Image.Interlace) 89 | { 90 | /* Need to perform 4 passes on the images: */ 91 | for (i = 0; i < 4; i++) 92 | for (row = topRow + InterlacedOffset[i]; row < topRow + height; 93 | row += InterlacedJumps[i]) 94 | { 95 | if (DGifGetLine(gifFile, image->scanLine(row), width) == GIF_ERROR) 96 | { 97 | qWarning("QGIFLibHandler::read: error %d", _GifError); 98 | return false; 99 | } 100 | // else 101 | // qDebug("got row %d: %d %d %d %d %d %d %d %d ...", row, 102 | // image->scanLine(row)[0], image->scanLine(row)[1], image->scanLine(row)[2], image->scanLine(row)[3], 103 | // image->scanLine(row)[4], image->scanLine(row)[5], image->scanLine(row)[6], image->scanLine(row)[7]); 104 | } 105 | } 106 | else 107 | { 108 | for (row = 0; row < height; row++) 109 | { 110 | if (DGifGetLine(gifFile, image->scanLine(row), width) == GIF_ERROR) 111 | { 112 | qWarning("QGIFLibHandler::read: error %d", _GifError); 113 | return false; 114 | } 115 | // else 116 | // qDebug("got row %d: %d %d %d %d %d %d %d %d ...", row, 117 | // image->scanLine(row)[0], image->scanLine(row)[1], image->scanLine(row)[2], image->scanLine(row)[3], 118 | // image->scanLine(row)[4], image->scanLine(row)[5], image->scanLine(row)[6], image->scanLine(row)[7]); 119 | } 120 | } 121 | break; 122 | case EXTENSION_RECORD_TYPE: 123 | { 124 | int extCode; 125 | GifByteType* extData; 126 | /* Skip any extension blocks in file: */ 127 | if (DGifGetExtension(gifFile, &extCode, &extData) == GIF_ERROR) 128 | { 129 | qWarning("QGIFLibHandler::read: error %d", _GifError); 130 | return false; 131 | } 132 | while (extData != NULL) 133 | { 134 | int len = extData[0]; 135 | switch (extCode) 136 | { 137 | case GRAPHICS_EXT_FUNC_CODE: // Graphics control extension 138 | qDebug("graphics control: %x %x %x %x %x", 139 | extData[0], extData[1], extData[2], extData[3], extData[4]); 140 | // Should be block size, packed fields, delay time, 141 | // transparent color, block terminator 142 | // see doc/gif89.txt in libgif source package 143 | // If the trans bit is set in packed fields, 144 | // then set the trans color to the one given 145 | if (extData[1] & 0x01) 146 | { 147 | transColor = extData[3]; 148 | qDebug("transparent color is at index %d", transColor); 149 | /// @todo is it correct to override default fill color? 150 | // image->fill(transColor); 151 | } 152 | break; 153 | case COMMENT_EXT_FUNC_CODE: 154 | { 155 | QByteArray comment((char*)(extData + 1), len); 156 | // qDebug("comment of len %d: \"%s\"", len, comment.constData()); 157 | image->setText("Description", comment); 158 | } 159 | break; 160 | case PLAINTEXT_EXT_FUNC_CODE: 161 | break; 162 | } 163 | if (DGifGetExtensionNext(gifFile, &extData) == GIF_ERROR) 164 | { 165 | qWarning("QGIFLibHandler::read: error %d", _GifError); 166 | return false; 167 | } 168 | } 169 | } 170 | break; 171 | case TERMINATE_RECORD_TYPE: 172 | break; 173 | default: 174 | break; 175 | } 176 | } 177 | while (recordType != TERMINATE_RECORD_TYPE); 178 | 179 | // BackGround = gifFile->SBackGroundColor; 180 | ColorMap = (gifFile->Image.ColorMap 181 | ? gifFile->Image.ColorMap 182 | : gifFile->SColorMap); 183 | if (!ColorMap) 184 | { 185 | qWarning("QGIFLibHandler::read: Image does not have a colormap"); 186 | return false; 187 | } 188 | int ccount = ColorMap->ColorCount; 189 | image->setNumColors(ccount); 190 | for (i = 0; i < ccount; ++i) 191 | { 192 | GifColorType gifColor = ColorMap->Colors[i]; 193 | QRgb color = gifColor.Blue | (gifColor.Green << 8) | (gifColor.Red << 16); 194 | // If this is not the transparent color, 195 | // set the alpha to opaque. 196 | if (i != transColor) 197 | color |= 0xff << 24; 198 | // qDebug("color %d: 0x%X", i, color); 199 | image->setColor(i, color); 200 | } 201 | 202 | return true; 203 | } 204 | 205 | bool QGIFLibHandler::canRead(QIODevice *device) 206 | { 207 | if (!device) { 208 | qWarning("QGIFLibHandler::canRead() called with no device"); 209 | return false; 210 | } 211 | 212 | char head[6]; 213 | if (device->peek(head, sizeof(head)) == sizeof(head)) 214 | return qstrncmp(head, "GIF87a", 6) == 0 215 | || qstrncmp(head, "GIF89a", 6) == 0; 216 | return false; 217 | } 218 | 219 | bool QGIFLibHandler::write ( const QImage & image ) 220 | { 221 | QImage toWrite(image); 222 | /// @todo how to specify dithering method 223 | if (toWrite.numColors() == 0 || toWrite.numColors() > 256) 224 | toWrite = image.convertToFormat(QImage::Format_Indexed8); 225 | 226 | QVector colorTable = toWrite.colorTable(); 227 | ColorMapObject cmap; 228 | // numColors must be a power of 2 229 | int numColors = 1 << BitSize(toWrite.numColors()); 230 | cmap.ColorCount = numColors; 231 | cmap.BitsPerPixel = 8; /// @todo based on numColors (or not? we did ask for Format_Indexed8, so the data is always 8-bit, right?) 232 | GifColorType* colorValues = (GifColorType*)malloc(cmap.ColorCount * sizeof(GifColorType)); 233 | cmap.Colors = colorValues; 234 | int c = 0; 235 | for(; c < toWrite.numColors(); ++c) 236 | { 237 | //qDebug("color %d has %02X%02X%02X", c, qRed(colorTable[c]), qGreen(colorTable[c]), qBlue(colorTable[c])); 238 | colorValues[c].Red = qRed(colorTable[c]); 239 | colorValues[c].Green = qGreen(colorTable[c]); 240 | colorValues[c].Blue = qBlue(colorTable[c]); 241 | } 242 | // In case we had an actual number of colors that's not a power of 2, 243 | // fill the rest with something (black perhaps). 244 | for (; c < numColors; ++c) 245 | { 246 | colorValues[c].Red = 0; 247 | colorValues[c].Green = 0; 248 | colorValues[c].Blue = 0; 249 | } 250 | /// @todo transparent GIFs (use alpha?) 251 | 252 | /// @todo how to specify which version, or decide based on features in use 253 | // Because of this call, libgif is not re-entrant 254 | EGifSetGifVersion("89a"); 255 | 256 | /// @todo write to m_device 257 | GifFileType * gif = EGifOpen(device(), doOutput); 258 | // GifFileType* gif = EGifOpenFileName("/tmp/out.gif", 0); 259 | /// @todo how to specify background 260 | 261 | if (EGifPutScreenDesc(gif, toWrite.width(), toWrite.height(), numColors, 0, &cmap) == GIF_ERROR) 262 | qCritical("EGifPutScreenDesc returned error %d", GifLastError()); 263 | 264 | QVariant descText = option(QImageIOHandler::Description); 265 | if (descText.type() == QVariant::String) 266 | { 267 | QString comment = descText.toString(); 268 | // Will be something like "Description: actual text" or just 269 | // ": actual text", so remove everything leading up to and 270 | // including the first colon and the space following it. 271 | int idx = comment.indexOf(": "); 272 | if (idx >= 0) 273 | comment.remove(0, idx + 2); 274 | // qDebug() << "comment:" << comment; 275 | if (!comment.isEmpty()) 276 | EGifPutComment(gif, comment.toUtf8().constData()); 277 | } 278 | // else 279 | // qDebug("description is of qvariant type %d", descText.type()); 280 | 281 | /// @todo foreach of multiple images in an animation... 282 | if (EGifPutImageDesc(gif, 0, 0, toWrite.width(), toWrite.height(), 0, &cmap) == GIF_ERROR) 283 | qCritical("EGifPutImageDesc returned error %d", GifLastError()); 284 | 285 | int lc = toWrite.height(); 286 | int llen = toWrite.bytesPerLine(); 287 | // qDebug("will write %d lines, %d bytes each", lc, llen); 288 | for (int l = 0; l < lc; ++l) 289 | { 290 | uchar* line = toWrite.scanLine(l); 291 | if (EGifPutLine(gif, (GifPixelType*)line, llen) == GIF_ERROR) 292 | { 293 | int i = GifLastError(); 294 | qCritical("EGifPutLine returned error %d", i); 295 | } 296 | } 297 | 298 | EGifCloseFile(gif); 299 | return true; 300 | } 301 | 302 | bool QGIFLibHandler::supportsOption ( ImageOption option ) const 303 | { 304 | // qDebug("supportsOption %d", option); 305 | switch (option) 306 | { 307 | // These are relevant only for reading 308 | case QImageIOHandler::ImageFormat: 309 | case QImageIOHandler::Size: 310 | // This is relevant for both reading and writing 311 | case QImageIOHandler::Description: 312 | return true; 313 | break; 314 | default: 315 | return false; 316 | } 317 | } 318 | 319 | void QGIFLibHandler::setOption ( ImageOption option, const QVariant & value ) 320 | { 321 | // qDebug("setOption given option %d, variant of type %d", option, value.type()); 322 | if (option == QImageIOHandler::Description) 323 | m_description = value.toString(); 324 | } 325 | 326 | QVariant QGIFLibHandler::option( ImageOption option ) const 327 | { 328 | switch (option) 329 | { 330 | case QImageIOHandler::ImageFormat: 331 | return QVariant(); /// @todo 332 | break; 333 | case QImageIOHandler::Size: 334 | return QVariant(); /// @todo 335 | break; 336 | case QImageIOHandler::Description: 337 | return QVariant(m_description); 338 | break; 339 | default: 340 | return QVariant(); 341 | } 342 | } 343 | --------------------------------------------------------------------------------