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