Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/image/FrameAnimator.cpp
Line
Count
Source (jump to first uncovered line)
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/. */
5
6
#include "FrameAnimator.h"
7
8
#include "mozilla/MemoryReporting.h"
9
#include "mozilla/Move.h"
10
#include "mozilla/CheckedInt.h"
11
#include "imgIContainer.h"
12
#include "LookupResult.h"
13
#include "MainThreadUtils.h"
14
#include "RasterImage.h"
15
#include "gfxPrefs.h"
16
17
#include "pixman.h"
18
#include <algorithm>
19
20
namespace mozilla {
21
22
using namespace gfx;
23
24
namespace image {
25
26
///////////////////////////////////////////////////////////////////////////////
27
// AnimationState implementation.
28
///////////////////////////////////////////////////////////////////////////////
29
30
const gfx::IntRect
31
AnimationState::UpdateState(bool aAnimationFinished,
32
                            RasterImage *aImage,
33
                            const gfx::IntSize& aSize,
34
                            bool aAllowInvalidation /* = true */)
35
0
{
36
0
  LookupResult result =
37
0
    SurfaceCache::Lookup(ImageKey(aImage),
38
0
                         RasterSurfaceKey(aSize,
39
0
                                          DefaultSurfaceFlags(),
40
0
                                          PlaybackType::eAnimated));
41
0
42
0
  return UpdateStateInternal(result, aAnimationFinished, aSize, aAllowInvalidation);
43
0
}
44
45
const gfx::IntRect
46
AnimationState::UpdateStateInternal(LookupResult& aResult,
47
                                    bool aAnimationFinished,
48
                                    const gfx::IntSize& aSize,
49
                                    bool aAllowInvalidation /* = true */)
50
0
{
51
0
  // Update mDiscarded and mIsCurrentlyDecoded.
52
0
  if (aResult.Type() == MatchType::NOT_FOUND) {
53
0
    // no frames, we've either been discarded, or never been decoded before.
54
0
    mDiscarded = mHasBeenDecoded;
55
0
    mIsCurrentlyDecoded = false;
56
0
  } else if (aResult.Type() == MatchType::PENDING) {
57
0
    // no frames yet, but a decoder is or will be working on it.
58
0
    mDiscarded = false;
59
0
    mIsCurrentlyDecoded = false;
60
0
    mHasRequestedDecode = true;
61
0
  } else {
62
0
    MOZ_ASSERT(aResult.Type() == MatchType::EXACT);
63
0
    mDiscarded = false;
64
0
    mHasRequestedDecode = true;
65
0
66
0
    // If mHasBeenDecoded is true then we know the true total frame count and
67
0
    // we can use it to determine if we have all the frames now so we know if
68
0
    // we are currently fully decoded.
69
0
    // If mHasBeenDecoded is false then we'll get another UpdateState call
70
0
    // when the decode finishes.
71
0
    if (mHasBeenDecoded) {
72
0
      Maybe<uint32_t> frameCount = FrameCount();
73
0
      MOZ_ASSERT(frameCount.isSome());
74
0
      mIsCurrentlyDecoded = aResult.Surface().IsFullyDecoded();
75
0
    }
76
0
  }
77
0
78
0
  gfx::IntRect ret;
79
0
80
0
  if (aAllowInvalidation) {
81
0
    // Update the value of mCompositedFrameInvalid.
82
0
    if (mIsCurrentlyDecoded || aAnimationFinished) {
83
0
      // Animated images that have finished their animation (ie because it is a
84
0
      // finite length animation) don't have RequestRefresh called on them, and so
85
0
      // mCompositedFrameInvalid would never get cleared. We clear it here (and
86
0
      // also in RasterImage::Decode when we create a decoder for an image that
87
0
      // has finished animated so it can display sooner than waiting until the
88
0
      // decode completes). We also do it if we are fully decoded. This is safe
89
0
      // to do for images that aren't finished animating because before we paint
90
0
      // the refresh driver will call into us to advance to the correct frame,
91
0
      // and that will succeed because we have all the frames.
92
0
      if (mCompositedFrameInvalid) {
93
0
        // Invalidate if we are marking the composited frame valid.
94
0
        ret.SizeTo(aSize);
95
0
      }
96
0
      mCompositedFrameInvalid = false;
97
0
    } else if (aResult.Type() == MatchType::NOT_FOUND ||
98
0
               aResult.Type() == MatchType::PENDING) {
99
0
      if (mHasRequestedDecode) {
100
0
        MOZ_ASSERT(gfxPrefs::ImageMemAnimatedDiscardable());
101
0
        mCompositedFrameInvalid = true;
102
0
      }
103
0
    }
104
0
    // Otherwise don't change the value of mCompositedFrameInvalid, it will be
105
0
    // updated by RequestRefresh.
106
0
  }
107
0
108
0
  return ret;
109
0
}
110
111
void
112
AnimationState::NotifyDecodeComplete()
113
0
{
114
0
  mHasBeenDecoded = true;
115
0
}
116
117
void
118
AnimationState::ResetAnimation()
119
0
{
120
0
  mCurrentAnimationFrameIndex = 0;
121
0
}
122
123
void
124
AnimationState::SetAnimationMode(uint16_t aAnimationMode)
125
0
{
126
0
  mAnimationMode = aAnimationMode;
127
0
}
128
129
void
130
AnimationState::UpdateKnownFrameCount(uint32_t aFrameCount)
131
0
{
132
0
  if (aFrameCount <= mFrameCount) {
133
0
    // Nothing to do. Since we can redecode animated images, we may see the same
134
0
    // sequence of updates replayed again, so seeing a smaller frame count than
135
0
    // what we already know about doesn't indicate an error.
136
0
    return;
137
0
  }
138
0
139
0
  MOZ_ASSERT(!mHasBeenDecoded, "Adding new frames after decoding is finished?");
140
0
  MOZ_ASSERT(aFrameCount <= mFrameCount + 1, "Skipped a frame?");
141
0
142
0
  mFrameCount = aFrameCount;
143
0
}
144
145
Maybe<uint32_t>
146
AnimationState::FrameCount() const
147
0
{
148
0
  return mHasBeenDecoded ? Some(mFrameCount) : Nothing();
149
0
}
150
151
void
152
AnimationState::SetFirstFrameRefreshArea(const IntRect& aRefreshArea)
153
0
{
154
0
  mFirstFrameRefreshArea = aRefreshArea;
155
0
}
156
157
void
158
AnimationState::InitAnimationFrameTimeIfNecessary()
159
0
{
160
0
  if (mCurrentAnimationFrameTime.IsNull()) {
161
0
    mCurrentAnimationFrameTime = TimeStamp::Now();
162
0
  }
163
0
}
164
165
void
166
AnimationState::SetAnimationFrameTime(const TimeStamp& aTime)
167
0
{
168
0
  mCurrentAnimationFrameTime = aTime;
169
0
}
170
171
bool
172
AnimationState::MaybeAdvanceAnimationFrameTime(const TimeStamp& aTime)
173
0
{
174
0
  if (!gfxPrefs::ImageAnimatedResumeFromLastDisplayed() ||
175
0
      mCurrentAnimationFrameTime >= aTime) {
176
0
    return false;
177
0
  }
178
0
179
0
  // We are configured to stop an animation when it is out of view, and restart
180
0
  // it from the same point when it comes back into view. The same applies if it
181
0
  // was discarded while out of view.
182
0
  mCurrentAnimationFrameTime = aTime;
183
0
  return true;
184
0
}
185
186
uint32_t
187
AnimationState::GetCurrentAnimationFrameIndex() const
188
0
{
189
0
  return mCurrentAnimationFrameIndex;
190
0
}
191
192
FrameTimeout
193
AnimationState::LoopLength() const
194
0
{
195
0
  // If we don't know the loop length yet, we have to treat it as infinite.
196
0
  if (!mLoopLength) {
197
0
    return FrameTimeout::Forever();
198
0
  }
199
0
200
0
  MOZ_ASSERT(mHasBeenDecoded, "We know the loop length but decoding isn't done?");
201
0
202
0
  // If we're not looping, a single loop time has no meaning.
203
0
  if (mAnimationMode != imgIContainer::kNormalAnimMode) {
204
0
    return FrameTimeout::Forever();
205
0
  }
206
0
207
0
  return *mLoopLength;
208
0
}
209
210
211
///////////////////////////////////////////////////////////////////////////////
212
// FrameAnimator implementation.
213
///////////////////////////////////////////////////////////////////////////////
214
215
TimeStamp
216
FrameAnimator::GetCurrentImgFrameEndTime(AnimationState& aState,
217
                                         FrameTimeout aCurrentTimeout) const
218
0
{
219
0
  if (aCurrentTimeout == FrameTimeout::Forever()) {
220
0
    // We need to return a sentinel value in this case, because our logic
221
0
    // doesn't work correctly if we have an infinitely long timeout. We use one
222
0
    // year in the future as the sentinel because it works with the loop in
223
0
    // RequestRefresh() below.
224
0
    // XXX(seth): It'd be preferable to make our logic work correctly with
225
0
    // infinitely long timeouts.
226
0
    return TimeStamp::NowLoRes() +
227
0
           TimeDuration::FromMilliseconds(31536000.0);
228
0
  }
229
0
230
0
  TimeDuration durationOfTimeout =
231
0
    TimeDuration::FromMilliseconds(double(aCurrentTimeout.AsMilliseconds()));
232
0
  return aState.mCurrentAnimationFrameTime + durationOfTimeout;
233
0
}
234
235
RefreshResult
236
FrameAnimator::AdvanceFrame(AnimationState& aState,
237
                            DrawableSurface& aFrames,
238
                            RawAccessFrameRef& aCurrentFrame,
239
                            TimeStamp aTime)
240
0
{
241
0
  AUTO_PROFILER_LABEL("FrameAnimator::AdvanceFrame", GRAPHICS);
242
0
243
0
  RefreshResult ret;
244
0
245
0
  // Determine what the next frame is, taking into account looping.
246
0
  uint32_t currentFrameIndex = aState.mCurrentAnimationFrameIndex;
247
0
  uint32_t nextFrameIndex = currentFrameIndex + 1;
248
0
249
0
  // Check if we're at the end of the loop. (FrameCount() returns Nothing() if
250
0
  // we don't know the total count yet.)
251
0
  if (aState.FrameCount() == Some(nextFrameIndex)) {
252
0
    // If we are not looping forever, initialize the loop counter
253
0
    if (aState.mLoopRemainingCount < 0 && aState.LoopCount() >= 0) {
254
0
      aState.mLoopRemainingCount = aState.LoopCount();
255
0
    }
256
0
257
0
    // If animation mode is "loop once", or we're at end of loop counter,
258
0
    // it's time to stop animating.
259
0
    if (aState.mAnimationMode == imgIContainer::kLoopOnceAnimMode ||
260
0
        aState.mLoopRemainingCount == 0) {
261
0
      ret.mAnimationFinished = true;
262
0
    }
263
0
264
0
    nextFrameIndex = 0;
265
0
266
0
    if (aState.mLoopRemainingCount > 0) {
267
0
      aState.mLoopRemainingCount--;
268
0
    }
269
0
270
0
    // If we're done, exit early.
271
0
    if (ret.mAnimationFinished) {
272
0
      return ret;
273
0
    }
274
0
  }
275
0
276
0
  if (nextFrameIndex >= aState.KnownFrameCount()) {
277
0
    // We've already advanced to the last decoded frame, nothing more we can do.
278
0
    // We're blocked by network/decoding from displaying the animation at the
279
0
    // rate specified, so that means the frame we are displaying (the latest
280
0
    // available) is the frame we want to be displaying at this time. So we
281
0
    // update the current animation time. If we didn't update the current
282
0
    // animation time then it could lag behind, which would indicate that we are
283
0
    // behind in the animation and should try to catch up. When we are done
284
0
    // decoding (and thus can loop around back to the start of the animation) we
285
0
    // would then jump to a random point in the animation to try to catch up.
286
0
    // But we were never behind in the animation.
287
0
    aState.mCurrentAnimationFrameTime = aTime;
288
0
    return ret;
289
0
  }
290
0
291
0
  // There can be frames in the surface cache with index >= KnownFrameCount()
292
0
  // which GetRawFrame() can access because an async decoder has decoded them,
293
0
  // but which AnimationState doesn't know about yet because we haven't received
294
0
  // the appropriate notification on the main thread. Make sure we stay in sync
295
0
  // with AnimationState.
296
0
  MOZ_ASSERT(nextFrameIndex < aState.KnownFrameCount());
297
0
  RawAccessFrameRef nextFrame = aFrames.RawAccessRef(nextFrameIndex);
298
0
299
0
  // We should always check to see if we have the next frame even if we have
300
0
  // previously finished decoding. If we needed to redecode (e.g. due to a draw
301
0
  // failure) we would have discarded all the old frames and may not yet have
302
0
  // the new ones. DrawableSurface::RawAccessRef promises to only return
303
0
  // finished frames.
304
0
  if (!nextFrame) {
305
0
    // Uh oh, the frame we want to show is currently being decoded (partial).
306
0
    // Similar to the above case, we could be blocked by network or decoding,
307
0
    // and so we should advance our current time rather than risk jumping
308
0
    // through the animation. We will wait until the next refresh driver tick
309
0
    // and try again.
310
0
    aState.mCurrentAnimationFrameTime = aTime;
311
0
    return ret;
312
0
  }
313
0
314
0
  if (nextFrame->GetTimeout() == FrameTimeout::Forever()) {
315
0
    ret.mAnimationFinished = true;
316
0
  }
317
0
318
0
  if (nextFrameIndex == 0) {
319
0
    MOZ_ASSERT(nextFrame->IsFullFrame());
320
0
    ret.mDirtyRect = aState.FirstFrameRefreshArea();
321
0
  } else if (!nextFrame->IsFullFrame()) {
322
0
    MOZ_ASSERT(nextFrameIndex == currentFrameIndex + 1);
323
0
    // Change frame
324
0
    if (!DoBlend(aCurrentFrame, nextFrame, nextFrameIndex, &ret.mDirtyRect)) {
325
0
      // something went wrong, move on to next
326
0
      NS_WARNING("FrameAnimator::AdvanceFrame(): Compositing of frame failed");
327
0
      nextFrame->SetCompositingFailed(true);
328
0
      aState.mCurrentAnimationFrameTime =
329
0
        GetCurrentImgFrameEndTime(aState, aCurrentFrame->GetTimeout());
330
0
      aState.mCurrentAnimationFrameIndex = nextFrameIndex;
331
0
      aState.mCompositedFrameRequested = false;
332
0
      aCurrentFrame = std::move(nextFrame);
333
0
      aFrames.Advance(nextFrameIndex);
334
0
335
0
      return ret;
336
0
    }
337
0
338
0
    nextFrame->SetCompositingFailed(false);
339
0
  } else {
340
0
    ret.mDirtyRect = nextFrame->GetDirtyRect();
341
0
  }
342
0
343
0
  aState.mCurrentAnimationFrameTime =
344
0
    GetCurrentImgFrameEndTime(aState, aCurrentFrame->GetTimeout());
345
0
346
0
  // If we can get closer to the current time by a multiple of the image's loop
347
0
  // time, we should. We can only do this if we're done decoding; otherwise, we
348
0
  // don't know the full loop length, and LoopLength() will have to return
349
0
  // FrameTimeout::Forever(). We also skip this for images with a finite loop
350
0
  // count if we have initialized mLoopRemainingCount (it only gets initialized
351
0
  // after one full loop).
352
0
  FrameTimeout loopTime = aState.LoopLength();
353
0
  if (loopTime != FrameTimeout::Forever() &&
354
0
      (aState.LoopCount() < 0 || aState.mLoopRemainingCount >= 0)) {
355
0
    TimeDuration delay = aTime - aState.mCurrentAnimationFrameTime;
356
0
    if (delay.ToMilliseconds() > loopTime.AsMilliseconds()) {
357
0
      // Explicitly use integer division to get the floor of the number of
358
0
      // loops.
359
0
      uint64_t loops = static_cast<uint64_t>(delay.ToMilliseconds())
360
0
                     / loopTime.AsMilliseconds();
361
0
362
0
      // If we have a finite loop count limit the number of loops we advance.
363
0
      if (aState.mLoopRemainingCount >= 0) {
364
0
        MOZ_ASSERT(aState.LoopCount() >= 0);
365
0
        loops = std::min(loops, CheckedUint64(aState.mLoopRemainingCount).value());
366
0
      }
367
0
368
0
      aState.mCurrentAnimationFrameTime +=
369
0
        TimeDuration::FromMilliseconds(loops * loopTime.AsMilliseconds());
370
0
371
0
      if (aState.mLoopRemainingCount >= 0) {
372
0
        MOZ_ASSERT(loops <= CheckedUint64(aState.mLoopRemainingCount).value());
373
0
        aState.mLoopRemainingCount -= CheckedInt32(loops).value();
374
0
      }
375
0
    }
376
0
  }
377
0
378
0
  // Set currentAnimationFrameIndex at the last possible moment
379
0
  aState.mCurrentAnimationFrameIndex = nextFrameIndex;
380
0
  aState.mCompositedFrameRequested = false;
381
0
  aCurrentFrame = std::move(nextFrame);
382
0
  aFrames.Advance(nextFrameIndex);
383
0
384
0
  // If we're here, we successfully advanced the frame.
385
0
  ret.mFrameAdvanced = true;
386
0
387
0
  return ret;
388
0
}
389
390
void
391
FrameAnimator::ResetAnimation(AnimationState& aState)
392
0
{
393
0
  aState.ResetAnimation();
394
0
395
0
  // Our surface provider is synchronized to our state, so we need to reset its
396
0
  // state as well, if we still have one.
397
0
  LookupResult result =
398
0
    SurfaceCache::Lookup(ImageKey(mImage),
399
0
                         RasterSurfaceKey(mSize,
400
0
                                          DefaultSurfaceFlags(),
401
0
                                          PlaybackType::eAnimated));
402
0
  if (!result) {
403
0
    return;
404
0
  }
405
0
406
0
  result.Surface().Reset();
407
0
}
408
409
RefreshResult
410
FrameAnimator::RequestRefresh(AnimationState& aState,
411
                              const TimeStamp& aTime,
412
                              bool aAnimationFinished)
413
0
{
414
0
  // By default, an empty RefreshResult.
415
0
  RefreshResult ret;
416
0
417
0
  if (aState.IsDiscarded()) {
418
0
    aState.MaybeAdvanceAnimationFrameTime(aTime);
419
0
    return ret;
420
0
  }
421
0
422
0
  // Get the animation frames once now, and pass them down to callees because
423
0
  // the surface could be discarded at anytime on a different thread. This is
424
0
  // must easier to reason about then trying to write code that is safe to
425
0
  // having the surface disappear at anytime.
426
0
  LookupResult result =
427
0
    SurfaceCache::Lookup(ImageKey(mImage),
428
0
                         RasterSurfaceKey(mSize,
429
0
                                          DefaultSurfaceFlags(),
430
0
                                          PlaybackType::eAnimated));
431
0
432
0
  ret.mDirtyRect = aState.UpdateStateInternal(result, aAnimationFinished, mSize);
433
0
  if (aState.IsDiscarded() || !result) {
434
0
    aState.MaybeAdvanceAnimationFrameTime(aTime);
435
0
    if (!ret.mDirtyRect.IsEmpty()) {
436
0
      ret.mFrameAdvanced = true;
437
0
    }
438
0
    return ret;
439
0
  }
440
0
441
0
  RawAccessFrameRef currentFrame =
442
0
    result.Surface().RawAccessRef(aState.mCurrentAnimationFrameIndex);
443
0
444
0
  // only advance the frame if the current time is greater than or
445
0
  // equal to the current frame's end time.
446
0
  if (!currentFrame) {
447
0
    MOZ_ASSERT(gfxPrefs::ImageMemAnimatedDiscardable());
448
0
    MOZ_ASSERT(aState.GetHasRequestedDecode() && !aState.GetIsCurrentlyDecoded());
449
0
    MOZ_ASSERT(aState.mCompositedFrameInvalid);
450
0
    // Nothing we can do but wait for our previous current frame to be decoded
451
0
    // again so we can determine what to do next.
452
0
    aState.MaybeAdvanceAnimationFrameTime(aTime);
453
0
    return ret;
454
0
  }
455
0
456
0
  TimeStamp currentFrameEndTime =
457
0
    GetCurrentImgFrameEndTime(aState, currentFrame->GetTimeout());
458
0
459
0
  // If nothing has accessed the composited frame since the last time we
460
0
  // advanced, then there is no point in continuing to advance the animation.
461
0
  // This has the effect of freezing the animation while not in view.
462
0
  if (!aState.mCompositedFrameRequested &&
463
0
      aState.MaybeAdvanceAnimationFrameTime(aTime)) {
464
0
    return ret;
465
0
  }
466
0
467
0
  while (currentFrameEndTime <= aTime) {
468
0
    TimeStamp oldFrameEndTime = currentFrameEndTime;
469
0
470
0
    RefreshResult frameRes = AdvanceFrame(aState, result.Surface(),
471
0
                                          currentFrame, aTime);
472
0
473
0
    // Accumulate our result for returning to callers.
474
0
    ret.Accumulate(frameRes);
475
0
476
0
    // currentFrame was updated by AdvanceFrame so it is still current.
477
0
    currentFrameEndTime =
478
0
      GetCurrentImgFrameEndTime(aState, currentFrame->GetTimeout());
479
0
480
0
    // If we didn't advance a frame, and our frame end time didn't change,
481
0
    // then we need to break out of this loop & wait for the frame(s)
482
0
    // to finish downloading.
483
0
    if (!frameRes.mFrameAdvanced && currentFrameEndTime == oldFrameEndTime) {
484
0
      break;
485
0
    }
486
0
  }
487
0
488
0
  // We should only mark the composited frame as valid and reset the dirty rect
489
0
  // if we advanced (meaning the next frame was actually produced somehow), the
490
0
  // composited frame was previously invalid (so we may need to repaint
491
0
  // everything) and the frame index is valid (to know we were doing blending
492
0
  // on the main thread, instead of on the decoder threads in advance).
493
0
  if (currentFrameEndTime > aTime && aState.mCompositedFrameInvalid &&
494
0
      mLastCompositedFrameIndex >= 0) {
495
0
    aState.mCompositedFrameInvalid = false;
496
0
    ret.mDirtyRect = IntRect(IntPoint(0,0), mSize);
497
0
  }
498
0
499
0
  MOZ_ASSERT(!aState.mIsCurrentlyDecoded || !aState.mCompositedFrameInvalid);
500
0
501
0
  return ret;
502
0
}
503
504
LookupResult
505
FrameAnimator::GetCompositedFrame(AnimationState& aState)
506
0
{
507
0
  aState.mCompositedFrameRequested = true;
508
0
509
0
  // If we have a composited version of this frame, return that.
510
0
  if (!aState.mCompositedFrameInvalid && mLastCompositedFrameIndex >= 0 &&
511
0
      (uint32_t(mLastCompositedFrameIndex) == aState.mCurrentAnimationFrameIndex)) {
512
0
    return LookupResult(DrawableSurface(mCompositingFrame->DrawableRef()),
513
0
                        MatchType::EXACT);
514
0
  }
515
0
516
0
  LookupResult result =
517
0
    SurfaceCache::Lookup(ImageKey(mImage),
518
0
                         RasterSurfaceKey(mSize,
519
0
                                          DefaultSurfaceFlags(),
520
0
                                          PlaybackType::eAnimated));
521
0
522
0
  if (aState.mCompositedFrameInvalid) {
523
0
    MOZ_ASSERT(gfxPrefs::ImageMemAnimatedDiscardable());
524
0
    MOZ_ASSERT(aState.GetHasRequestedDecode());
525
0
    MOZ_ASSERT(!aState.GetIsCurrentlyDecoded());
526
0
    if (result.Type() == MatchType::NOT_FOUND) {
527
0
      return result;
528
0
    }
529
0
    return LookupResult(MatchType::PENDING);
530
0
  }
531
0
532
0
  // Otherwise return the raw frame. DoBlend is required to ensure that we only
533
0
  // hit this case if the frame is not paletted and doesn't require compositing.
534
0
  if (!result) {
535
0
    return result;
536
0
  }
537
0
538
0
  // Seek to the appropriate frame. If seeking fails, it means that we couldn't
539
0
  // get the frame we're looking for; treat this as if the lookup failed.
540
0
  if (NS_FAILED(result.Surface().Seek(aState.mCurrentAnimationFrameIndex))) {
541
0
    if (result.Type() == MatchType::NOT_FOUND) {
542
0
      return result;
543
0
    }
544
0
    return LookupResult(MatchType::PENDING);
545
0
  }
546
0
547
0
  MOZ_ASSERT(!result.Surface()->GetIsPaletted(),
548
0
             "About to return a paletted frame");
549
0
550
0
  return result;
551
0
}
552
553
static void
554
DoCollectSizeOfCompositingSurfaces(const RawAccessFrameRef& aSurface,
555
                                   SurfaceMemoryCounterType aType,
556
                                   nsTArray<SurfaceMemoryCounter>& aCounters,
557
                                   MallocSizeOf aMallocSizeOf)
558
0
{
559
0
  // Concoct a SurfaceKey for this surface.
560
0
  SurfaceKey key = RasterSurfaceKey(aSurface->GetImageSize(),
561
0
                                    DefaultSurfaceFlags(),
562
0
                                    PlaybackType::eStatic);
563
0
564
0
  // Create a counter for this surface.
565
0
  SurfaceMemoryCounter counter(key, /* aIsLocked = */ true,
566
0
                               /* aCannotSubstitute */ false,
567
0
                               /* aIsFactor2 */ false, aType);
568
0
569
0
  // Extract the surface's memory usage information.
570
0
  size_t heap = 0, nonHeap = 0, handles = 0;
571
0
  aSurface->AddSizeOfExcludingThis(aMallocSizeOf, heap, nonHeap, handles);
572
0
  counter.Values().SetDecodedHeap(heap);
573
0
  counter.Values().SetDecodedNonHeap(nonHeap);
574
0
  counter.Values().SetExternalHandles(handles);
575
0
576
0
  // Record it.
577
0
  aCounters.AppendElement(counter);
578
0
}
579
580
void
581
FrameAnimator::CollectSizeOfCompositingSurfaces(
582
    nsTArray<SurfaceMemoryCounter>& aCounters,
583
    MallocSizeOf aMallocSizeOf) const
584
0
{
585
0
  if (mCompositingFrame) {
586
0
    DoCollectSizeOfCompositingSurfaces(mCompositingFrame,
587
0
                                       SurfaceMemoryCounterType::COMPOSITING,
588
0
                                       aCounters,
589
0
                                       aMallocSizeOf);
590
0
  }
591
0
592
0
  if (mCompositingPrevFrame) {
593
0
    DoCollectSizeOfCompositingSurfaces(mCompositingPrevFrame,
594
0
                                       SurfaceMemoryCounterType::COMPOSITING_PREV,
595
0
                                       aCounters,
596
0
                                       aMallocSizeOf);
597
0
  }
598
0
}
599
600
//******************************************************************************
601
// DoBlend gets called when the timer for animation get fired and we have to
602
// update the composited frame of the animation.
603
bool
604
FrameAnimator::DoBlend(const RawAccessFrameRef& aPrevFrame,
605
                       const RawAccessFrameRef& aNextFrame,
606
                       uint32_t aNextFrameIndex,
607
                       IntRect* aDirtyRect)
608
0
{
609
0
  MOZ_ASSERT(aPrevFrame && aNextFrame, "Should have frames here");
610
0
611
0
  DisposalMethod prevDisposalMethod = aPrevFrame->GetDisposalMethod();
612
0
  bool prevHasAlpha = aPrevFrame->FormatHasAlpha();
613
0
  if (prevDisposalMethod == DisposalMethod::RESTORE_PREVIOUS &&
614
0
      !mCompositingPrevFrame) {
615
0
    prevDisposalMethod = DisposalMethod::CLEAR;
616
0
  }
617
0
618
0
  IntRect prevRect = aPrevFrame->GetBoundedBlendRect();
619
0
  bool isFullPrevFrame = prevRect.IsEqualRect(0, 0, mSize.width, mSize.height);
620
0
621
0
  // Optimization: DisposeClearAll if the previous frame is the same size as
622
0
  //               container and it's clearing itself
623
0
  if (isFullPrevFrame &&
624
0
      (prevDisposalMethod == DisposalMethod::CLEAR)) {
625
0
    prevDisposalMethod = DisposalMethod::CLEAR_ALL;
626
0
  }
627
0
628
0
  DisposalMethod nextDisposalMethod = aNextFrame->GetDisposalMethod();
629
0
  bool nextHasAlpha = aNextFrame->FormatHasAlpha();
630
0
631
0
  IntRect nextRect = aNextFrame->GetBoundedBlendRect();
632
0
  bool isFullNextFrame = nextRect.IsEqualRect(0, 0, mSize.width, mSize.height);
633
0
634
0
  if (!aNextFrame->GetIsPaletted()) {
635
0
    // Optimization: Skip compositing if the previous frame wants to clear the
636
0
    //               whole image
637
0
    if (prevDisposalMethod == DisposalMethod::CLEAR_ALL) {
638
0
      aDirtyRect->SetRect(0, 0, mSize.width, mSize.height);
639
0
      return true;
640
0
    }
641
0
642
0
    // Optimization: Skip compositing if this frame is the same size as the
643
0
    //               container and it's fully drawing over prev frame (no alpha)
644
0
    if (isFullNextFrame &&
645
0
        (nextDisposalMethod != DisposalMethod::RESTORE_PREVIOUS) &&
646
0
        !nextHasAlpha) {
647
0
      aDirtyRect->SetRect(0, 0, mSize.width, mSize.height);
648
0
      return true;
649
0
    }
650
0
  }
651
0
652
0
  // Calculate area that needs updating
653
0
  switch (prevDisposalMethod) {
654
0
    default:
655
0
      MOZ_FALLTHROUGH_ASSERT("Unexpected DisposalMethod");
656
0
    case DisposalMethod::NOT_SPECIFIED:
657
0
    case DisposalMethod::KEEP:
658
0
      *aDirtyRect = nextRect;
659
0
      break;
660
0
661
0
    case DisposalMethod::CLEAR_ALL:
662
0
      // Whole image container is cleared
663
0
      aDirtyRect->SetRect(0, 0, mSize.width, mSize.height);
664
0
      break;
665
0
666
0
    case DisposalMethod::CLEAR:
667
0
      // Calc area that needs to be redrawn (the combination of previous and
668
0
      // this frame)
669
0
      // XXX - This could be done with multiple framechanged calls
670
0
      //       Having aPrevFrame way at the top of the image, and aNextFrame
671
0
      //       way at the bottom, and both frames being small, we'd be
672
0
      //       telling framechanged to refresh the whole image when only two
673
0
      //       small areas are needed.
674
0
      aDirtyRect->UnionRect(nextRect, prevRect);
675
0
      break;
676
0
677
0
    case DisposalMethod::RESTORE_PREVIOUS:
678
0
      aDirtyRect->SetRect(0, 0, mSize.width, mSize.height);
679
0
      break;
680
0
  }
681
0
682
0
  // Optimization:
683
0
  //   Skip compositing if the last composited frame is this frame
684
0
  //   (Only one composited frame was made for this animation.  Example:
685
0
  //    Only Frame 3 of a 10 frame image required us to build a composite frame
686
0
  //    On the second loop, we do not need to rebuild the frame
687
0
  //    since it's still sitting in compositingFrame)
688
0
  if (mLastCompositedFrameIndex == int32_t(aNextFrameIndex)) {
689
0
    return true;
690
0
  }
691
0
692
0
  bool needToBlankComposite = false;
693
0
694
0
  // Create the Compositing Frame
695
0
  if (!mCompositingFrame) {
696
0
    RefPtr<imgFrame> newFrame = new imgFrame;
697
0
    nsresult rv = newFrame->InitForAnimator(mSize,
698
0
                                            SurfaceFormat::B8G8R8A8);
699
0
    if (NS_FAILED(rv)) {
700
0
      mCompositingFrame.reset();
701
0
      return false;
702
0
    }
703
0
    mCompositingFrame = newFrame->RawAccessRef();
704
0
    needToBlankComposite = true;
705
0
  } else if (int32_t(aNextFrameIndex) != mLastCompositedFrameIndex+1) {
706
0
707
0
    // If we are not drawing on top of last composited frame,
708
0
    // then we are building a new composite frame, so let's clear it first.
709
0
    needToBlankComposite = true;
710
0
  }
711
0
712
0
  // More optimizations possible when next frame is not transparent
713
0
  // But if the next frame has DisposalMethod::RESTORE_PREVIOUS,
714
0
  // this "no disposal" optimization is not possible,
715
0
  // because the frame in "after disposal operation" state
716
0
  // needs to be stored in compositingFrame, so it can be
717
0
  // copied into compositingPrevFrame later.
718
0
  bool doDisposal = true;
719
0
  if (!nextHasAlpha && nextDisposalMethod != DisposalMethod::RESTORE_PREVIOUS) {
720
0
    if (isFullNextFrame) {
721
0
      // Optimization: No need to dispose prev.frame when
722
0
      // next frame is full frame and not transparent.
723
0
      doDisposal = false;
724
0
      // No need to blank the composite frame
725
0
      needToBlankComposite = false;
726
0
    } else {
727
0
      if ((prevRect.X() >= nextRect.X()) && (prevRect.Y() >= nextRect.Y()) &&
728
0
          (prevRect.XMost() <= nextRect.XMost()) &&
729
0
          (prevRect.YMost() <= nextRect.YMost())) {
730
0
        // Optimization: No need to dispose prev.frame when
731
0
        // next frame fully overlaps previous frame.
732
0
        doDisposal = false;
733
0
      }
734
0
    }
735
0
  }
736
0
737
0
  if (doDisposal) {
738
0
    // Dispose of previous: clear, restore, or keep (copy)
739
0
    switch (prevDisposalMethod) {
740
0
      case DisposalMethod::CLEAR:
741
0
        if (needToBlankComposite) {
742
0
          // If we just created the composite, it could have anything in its
743
0
          // buffer. Clear whole frame
744
0
          ClearFrame(mCompositingFrame.Data(),
745
0
                     mCompositingFrame->GetRect());
746
0
        } else {
747
0
          // Only blank out previous frame area (both color & Mask/Alpha)
748
0
          ClearFrame(mCompositingFrame.Data(),
749
0
                     mCompositingFrame->GetRect(),
750
0
                     prevRect);
751
0
        }
752
0
        break;
753
0
754
0
      case DisposalMethod::CLEAR_ALL:
755
0
        ClearFrame(mCompositingFrame.Data(),
756
0
                   mCompositingFrame->GetRect());
757
0
        break;
758
0
759
0
      case DisposalMethod::RESTORE_PREVIOUS:
760
0
        // It would be better to copy only the area changed back to
761
0
        // compositingFrame.
762
0
        if (mCompositingPrevFrame) {
763
0
          CopyFrameImage(mCompositingPrevFrame.Data(),
764
0
                         mCompositingPrevFrame->GetRect(),
765
0
                         mCompositingFrame.Data(),
766
0
                         mCompositingFrame->GetRect());
767
0
768
0
          // destroy only if we don't need it for this frame's disposal
769
0
          if (nextDisposalMethod != DisposalMethod::RESTORE_PREVIOUS) {
770
0
            mCompositingPrevFrame.reset();
771
0
          }
772
0
        } else {
773
0
          ClearFrame(mCompositingFrame.Data(),
774
0
                     mCompositingFrame->GetRect());
775
0
        }
776
0
        break;
777
0
778
0
      default:
779
0
        MOZ_FALLTHROUGH_ASSERT("Unexpected DisposalMethod");
780
0
      case DisposalMethod::NOT_SPECIFIED:
781
0
      case DisposalMethod::KEEP:
782
0
        // Copy previous frame into compositingFrame before we put the new
783
0
        // frame on top
784
0
        // Assumes that the previous frame represents a full frame (it could be
785
0
        // smaller in size than the container, as long as the frame before it
786
0
        // erased itself)
787
0
        // Note: Frame 1 never gets into DoBlend(), so (aNextFrameIndex - 1)
788
0
        // will always be a valid frame number.
789
0
        if (mLastCompositedFrameIndex != int32_t(aNextFrameIndex - 1)) {
790
0
          if (isFullPrevFrame && !aPrevFrame->GetIsPaletted()) {
791
0
            // Just copy the bits
792
0
            CopyFrameImage(aPrevFrame.Data(),
793
0
                           prevRect,
794
0
                           mCompositingFrame.Data(),
795
0
                           mCompositingFrame->GetRect());
796
0
          } else {
797
0
            if (needToBlankComposite) {
798
0
              // Only blank composite when prev is transparent or not full.
799
0
              if (prevHasAlpha || !isFullPrevFrame) {
800
0
                ClearFrame(mCompositingFrame.Data(),
801
0
                           mCompositingFrame->GetRect());
802
0
              }
803
0
            }
804
0
            DrawFrameTo(aPrevFrame.Data(), aPrevFrame->GetRect(),
805
0
                        aPrevFrame.PaletteDataLength(),
806
0
                        prevHasAlpha,
807
0
                        mCompositingFrame.Data(),
808
0
                        mCompositingFrame->GetRect(),
809
0
                        aPrevFrame->GetBlendMethod(),
810
0
                        aPrevFrame->GetBlendRect());
811
0
          }
812
0
        }
813
0
    }
814
0
  } else if (needToBlankComposite) {
815
0
    // If we just created the composite, it could have anything in its
816
0
    // buffers. Clear them
817
0
    ClearFrame(mCompositingFrame.Data(),
818
0
               mCompositingFrame->GetRect());
819
0
  }
820
0
821
0
  // Check if the frame we are composing wants the previous image restored after
822
0
  // it is done. Don't store it (again) if last frame wanted its image restored
823
0
  // too
824
0
  if ((nextDisposalMethod == DisposalMethod::RESTORE_PREVIOUS) &&
825
0
      (prevDisposalMethod != DisposalMethod::RESTORE_PREVIOUS)) {
826
0
    // We are storing the whole image.
827
0
    // It would be better if we just stored the area that aNextFrame is going to
828
0
    // overwrite.
829
0
    if (!mCompositingPrevFrame) {
830
0
      RefPtr<imgFrame> newFrame = new imgFrame;
831
0
      nsresult rv = newFrame->InitForAnimator(mSize,
832
0
                                              SurfaceFormat::B8G8R8A8);
833
0
      if (NS_FAILED(rv)) {
834
0
        mCompositingPrevFrame.reset();
835
0
        return false;
836
0
      }
837
0
838
0
      mCompositingPrevFrame = newFrame->RawAccessRef();
839
0
    }
840
0
841
0
    CopyFrameImage(mCompositingFrame.Data(),
842
0
                   mCompositingFrame->GetRect(),
843
0
                   mCompositingPrevFrame.Data(),
844
0
                   mCompositingPrevFrame->GetRect());
845
0
846
0
    mCompositingPrevFrame->Finish();
847
0
  }
848
0
849
0
  // blit next frame into it's correct spot
850
0
  DrawFrameTo(aNextFrame.Data(), aNextFrame->GetRect(),
851
0
              aNextFrame.PaletteDataLength(),
852
0
              nextHasAlpha,
853
0
              mCompositingFrame.Data(),
854
0
              mCompositingFrame->GetRect(),
855
0
              aNextFrame->GetBlendMethod(),
856
0
              aNextFrame->GetBlendRect());
857
0
858
0
  // Tell the image that it is fully 'downloaded'.
859
0
  mCompositingFrame->Finish();
860
0
861
0
  mLastCompositedFrameIndex = int32_t(aNextFrameIndex);
862
0
863
0
  return true;
864
0
}
865
866
//******************************************************************************
867
// Fill aFrame with black. Does also clears the mask.
868
void
869
FrameAnimator::ClearFrame(uint8_t* aFrameData, const IntRect& aFrameRect)
870
0
{
871
0
  if (!aFrameData) {
872
0
    return;
873
0
  }
874
0
875
0
  memset(aFrameData, 0, aFrameRect.Width() * aFrameRect.Height() * 4);
876
0
}
877
878
//******************************************************************************
879
void
880
FrameAnimator::ClearFrame(uint8_t* aFrameData, const IntRect& aFrameRect,
881
                          const IntRect& aRectToClear)
882
0
{
883
0
  if (!aFrameData || aFrameRect.Width() <= 0 || aFrameRect.Height() <= 0 ||
884
0
      aRectToClear.Width() <= 0 || aRectToClear.Height() <= 0) {
885
0
    return;
886
0
  }
887
0
888
0
  IntRect toClear = aFrameRect.Intersect(aRectToClear);
889
0
  if (toClear.IsEmpty()) {
890
0
    return;
891
0
  }
892
0
893
0
  uint32_t bytesPerRow = aFrameRect.Width() * 4;
894
0
  for (int row = toClear.Y(); row < toClear.YMost(); ++row) {
895
0
    memset(aFrameData + toClear.X() * 4 + row * bytesPerRow, 0,
896
0
           toClear.Width() * 4);
897
0
  }
898
0
}
899
900
//******************************************************************************
901
// Whether we succeed or fail will not cause a crash, and there's not much
902
// we can do about a failure, so there we don't return a nsresult
903
bool
904
FrameAnimator::CopyFrameImage(const uint8_t* aDataSrc,
905
                              const IntRect& aRectSrc,
906
                              uint8_t* aDataDest,
907
                              const IntRect& aRectDest)
908
0
{
909
0
  uint32_t dataLengthSrc = aRectSrc.Width() * aRectSrc.Height() * 4;
910
0
  uint32_t dataLengthDest = aRectDest.Width() * aRectDest.Height() * 4;
911
0
912
0
  if (!aDataDest || !aDataSrc || dataLengthSrc != dataLengthDest) {
913
0
    return false;
914
0
  }
915
0
916
0
  memcpy(aDataDest, aDataSrc, dataLengthDest);
917
0
918
0
  return true;
919
0
}
920
921
nsresult
922
FrameAnimator::DrawFrameTo(const uint8_t* aSrcData, const IntRect& aSrcRect,
923
                           uint32_t aSrcPaletteLength, bool aSrcHasAlpha,
924
                           uint8_t* aDstPixels, const IntRect& aDstRect,
925
                           BlendMethod aBlendMethod, const IntRect& aBlendRect)
926
0
{
927
0
  NS_ENSURE_ARG_POINTER(aSrcData);
928
0
  NS_ENSURE_ARG_POINTER(aDstPixels);
929
0
930
0
  // According to both AGIF and APNG specs, offsets are unsigned
931
0
  if (aSrcRect.X() < 0 || aSrcRect.Y() < 0) {
932
0
    NS_WARNING("FrameAnimator::DrawFrameTo: negative offsets not allowed");
933
0
    return NS_ERROR_FAILURE;
934
0
  }
935
0
936
0
  // Outside the destination frame, skip it
937
0
  if ((aSrcRect.X() > aDstRect.Width()) || (aSrcRect.Y() > aDstRect.Height())) {
938
0
    return NS_OK;
939
0
  }
940
0
941
0
  if (aSrcPaletteLength) {
942
0
    // Larger than the destination frame, clip it
943
0
    int32_t width = std::min(aSrcRect.Width(), aDstRect.Width() - aSrcRect.X());
944
0
    int32_t height = std::min(aSrcRect.Height(), aDstRect.Height() - aSrcRect.Y());
945
0
946
0
    // The clipped image must now fully fit within destination image frame
947
0
    NS_ASSERTION((aSrcRect.X() >= 0) && (aSrcRect.Y() >= 0) &&
948
0
                 (aSrcRect.X() + width <= aDstRect.Width()) &&
949
0
                 (aSrcRect.Y() + height <= aDstRect.Height()),
950
0
                "FrameAnimator::DrawFrameTo: Invalid aSrcRect");
951
0
952
0
    // clipped image size may be smaller than source, but not larger
953
0
    NS_ASSERTION((width <= aSrcRect.Width()) && (height <= aSrcRect.Height()),
954
0
      "FrameAnimator::DrawFrameTo: source must be smaller than dest");
955
0
956
0
    // Get pointers to image data
957
0
    const uint8_t* srcPixels = aSrcData + aSrcPaletteLength;
958
0
    uint32_t* dstPixels = reinterpret_cast<uint32_t*>(aDstPixels);
959
0
    const uint32_t* colormap = reinterpret_cast<const uint32_t*>(aSrcData);
960
0
961
0
    // Skip to the right offset
962
0
    dstPixels += aSrcRect.X() + (aSrcRect.Y() * aDstRect.Width());
963
0
    if (!aSrcHasAlpha) {
964
0
      for (int32_t r = height; r > 0; --r) {
965
0
        for (int32_t c = 0; c < width; c++) {
966
0
          dstPixels[c] = colormap[srcPixels[c]];
967
0
        }
968
0
        // Go to the next row in the source resp. destination image
969
0
        srcPixels += aSrcRect.Width();
970
0
        dstPixels += aDstRect.Width();
971
0
      }
972
0
    } else {
973
0
      for (int32_t r = height; r > 0; --r) {
974
0
        for (int32_t c = 0; c < width; c++) {
975
0
          const uint32_t color = colormap[srcPixels[c]];
976
0
          if (color) {
977
0
            dstPixels[c] = color;
978
0
          }
979
0
        }
980
0
        // Go to the next row in the source resp. destination image
981
0
        srcPixels += aSrcRect.Width();
982
0
        dstPixels += aDstRect.Width();
983
0
      }
984
0
    }
985
0
  } else {
986
0
    pixman_image_t* src =
987
0
      pixman_image_create_bits(
988
0
          aSrcHasAlpha ? PIXMAN_a8r8g8b8 : PIXMAN_x8r8g8b8,
989
0
          aSrcRect.Width(), aSrcRect.Height(),
990
0
          reinterpret_cast<uint32_t*>(const_cast<uint8_t*>(aSrcData)),
991
0
          aSrcRect.Width() * 4);
992
0
    if (!src) {
993
0
      return NS_ERROR_OUT_OF_MEMORY;
994
0
    }
995
0
    pixman_image_t* dst =
996
0
      pixman_image_create_bits(PIXMAN_a8r8g8b8,
997
0
                               aDstRect.Width(),
998
0
                               aDstRect.Height(),
999
0
                               reinterpret_cast<uint32_t*>(aDstPixels),
1000
0
                               aDstRect.Width() * 4);
1001
0
    if (!dst) {
1002
0
      pixman_image_unref(src);
1003
0
      return NS_ERROR_OUT_OF_MEMORY;
1004
0
    }
1005
0
1006
0
    // XXX(seth): This is inefficient but we'll remove it quite soon when we
1007
0
    // move frame compositing into SurfacePipe. For now we need this because
1008
0
    // RemoveFrameRectFilter has transformed PNG frames with frame rects into
1009
0
    // imgFrame's with no frame rects, but with a region of 0 alpha where the
1010
0
    // frame rect should be. This works really nicely if we're using
1011
0
    // BlendMethod::OVER, but BlendMethod::SOURCE will result in that frame rect
1012
0
    // area overwriting the previous frame, which makes the animation look
1013
0
    // wrong. This quick hack fixes that by first compositing the whle new frame
1014
0
    // with BlendMethod::OVER, and then recopying the area that uses
1015
0
    // BlendMethod::SOURCE if needed. To make this work, the decoder has to
1016
0
    // provide a "blend rect" that tells us where to do this. This is just the
1017
0
    // frame rect, but hidden in a way that makes it invisible to most of the
1018
0
    // system, so we can keep eliminating dependencies on it.
1019
0
    auto op = aBlendMethod == BlendMethod::SOURCE ? PIXMAN_OP_SRC
1020
0
                                                  : PIXMAN_OP_OVER;
1021
0
1022
0
    if (aBlendMethod == BlendMethod::OVER ||
1023
0
        (aBlendMethod == BlendMethod::SOURCE && aSrcRect.IsEqualEdges(aBlendRect))) {
1024
0
      // We don't need to do anything clever. (Or, in the case where no blend
1025
0
      // rect was specified, we can't.)
1026
0
      pixman_image_composite32(op,
1027
0
                               src,
1028
0
                               nullptr,
1029
0
                               dst,
1030
0
                               0, 0,
1031
0
                               0, 0,
1032
0
                               aSrcRect.X(), aSrcRect.Y(),
1033
0
                               aSrcRect.Width(), aSrcRect.Height());
1034
0
    } else {
1035
0
      // We need to do the OVER followed by SOURCE trick above.
1036
0
      pixman_image_composite32(PIXMAN_OP_OVER,
1037
0
                               src,
1038
0
                               nullptr,
1039
0
                               dst,
1040
0
                               0, 0,
1041
0
                               0, 0,
1042
0
                               aSrcRect.X(), aSrcRect.Y(),
1043
0
                               aSrcRect.Width(), aSrcRect.Height());
1044
0
      pixman_image_composite32(PIXMAN_OP_SRC,
1045
0
                               src,
1046
0
                               nullptr,
1047
0
                               dst,
1048
0
                               aBlendRect.X(), aBlendRect.Y(),
1049
0
                               0, 0,
1050
0
                               aBlendRect.X(), aBlendRect.Y(),
1051
0
                               aBlendRect.Width(), aBlendRect.Height());
1052
0
    }
1053
0
1054
0
    pixman_image_unref(src);
1055
0
    pixman_image_unref(dst);
1056
0
  }
1057
0
1058
0
  return NS_OK;
1059
0
}
1060
1061
} // namespace image
1062
} // namespace mozilla