Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "FrameBlender.h"
8 #include "mozilla/MemoryReporting.h"
9 #include "MainThreadUtils.h"
11 #include "pixman.h"
13 using namespace mozilla;
14 using namespace mozilla::image;
16 namespace mozilla {
17 namespace image {
19 FrameBlender::FrameBlender(FrameSequence* aSequenceToUse /* = nullptr */)
20 : mFrames(aSequenceToUse)
21 , mAnim(nullptr)
22 , mLoopCount(-1)
23 {
24 if (!mFrames) {
25 mFrames = new FrameSequence();
26 }
27 }
29 FrameBlender::~FrameBlender()
30 {
31 delete mAnim;
32 }
34 already_AddRefed<FrameSequence>
35 FrameBlender::GetFrameSequence()
36 {
37 nsRefPtr<FrameSequence> seq(mFrames);
38 return seq.forget();
39 }
41 imgFrame*
42 FrameBlender::GetFrame(uint32_t framenum) const
43 {
44 if (!mAnim) {
45 NS_ASSERTION(framenum == 0, "Don't ask for a frame > 0 if we're not animated!");
46 return mFrames->GetFrame(0);
47 }
48 if (mAnim->lastCompositedFrameIndex == int32_t(framenum))
49 return mAnim->compositingFrame;
50 return mFrames->GetFrame(framenum);
51 }
53 imgFrame*
54 FrameBlender::RawGetFrame(uint32_t framenum) const
55 {
56 if (!mAnim) {
57 NS_ASSERTION(framenum == 0, "Don't ask for a frame > 0 if we're not animated!");
58 return mFrames->GetFrame(0);
59 }
61 return mFrames->GetFrame(framenum);
62 }
64 uint32_t
65 FrameBlender::GetNumFrames() const
66 {
67 return mFrames->GetNumFrames();
68 }
70 int32_t
71 FrameBlender::GetTimeoutForFrame(uint32_t framenum) const
72 {
73 const int32_t timeout = RawGetFrame(framenum)->GetRawTimeout();
74 // Ensure a minimal time between updates so we don't throttle the UI thread.
75 // consider 0 == unspecified and make it fast but not too fast. Unless we have
76 // a single loop GIF. See bug 890743, bug 125137, bug 139677, and bug 207059.
77 // The behavior of recent IE and Opera versions seems to be:
78 // IE 6/Win:
79 // 10 - 50ms go 100ms
80 // >50ms go correct speed
81 // Opera 7 final/Win:
82 // 10ms goes 100ms
83 // >10ms go correct speed
84 // It seems that there are broken tools out there that set a 0ms or 10ms
85 // timeout when they really want a "default" one. So munge values in that
86 // range.
87 if (timeout >= 0 && timeout <= 10 && mLoopCount != 0)
88 return 100;
89 return timeout;
90 }
92 void
93 FrameBlender::SetLoopCount(int32_t aLoopCount)
94 {
95 mLoopCount = aLoopCount;
96 }
98 int32_t
99 FrameBlender::GetLoopCount() const
100 {
101 return mLoopCount;
102 }
104 void
105 FrameBlender::RemoveFrame(uint32_t framenum)
106 {
107 NS_ABORT_IF_FALSE(framenum < GetNumFrames(), "Deleting invalid frame!");
109 mFrames->RemoveFrame(framenum);
110 }
112 void
113 FrameBlender::ClearFrames()
114 {
115 // Forget our old frame sequence, letting whoever else has it deal with it.
116 mFrames = new FrameSequence();
117 }
119 void
120 FrameBlender::InsertFrame(uint32_t framenum, imgFrame* aFrame)
121 {
122 NS_ABORT_IF_FALSE(framenum <= GetNumFrames(), "Inserting invalid frame!");
123 mFrames->InsertFrame(framenum, aFrame);
124 if (GetNumFrames() > 1) {
125 EnsureAnimExists();
126 }
127 }
129 imgFrame*
130 FrameBlender::SwapFrame(uint32_t framenum, imgFrame* aFrame)
131 {
132 NS_ABORT_IF_FALSE(framenum < GetNumFrames(), "Swapping invalid frame!");
134 imgFrame* ret;
136 // Steal the imgFrame from wherever it's currently stored
137 if (mAnim && mAnim->lastCompositedFrameIndex == int32_t(framenum)) {
138 ret = mAnim->compositingFrame.Forget();
139 mAnim->lastCompositedFrameIndex = -1;
140 nsAutoPtr<imgFrame> toDelete(mFrames->SwapFrame(framenum, aFrame));
141 } else {
142 ret = mFrames->SwapFrame(framenum, aFrame);
143 }
145 return ret;
146 }
148 void
149 FrameBlender::EnsureAnimExists()
150 {
151 if (!mAnim) {
152 // Create the animation context
153 mAnim = new Anim();
155 // We should only get into this code path directly after we've created our
156 // second frame (hence we know we're animated).
157 MOZ_ASSERT(GetNumFrames() == 2);
158 }
159 }
161 //******************************************************************************
162 // DoBlend gets called when the timer for animation get fired and we have to
163 // update the composited frame of the animation.
164 bool
165 FrameBlender::DoBlend(nsIntRect* aDirtyRect,
166 uint32_t aPrevFrameIndex,
167 uint32_t aNextFrameIndex)
168 {
169 if (!aDirtyRect) {
170 return false;
171 }
173 const FrameDataPair& prevFrame = mFrames->GetFrame(aPrevFrameIndex);
174 const FrameDataPair& nextFrame = mFrames->GetFrame(aNextFrameIndex);
175 if (!prevFrame.HasFrameData() || !nextFrame.HasFrameData()) {
176 return false;
177 }
179 int32_t prevFrameDisposalMethod = prevFrame->GetFrameDisposalMethod();
180 if (prevFrameDisposalMethod == FrameBlender::kDisposeRestorePrevious &&
181 !mAnim->compositingPrevFrame)
182 prevFrameDisposalMethod = FrameBlender::kDisposeClear;
184 nsIntRect prevFrameRect = prevFrame->GetRect();
185 bool isFullPrevFrame = (prevFrameRect.x == 0 && prevFrameRect.y == 0 &&
186 prevFrameRect.width == mSize.width &&
187 prevFrameRect.height == mSize.height);
189 // Optimization: DisposeClearAll if the previous frame is the same size as
190 // container and it's clearing itself
191 if (isFullPrevFrame &&
192 (prevFrameDisposalMethod == FrameBlender::kDisposeClear))
193 prevFrameDisposalMethod = FrameBlender::kDisposeClearAll;
195 int32_t nextFrameDisposalMethod = nextFrame->GetFrameDisposalMethod();
196 nsIntRect nextFrameRect = nextFrame->GetRect();
197 bool isFullNextFrame = (nextFrameRect.x == 0 && nextFrameRect.y == 0 &&
198 nextFrameRect.width == mSize.width &&
199 nextFrameRect.height == mSize.height);
201 if (!nextFrame->GetIsPaletted()) {
202 // Optimization: Skip compositing if the previous frame wants to clear the
203 // whole image
204 if (prevFrameDisposalMethod == FrameBlender::kDisposeClearAll) {
205 aDirtyRect->SetRect(0, 0, mSize.width, mSize.height);
206 return true;
207 }
209 // Optimization: Skip compositing if this frame is the same size as the
210 // container and it's fully drawing over prev frame (no alpha)
211 if (isFullNextFrame &&
212 (nextFrameDisposalMethod != FrameBlender::kDisposeRestorePrevious) &&
213 !nextFrame->GetHasAlpha()) {
214 aDirtyRect->SetRect(0, 0, mSize.width, mSize.height);
215 return true;
216 }
217 }
219 // Calculate area that needs updating
220 switch (prevFrameDisposalMethod) {
221 default:
222 case FrameBlender::kDisposeNotSpecified:
223 case FrameBlender::kDisposeKeep:
224 *aDirtyRect = nextFrameRect;
225 break;
227 case FrameBlender::kDisposeClearAll:
228 // Whole image container is cleared
229 aDirtyRect->SetRect(0, 0, mSize.width, mSize.height);
230 break;
232 case FrameBlender::kDisposeClear:
233 // Calc area that needs to be redrawn (the combination of previous and
234 // this frame)
235 // XXX - This could be done with multiple framechanged calls
236 // Having prevFrame way at the top of the image, and nextFrame
237 // way at the bottom, and both frames being small, we'd be
238 // telling framechanged to refresh the whole image when only two
239 // small areas are needed.
240 aDirtyRect->UnionRect(nextFrameRect, prevFrameRect);
241 break;
243 case FrameBlender::kDisposeRestorePrevious:
244 aDirtyRect->SetRect(0, 0, mSize.width, mSize.height);
245 break;
246 }
248 // Optimization:
249 // Skip compositing if the last composited frame is this frame
250 // (Only one composited frame was made for this animation. Example:
251 // Only Frame 3 of a 10 frame image required us to build a composite frame
252 // On the second loop, we do not need to rebuild the frame
253 // since it's still sitting in compositingFrame)
254 if (mAnim->lastCompositedFrameIndex == int32_t(aNextFrameIndex)) {
255 return true;
256 }
258 bool needToBlankComposite = false;
260 // Create the Compositing Frame
261 if (!mAnim->compositingFrame) {
262 mAnim->compositingFrame.SetFrame(new imgFrame());
263 nsresult rv = mAnim->compositingFrame->Init(0, 0, mSize.width, mSize.height,
264 gfxImageFormat::ARGB32);
265 if (NS_FAILED(rv)) {
266 mAnim->compositingFrame.SetFrame(nullptr);
267 return false;
268 }
269 mAnim->compositingFrame.LockAndGetData();
270 needToBlankComposite = true;
271 } else if (int32_t(aNextFrameIndex) != mAnim->lastCompositedFrameIndex+1) {
273 // If we are not drawing on top of last composited frame,
274 // then we are building a new composite frame, so let's clear it first.
275 needToBlankComposite = true;
276 }
278 // More optimizations possible when next frame is not transparent
279 // But if the next frame has FrameBlender::kDisposeRestorePrevious,
280 // this "no disposal" optimization is not possible,
281 // because the frame in "after disposal operation" state
282 // needs to be stored in compositingFrame, so it can be
283 // copied into compositingPrevFrame later.
284 bool doDisposal = true;
285 if (!nextFrame->GetHasAlpha() &&
286 nextFrameDisposalMethod != FrameBlender::kDisposeRestorePrevious) {
287 if (isFullNextFrame) {
288 // Optimization: No need to dispose prev.frame when
289 // next frame is full frame and not transparent.
290 doDisposal = false;
291 // No need to blank the composite frame
292 needToBlankComposite = false;
293 } else {
294 if ((prevFrameRect.x >= nextFrameRect.x) &&
295 (prevFrameRect.y >= nextFrameRect.y) &&
296 (prevFrameRect.x + prevFrameRect.width <= nextFrameRect.x + nextFrameRect.width) &&
297 (prevFrameRect.y + prevFrameRect.height <= nextFrameRect.y + nextFrameRect.height)) {
298 // Optimization: No need to dispose prev.frame when
299 // next frame fully overlaps previous frame.
300 doDisposal = false;
301 }
302 }
303 }
305 if (doDisposal) {
306 // Dispose of previous: clear, restore, or keep (copy)
307 switch (prevFrameDisposalMethod) {
308 case FrameBlender::kDisposeClear:
309 if (needToBlankComposite) {
310 // If we just created the composite, it could have anything in its
311 // buffer. Clear whole frame
312 ClearFrame(mAnim->compositingFrame.GetFrameData(),
313 mAnim->compositingFrame.GetFrame()->GetRect());
314 } else {
315 // Only blank out previous frame area (both color & Mask/Alpha)
316 ClearFrame(mAnim->compositingFrame.GetFrameData(),
317 mAnim->compositingFrame.GetFrame()->GetRect(),
318 prevFrameRect);
319 }
320 break;
322 case FrameBlender::kDisposeClearAll:
323 ClearFrame(mAnim->compositingFrame.GetFrameData(),
324 mAnim->compositingFrame.GetFrame()->GetRect());
325 break;
327 case FrameBlender::kDisposeRestorePrevious:
328 // It would be better to copy only the area changed back to
329 // compositingFrame.
330 if (mAnim->compositingPrevFrame) {
331 CopyFrameImage(mAnim->compositingPrevFrame.GetFrameData(),
332 mAnim->compositingPrevFrame.GetFrame()->GetRect(),
333 mAnim->compositingFrame.GetFrameData(),
334 mAnim->compositingFrame.GetFrame()->GetRect());
336 // destroy only if we don't need it for this frame's disposal
337 if (nextFrameDisposalMethod != FrameBlender::kDisposeRestorePrevious)
338 mAnim->compositingPrevFrame.SetFrame(nullptr);
339 } else {
340 ClearFrame(mAnim->compositingFrame.GetFrameData(),
341 mAnim->compositingFrame.GetFrame()->GetRect());
342 }
343 break;
345 default:
346 // Copy previous frame into compositingFrame before we put the new frame on top
347 // Assumes that the previous frame represents a full frame (it could be
348 // smaller in size than the container, as long as the frame before it erased
349 // itself)
350 // Note: Frame 1 never gets into DoBlend(), so (aNextFrameIndex - 1) will
351 // always be a valid frame number.
352 if (mAnim->lastCompositedFrameIndex != int32_t(aNextFrameIndex - 1)) {
353 if (isFullPrevFrame && !prevFrame->GetIsPaletted()) {
354 // Just copy the bits
355 CopyFrameImage(prevFrame.GetFrameData(),
356 prevFrame.GetFrame()->GetRect(),
357 mAnim->compositingFrame.GetFrameData(),
358 mAnim->compositingFrame.GetFrame()->GetRect());
359 } else {
360 if (needToBlankComposite) {
361 // Only blank composite when prev is transparent or not full.
362 if (prevFrame->GetHasAlpha() || !isFullPrevFrame) {
363 ClearFrame(mAnim->compositingFrame.GetFrameData(),
364 mAnim->compositingFrame.GetFrame()->GetRect());
365 }
366 }
367 DrawFrameTo(prevFrame.GetFrameData(), prevFrameRect,
368 prevFrame.GetFrame()->PaletteDataLength(),
369 prevFrame.GetFrame()->GetHasAlpha(),
370 mAnim->compositingFrame.GetFrameData(),
371 mAnim->compositingFrame.GetFrame()->GetRect(),
372 FrameBlendMethod(prevFrame.GetFrame()->GetBlendMethod()));
373 }
374 }
375 }
376 } else if (needToBlankComposite) {
377 // If we just created the composite, it could have anything in it's
378 // buffers. Clear them
379 ClearFrame(mAnim->compositingFrame.GetFrameData(),
380 mAnim->compositingFrame.GetFrame()->GetRect());
381 }
383 // Check if the frame we are composing wants the previous image restored afer
384 // it is done. Don't store it (again) if last frame wanted its image restored
385 // too
386 if ((nextFrameDisposalMethod == FrameBlender::kDisposeRestorePrevious) &&
387 (prevFrameDisposalMethod != FrameBlender::kDisposeRestorePrevious)) {
388 // We are storing the whole image.
389 // It would be better if we just stored the area that nextFrame is going to
390 // overwrite.
391 if (!mAnim->compositingPrevFrame) {
392 mAnim->compositingPrevFrame.SetFrame(new imgFrame());
393 nsresult rv = mAnim->compositingPrevFrame->Init(0, 0, mSize.width, mSize.height,
394 gfxImageFormat::ARGB32);
395 if (NS_FAILED(rv)) {
396 mAnim->compositingPrevFrame.SetFrame(nullptr);
397 return false;
398 }
400 mAnim->compositingPrevFrame.LockAndGetData();
401 }
403 CopyFrameImage(mAnim->compositingFrame.GetFrameData(),
404 mAnim->compositingFrame.GetFrame()->GetRect(),
405 mAnim->compositingPrevFrame.GetFrameData(),
406 mAnim->compositingPrevFrame.GetFrame()->GetRect());
407 }
409 // blit next frame into it's correct spot
410 DrawFrameTo(nextFrame.GetFrameData(), nextFrameRect,
411 nextFrame.GetFrame()->PaletteDataLength(),
412 nextFrame.GetFrame()->GetHasAlpha(),
413 mAnim->compositingFrame.GetFrameData(),
414 mAnim->compositingFrame.GetFrame()->GetRect(),
415 FrameBlendMethod(nextFrame.GetFrame()->GetBlendMethod()));
417 // Set timeout of CompositeFrame to timeout of frame we just composed
418 // Bug 177948
419 int32_t timeout = nextFrame->GetRawTimeout();
420 mAnim->compositingFrame->SetRawTimeout(timeout);
422 // Tell the image that it is fully 'downloaded'.
423 nsresult rv = mAnim->compositingFrame->ImageUpdated(mAnim->compositingFrame->GetRect());
424 if (NS_FAILED(rv)) {
425 return false;
426 }
428 mAnim->lastCompositedFrameIndex = int32_t(aNextFrameIndex);
430 return true;
431 }
433 //******************************************************************************
434 // Fill aFrame with black. Does also clears the mask.
435 void
436 FrameBlender::ClearFrame(uint8_t* aFrameData, const nsIntRect& aFrameRect)
437 {
438 if (!aFrameData)
439 return;
441 memset(aFrameData, 0, aFrameRect.width * aFrameRect.height * 4);
442 }
444 //******************************************************************************
445 void
446 FrameBlender::ClearFrame(uint8_t* aFrameData, const nsIntRect& aFrameRect, const nsIntRect& aRectToClear)
447 {
448 if (!aFrameData || aFrameRect.width <= 0 || aFrameRect.height <= 0 ||
449 aRectToClear.width <= 0 || aRectToClear.height <= 0) {
450 return;
451 }
453 nsIntRect toClear = aFrameRect.Intersect(aRectToClear);
454 if (toClear.IsEmpty()) {
455 return;
456 }
458 uint32_t bytesPerRow = aFrameRect.width * 4;
459 for (int row = toClear.y; row < toClear.y + toClear.height; ++row) {
460 memset(aFrameData + toClear.x * 4 + row * bytesPerRow, 0, toClear.width * 4);
461 }
462 }
464 //******************************************************************************
465 // Whether we succeed or fail will not cause a crash, and there's not much
466 // we can do about a failure, so there we don't return a nsresult
467 bool
468 FrameBlender::CopyFrameImage(const uint8_t *aDataSrc, const nsIntRect& aRectSrc,
469 uint8_t *aDataDest, const nsIntRect& aRectDest)
470 {
471 uint32_t dataLengthSrc = aRectSrc.width * aRectSrc.height * 4;
472 uint32_t dataLengthDest = aRectDest.width * aRectDest.height * 4;
474 if (!aDataDest || !aDataSrc || dataLengthSrc != dataLengthDest) {
475 return false;
476 }
478 memcpy(aDataDest, aDataSrc, dataLengthDest);
480 return true;
481 }
483 nsresult
484 FrameBlender::DrawFrameTo(const uint8_t *aSrcData, const nsIntRect& aSrcRect,
485 uint32_t aSrcPaletteLength, bool aSrcHasAlpha,
486 uint8_t *aDstPixels, const nsIntRect& aDstRect,
487 FrameBlender::FrameBlendMethod aBlendMethod)
488 {
489 NS_ENSURE_ARG_POINTER(aSrcData);
490 NS_ENSURE_ARG_POINTER(aDstPixels);
492 // According to both AGIF and APNG specs, offsets are unsigned
493 if (aSrcRect.x < 0 || aSrcRect.y < 0) {
494 NS_WARNING("FrameBlender::DrawFrameTo: negative offsets not allowed");
495 return NS_ERROR_FAILURE;
496 }
497 // Outside the destination frame, skip it
498 if ((aSrcRect.x > aDstRect.width) || (aSrcRect.y > aDstRect.height)) {
499 return NS_OK;
500 }
502 if (aSrcPaletteLength) {
503 // Larger than the destination frame, clip it
504 int32_t width = std::min(aSrcRect.width, aDstRect.width - aSrcRect.x);
505 int32_t height = std::min(aSrcRect.height, aDstRect.height - aSrcRect.y);
507 // The clipped image must now fully fit within destination image frame
508 NS_ASSERTION((aSrcRect.x >= 0) && (aSrcRect.y >= 0) &&
509 (aSrcRect.x + width <= aDstRect.width) &&
510 (aSrcRect.y + height <= aDstRect.height),
511 "FrameBlender::DrawFrameTo: Invalid aSrcRect");
513 // clipped image size may be smaller than source, but not larger
514 NS_ASSERTION((width <= aSrcRect.width) && (height <= aSrcRect.height),
515 "FrameBlender::DrawFrameTo: source must be smaller than dest");
517 // Get pointers to image data
518 const uint8_t *srcPixels = aSrcData + aSrcPaletteLength;
519 uint32_t *dstPixels = reinterpret_cast<uint32_t*>(aDstPixels);
520 const uint32_t *colormap = reinterpret_cast<const uint32_t*>(aSrcData);
522 // Skip to the right offset
523 dstPixels += aSrcRect.x + (aSrcRect.y * aDstRect.width);
524 if (!aSrcHasAlpha) {
525 for (int32_t r = height; r > 0; --r) {
526 for (int32_t c = 0; c < width; c++) {
527 dstPixels[c] = colormap[srcPixels[c]];
528 }
529 // Go to the next row in the source resp. destination image
530 srcPixels += aSrcRect.width;
531 dstPixels += aDstRect.width;
532 }
533 } else {
534 for (int32_t r = height; r > 0; --r) {
535 for (int32_t c = 0; c < width; c++) {
536 const uint32_t color = colormap[srcPixels[c]];
537 if (color)
538 dstPixels[c] = color;
539 }
540 // Go to the next row in the source resp. destination image
541 srcPixels += aSrcRect.width;
542 dstPixels += aDstRect.width;
543 }
544 }
545 } else {
546 pixman_image_t* src = pixman_image_create_bits(aSrcHasAlpha ? PIXMAN_a8r8g8b8 : PIXMAN_x8r8g8b8,
547 aSrcRect.width,
548 aSrcRect.height,
549 reinterpret_cast<uint32_t*>(const_cast<uint8_t*>(aSrcData)),
550 aSrcRect.width * 4);
551 pixman_image_t* dst = pixman_image_create_bits(PIXMAN_a8r8g8b8,
552 aDstRect.width,
553 aDstRect.height,
554 reinterpret_cast<uint32_t*>(aDstPixels),
555 aDstRect.width * 4);
557 pixman_image_composite32(aBlendMethod == FrameBlender::kBlendSource ? PIXMAN_OP_SRC : PIXMAN_OP_OVER,
558 src,
559 nullptr,
560 dst,
561 0, 0,
562 0, 0,
563 aSrcRect.x, aSrcRect.y,
564 aSrcRect.width, aSrcRect.height);
566 pixman_image_unref(src);
567 pixman_image_unref(dst);
568 }
570 return NS_OK;
571 }
573 void
574 FrameBlender::Discard()
575 {
576 MOZ_ASSERT(NS_IsMainThread());
578 // As soon as an image becomes animated, it becomes non-discardable and any
579 // timers are cancelled.
580 NS_ABORT_IF_FALSE(!mAnim, "Asked to discard for animated image!");
582 // Delete all the decoded frames, then clear the array.
583 ClearFrames();
584 }
586 size_t
587 FrameBlender::SizeOfDecodedWithComputedFallbackIfHeap(gfxMemoryLocation aLocation,
588 MallocSizeOf aMallocSizeOf) const
589 {
590 size_t n = mFrames->SizeOfDecodedWithComputedFallbackIfHeap(aLocation, aMallocSizeOf);
592 if (mAnim) {
593 if (mAnim->compositingFrame) {
594 n += mAnim->compositingFrame->SizeOfExcludingThisWithComputedFallbackIfHeap(aLocation, aMallocSizeOf);
595 }
596 if (mAnim->compositingPrevFrame) {
597 n += mAnim->compositingPrevFrame->SizeOfExcludingThisWithComputedFallbackIfHeap(aLocation, aMallocSizeOf);
598 }
599 }
601 return n;
602 }
604 void
605 FrameBlender::ResetAnimation()
606 {
607 if (mAnim) {
608 mAnim->lastCompositedFrameIndex = -1;
609 }
610 }
612 } // namespace image
613 } // namespace mozilla