KHtml

gifloader.cpp
1 /*
2  Large image load library -- GIF decoder
3 
4  Copyright (C) 2004 Maksim Orlovich <[email protected]>
5  Based almost fully on animated gif playback code,
6  (C) 2004 Daniel Duley (Mosfet) <[email protected]>
7 
8  Permission is hereby granted, free of charge, to any person obtaining a copy
9  of this software and associated documentation files (the "Software"), to deal
10  in the Software without restriction, including without limitation the rights
11  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12  copies of the Software, and to permit persons to whom the Software is
13  furnished to do so, subject to the following conditions:
14 
15  The above copyright notice and this permission notice shall be included in
16  all copies or substantial portions of the Software.
17 
18  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21  AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
22  AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
23  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 
25 */
26 
27 #include "gifloader.h"
28 
29 #include "animprovider.h"
30 
31 #include "imageloader.h"
32 #include "imagemanager.h"
33 #include "pixmapplane.h"
34 #include "updater.h"
35 
36 #include <QByteArray>
37 #include <QPainter>
38 #include <QVector>
39 #include "khtml_debug.h"
40 
41 #include <stdlib.h>
42 
43 extern "C" {
44 #include <gif_lib.h>
45 }
46 
47 /* avoid cpp warning about undefined macro, old giflib had no GIFLIB_MAJOR */
48 #ifndef GIFLIB_MAJOR
49 #define GIFLIB_MAJOR 4
50 #endif
51 
52 // #define DEBUG_GIFLOADER
53 
54 namespace khtmlImLoad
55 {
56 
57 static int INTERLACED_OFFSET[] = { 0, 4, 2, 1 };
58 static int INTERLACED_JUMP [] = { 8, 8, 4, 2 };
59 
60 enum GIFConstants {
61  //Graphics control extension has most of animation info
62  GCE_Code = 0xF9,
63  GCE_Size = 4,
64  //Fields of the above
65  GCE_Flags = 0,
66  GCE_Delay = 1,
67  GCE_TransColor = 3,
68  //Contents of mask
69  GCE_DisposalMask = 0x1C,
70  GCE_DisposalUnspecified = 0x00,
71  GCE_DisposalLeave = 0x04,
72  GCE_DisposalBG = 0x08,
73  GCE_DisposalRestore = 0x0C,
74  GCE_UndocumentedMode4 = 0x10,
75  GCE_TransColorMask = 0x01
76 };
77 
78 struct GIFFrameInfo {
79  bool trans;
80  QColor bg;
81  QRect geom;
82  unsigned int delay;
83  char mode;
84  //###
85 };
86 
87 /**
88  An anim provider for the animated GIFs. We keep a backing store for
89  the screen.
90 */
91 class GIFAnimProvider: public AnimProvider
92 {
93 protected:
94  QVector<GIFFrameInfo> frameInfo;
95  int frame; // refers to the /current/ frame
96 
97  // State of gif screen after the previous image.
98  // we paint the current frame on top of it, and don't touch until
99  // the current frame is disposed
100  QPixmap canvas;
101  QColor bgColor;
102  bool firstTime;
103 
104  // Previous mode being background seems to trigger an OpSrc rather than OpOver updating
105  bool previousWasBG;
106 public:
107  GIFAnimProvider(PixmapPlane *plane, Image *img, QVector<GIFFrameInfo> _frames, QColor bg):
108  AnimProvider(plane, img), bgColor(bg), firstTime(true), previousWasBG(false)
109  {
110  frameInfo = _frames;
111  frame = 0;
112  canvas = QPixmap(img->size());
113  canvas.fill(bgColor);
114  }
115 
116  // Renders a portion of the current frame's image on the painter..
117  void renderCurImage(int dx, int dy, QPainter *p, int sx, int sy, int width, int height)
118  {
119  QRect frameGeom = frameInfo[frame].geom;
120 
121  // Take the passed paint rectangle in gif screen coordinates, and
122  // clip it to the frame's geometry
123  QRect screenPaintRect = QRect(sx, sy, width, height) & frameGeom;
124 
125  // Same thing but in the frame's coordinate system
126  QRect framePaintRect = screenPaintRect.translated(-frameGeom.topLeft());
127 
128  curFrame->paint(dx + screenPaintRect.x() - sx, dy + screenPaintRect.y() - sy, p,
129  framePaintRect.x(), framePaintRect.y(),
130  framePaintRect.width(), framePaintRect.height());
131  }
132 
133  // Renders current gif screen state on the painter
134  void renderCurScreen(int dx, int dy, QPainter *p, int sx, int sy, int width, int height)
135  {
136  // Depending on the previous frame's mode, we may have to cut out a hole when
137  // painting the canvas, since if previous frame had BG disposal, we have to do OpSrc.
138  if (previousWasBG) {
139  QRegion canvasDrawRegion(sx, sy, width, height);
140  canvasDrawRegion -= frameInfo[frame].geom;
141  QVector<QRect> srcRects = canvasDrawRegion.rects();
142 
143  foreach (const QRect &r, srcRects) {
144  p->drawPixmap(QPoint(dx + r.x() - sx, dy + r.y() - sy), canvas, r);
145  }
146  } else {
147  p->drawPixmap(dx, dy, canvas, sx, sy, width, height);
148  }
149 
150  // Now render the current frame's overlay
151  renderCurImage(dx, dy, p, sx, sy, width, height);
152  }
153 
154  // Update screen, incorporating the dispose operator for current image
155  void updateScreenAfterDispose()
156  {
157  previousWasBG = false;
158 
159  // If we're the last frame, just clear the canvas...
160  if (frame == frameInfo.size() - 1) {
161  canvas.fill(bgColor);
162  return;
163  }
164 
165  switch (frameInfo[frame].mode) {
166  case GCE_DisposalRestore:
167  case GCE_UndocumentedMode4: // Not in the spec, mozilla inteprets as above
168  // "restore" means the state of the canvas should be the
169  // same as before the current frame.. But we don't touch it
170  // when painting, so it's a no-op.
171  return;
172 
173  case GCE_DisposalLeave:
174  case GCE_DisposalUnspecified: { // Qt3 appears to interpret this as leave
175  // Update the canvas with current image.
176  QPainter p(&canvas);
177  if (previousWasBG) {
179  }
180  renderCurImage(0, 0, &p, 0, 0, canvas.width(), canvas.height());
181  return;
182  }
183 
184  case GCE_DisposalBG: {
185  previousWasBG = true;
186  // Clear with bg color --- the frame rect only
187  QPainter p(&canvas);
189  p.fillRect(frameInfo[frame].geom, bgColor);
190  return;
191  }
192 
193  default:
194  // Including GCE_DisposalUnspecified -- ???
195  break;
196  }
197  }
198 
199  void paint(int dx, int dy, QPainter *p, int sx, int sy, int width, int height) override
200  {
201  if (!width || !height) {
202  return; //Nothing to draw.
203  }
204 
205  // Move over to next frame if need be, incorporating
206  // the change effect of current one onto the screen.
207  if (shouldSwitchFrame) {
208  updateScreenAfterDispose();
209 
210  ++frame;
211  if (frame >= frameInfo.size()) {
212  if (animationAdvice == KHTMLSettings::KAnimationLoopOnce) {
213  animationAdvice = KHTMLSettings::KAnimationDisabled;
214  }
215  frame = 0;
216  }
217  nextFrame();
218  }
219 
220  // Request next frame to be drawn...
221  if (shouldSwitchFrame || firstTime) {
222  shouldSwitchFrame = false;
223  firstTime = false;
224 
225  // ### FIXME: adjust based on actual interframe timing -- the jitter is
226  // likely to be quite big
227  ImageManager::animTimer()->nextFrameIn(this, frameInfo[frame].delay);
228  }
229 
230  // Render the currently active frame
231  renderCurScreen(dx, dy, p, sx, sy, width, height);
232 
233 #ifdef DEBUG_GIFLOADER
234  p->drawText(QPoint(dx - sx, dy - sy + p->fontMetrics().height()), QString::number(frame));
235 #endif
236  }
237 
238  AnimProvider *clone(PixmapPlane *plane) override
239  {
240  if (frame0->height == 0 || frame0->width == 0 ||
241  plane->height == 0 || plane->width == 0) {
242  return nullptr;
243  }
244 
245  float heightRatio = frame0->height / plane->height;
246  float widthRatio = frame0->width / plane->width;
247 
248  QVector<GIFFrameInfo> newFrameInfo;
249  Q_FOREACH (const GIFFrameInfo &oldFrame, frameInfo) {
250  GIFFrameInfo newFrame(oldFrame);
251 
252  newFrame.geom.setWidth(oldFrame.geom.width() * widthRatio);
253  newFrame.geom.setHeight(oldFrame.geom.height() * heightRatio);
254  newFrame.geom.setX(oldFrame.geom.x() * widthRatio);
255  newFrame.geom.setY(oldFrame.geom.y() * heightRatio);
256  newFrameInfo.append(newFrame);
257  }
258 
259  return new GIFAnimProvider(plane, image, newFrameInfo, bgColor);
260  }
261 };
262 
263 class GIFLoader: public ImageLoader
264 {
265  QByteArray buffer;
266  int bufferReadPos;
267 public:
268  GIFLoader()
269  {
270  bufferReadPos = 0;
271  }
272 
273  ~GIFLoader()
274  {
275  }
276 
277  int processData(uchar *data, int length) override
278  {
279  //Collect data in the buffer
280  int pos = buffer.size();
281  buffer.resize(buffer.size() + length);
282  memcpy(buffer.data() + pos, data, length);
283  return length;
284  }
285 
286  static int gifReaderBridge(GifFileType *gifInfo, GifByteType *data, int limit)
287  {
288  GIFLoader *me = static_cast<GIFLoader *>(gifInfo->UserData);
289 
290  int remBytes = me->buffer.size() - me->bufferReadPos;
291  int toRet = qMin(remBytes, limit);
292 
293  memcpy(data, me->buffer.data() + me->bufferReadPos, toRet);
294  me->bufferReadPos += toRet;
295 
296  return toRet;
297  }
298 
299 #if GIFLIB_MAJOR >= 5
300  static unsigned int decode16Bit(unsigned char *signedLoc)
301 #else
302  static unsigned int decode16Bit(char *signedLoc)
303 #endif
304  {
305  unsigned char *loc = reinterpret_cast<unsigned char *>(signedLoc);
306 
307  //GIFs are little-endian
308  return loc[0] | (((unsigned int)loc[1]) << 8);
309  }
310 
311  static void palettedToRGB(uchar *out, uchar *in, ImageFormat &format, int w)
312  {
313  int outPos = 0;
314  for (int x = 0; x < w; ++x) {
315  int colorCode = in[x];
316  QRgb color = 0;
317  if (colorCode < format.palette.size()) {
318  color = format.palette[colorCode];
319  }
320 
321  *reinterpret_cast<QRgb *>(&out[outPos]) = color;
322  outPos += 4;
323  }
324  }
325 
326  // Read a color from giflib palette, with range checking
327  static QColor colorMapColor(ColorMapObject *map, int index)
328  {
329  QColor col(Qt::black);
330  if (!map) {
331  return col;
332  }
333 
334  if (index < map->ColorCount)
335  col = QColor(map->Colors[index].Red,
336  map->Colors[index].Green,
337  map->Colors[index].Blue);
338  return col;
339  }
340 
341  static void printColorMap(ColorMapObject *map)
342  {
343  for (int c = 0; c < map->ColorCount; ++c)
344  qCDebug(KHTML_LOG) << " " << map << c << map->Colors[c].Red
345  << map->Colors[c].Green
346  << map->Colors[c].Blue;
347  }
348 
349  int processEOF() override
350  {
351  //Feed the buffered data to libUnGif
352 #if GIFLIB_MAJOR >= 5
353  int errorCode;
354  GifFileType *file = DGifOpen(this, gifReaderBridge, &errorCode);
355 #else
356  GifFileType *file = DGifOpen(this, gifReaderBridge);
357 #endif
358 
359  if (!file) {
360  return Error;
361  }
362 
363  if (DGifSlurp(file) == GIF_ERROR) {
364 #if GIFLIB_MAJOR >= 5 && GIFLIB_MINOR >= 1
365  DGifCloseFile(file, &errorCode);
366  return errorCode;
367 #else
368  DGifCloseFile(file);
369  return Error;
370 #endif
371  }
372 
373  //We use canvas size only for animations
374  if (file->ImageCount > 1) {
375  // Verify it..
376  if (!ImageManager::isAcceptableSize(file->SWidth, file->SHeight)) {
377 #if GIFLIB_MAJOR >= 5 && GIFLIB_MINOR >= 1
378  DGifCloseFile(file, &errorCode);
379  return errorCode;
380 #else
381  DGifCloseFile(file);
382  return Error;
383 #endif
384  }
385  notifyImageInfo(file->SWidth, file->SHeight);
386  }
387 
388  // Check each frame to be within the size limit policy
389  for (int frame = 0; frame < file->ImageCount; ++frame) {
390  //Extract colormap, geometry, so that we can create the frame
391  SavedImage *curFrame = &file->SavedImages[frame];
392  if (!ImageManager::isAcceptableSize(curFrame->ImageDesc.Width, curFrame->ImageDesc.Height)) {
393 #if GIFLIB_MAJOR >= 5 && GIFLIB_MINOR >= 1
394  DGifCloseFile(file, &errorCode);
395  return errorCode;
396 #else
397  DGifCloseFile(file);
398  return Error;
399 #endif
400  }
401  }
402 
403  QVector<GIFFrameInfo> frameProps;
404 
405  // First, use the screen color map...
406  ColorMapObject *globalColorMap = file->SColorMap;
407 
408  // If for some reason there is none, pick one from an image,
409  // and pray it works.
410  if (!globalColorMap) {
411  globalColorMap = file->Image.ColorMap;
412  }
413 
414  QColor bgColor = colorMapColor(globalColorMap, file->SBackGroundColor);
415 
416  //Extract out all the frames
417  for (int frame = 0; frame < file->ImageCount; ++frame) {
418  //Extract colormap, geometry, so that we can create the frame
419  SavedImage *curFrame = &file->SavedImages[frame];
420  int w = curFrame->ImageDesc.Width;
421  int h = curFrame->ImageDesc.Height;
422 
423  //For non-animated images, use the frame size for dimension
424  if (frame == 0 && file->ImageCount == 1) {
425  notifyImageInfo(w, h);
426  }
427 
428  ColorMapObject *colorMap = curFrame->ImageDesc.ColorMap;
429  if (!colorMap) {
430  colorMap = globalColorMap;
431  }
432 
433  GIFFrameInfo frameInf;
434  int trans = -1;
435  frameInf.delay = 100;
436  frameInf.mode = GCE_DisposalUnspecified;
437 
438  //Go through the extension blocks to see whether there is a color key,
439  //and animation info inside the graphics control extension (GCE) block
440  for (int ext = 0; ext < curFrame->ExtensionBlockCount; ++ext) {
441  ExtensionBlock *curExt = &curFrame->ExtensionBlocks[ext];
442  if ((curExt->Function == GCE_Code) && (curExt->ByteCount >= GCE_Size)) {
443  if (curExt->Bytes[GCE_Flags] & GCE_TransColorMask) {
444  trans = ((unsigned char)curExt->Bytes[GCE_TransColor]);
445  }
446 
447  frameInf.mode = curExt->Bytes[GCE_Flags] & GCE_DisposalMask;
448  frameInf.delay = decode16Bit(&curExt->Bytes[GCE_Delay]) * 10;
449 
450  if (frameInf.delay < 100) {
451  frameInf.delay = 100;
452  }
453  }
454  }
455 
456 #ifdef DEBUG_GIFLOADER
457  frameInf.delay = 1500;
458 #endif
459 
460  // The only thing I found resembling an explanation suggests that we should
461  // set bgColor to transparent if the first frame's GCE is such...
462  // Let's hope this is what actually happens.. (Man, I wish testcasing GIFs was manageable)
463  if (frame == 0 && trans != -1) {
464  bgColor = QColor(Qt::transparent);
465  }
466 
467  // If we have transparency, we need to go an RGBA mode.
468  ImageFormat format;
469  if (trans != -1) {
470  format.type = ImageFormat::Image_ARGB_32; // Premultiply always OK, since we only have 0/1 alpha
471  } else {
472  format.type = ImageFormat::Image_Palette_8;
473  }
474 
475  // Read in colors for the palette... Don't waste memory on
476  // any extra ones beyond 256, though
477  int colorCount = colorMap ? colorMap->ColorCount : 0;
478  for (int c = 0; c < colorCount && c < 256; ++c) {
479  format.palette.append(qRgba(colorMap->Colors[c].Red,
480  colorMap->Colors[c].Green,
481  colorMap->Colors[c].Blue, 255));
482  }
483 
484  // Pad with black as a precaution
485  for (int c = colorCount; c < 256; ++c) {
486  format.palette.append(qRgba(0, 0, 0, 255));
487  }
488 
489  //Put in the colorkey color
490  if (trans != -1) {
491  format.palette[trans] = qRgba(0, 0, 0, 0);
492  }
493 
494  //Now we can declare frame format
495  notifyAppendFrame(w, h, format);
496 
497  frameInf.bg = bgColor;
498  frameInf.geom = QRect(curFrame->ImageDesc.Left,
499  curFrame->ImageDesc.Top,
500  w, h);
501 
502  frameInf.trans = format.hasAlpha();
503  frameProps.append(frameInf);
504 
505 #ifdef DEBUG_GIFLOADER
506  qDebug("frame:%d:%d,%d:%dx%d, trans:%d, mode:%d", frame, frameInf.geom.x(), frameInf.geom.y(), w, h, trans, frameInf.mode);
507 #endif
508 
509  //Decode the scanlines
510  uchar *buf;
511  if (format.hasAlpha()) {
512  buf = new uchar[w * 4];
513  } else {
514  buf = new uchar[w];
515  }
516 
517  if (curFrame->ImageDesc.Interlace) {
518  // Interlaced. Considering we don't do progressive loading of gif's,
519  // a useless annoyance... The way it works is that on the first pass
520  // it renders scanlines 8*n, on second 8*n + 4,
521  // third then 4*n + 2, and finally 2*n + 1 (the odd lines)
522  // e.g.:
523  // 0, 8, 16, ...
524  // 4, 12, 20, ...
525  // 2, 6, 10, 14, ...
526  // 1, 3, 5, 7, ...
527  //
528  // Anyway, the bottom line is that INTERLACED_OFFSET contains
529  // the initial position, and INTERLACED_JUMP has the increment.
530  // However, imload expects a top-bottom scan of the image...
531  // so what we do it is keep track of which lines are actually
532  // new via nextNewLine variable, and leave others unchanged.
533 
534  int interlacedImageScanline = 0; // scanline in interlaced image we are reading from
535  for (int pass = 0; pass < 4; ++pass) {
536  int nextNewLine = INTERLACED_OFFSET[pass];
537 
538  for (int line = 0; line < h; ++line) {
539  if (line == nextNewLine) {
540  uchar *toFeed = (uchar *) curFrame->RasterBits + w * interlacedImageScanline;
541  if (format.hasAlpha()) {
542  palettedToRGB(buf, toFeed, format, w);
543  toFeed = buf;
544  }
545 
546  notifyScanline(pass + 1, toFeed);
547  ++interlacedImageScanline;
548  nextNewLine += INTERLACED_JUMP[pass];
549  } else {
550  // No new information for this scanline, so just
551  // get it from loader, and feed it right back in
552  requestScanline(line, buf);
553  notifyScanline(pass + 1, buf);
554  }
555  } // for every scanline
556  } // for pass..
557  } // if interlaced
558  else {
559  for (int line = 0; line < h; ++line) {
560  uchar *toFeed = (uchar *) file->SavedImages[frame].RasterBits + w * line;
561  if (format.hasAlpha()) {
562  palettedToRGB(buf, toFeed, format, w);
563  toFeed = buf;
564  }
565  notifyScanline(1, toFeed);
566  }
567  }
568  delete[] buf;
569  }
570 
571  if (file->ImageCount > 1) {
572  //need animation provider
573  PixmapPlane *frame0 = requestFrame0();
574  frame0->animProvider = new GIFAnimProvider(frame0, image, frameProps, bgColor);
575  }
576 
577 #if GIFLIB_MAJOR >= 5 && GIFLIB_MINOR >= 1
578  DGifCloseFile(file, &errorCode);
579 #else
580  DGifCloseFile(file);
581 #endif
582 
583  return Done;
584  }
585 };
586 
587 ImageLoaderProvider::Type GIFLoaderProvider::type()
588 {
589  return Efficient;
590 }
591 
592 ImageLoader *GIFLoaderProvider::loaderFor(const QByteArray &prefix)
593 {
594  uchar *data = (uchar *)prefix.data();
595  if (prefix.size() < 6) {
596  return nullptr;
597  }
598 
599  if (data[0] == 'G' &&
600  data[1] == 'I' &&
601  data[2] == 'F' &&
602  data[3] == '8' &&
603  ((data[4] == '7') || (data[4] == '9')) &&
604  data[5] == 'a') {
605  return new GIFLoader;
606  }
607 
608  return nullptr;
609 }
610 
611 }
612 
int width() const const
void fillRect(const QRectF &rectangle, const QBrush &brush)
void setCompositionMode(QPainter::CompositionMode mode)
void append(const T &value)
void fill(const QColor &color)
int height() const const
int x() const const
int y() const const
void resize(int size)
QString number(int n, int base)
void drawPixmap(const QRectF &target, const QPixmap &pixmap, const QRectF &source)
void drawText(const QPointF &position, const QString &text)
QRect translated(int dx, int dy) const const
CompositionMode_Source
int height() const const
if(recurs()&&!first)
int width() const const
QFontMetrics fontMetrics() const const
int height() const const
QPoint topLeft() const const
char * data()
int size() const const
int size() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Tue Oct 26 2021 22:48:01 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.