|
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 "FrameBlender.h" |
|
7 |
|
8 #include "mozilla/MemoryReporting.h" |
|
9 #include "MainThreadUtils.h" |
|
10 |
|
11 #include "pixman.h" |
|
12 |
|
13 using namespace mozilla; |
|
14 using namespace mozilla::image; |
|
15 |
|
16 namespace mozilla { |
|
17 namespace image { |
|
18 |
|
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 } |
|
28 |
|
29 FrameBlender::~FrameBlender() |
|
30 { |
|
31 delete mAnim; |
|
32 } |
|
33 |
|
34 already_AddRefed<FrameSequence> |
|
35 FrameBlender::GetFrameSequence() |
|
36 { |
|
37 nsRefPtr<FrameSequence> seq(mFrames); |
|
38 return seq.forget(); |
|
39 } |
|
40 |
|
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 } |
|
52 |
|
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 } |
|
60 |
|
61 return mFrames->GetFrame(framenum); |
|
62 } |
|
63 |
|
64 uint32_t |
|
65 FrameBlender::GetNumFrames() const |
|
66 { |
|
67 return mFrames->GetNumFrames(); |
|
68 } |
|
69 |
|
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 } |
|
91 |
|
92 void |
|
93 FrameBlender::SetLoopCount(int32_t aLoopCount) |
|
94 { |
|
95 mLoopCount = aLoopCount; |
|
96 } |
|
97 |
|
98 int32_t |
|
99 FrameBlender::GetLoopCount() const |
|
100 { |
|
101 return mLoopCount; |
|
102 } |
|
103 |
|
104 void |
|
105 FrameBlender::RemoveFrame(uint32_t framenum) |
|
106 { |
|
107 NS_ABORT_IF_FALSE(framenum < GetNumFrames(), "Deleting invalid frame!"); |
|
108 |
|
109 mFrames->RemoveFrame(framenum); |
|
110 } |
|
111 |
|
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 } |
|
118 |
|
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 } |
|
128 |
|
129 imgFrame* |
|
130 FrameBlender::SwapFrame(uint32_t framenum, imgFrame* aFrame) |
|
131 { |
|
132 NS_ABORT_IF_FALSE(framenum < GetNumFrames(), "Swapping invalid frame!"); |
|
133 |
|
134 imgFrame* ret; |
|
135 |
|
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 } |
|
144 |
|
145 return ret; |
|
146 } |
|
147 |
|
148 void |
|
149 FrameBlender::EnsureAnimExists() |
|
150 { |
|
151 if (!mAnim) { |
|
152 // Create the animation context |
|
153 mAnim = new Anim(); |
|
154 |
|
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 } |
|
160 |
|
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 } |
|
172 |
|
173 const FrameDataPair& prevFrame = mFrames->GetFrame(aPrevFrameIndex); |
|
174 const FrameDataPair& nextFrame = mFrames->GetFrame(aNextFrameIndex); |
|
175 if (!prevFrame.HasFrameData() || !nextFrame.HasFrameData()) { |
|
176 return false; |
|
177 } |
|
178 |
|
179 int32_t prevFrameDisposalMethod = prevFrame->GetFrameDisposalMethod(); |
|
180 if (prevFrameDisposalMethod == FrameBlender::kDisposeRestorePrevious && |
|
181 !mAnim->compositingPrevFrame) |
|
182 prevFrameDisposalMethod = FrameBlender::kDisposeClear; |
|
183 |
|
184 nsIntRect prevFrameRect = prevFrame->GetRect(); |
|
185 bool isFullPrevFrame = (prevFrameRect.x == 0 && prevFrameRect.y == 0 && |
|
186 prevFrameRect.width == mSize.width && |
|
187 prevFrameRect.height == mSize.height); |
|
188 |
|
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; |
|
194 |
|
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); |
|
200 |
|
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 } |
|
208 |
|
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 } |
|
218 |
|
219 // Calculate area that needs updating |
|
220 switch (prevFrameDisposalMethod) { |
|
221 default: |
|
222 case FrameBlender::kDisposeNotSpecified: |
|
223 case FrameBlender::kDisposeKeep: |
|
224 *aDirtyRect = nextFrameRect; |
|
225 break; |
|
226 |
|
227 case FrameBlender::kDisposeClearAll: |
|
228 // Whole image container is cleared |
|
229 aDirtyRect->SetRect(0, 0, mSize.width, mSize.height); |
|
230 break; |
|
231 |
|
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; |
|
242 |
|
243 case FrameBlender::kDisposeRestorePrevious: |
|
244 aDirtyRect->SetRect(0, 0, mSize.width, mSize.height); |
|
245 break; |
|
246 } |
|
247 |
|
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 } |
|
257 |
|
258 bool needToBlankComposite = false; |
|
259 |
|
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) { |
|
272 |
|
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 } |
|
277 |
|
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 } |
|
304 |
|
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; |
|
321 |
|
322 case FrameBlender::kDisposeClearAll: |
|
323 ClearFrame(mAnim->compositingFrame.GetFrameData(), |
|
324 mAnim->compositingFrame.GetFrame()->GetRect()); |
|
325 break; |
|
326 |
|
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()); |
|
335 |
|
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; |
|
344 |
|
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 } |
|
382 |
|
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 } |
|
399 |
|
400 mAnim->compositingPrevFrame.LockAndGetData(); |
|
401 } |
|
402 |
|
403 CopyFrameImage(mAnim->compositingFrame.GetFrameData(), |
|
404 mAnim->compositingFrame.GetFrame()->GetRect(), |
|
405 mAnim->compositingPrevFrame.GetFrameData(), |
|
406 mAnim->compositingPrevFrame.GetFrame()->GetRect()); |
|
407 } |
|
408 |
|
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())); |
|
416 |
|
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); |
|
421 |
|
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 } |
|
427 |
|
428 mAnim->lastCompositedFrameIndex = int32_t(aNextFrameIndex); |
|
429 |
|
430 return true; |
|
431 } |
|
432 |
|
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; |
|
440 |
|
441 memset(aFrameData, 0, aFrameRect.width * aFrameRect.height * 4); |
|
442 } |
|
443 |
|
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 } |
|
452 |
|
453 nsIntRect toClear = aFrameRect.Intersect(aRectToClear); |
|
454 if (toClear.IsEmpty()) { |
|
455 return; |
|
456 } |
|
457 |
|
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 } |
|
463 |
|
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; |
|
473 |
|
474 if (!aDataDest || !aDataSrc || dataLengthSrc != dataLengthDest) { |
|
475 return false; |
|
476 } |
|
477 |
|
478 memcpy(aDataDest, aDataSrc, dataLengthDest); |
|
479 |
|
480 return true; |
|
481 } |
|
482 |
|
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); |
|
491 |
|
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 } |
|
501 |
|
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); |
|
506 |
|
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"); |
|
512 |
|
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"); |
|
516 |
|
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); |
|
521 |
|
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); |
|
556 |
|
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); |
|
565 |
|
566 pixman_image_unref(src); |
|
567 pixman_image_unref(dst); |
|
568 } |
|
569 |
|
570 return NS_OK; |
|
571 } |
|
572 |
|
573 void |
|
574 FrameBlender::Discard() |
|
575 { |
|
576 MOZ_ASSERT(NS_IsMainThread()); |
|
577 |
|
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!"); |
|
581 |
|
582 // Delete all the decoded frames, then clear the array. |
|
583 ClearFrames(); |
|
584 } |
|
585 |
|
586 size_t |
|
587 FrameBlender::SizeOfDecodedWithComputedFallbackIfHeap(gfxMemoryLocation aLocation, |
|
588 MallocSizeOf aMallocSizeOf) const |
|
589 { |
|
590 size_t n = mFrames->SizeOfDecodedWithComputedFallbackIfHeap(aLocation, aMallocSizeOf); |
|
591 |
|
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 } |
|
600 |
|
601 return n; |
|
602 } |
|
603 |
|
604 void |
|
605 FrameBlender::ResetAnimation() |
|
606 { |
|
607 if (mAnim) { |
|
608 mAnim->lastCompositedFrameIndex = -1; |
|
609 } |
|
610 } |
|
611 |
|
612 } // namespace image |
|
613 } // namespace mozilla |