Changeset 28068 in webkit
- Timestamp:
- Nov 26, 2007 7:01:23 PM (16 years ago)
- Location:
- trunk/WebCore
- Files:
-
- 6 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/WebCore/ChangeLog
r28067 r28068 1 2007-11-26 Peter Kasting <zerodpx@gmail.com> 2 3 Reviewed by Alp Toker. 4 5 http://bugs.webkit.org/show_bug.cgi?id=15974 6 GIF decoding should respect frames' specified disposal methods. 7 8 * platform/image-decoders/ImageDecoder.h: 9 (WebCore::RGBA32Buffer::): 10 (WebCore::RGBA32Buffer::RGBA32Buffer): 11 (WebCore::RGBA32Buffer::disposalMethod): 12 (WebCore::RGBA32Buffer::setDisposalMethod): 13 * platform/image-decoders/gif/GIFImageDecoder.cpp: 14 (WebCore::GIFImageDecoder::frameBufferAtIndex): 15 (WebCore::GIFImageDecoder::initFrameBuffer): 16 (WebCore::GIFImageDecoder::prepEmptyFrameBuffer): 17 (WebCore::GIFImageDecoder::haveDecodedRow): 18 (WebCore::GIFImageDecoder::frameComplete): 19 * platform/image-decoders/gif/GIFImageDecoder.h: 20 * platform/image-decoders/gif/GIFImageReader.cpp: 21 (GIFImageReader::read): 22 * platform/image-decoders/gif/GIFImageReader.h: 23 (GIFFrameReader::GIFFrameReader): 24 1 25 2007-11-26 Adam Roben <aroben@apple.com> 2 26 -
trunk/WebCore/platform/image-decoders/ImageDecoder.h
r27902 r28068 42 42 public: 43 43 enum FrameStatus { FrameEmpty, FramePartial, FrameComplete }; 44 enum FrameDisposalMethod { 45 // If you change the numeric values of these, make sure you audit all 46 // users, as some users may cast raw values to/from these constants. 47 DisposeNotSpecified = 0, // Leave frame in framebuffer 48 DisposeKeep = 1, // Leave frame in framebuffer 49 DisposeOverwriteBgcolor = 2, // Clear frame to transparent 50 DisposeOverwritePrevious = 3, // Clear frame to previous framebuffer contents 51 }; 44 52 45 53 RGBA32Buffer() : m_height(0), m_status(FrameEmpty), m_duration(0), 46 m_ includeInNextFrame(false), m_hasAlpha(false)54 m_disposalMethod(DisposeNotSpecified), m_hasAlpha(false) 47 55 {} 48 56 … … 53 61 FrameStatus status() const { return m_status; } 54 62 unsigned duration() const { return m_duration; } 55 bool includeInNextFrame() const { return m_includeInNextFrame; }63 FrameDisposalMethod disposalMethod() const { return m_disposalMethod; } 56 64 bool hasAlpha() const { return m_hasAlpha; } 57 65 … … 60 68 void setStatus(FrameStatus s) { m_status = s; } 61 69 void setDuration(unsigned duration) { m_duration = duration; } 62 void set IncludeInNextFrame(bool n) { m_includeInNextFrame = n; }70 void setDisposalMethod(FrameDisposalMethod method) { m_disposalMethod = method; } 63 71 void setHasAlpha(bool alpha) { m_hasAlpha = alpha; } 64 72 … … 87 95 FrameStatus m_status; // Whether or not this frame is completely finished decoding. 88 96 unsigned m_duration; // The animation delay. 89 bool m_includeInNextFrame; // Whether or not the next buffer should be initially populated with our data.97 FrameDisposalMethod m_disposalMethod; // What to do with this frame's data when initializing the next frame. 90 98 bool m_hasAlpha; // Whether or not any of the pixels in the buffer have transparency. 91 99 }; -
trunk/WebCore/platform/image-decoders/gif/GIFImageDecoder.cpp
r27902 r28068 159 159 RGBA32Buffer* GIFImageDecoder::frameBufferAtIndex(size_t index) 160 160 { 161 if (index < 0 || index>= frameCount())161 if (index >= frameCount()) 162 162 return 0; 163 163 … … 195 195 } 196 196 197 void GIFImageDecoder::initFrameBuffer(RGBA32Buffer& buffer, 198 RGBA32Buffer* previousBuffer, 199 bool compositeWithPreviousFrame) 197 void GIFImageDecoder::initFrameBuffer(unsigned frameIndex) 200 198 { 201 199 // Initialize the frame rect in our buffer. … … 209 207 frameRect.setHeight(m_size.height() - m_reader->frameYOffset()); 210 208 211 buffer.setRect(frameRect); 212 213 bool isSubRect = (frameRect.x() > 0 || frameRect.y() > 0 || 214 frameRect.width() < m_size.width() || 215 frameRect.height() < m_size.height()); 209 RGBA32Buffer* const buffer = &m_frameBufferCache[frameIndex]; 210 buffer->setRect(frameRect); 216 211 217 // Let's resize our buffer now to the correct width/height and then 218 // initialize portions of it if needed. 219 RGBA32Array& bytes = buffer.bytes(); 220 221 // If the disposal method of the previous frame said to stick around, then we need 222 // to copy that frame into our frame. We also dont want to have any impact on 223 // anything outside our frame's rect, so if we don't overlay the entire image, 224 // then also composite with the previous frame. 225 if (previousBuffer && (compositeWithPreviousFrame || isSubRect)) { 226 bytes = previousBuffer->bytes(); 227 buffer.ensureHeight(m_size.height()); 228 buffer.setHasAlpha(previousBuffer->hasAlpha()); 229 } 230 else // Resize to the width and height of the image. 231 bytes.resize(m_size.width() * m_size.height()); 232 233 if (isSubRect) { 234 // We need to go ahead and initialize the first frame to make sure 235 // that areas outside the subrect start off transparent. 236 if (!previousBuffer) { 237 bytes.fill(0); 238 buffer.setHasAlpha(true); 239 } else if (!compositeWithPreviousFrame) { 240 // Now this is an interesting case. In the case where we fill 241 // the entire image, we effectively do a full clear of the image (and thus 242 // don't have to initialize anything in our buffer). 243 // 244 // However in the case where we only fill a piece of the image, two problems occur: 245 // (1) We need to wipe out the area occupied by the previous frame, which 246 // could also have been a subrect. 247 // (2) Anything outside the previous frame's rect *and* outside our current 248 // frame's rect should be left alone. 249 // We have handled (2) by just initializing our buffer from the previous frame. 250 // Our subrect will correctly overwrite the previous frame's contents as we 251 // decode rows. However that still leaves the problem of having to wipe out 252 // the area occupied by the previous frame that does not overlap with 253 // the new frame. 254 if (previousBuffer->rect() != frameRect) { 255 // We have to restore the entire previous subframe with the first frame's contents. 256 RGBA32Buffer* firstBuffer = &m_frameBufferCache[0]; 257 bool sawAlpha = buffer.hasAlpha(); 258 IntRect prevRect = previousBuffer->rect(); 259 unsigned end = prevRect.y() + prevRect.height(); 260 261 // Given that we allocate buffers to be the same size as previous buffers, 262 // I think this assert should be valid. 263 ASSERT(IntRect(IntPoint(0,0), m_size).contains(firstBuffer->rect())); 264 265 for (unsigned i = prevRect.y(); i < end; i++) { 266 unsigned* curr = buffer.bytes().data() + (i * m_size.width() + prevRect.x()); 267 unsigned* orig = firstBuffer->bytes().data() + (i * m_size.width() + prevRect.x()); 268 unsigned* end = curr + prevRect.width(); 269 unsigned* origEnd = orig + firstBuffer->rect().width(); 270 271 while (curr != end && orig != origEnd) { 272 if (!sawAlpha) { 273 sawAlpha = true; 274 buffer.setHasAlpha(true); 275 } 276 *curr++ = *orig++; 277 } 278 } 212 if (frameIndex == 0) { 213 // This is the first frame, so we're not relying on any previous data. 214 prepEmptyFrameBuffer(buffer); 215 } else { 216 // The starting state for this frame depends on the previous frame's 217 // disposal method. 218 // 219 // Frames that use the DisposeOverwritePrevious method are effectively 220 // no-ops in terms of changing the starting state of a frame compared to 221 // the starting state of the previous frame, so skip over them. (If the 222 // first frame specifies this method, it will get treated like 223 // DisposeOverwriteBgcolor below and reset to a completely empty image.) 224 const RGBA32Buffer* prevBuffer = &m_frameBufferCache[--frameIndex]; 225 RGBA32Buffer::FrameDisposalMethod prevMethod = 226 prevBuffer->disposalMethod(); 227 while ((frameIndex > 0) && 228 (prevMethod == RGBA32Buffer::DisposeOverwritePrevious)) { 229 prevBuffer = &m_frameBufferCache[--frameIndex]; 230 prevMethod = prevBuffer->disposalMethod(); 231 } 232 233 if ((prevMethod == RGBA32Buffer::DisposeNotSpecified) || 234 (prevMethod == RGBA32Buffer::DisposeKeep)) { 235 // Preserve the last frame as the starting state for this frame. 236 buffer->bytes() = prevBuffer->bytes(); 237 } else { 238 // We want to clear the previous frame to transparent, without 239 // affecting pixels in the image outside of the frame. 240 const IntRect& prevRect = prevBuffer->rect(); 241 if ((frameIndex == 0) || 242 prevRect.contains(IntRect(IntPoint(0, 0), m_size))) { 243 // Clearing the first frame, or a frame the size of the whole 244 // image, results in a completely empty image. 245 prepEmptyFrameBuffer(buffer); 246 } else { 247 // Copy the whole previous buffer, then clear just its frame. 248 buffer->bytes() = prevBuffer->bytes(); 249 for (int y = prevRect.y(); y < prevRect.bottom(); ++y) { 250 unsigned* const currentRow = 251 buffer->bytes().data() + (y * m_size.width()); 252 for (int x = prevRect.x(); x < prevRect.right(); ++x) 253 buffer->setRGBA(*(currentRow + x), 0, 0, 0, 0); 254 } 255 if ((prevRect.width() > 0) && (prevRect.height() > 0)) 256 buffer->setHasAlpha(true); 279 257 } 280 258 } … … 282 260 283 261 // Update our status to be partially complete. 284 buffer.setStatus(RGBA32Buffer::FramePartial); 262 buffer->setStatus(RGBA32Buffer::FramePartial); 263 264 // Reset the alpha pixel tracker for this frame. 265 m_currentBufferSawAlpha = false; 266 } 267 268 void GIFImageDecoder::prepEmptyFrameBuffer(RGBA32Buffer* buffer) const 269 { 270 buffer->bytes().resize(m_size.width() * m_size.height()); 271 buffer->bytes().fill(0); 272 buffer->setHasAlpha(true); 285 273 } 286 274 … … 291 279 unsigned repeatCount) // How many times to repeat the row 292 280 { 293 // Resize to the width and height of the image.281 // Initialize the frame if necessary. 294 282 RGBA32Buffer& buffer = m_frameBufferCache[frameIndex]; 295 RGBA32Buffer* previousBuffer = (frameIndex > 0) ? &m_frameBufferCache[frameIndex-1] : 0;296 bool compositeWithPreviousFrame = previousBuffer && previousBuffer->includeInNextFrame();297 298 283 if (buffer.status() == RGBA32Buffer::FrameEmpty) 299 initFrameBuffer( buffer, previousBuffer, compositeWithPreviousFrame);284 initFrameBuffer(frameIndex); 300 285 301 286 // Do nothing for bogus data. … … 322 307 unsigned char* currentRowByte = rowBuffer; 323 308 324 bool hasAlpha = m_reader->isTransparent();325 bool sawAlpha = false;326 309 while (currentRowByte != rowEnd && currDst < dstEnd) { 327 if ((! hasAlpha|| *currentRowByte != m_reader->transparentPixel()) && *currentRowByte < colorMapSize) {310 if ((!m_reader->isTransparent() || *currentRowByte != m_reader->transparentPixel()) && *currentRowByte < colorMapSize) { 328 311 unsigned colorIndex = *currentRowByte * 3; 329 312 unsigned red = colorMap[colorIndex]; … … 332 315 RGBA32Buffer::setRGBA(*currDst, red, green, blue, 255); 333 316 } else { 334 if (!sawAlpha) { 335 sawAlpha = true; 336 buffer.setHasAlpha(true); 337 } 338 339 if (!compositeWithPreviousFrame) 340 RGBA32Buffer::setRGBA(*currDst, 0, 0, 0, 0); 317 m_currentBufferSawAlpha = true; 341 318 } 342 319 currDst++; … … 364 341 } 365 342 366 void GIFImageDecoder::frameComplete(unsigned frameIndex, unsigned frameDuration, bool includeInNextFrame)343 void GIFImageDecoder::frameComplete(unsigned frameIndex, unsigned frameDuration, RGBA32Buffer::FrameDisposalMethod disposalMethod) 367 344 { 368 345 RGBA32Buffer& buffer = m_frameBufferCache[frameIndex]; 346 buffer.ensureHeight(m_size.height()); 369 347 buffer.setStatus(RGBA32Buffer::FrameComplete); 370 348 buffer.setDuration(frameDuration); 371 buffer.setIncludeInNextFrame(includeInNextFrame); 372 buffer.ensureHeight(m_size.height()); 349 buffer.setDisposalMethod(disposalMethod); 350 351 if (!m_currentBufferSawAlpha) { 352 // The whole frame was non-transparent, so it's possible that the entire 353 // resulting buffer was non-transparent, and we can setHasAlpha(false). 354 if (buffer.rect().contains(IntRect(IntPoint(0, 0), m_size))) { 355 buffer.setHasAlpha(false); 356 } else if (frameIndex > 0) { 357 // Tricky case. This frame does not have alpha only if everywhere 358 // outside its rect doesn't have alpha. To know whether this is 359 // true, we check the start state of the frame -- if it doesn't have 360 // alpha, we're safe. 361 // 362 // First skip over prior DisposeOverwritePrevious frames (since they 363 // don't affect the start state of this frame) the same way we do in 364 // initFrameBuffer(). 365 const RGBA32Buffer* prevBuffer = &m_frameBufferCache[--frameIndex]; 366 while ((frameIndex > 0) && 367 (prevBuffer->disposalMethod() == 368 RGBA32Buffer::DisposeOverwritePrevious)) 369 prevBuffer = &m_frameBufferCache[--frameIndex]; 370 371 // Now, if we're at a DisposeNotSpecified or DisposeKeep frame, then 372 // we can say we have no alpha if that frame had no alpha. But 373 // since in initFrameBuffer() we already copied that frame's alpha 374 // state into the current frame's, we need do nothing at all here. 375 // 376 // The only remaining case is a DisposeOverwriteBgcolor frame. If 377 // it had no alpha, and its rect is contained in the current frame's 378 // rect, we know the current frame has no alpha. 379 if ((prevBuffer->disposalMethod() == 380 RGBA32Buffer::DisposeOverwriteBgcolor) && 381 !prevBuffer->hasAlpha() && 382 buffer.rect().contains(prevBuffer->rect())) 383 buffer.setHasAlpha(false); 384 } 385 } 373 386 } 374 387 -
trunk/WebCore/platform/image-decoders/gif/GIFImageDecoder.h
r27902 r28068 66 66 void haveDecodedRow(unsigned frameIndex, unsigned char* rowBuffer, unsigned char* rowEnd, unsigned rowNumber, 67 67 unsigned repeatCount); 68 void frameComplete(unsigned frameIndex, unsigned frameDuration, bool includeInNextFrame);68 void frameComplete(unsigned frameIndex, unsigned frameDuration, RGBA32Buffer::FrameDisposalMethod disposalMethod); 69 69 void gifComplete(); 70 70 71 71 private: 72 // Called to initialize a new frame buffer (potentially compositing it 73 // with the previous frame and/or clearing bits in our image based off 74 // the previous frame as well). 75 void initFrameBuffer(RGBA32Buffer& buffer, 76 RGBA32Buffer* previousBuffer, 77 bool compositeWithPreviousFrame); 72 // Called to initialize the frame buffer with the given index, based on the 73 // previous frame's disposal method. 74 void initFrameBuffer(unsigned frameIndex); 75 76 // A helper for initFrameBuffer(), this sets the size of the buffer, and 77 // fills it with transparent pixels. 78 void prepEmptyFrameBuffer(RGBA32Buffer* buffer) const; 78 79 79 80 bool m_frameCountValid; 81 bool m_currentBufferSawAlpha; 80 82 mutable GIFImageDecoderPrivate* m_reader; 81 83 }; -
trunk/WebCore/platform/image-decoders/gif/GIFImageReader.cpp
r27902 r28068 642 642 // ignoring gfx control extension 643 643 } 644 frame_reader->disposal_method = (gdispose)(((*q) >> 2) & 0x7); 644 // NOTE: This relies on the values in the FrameDisposalMethod enum 645 // matching those in the GIF spec! 646 frame_reader->disposal_method = (WebCore::RGBA32Buffer::FrameDisposalMethod)(((*q) >> 2) & 0x7); 645 647 // Some specs say 3rd bit (value 4), other specs say value 3 646 648 // Let's choose 3 (the more popular) 647 649 if (frame_reader->disposal_method == 4) 648 frame_reader->disposal_method = (gdispose)3;650 frame_reader->disposal_method = WebCore::RGBA32Buffer::DisposeOverwritePrevious; 649 651 frame_reader->delay_time = GETINT16(q + 1) * 10; 650 652 } … … 883 885 if (clientptr && frame_reader) 884 886 clientptr->frameComplete(images_decoded - 1, frame_reader->delay_time, 885 frame_reader->disposal_method == DISPOSE_KEEP);887 frame_reader->disposal_method); 886 888 887 889 /* Clear state from this image */ -
trunk/WebCore/platform/image-decoders/gif/GIFImageReader.h
r27902 r28068 77 77 } gstate; 78 78 79 /* "Disposal" method indicates how the image should be handled in the80 framebuffer before the subsequent image is displayed. */81 typedef enum82 {83 DISPOSE_NOT_SPECIFIED = 0,84 DISPOSE_KEEP = 1, /* Leave it in the framebuffer */85 DISPOSE_OVERWRITE_BGCOLOR = 2, /* Overwrite with background color */86 DISPOSE_OVERWRITE_PREVIOUS = 3 /* Save-under */87 } gdispose;88 89 79 struct GIFFrameReader { 90 80 /* LZW decoder state machine */ … … 112 102 unsigned int height, width; 113 103 int tpixel; /* Index of transparent pixel */ 114 gdisposedisposal_method; /* Restore to background, leave in place, etc.*/104 WebCore::RGBA32Buffer::FrameDisposalMethod disposal_method; /* Restore to background, leave in place, etc.*/ 115 105 unsigned char *local_colormap; /* Per-image colormap */ 116 106 int local_colormap_size; /* Size of local colormap array. */ … … 141 131 x_offset = y_offset = width = height = 0; 142 132 tpixel = 0; 143 disposal_method = DISPOSE_NOT_SPECIFIED;133 disposal_method = WebCore::RGBA32Buffer::DisposeNotSpecified; 144 134 145 135 local_colormap = 0;
Note: See TracChangeset
for help on using the changeset viewer.