Warning, file /frameworks/khtml/src/imload/decoders/gifloader.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002     Large image load library -- GIF decoder
0003 
0004     Copyright (C) 2004 Maksim Orlovich <maksim@kde.org>
0005     Based almost fully on animated gif playback code,
0006          (C) 2004 Daniel Duley (Mosfet) <dan.duley@verizon.net>
0007 
0008     Permission is hereby granted, free of charge, to any person obtaining a copy
0009     of this software and associated documentation files (the "Software"), to deal
0010     in the Software without restriction, including without limitation the rights
0011     to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
0012     copies of the Software, and to permit persons to whom the Software is
0013     furnished to do so, subject to the following conditions:
0014 
0015     The above copyright notice and this permission notice shall be included in
0016     all copies or substantial portions of the Software.
0017 
0018     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
0019     IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
0020     FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
0021     AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
0022     AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
0023     CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
0024 
0025 */
0026 
0027 #include "gifloader.h"
0028 
0029 #include "animprovider.h"
0030 
0031 #include "imageloader.h"
0032 #include "imagemanager.h"
0033 #include "pixmapplane.h"
0034 #include "updater.h"
0035 
0036 #include <QByteArray>
0037 #include <QPainter>
0038 #include <QVector>
0039 #include "khtml_debug.h"
0040 
0041 #include <stdlib.h>
0042 
0043 extern "C" {
0044 #include <gif_lib.h>
0045 }
0046 
0047 /* avoid cpp warning about undefined macro, old giflib had no GIFLIB_MAJOR */
0048 #ifndef GIFLIB_MAJOR
0049 #define GIFLIB_MAJOR 4
0050 #endif
0051 
0052 // #define DEBUG_GIFLOADER
0053 
0054 namespace khtmlImLoad
0055 {
0056 
0057 static int INTERLACED_OFFSET[] = { 0, 4, 2, 1 };
0058 static int INTERLACED_JUMP  [] = { 8, 8, 4, 2 };
0059 
0060 enum GIFConstants {
0061     //Graphics control extension has most of animation info
0062     GCE_Code                = 0xF9,
0063     GCE_Size                = 4,
0064     //Fields of the above
0065     GCE_Flags               = 0,
0066     GCE_Delay               = 1,
0067     GCE_TransColor          = 3,
0068     //Contents of mask
0069     GCE_DisposalMask        = 0x1C,
0070     GCE_DisposalUnspecified = 0x00,
0071     GCE_DisposalLeave       = 0x04,
0072     GCE_DisposalBG          = 0x08,
0073     GCE_DisposalRestore     = 0x0C,
0074     GCE_UndocumentedMode4   = 0x10,
0075     GCE_TransColorMask      = 0x01
0076 };
0077 
0078 struct GIFFrameInfo {
0079     bool         trans;
0080     QColor       bg;
0081     QRect        geom;
0082     unsigned int delay;
0083     char         mode;
0084     //###
0085 };
0086 
0087 /**
0088  An anim provider for the animated GIFs. We keep a backing store for
0089  the screen.
0090 */
0091 class GIFAnimProvider: public AnimProvider
0092 {
0093 protected:
0094     QVector<GIFFrameInfo> frameInfo;
0095     int                   frame; // refers to the /current/ frame
0096 
0097     // State of gif screen after the previous image.
0098     // we paint the current frame on top of it, and don't touch until
0099     // the current frame is disposed
0100     QPixmap               canvas;
0101     QColor                bgColor;
0102     bool                  firstTime;
0103 
0104     // Previous mode being background seems to trigger an OpSrc rather than OpOver updating
0105     bool                  previousWasBG;
0106 public:
0107     GIFAnimProvider(PixmapPlane *plane, Image *img, QVector<GIFFrameInfo> _frames, QColor bg):
0108         AnimProvider(plane, img), bgColor(bg), firstTime(true), previousWasBG(false)
0109     {
0110         frameInfo = _frames;
0111         frame     = 0;
0112         canvas    = QPixmap(img->size());
0113         canvas.fill(bgColor);
0114     }
0115 
0116     // Renders a portion of the current frame's image on the painter..
0117     void renderCurImage(int dx, int dy, QPainter *p, int sx, int sy, int width, int height)
0118     {
0119         QRect frameGeom = frameInfo[frame].geom;
0120 
0121         // Take the passed paint rectangle in gif screen coordinates, and
0122         // clip it to the frame's geometry
0123         QRect screenPaintRect = QRect(sx, sy, width, height) & frameGeom;
0124 
0125         // Same thing but in the frame's coordinate system
0126         QRect framePaintRect  = screenPaintRect.translated(-frameGeom.topLeft());
0127 
0128         curFrame->paint(dx + screenPaintRect.x() - sx, dy + screenPaintRect.y() - sy, p,
0129                         framePaintRect.x(), framePaintRect.y(),
0130                         framePaintRect.width(), framePaintRect.height());
0131     }
0132 
0133     // Renders current gif screen state on the painter
0134     void renderCurScreen(int dx, int dy, QPainter *p, int sx, int sy, int width, int height)
0135     {
0136         // Depending on the previous frame's mode, we may have to cut out a hole when
0137         // painting the canvas, since if previous frame had BG disposal, we have to do OpSrc.
0138         if (previousWasBG) {
0139             QRegion canvasDrawRegion(sx, sy, width, height);
0140             canvasDrawRegion -= frameInfo[frame].geom;
0141             QVector<QRect> srcRects = canvasDrawRegion.rects();
0142 
0143             foreach (const QRect &r, srcRects) {
0144                 p->drawPixmap(QPoint(dx + r.x() - sx, dy + r.y() - sy), canvas, r);
0145             }
0146         } else {
0147             p->drawPixmap(dx, dy, canvas, sx, sy, width, height);
0148         }
0149 
0150         // Now render the current frame's overlay
0151         renderCurImage(dx, dy, p, sx, sy, width, height);
0152     }
0153 
0154     // Update screen, incorporating the dispose operator for current image
0155     void updateScreenAfterDispose()
0156     {
0157         previousWasBG = false;
0158 
0159         // If we're the last frame, just clear the canvas...
0160         if (frame == frameInfo.size() - 1) {
0161             canvas.fill(bgColor);
0162             return;
0163         }
0164 
0165         switch (frameInfo[frame].mode) {
0166         case GCE_DisposalRestore:
0167         case GCE_UndocumentedMode4: // Not in the spec, mozilla inteprets as above
0168             // "restore" means the state of the canvas should be the
0169             // same as before the current frame.. But we don't touch it
0170             // when painting, so it's a no-op.
0171             return;
0172 
0173         case GCE_DisposalLeave:
0174         case GCE_DisposalUnspecified: { // Qt3 appears to interpret this as leave
0175             // Update the canvas with current image.
0176             QPainter p(&canvas);
0177             if (previousWasBG) {
0178                 p.setCompositionMode(QPainter::CompositionMode_Source);
0179             }
0180             renderCurImage(0, 0, &p, 0, 0, canvas.width(), canvas.height());
0181             return;
0182         }
0183 
0184         case GCE_DisposalBG: {
0185             previousWasBG = true;
0186             // Clear with bg color --- the frame rect only
0187             QPainter p(&canvas);
0188             p.setCompositionMode(QPainter::CompositionMode_Source);
0189             p.fillRect(frameInfo[frame].geom, bgColor);
0190             return;
0191         }
0192 
0193         default:
0194             // Including GCE_DisposalUnspecified -- ???
0195             break;
0196         }
0197     }
0198 
0199     void paint(int dx, int dy, QPainter *p, int sx, int sy, int width, int height) override
0200     {
0201         if (!width || !height) {
0202             return;    //Nothing to draw.
0203         }
0204 
0205         // Move over to next frame if need be, incorporating
0206         // the change effect of current one onto the screen.
0207         if (shouldSwitchFrame) {
0208             updateScreenAfterDispose();
0209 
0210             ++frame;
0211             if (frame >= frameInfo.size()) {
0212                 if (animationAdvice == KHTMLSettings::KAnimationLoopOnce) {
0213                     animationAdvice = KHTMLSettings::KAnimationDisabled;
0214                 }
0215                 frame = 0;
0216             }
0217             nextFrame();
0218         }
0219 
0220         // Request next frame to be drawn...
0221         if (shouldSwitchFrame || firstTime) {
0222             shouldSwitchFrame = false;
0223             firstTime         = false;
0224 
0225             // ### FIXME: adjust based on actual interframe timing -- the jitter is
0226             // likely to be quite big
0227             ImageManager::animTimer()->nextFrameIn(this, frameInfo[frame].delay);
0228         }
0229 
0230         // Render the currently active frame
0231         renderCurScreen(dx, dy, p, sx, sy, width, height);
0232 
0233 #ifdef DEBUG_GIFLOADER
0234         p->drawText(QPoint(dx - sx, dy - sy + p->fontMetrics().height()), QString::number(frame));
0235 #endif
0236     }
0237 
0238     AnimProvider *clone(PixmapPlane *plane) override
0239     {
0240         if (frame0->height == 0 || frame0->width == 0 ||
0241                 plane->height == 0 || plane->width == 0) {
0242             return nullptr;
0243         }
0244 
0245         float heightRatio = frame0->height / plane->height;
0246         float widthRatio = frame0->width / plane->width;
0247 
0248         QVector<GIFFrameInfo> newFrameInfo;
0249         Q_FOREACH (const GIFFrameInfo &oldFrame, frameInfo) {
0250             GIFFrameInfo newFrame(oldFrame);
0251 
0252             newFrame.geom.setWidth(oldFrame.geom.width() * widthRatio);
0253             newFrame.geom.setHeight(oldFrame.geom.height() * heightRatio);
0254             newFrame.geom.setX(oldFrame.geom.x() * widthRatio);
0255             newFrame.geom.setY(oldFrame.geom.y() * heightRatio);
0256             newFrameInfo.append(newFrame);
0257         }
0258 
0259         return new GIFAnimProvider(plane, image, newFrameInfo, bgColor);
0260     }
0261 };
0262 
0263 class GIFLoader: public ImageLoader
0264 {
0265     QByteArray buffer;
0266     int        bufferReadPos;
0267 public:
0268     GIFLoader()
0269     {
0270         bufferReadPos = 0;
0271     }
0272 
0273     ~GIFLoader()
0274     {
0275     }
0276 
0277     int processData(uchar *data, int length) override
0278     {
0279         //Collect data in the buffer
0280         int pos = buffer.size();
0281         buffer.resize(buffer.size() + length);
0282         memcpy(buffer.data() + pos, data, length);
0283         return length;
0284     }
0285 
0286     static int gifReaderBridge(GifFileType *gifInfo, GifByteType *data, int limit)
0287     {
0288         GIFLoader *me = static_cast<GIFLoader *>(gifInfo->UserData);
0289 
0290         int remBytes = me->buffer.size() - me->bufferReadPos;
0291         int toRet    = qMin(remBytes, limit);
0292 
0293         memcpy(data, me->buffer.data() + me->bufferReadPos, toRet);
0294         me->bufferReadPos += toRet;
0295 
0296         return toRet;
0297     }
0298 
0299 #if GIFLIB_MAJOR >= 5
0300     static unsigned int decode16Bit(unsigned char *signedLoc)
0301 #else
0302     static unsigned int decode16Bit(char *signedLoc)
0303 #endif
0304     {
0305         unsigned char *loc = reinterpret_cast<unsigned char *>(signedLoc);
0306 
0307         //GIFs are little-endian
0308         return loc[0] | (((unsigned int)loc[1]) << 8);
0309     }
0310 
0311     static void palettedToRGB(uchar *out, uchar *in, ImageFormat &format, int w)
0312     {
0313         int outPos = 0;
0314         for (int x = 0; x < w; ++x) {
0315             int colorCode = in[x];
0316             QRgb color = 0;
0317             if (colorCode < format.palette.size()) {
0318                 color = format.palette[colorCode];
0319             }
0320 
0321             *reinterpret_cast<QRgb *>(&out[outPos]) = color;
0322             outPos += 4;
0323         }
0324     }
0325 
0326     // Read a color from giflib palette, with range checking
0327     static QColor colorMapColor(ColorMapObject *map, int index)
0328     {
0329         QColor col(Qt::black);
0330         if (!map) {
0331             return col;
0332         }
0333 
0334         if (index < map->ColorCount)
0335             col = QColor(map->Colors[index].Red,
0336                          map->Colors[index].Green,
0337                          map->Colors[index].Blue);
0338         return col;
0339     }
0340 
0341     static void printColorMap(ColorMapObject *map)
0342     {
0343         for (int c = 0; c < map->ColorCount; ++c)
0344             qCDebug(KHTML_LOG) << "  " << map << c << map->Colors[c].Red
0345                      << map->Colors[c].Green
0346                      << map->Colors[c].Blue;
0347     }
0348 
0349     int processEOF() override
0350     {
0351         //Feed the buffered data to libUnGif
0352 #if GIFLIB_MAJOR >= 5
0353         int errorCode;
0354         GifFileType *file = DGifOpen(this, gifReaderBridge, &errorCode);
0355 #else
0356         GifFileType *file = DGifOpen(this, gifReaderBridge);
0357 #endif
0358 
0359         if (!file) {
0360             return Error;
0361         }
0362 
0363         if (DGifSlurp(file) == GIF_ERROR) {
0364 #if GIFLIB_MAJOR >= 5 && GIFLIB_MINOR >= 1
0365             DGifCloseFile(file, &errorCode);
0366             return errorCode;
0367 #else
0368             DGifCloseFile(file);
0369             return Error;
0370 #endif
0371         }
0372 
0373         //We use canvas size only for animations
0374         if (file->ImageCount > 1) {
0375             // Verify it..
0376             if (!ImageManager::isAcceptableSize(file->SWidth, file->SHeight)) {
0377 #if GIFLIB_MAJOR >= 5 && GIFLIB_MINOR >= 1
0378                 DGifCloseFile(file, &errorCode);
0379                 return errorCode;
0380 #else
0381                 DGifCloseFile(file);
0382                 return Error;
0383 #endif
0384             }
0385             notifyImageInfo(file->SWidth, file->SHeight);
0386         }
0387 
0388         // Check each frame to be within the size limit policy
0389         for (int frame = 0; frame < file->ImageCount; ++frame) {
0390             //Extract colormap, geometry, so that we can create the frame
0391             SavedImage *curFrame = &file->SavedImages[frame];
0392             if (!ImageManager::isAcceptableSize(curFrame->ImageDesc.Width, curFrame->ImageDesc.Height)) {
0393 #if GIFLIB_MAJOR >= 5 && GIFLIB_MINOR >= 1
0394                 DGifCloseFile(file, &errorCode);
0395                 return errorCode;
0396 #else
0397                 DGifCloseFile(file);
0398                 return Error;
0399 #endif
0400             }
0401         }
0402 
0403         QVector<GIFFrameInfo> frameProps;
0404 
0405         // First, use the screen color map...
0406         ColorMapObject *globalColorMap = file->SColorMap;
0407 
0408         // If for some reason there is none, pick one from an image,
0409         // and pray it works.
0410         if (!globalColorMap) {
0411             globalColorMap = file->Image.ColorMap;
0412         }
0413 
0414         QColor bgColor = colorMapColor(globalColorMap, file->SBackGroundColor);
0415 
0416         //Extract out all the frames
0417         for (int frame = 0; frame < file->ImageCount; ++frame) {
0418             //Extract colormap, geometry, so that we can create the frame
0419             SavedImage *curFrame = &file->SavedImages[frame];
0420             int w = curFrame->ImageDesc.Width;
0421             int h = curFrame->ImageDesc.Height;
0422 
0423             //For non-animated images, use the frame size for dimension
0424             if (frame == 0 && file->ImageCount == 1) {
0425                 notifyImageInfo(w, h);
0426             }
0427 
0428             ColorMapObject *colorMap = curFrame->ImageDesc.ColorMap;
0429             if (!colorMap) {
0430                 colorMap = globalColorMap;
0431             }
0432 
0433             GIFFrameInfo frameInf;
0434             int          trans = -1;
0435             frameInf.delay = 100;
0436             frameInf.mode  = GCE_DisposalUnspecified;
0437 
0438             //Go through the extension blocks to see whether there is a color key,
0439             //and animation info inside the graphics control extension (GCE) block
0440             for (int ext = 0; ext < curFrame->ExtensionBlockCount; ++ext) {
0441                 ExtensionBlock *curExt = &curFrame->ExtensionBlocks[ext];
0442                 if ((curExt->Function  == GCE_Code) && (curExt->ByteCount >= GCE_Size)) {
0443                     if (curExt->Bytes[GCE_Flags] & GCE_TransColorMask) {
0444                         trans = ((unsigned char)curExt->Bytes[GCE_TransColor]);
0445                     }
0446 
0447                     frameInf.mode  = curExt->Bytes[GCE_Flags] & GCE_DisposalMask;
0448                     frameInf.delay = decode16Bit(&curExt->Bytes[GCE_Delay]) * 10;
0449 
0450                     if (frameInf.delay < 100) {
0451                         frameInf.delay = 100;
0452                     }
0453                 }
0454             }
0455 
0456 #ifdef DEBUG_GIFLOADER
0457             frameInf.delay = 1500;
0458 #endif
0459 
0460             // The only thing I found resembling an explanation suggests that we should
0461             // set bgColor to transparent if the first frame's GCE is such...
0462             // Let's hope this is what actually happens.. (Man, I wish testcasing GIFs was manageable)
0463             if (frame == 0 && trans != -1) {
0464                 bgColor = QColor(Qt::transparent);
0465             }
0466 
0467             // If we have transparency, we need to go an RGBA mode.
0468             ImageFormat format;
0469             if (trans != -1) {
0470                 format.type = ImageFormat::Image_ARGB_32;    // Premultiply always OK, since we only have 0/1 alpha
0471             } else {
0472                 format.type = ImageFormat::Image_Palette_8;
0473             }
0474 
0475             // Read in colors for the palette... Don't waste memory on
0476             // any extra ones beyond 256, though
0477             int colorCount = colorMap ? colorMap->ColorCount : 0;
0478             for (int c = 0; c < colorCount && c < 256; ++c) {
0479                 format.palette.append(qRgba(colorMap->Colors[c].Red,
0480                                             colorMap->Colors[c].Green,
0481                                             colorMap->Colors[c].Blue, 255));
0482             }
0483 
0484             // Pad with black as a precaution
0485             for (int c = colorCount; c < 256; ++c) {
0486                 format.palette.append(qRgba(0, 0, 0, 255));
0487             }
0488 
0489             //Put in the colorkey color
0490             if (trans != -1) {
0491                 format.palette[trans] = qRgba(0, 0, 0, 0);
0492             }
0493 
0494             //Now we can declare frame format
0495             notifyAppendFrame(w, h, format);
0496 
0497             frameInf.bg   = bgColor;
0498             frameInf.geom = QRect(curFrame->ImageDesc.Left,
0499                                   curFrame->ImageDesc.Top,
0500                                   w, h);
0501 
0502             frameInf.trans  = format.hasAlpha();
0503             frameProps.append(frameInf);
0504 
0505 #ifdef DEBUG_GIFLOADER
0506             qDebug("frame:%d:%d,%d:%dx%d, trans:%d, mode:%d", frame, frameInf.geom.x(), frameInf.geom.y(), w, h, trans, frameInf.mode);
0507 #endif
0508 
0509             //Decode the scanlines
0510             uchar *buf;
0511             if (format.hasAlpha()) {
0512                 buf = new uchar[w * 4];
0513             } else {
0514                 buf = new uchar[w];
0515             }
0516 
0517             if (curFrame->ImageDesc.Interlace) {
0518                 // Interlaced. Considering we don't do progressive loading of gif's,
0519                 // a useless annoyance... The way it works is that on the first pass
0520                 // it renders scanlines 8*n, on second 8*n + 4,
0521                 // third then 4*n + 2, and finally 2*n + 1 (the odd lines)
0522                 // e.g.:
0523                 // 0, 8,  16, ...
0524                 // 4, 12, 20, ...
0525                 // 2, 6, 10, 14, ...
0526                 // 1, 3, 5, 7, ...
0527                 //
0528                 // Anyway, the bottom line is that INTERLACED_OFFSET contains
0529                 // the initial position, and INTERLACED_JUMP has the increment.
0530                 // However, imload expects a top-bottom scan of the image...
0531                 // so what we do it is keep track of which lines are actually
0532                 // new via nextNewLine variable, and leave others unchanged.
0533 
0534                 int interlacedImageScanline = 0; // scanline in interlaced image we are reading from
0535                 for (int pass = 0; pass < 4; ++pass) {
0536                     int nextNewLine = INTERLACED_OFFSET[pass];
0537 
0538                     for (int line = 0; line < h; ++line) {
0539                         if (line == nextNewLine) {
0540                             uchar *toFeed = (uchar *) curFrame->RasterBits + w * interlacedImageScanline;
0541                             if (format.hasAlpha()) {
0542                                 palettedToRGB(buf, toFeed, format, w);
0543                                 toFeed = buf;
0544                             }
0545 
0546                             notifyScanline(pass + 1, toFeed);
0547                             ++interlacedImageScanline;
0548                             nextNewLine += INTERLACED_JUMP[pass];
0549                         } else {
0550                             // No new information for this scanline, so just
0551                             // get it from loader, and feed it right back in
0552                             requestScanline(line, buf);
0553                             notifyScanline(pass + 1, buf);
0554                         }
0555                     } // for every scanline
0556                 } // for pass..
0557             } // if interlaced
0558             else {
0559                 for (int line = 0; line < h; ++line) {
0560                     uchar *toFeed = (uchar *) file->SavedImages[frame].RasterBits + w * line;
0561                     if (format.hasAlpha()) {
0562                         palettedToRGB(buf, toFeed, format, w);
0563                         toFeed = buf;
0564                     }
0565                     notifyScanline(1, toFeed);
0566                 }
0567             }
0568             delete[] buf;
0569         }
0570 
0571         if (file->ImageCount > 1) {
0572             //need animation provider
0573             PixmapPlane *frame0  = requestFrame0();
0574             frame0->animProvider = new GIFAnimProvider(frame0, image, frameProps, bgColor);
0575         }
0576 
0577 #if GIFLIB_MAJOR >= 5 && GIFLIB_MINOR >= 1
0578         DGifCloseFile(file, &errorCode);
0579 #else
0580         DGifCloseFile(file);
0581 #endif
0582 
0583         return Done;
0584     }
0585 };
0586 
0587 ImageLoaderProvider::Type GIFLoaderProvider::type()
0588 {
0589     return Efficient;
0590 }
0591 
0592 ImageLoader *GIFLoaderProvider::loaderFor(const QByteArray &prefix)
0593 {
0594     uchar *data = (uchar *)prefix.data();
0595     if (prefix.size() < 6) {
0596         return nullptr;
0597     }
0598 
0599     if (data[0] == 'G'  &&
0600             data[1] == 'I'  &&
0601             data[2] == 'F'  &&
0602             data[3] == '8'  &&
0603             ((data[4] == '7') || (data[4] == '9')) &&
0604             data[5] == 'a') {
0605         return new GIFLoader;
0606     }
0607 
0608     return nullptr;
0609 }
0610 
0611 }
0612