ASSERTION FAILED: !canAnimate() && !m_currentFrame
[WebKit.git] / Source / WebCore / platform / graphics / BitmapImage.cpp
1 /*
2  * Copyright (C) 2006 Samuel Weinig (sam.weinig@gmail.com)
3  * Copyright (C) 2004, 2005, 2006, 2008, 2015 Apple Inc. All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
15  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
18  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  */
26
27 #include "config.h"
28 #include "BitmapImage.h"
29
30 #include "FloatRect.h"
31 #include "GraphicsContext.h"
32 #include "ImageBuffer.h"
33 #include "ImageObserver.h"
34 #include "IntRect.h"
35 #include "Logging.h"
36 #include "Settings.h"
37 #include "TextStream.h"
38 #include "Timer.h"
39 #include <wtf/CurrentTime.h>
40 #include <wtf/Vector.h>
41 #include <wtf/text/WTFString.h>
42
43 #if PLATFORM(IOS)
44 #include "RuntimeApplicationChecks.h"
45 #endif
46
47 namespace WebCore {
48
49 BitmapImage::BitmapImage(ImageObserver* observer)
50     : Image(observer)
51     , m_source(this)
52 {
53 }
54
55 BitmapImage::BitmapImage(NativeImagePtr&& image, ImageObserver* observer)
56     : Image(observer)
57     , m_source(WTFMove(image))
58 {
59 }
60
61 BitmapImage::~BitmapImage()
62 {
63     invalidatePlatformData();
64     clearTimer();
65     m_source.clearImage();
66     m_source.stopAsyncDecodingQueue();
67 }
68
69 void BitmapImage::updateFromSettings(const Settings& settings)
70 {
71     m_allowSubsampling = settings.imageSubsamplingEnabled();
72 #if PLATFORM(IOS)
73     if (!IOSApplication::isIBooks())
74 #endif
75         m_allowLargeImageAsyncDecoding = settings.largeImageAsyncDecodingEnabled();
76     m_allowAnimatedImageAsyncDecoding = settings.animatedImageAsyncDecodingEnabled();
77     m_showDebugBackground = settings.showDebugBorders();
78 }
79
80 void BitmapImage::destroyDecodedData(bool destroyAll)
81 {
82     LOG(Images, "BitmapImage::%s - %p - url: %s", __FUNCTION__, this, sourceURL().string().utf8().data());
83
84     if (!destroyAll)
85         m_source.destroyDecodedDataBeforeFrame(m_currentFrame);
86     else if (!canDestroyDecodedData())
87         m_source.destroyAllDecodedDataExcludeFrame(m_currentFrame);
88     else {
89         m_source.destroyAllDecodedData();
90         m_currentFrameDecodingStatus = ImageFrame::DecodingStatus::Invalid;
91     }
92
93     // There's no need to throw away the decoder unless we're explicitly asked
94     // to destroy all of the frames.
95     if (!destroyAll || m_source.hasAsyncDecodingQueue())
96         m_source.clearFrameBufferCache(m_currentFrame);
97     else
98         m_source.resetData(data());
99
100     invalidatePlatformData();
101 }
102
103 void BitmapImage::destroyDecodedDataIfNecessary(bool destroyAll)
104 {
105     // If we have decoded frames but there is no encoded data, we shouldn't destroy
106     // the decoded image since we won't be able to reconstruct it later.
107     if (!data() && frameCount())
108         return;
109
110     if (m_source.decodedSize() < LargeAnimationCutoff)
111         return;
112
113     destroyDecodedData(destroyAll);
114 }
115
116 EncodedDataStatus BitmapImage::dataChanged(bool allDataReceived)
117 {
118     if (!shouldUseAsyncDecodingForLargeImages())
119         m_source.destroyIncompleteDecodedData();
120
121     m_currentFrameDecodingStatus = ImageFrame::DecodingStatus::Invalid;
122     return m_source.dataChanged(data(), allDataReceived);
123 }
124
125 NativeImagePtr BitmapImage::frameImageAtIndexCacheIfNeeded(size_t index, SubsamplingLevel subsamplingLevel, const GraphicsContext* targetContext)
126 {
127     if (!frameHasFullSizeNativeImageAtIndex(index, subsamplingLevel)) {
128         LOG(Images, "BitmapImage::%s - %p - url: %s [subsamplingLevel was %d, resampling]", __FUNCTION__, this, sourceURL().string().utf8().data(), static_cast<int>(frameSubsamplingLevelAtIndex(index)));
129         invalidatePlatformData();
130     }
131
132     return m_source.frameImageAtIndexCacheIfNeeded(index, subsamplingLevel, targetContext);
133 }
134
135 NativeImagePtr BitmapImage::nativeImage(const GraphicsContext* targetContext)
136 {
137     return frameImageAtIndexCacheIfNeeded(0, SubsamplingLevel::Default, targetContext);
138 }
139
140 NativeImagePtr BitmapImage::nativeImageForCurrentFrame(const GraphicsContext* targetContext)
141 {
142     return frameImageAtIndexCacheIfNeeded(m_currentFrame, SubsamplingLevel::Default, targetContext);
143 }
144
145 #if USE(CG)
146 NativeImagePtr BitmapImage::nativeImageOfSize(const IntSize& size, const GraphicsContext* targetContext)
147 {
148     size_t count = frameCount();
149
150     for (size_t i = 0; i < count; ++i) {
151         auto image = frameImageAtIndexCacheIfNeeded(i, SubsamplingLevel::Default, targetContext);
152         if (image && nativeImageSize(image) == size)
153             return image;
154     }
155
156     // Fallback to the first frame image if we can't find the right size
157     return frameImageAtIndexCacheIfNeeded(0, SubsamplingLevel::Default, targetContext);
158 }
159
160 Vector<NativeImagePtr> BitmapImage::framesNativeImages()
161 {
162     Vector<NativeImagePtr> images;
163     size_t count = frameCount();
164
165     for (size_t i = 0; i < count; ++i) {
166         if (auto image = frameImageAtIndexCacheIfNeeded(i))
167             images.append(image);
168     }
169
170     return images;
171 }
172 #endif
173
174 #if !ASSERT_DISABLED
175 bool BitmapImage::notSolidColor()
176 {
177     return size().width() != 1 || size().height() != 1 || frameCount() > 1;
178 }
179 #endif
180
181 void BitmapImage::draw(GraphicsContext& context, const FloatRect& destRect, const FloatRect& srcRect, CompositeOperator op, BlendMode mode, DecodingMode decodingMode, ImageOrientationDescription description)
182 {
183     if (destRect.isEmpty() || srcRect.isEmpty())
184         return;
185
186     FloatSize scaleFactorForDrawing = context.scaleFactorForDrawing(destRect, srcRect);
187     IntSize sizeForDrawing = expandedIntSize(size() * scaleFactorForDrawing);
188
189     m_currentSubsamplingLevel = m_allowSubsampling ? m_source.subsamplingLevelForScaleFactor(context, scaleFactorForDrawing) : SubsamplingLevel::Default;
190     LOG(Images, "BitmapImage::%s - %p - url: %s [subsamplingLevel = %d scaleFactorForDrawing = (%.4f, %.4f)]", __FUNCTION__, this, sourceURL().string().utf8().data(), static_cast<int>(m_currentSubsamplingLevel), scaleFactorForDrawing.width(), scaleFactorForDrawing.height());
191
192     NativeImagePtr image;
193     if (decodingMode == DecodingMode::Asynchronous && shouldUseAsyncDecodingForLargeImages()) {
194         ASSERT(!canAnimate());
195         ASSERT(!m_currentFrame || m_animationFinished);
196
197         bool frameIsCompatible = frameHasDecodedNativeImageCompatibleWithOptionsAtIndex(m_currentFrame, m_currentSubsamplingLevel, DecodingOptions(sizeForDrawing));
198         bool frameIsBeingDecoded = frameIsBeingDecodedAndIsCompatibleWithOptionsAtIndex(m_currentFrame, DecodingOptions(sizeForDrawing));
199
200         // If the current frame is incomplete, a new request for decoding this frame has to be made even if
201         // it is currently being decoded. New data may have been received since the previous request was made.
202         if ((!frameIsCompatible && !frameIsBeingDecoded) || m_currentFrameDecodingStatus == ImageFrame::DecodingStatus::Invalid) {
203             LOG(Images, "BitmapImage::%s - %p - url: %s [requesting large async decoding]", __FUNCTION__, this, sourceURL().string().utf8().data());
204             m_source.requestFrameAsyncDecodingAtIndex(m_currentFrame, m_currentSubsamplingLevel, sizeForDrawing);
205             m_currentFrameDecodingStatus = ImageFrame::DecodingStatus::Decoding;
206         }
207
208         if (!frameHasDecodedNativeImageCompatibleWithOptionsAtIndex(m_currentFrame, m_currentSubsamplingLevel, DecodingMode::Asynchronous)) {
209             if (m_showDebugBackground)
210                 fillWithSolidColor(context, destRect, Color(Color::yellow).colorWithAlpha(0.5), op);
211             return;
212         }
213
214         image = frameImageAtIndex(m_currentFrame);
215         LOG(Images, "BitmapImage::%s - %p - url: %s [a decoded image frame is available for drawing]", __FUNCTION__, this, sourceURL().string().utf8().data());
216     } else {
217         StartAnimationStatus status = internalStartAnimation();
218         ASSERT_IMPLIES(status == StartAnimationStatus::DecodingActive, (!m_currentFrame && !m_repetitionsComplete) || frameHasFullSizeNativeImageAtIndex(m_currentFrame, m_currentSubsamplingLevel));
219
220         if (status == StartAnimationStatus::DecodingActive && m_showDebugBackground) {
221             fillWithSolidColor(context, destRect, Color(Color::yellow).colorWithAlpha(0.5), op);
222             return;
223         }
224
225         if (frameIsBeingDecodedAndIsCompatibleWithOptionsAtIndex(m_currentFrame, DecodingMode::Asynchronous)) {
226             // FIXME: instead of showing the yellow rectangle and returning we need to wait for this the frame to finish decoding.
227             if (m_showDebugBackground) {
228                 fillWithSolidColor(context, destRect, Color(Color::yellow).colorWithAlpha(0.5), op);
229                 LOG(Images, "BitmapImage::%s - %p - url: %s [waiting for async decoding to finish]", __FUNCTION__, this, sourceURL().string().utf8().data());
230             }
231             return;
232         }
233
234         image = frameImageAtIndexCacheIfNeeded(m_currentFrame, m_currentSubsamplingLevel, &context);
235         if (!image) // If it's too early we won't have an image yet.
236             return;
237
238         if (m_currentFrameDecodingStatus != ImageFrame::DecodingStatus::Complete)
239             ++m_decodeCountForTesting;
240     }
241
242     ASSERT(image);
243     Color color = singlePixelSolidColor();
244     if (color.isValid()) {
245         fillWithSolidColor(context, destRect, color, op);
246         return;
247     }
248
249     ImageOrientation orientation(description.imageOrientation());
250     if (description.respectImageOrientation() == RespectImageOrientation)
251         orientation = frameOrientationAtIndex(m_currentFrame);
252
253     drawNativeImage(image, context, destRect, srcRect, IntSize(size()), op, mode, orientation);
254     m_currentFrameDecodingStatus = frameDecodingStatusAtIndex(m_currentFrame);
255
256     if (imageObserver())
257         imageObserver()->didDraw(*this);
258 }
259
260 void BitmapImage::drawPattern(GraphicsContext& ctxt, const FloatRect& destRect, const FloatRect& tileRect, const AffineTransform& transform, const FloatPoint& phase, const FloatSize& spacing, CompositeOperator op, BlendMode blendMode)
261 {
262     if (tileRect.isEmpty())
263         return;
264
265     if (!ctxt.drawLuminanceMask()) {
266         Image::drawPattern(ctxt, destRect, tileRect, transform, phase, spacing, op, blendMode);
267         return;
268     }
269
270     if (!m_cachedImage) {
271         auto buffer = ImageBuffer::createCompatibleBuffer(expandedIntSize(tileRect.size()), ColorSpaceSRGB, ctxt);
272         if (!buffer)
273             return;
274
275         ImageObserver* observer = imageObserver();
276
277         // Temporarily reset image observer, we don't want to receive any changeInRect() calls due to this relayout.
278         setImageObserver(nullptr);
279
280         draw(buffer->context(), tileRect, tileRect, op, blendMode, DecodingMode::Synchronous, ImageOrientationDescription());
281
282         setImageObserver(observer);
283         buffer->convertToLuminanceMask();
284
285         m_cachedImage = buffer->copyImage(DontCopyBackingStore, Unscaled);
286         if (!m_cachedImage)
287             return;
288     }
289
290     ctxt.setDrawLuminanceMask(false);
291     m_cachedImage->drawPattern(ctxt, destRect, tileRect, transform, phase, spacing, op, blendMode);
292 }
293
294 bool BitmapImage::shouldAnimate()
295 {
296     return repetitionCount() && !m_animationFinished && imageObserver();
297 }
298
299 bool BitmapImage::canAnimate()
300 {
301     return shouldAnimate() && frameCount() > 1;
302 }
303
304 bool BitmapImage::shouldUseAsyncDecodingForLargeImages()
305 {
306     return !canAnimate() && m_allowLargeImageAsyncDecoding && m_source.shouldUseAsyncDecoding();
307 }
308
309 bool BitmapImage::shouldUseAsyncDecodingForAnimatedImages()
310 {
311     return canAnimate() && m_allowAnimatedImageAsyncDecoding && (shouldUseAsyncDecodingForAnimatedImagesForTesting() || m_source.shouldUseAsyncDecoding());
312 }
313
314 void BitmapImage::clearTimer()
315 {
316     m_frameTimer = nullptr;
317 }
318
319 void BitmapImage::startTimer(Seconds delay)
320 {
321     ASSERT(!m_frameTimer);
322     m_frameTimer = std::make_unique<Timer>(*this, &BitmapImage::advanceAnimation);
323     m_frameTimer->startOneShot(delay);
324 }
325
326 bool BitmapImage::canDestroyDecodedData()
327 {
328     // Animated images should preserve the current frame till the next one finishes decoding.
329     if (m_source.hasAsyncDecodingQueue())
330         return false;
331
332     // Small image should be decoded synchronously. Deleting its decoded frame is fine.
333     if (!shouldUseAsyncDecodingForLargeImages())
334         return true;
335
336     return !imageObserver() || imageObserver()->canDestroyDecodedData(*this);
337 }
338
339 BitmapImage::StartAnimationStatus BitmapImage::internalStartAnimation()
340 {
341     if (!canAnimate())
342         return StartAnimationStatus::CannotStart;
343
344     if (m_frameTimer)
345         return StartAnimationStatus::TimerActive;
346
347     // Don't start a new animation until we draw the frame that is currently being decoded.
348     size_t nextFrame = (m_currentFrame + 1) % frameCount();
349     if (frameIsBeingDecodedAndIsCompatibleWithOptionsAtIndex(nextFrame, DecodingMode::Asynchronous)) {
350         LOG(Images, "BitmapImage::%s - %p - url: %s [nextFrame = %ld is being decoded]", __FUNCTION__, this, sourceURL().string().utf8().data(), nextFrame);
351         return StartAnimationStatus::DecodingActive;
352     }
353
354     if (m_currentFrame >= frameCount() - 1) {
355         // Don't advance past the last frame if we haven't decoded the whole image
356         // yet and our repetition count is potentially unset. The repetition count
357         // in a GIF can potentially come after all the rest of the image data, so
358         // wait on it.
359         if (!m_source.isAllDataReceived() && repetitionCount() == RepetitionCountOnce)
360             return StartAnimationStatus::IncompleteData;
361
362         ++m_repetitionsComplete;
363
364         // Check for the end of animation.
365         if (repetitionCount() != RepetitionCountInfinite && m_repetitionsComplete >= repetitionCount()) {
366             m_animationFinished = true;
367             destroyDecodedDataIfNecessary(false);
368             return StartAnimationStatus::CannotStart;
369         }
370
371         destroyDecodedDataIfNecessary(true);
372     }
373
374     // Don't advance the animation to an incomplete frame.
375     if (!m_source.isAllDataReceived() && !frameIsCompleteAtIndex(nextFrame))
376         return StartAnimationStatus::IncompleteData;
377
378     MonotonicTime time = MonotonicTime::now();
379
380     // Handle initial state.
381     if (!m_desiredFrameStartTime)
382         m_desiredFrameStartTime = time;
383
384     // Setting 'm_desiredFrameStartTime' to 'time' means we are late; otherwise we are early.
385     m_desiredFrameStartTime = std::max(time, m_desiredFrameStartTime + Seconds { frameDurationAtIndex(m_currentFrame) });
386
387     // Request async decoding for nextFrame only if this is required. If nextFrame is not in the frameCache,
388     // it will be decoded on a separate work queue. When decoding nextFrame finishes, we will be notified
389     // through the callback newFrameNativeImageAvailableAtIndex(). Otherwise, advanceAnimation() will be called
390     // when the timer fires and m_currentFrame will be advanced to nextFrame since it is not being decoded.
391     if (shouldUseAsyncDecodingForAnimatedImages()) {
392         if (frameHasDecodedNativeImageCompatibleWithOptionsAtIndex(nextFrame, m_currentSubsamplingLevel, { }))
393             LOG(Images, "BitmapImage::%s - %p - url: %s [cachedFrameCount = %ld nextFrame = %ld]", __FUNCTION__, this, sourceURL().string().utf8().data(), ++m_cachedFrameCount, nextFrame);
394         else {
395             m_source.requestFrameAsyncDecodingAtIndex(nextFrame, m_currentSubsamplingLevel);
396             m_currentFrameDecodingStatus = ImageFrame::DecodingStatus::Decoding;
397             LOG(Images, "BitmapImage::%s - %p - url: %s [requesting async decoding for nextFrame = %ld]", __FUNCTION__, this, sourceURL().string().utf8().data(), nextFrame);
398         }
399
400         m_desiredFrameDecodeTimeForTesting = time + std::max(m_frameDecodingDurationForTesting, 0_s);
401         if (m_clearDecoderAfterAsyncFrameRequestForTesting)
402             m_source.resetData(data());
403     }
404
405     ASSERT(!m_frameTimer);
406     startTimer(m_desiredFrameStartTime - time);
407     return StartAnimationStatus::Started;
408 }
409
410 void BitmapImage::advanceAnimation()
411 {
412     clearTimer();
413
414     // Pretend as if decoding nextFrame has taken m_frameDecodingDurationForTesting from
415     // the time this decoding was requested.
416     if (shouldUseAsyncDecodingForAnimatedImagesForTesting()) {
417         MonotonicTime time = MonotonicTime::now();
418         // Start a timer with the remaining time from now till the m_desiredFrameDecodeTime.
419         if (m_desiredFrameDecodeTimeForTesting > std::max(time, m_desiredFrameStartTime)) {
420             startTimer(m_desiredFrameDecodeTimeForTesting - time);
421             return;
422         }
423     }
424
425     // Don't advance to nextFrame unless its decoding has finished or was not required.
426     size_t nextFrame = (m_currentFrame + 1) % frameCount();
427     if (!frameIsBeingDecodedAndIsCompatibleWithOptionsAtIndex(nextFrame, DecodingMode::Asynchronous))
428         internalAdvanceAnimation();
429     else {
430         // Force repaint if showDebugBackground() is on.
431         if (m_showDebugBackground)
432             imageObserver()->changedInRect(*this);
433         LOG(Images, "BitmapImage::%s - %p - url: %s [lateFrameCount = %ld nextFrame = %ld]", __FUNCTION__, this, sourceURL().string().utf8().data(), ++m_lateFrameCount, nextFrame);
434     }
435 }
436
437 void BitmapImage::internalAdvanceAnimation()
438 {
439     m_currentFrame = (m_currentFrame + 1) % frameCount();
440     ASSERT(!frameIsBeingDecodedAndIsCompatibleWithOptionsAtIndex(m_currentFrame, DecodingMode::Asynchronous));
441
442     destroyDecodedDataIfNecessary(false);
443
444     if (m_currentFrameDecodingStatus == ImageFrame::DecodingStatus::Decoding)
445         m_currentFrameDecodingStatus = frameDecodingStatusAtIndex(m_currentFrame);
446     if (imageObserver())
447         imageObserver()->imageFrameAvailable(*this, ImageAnimatingState::Yes);
448
449     LOG(Images, "BitmapImage::%s - %p - url: %s [m_currentFrame = %ld]", __FUNCTION__, this, sourceURL().string().utf8().data(), m_currentFrame);
450 }
451
452 bool BitmapImage::isAnimating() const
453 {
454     return !!m_frameTimer;
455 }
456
457 void BitmapImage::stopAnimation()
458 {
459     // This timer is used to animate all occurrences of this image. Don't invalidate
460     // the timer unless all renderers have stopped drawing.
461     clearTimer();
462     if (canAnimate())
463         m_source.stopAsyncDecodingQueue();
464 }
465
466 void BitmapImage::resetAnimation()
467 {
468     stopAnimation();
469     m_currentFrame = 0;
470     m_repetitionsComplete = RepetitionCountNone;
471     m_desiredFrameStartTime = { };
472     m_animationFinished = false;
473
474     // For extremely large animations, when the animation is reset, we just throw everything away.
475     destroyDecodedDataIfNecessary(true);
476 }
477
478 void BitmapImage::imageFrameAvailableAtIndex(size_t index)
479 {
480     LOG(Images, "BitmapImage::%s - %p - url: %s [requested frame %ld is now available]", __FUNCTION__, this, sourceURL().string().utf8().data(), index);
481
482     if (canAnimate()) {
483         if (index == (m_currentFrame + 1) % frameCount()) {
484             // Don't advance to nextFrame unless the timer was fired before its decoding finishes.
485             if (!m_frameTimer)
486                 internalAdvanceAnimation();
487             else
488                 LOG(Images, "BitmapImage::%s - %p - url: %s [earlyFrameCount = %ld nextFrame = %ld]", __FUNCTION__, this, sourceURL().string().utf8().data(), ++m_earlyFrameCount, index);
489             return;
490         }
491
492         // Because of image partial loading, an image may start decoding as a large static image. But
493         // when more data is received, frameCount() changes to be > 1 so the image starts animating.
494         // The animation may even start before finishing the decoding of the first frame.
495         ASSERT(!m_repetitionsComplete);
496         LOG(Images, "BitmapImage::%s - %p - url: %s [More data makes frameCount() > 1]", __FUNCTION__, this, sourceURL().string().utf8().data());
497     }
498
499     ASSERT(index == m_currentFrame && !m_currentFrame);
500     if (m_source.isAsyncDecodingQueueIdle())
501         m_source.stopAsyncDecodingQueue();
502     if (m_currentFrameDecodingStatus == ImageFrame::DecodingStatus::Decoding)
503         m_currentFrameDecodingStatus = frameDecodingStatusAtIndex(m_currentFrame);
504
505     if (m_currentFrameDecodingStatus == ImageFrame::DecodingStatus::Complete)
506         ++m_decodeCountForTesting;
507
508     if (imageObserver())
509         imageObserver()->imageFrameAvailable(*this, ImageAnimatingState::No);
510 }
511
512 unsigned BitmapImage::decodeCountForTesting() const
513 {
514     return m_decodeCountForTesting;
515 }
516
517 void BitmapImage::dump(TextStream& ts) const
518 {
519     Image::dump(ts);
520     
521     if (isAnimated())
522         ts.dumpProperty("current-frame", m_currentFrame);
523     
524     m_source.dump(ts);
525 }
526
527 }