Bug 708116. Factor out logic for updating the current frame of a video element into a helper object. r=doublec

This commit is contained in:
Robert O'Callahan 2012-02-15 17:35:01 +13:00
parent 821e0def4e
commit 8b57a3e190
15 changed files with 249 additions and 169 deletions

View File

@ -48,8 +48,8 @@
#include "nsCycleCollectionParticipant.h"
#include "nsILoadGroup.h"
#include "nsIObserver.h"
#include "ImageLayers.h"
#include "nsAudioStream.h"
#include "VideoFrameContainer.h"
// Define to output information on decoding and painting framerate
/* #define DEBUG_FRAME_RATE 1 */
@ -60,12 +60,10 @@ typedef PRUint16 nsMediaReadyState;
class nsHTMLMediaElement : public nsGenericHTMLElement,
public nsIObserver
{
typedef mozilla::layers::ImageContainer ImageContainer;
public:
typedef mozilla::TimeStamp TimeStamp;
typedef mozilla::TimeDuration TimeDuration;
typedef mozilla::layers::ImageContainer ImageContainer;
typedef mozilla::VideoFrameContainer VideoFrameContainer;
enum CanPlayStatus {
CANPLAY_NO,
@ -192,7 +190,12 @@ public:
// Called by the media decoder and the video frame to get the
// ImageContainer containing the video data.
ImageContainer* GetImageContainer();
VideoFrameContainer* GetVideoFrameContainer();
ImageContainer* GetImageContainer()
{
VideoFrameContainer* container = GetVideoFrameContainer();
return container ? container->GetImageContainer() : nsnull;
}
// Called by the video frame to get the print surface, if this is
// a static document and we're not actually playing video
@ -569,9 +572,9 @@ protected:
// The current decoder. Load() has been called on this decoder.
nsRefPtr<nsMediaDecoder> mDecoder;
// A reference to the ImageContainer which contains the current frame
// A reference to the VideoFrameContainer which contains the current frame
// of video to display.
nsRefPtr<ImageContainer> mImageContainer;
nsRefPtr<VideoFrameContainer> mVideoFrameContainer;
// Holds a reference to the first channel we open to the media resource.
// Once the decoder is created, control over the channel passes to the
@ -642,7 +645,10 @@ protected:
PreloadAction mPreloadAction;
// Size of the media. Updated by the decoder on the main thread if
// it changes. Defaults to a width and height of -1 inot set.
// it changes. Defaults to a width and height of -1 if not set.
// We keep this separate from the intrinsic size stored in the
// VideoFrameContainer so that it doesn't change unexpectedly under us
// due to decoder activity.
nsIntSize mMediaSize;
// Time that the last timeupdate event was fired. Read/Write from the

View File

@ -1468,6 +1468,9 @@ nsHTMLMediaElement::~nsHTMLMediaElement()
NS_ASSERTION(!mHasSelfReference,
"How can we be destroyed if we're still holding a self reference?");
if (mVideoFrameContainer) {
mVideoFrameContainer->ForgetElement();
}
UnregisterFreezableElement();
if (mDecoder) {
RemoveMediaElementFromURITable();
@ -2471,10 +2474,10 @@ void nsHTMLMediaElement::NotifyAutoplayDataReady()
}
}
ImageContainer* nsHTMLMediaElement::GetImageContainer()
VideoFrameContainer* nsHTMLMediaElement::GetVideoFrameContainer()
{
if (mImageContainer)
return mImageContainer;
if (mVideoFrameContainer)
return mVideoFrameContainer;
// If we have a print surface, this is just a static image so
// no image container is required
@ -2486,8 +2489,9 @@ ImageContainer* nsHTMLMediaElement::GetImageContainer()
if (!video)
return nsnull;
mImageContainer = LayerManager::CreateImageContainer();
return mImageContainer;
mVideoFrameContainer =
new VideoFrameContainer(this, LayerManager::CreateImageContainer());
return mVideoFrameContainer;
}
nsresult nsHTMLMediaElement::DispatchAudioAvailableEvent(float* aFrameBuffer,

View File

@ -209,12 +209,14 @@ NS_IMETHODIMP nsHTMLVideoElement::GetMozPresentedFrames(PRUint32 *aMozPresentedF
NS_IMETHODIMP nsHTMLVideoElement::GetMozPaintedFrames(PRUint32 *aMozPaintedFrames)
{
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
*aMozPaintedFrames = (!mDecoder || !GetImageContainer()) ? 0 : GetImageContainer()->GetPaintCount();
ImageContainer* container = GetImageContainer();
*aMozPaintedFrames = container ? container->GetPaintCount() : 0;
return NS_OK;
}
NS_IMETHODIMP nsHTMLVideoElement::GetMozFrameDelay(double *aMozFrameDelay) {
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
*aMozFrameDelay = mDecoder ? mDecoder->GetFrameDelay() : 0;
VideoFrameContainer* container = GetVideoFrameContainer();
*aMozFrameDelay = container ? container->GetFrameDelay() : 0;
return NS_OK;
}

View File

@ -46,25 +46,27 @@ LIBRARY_NAME = gkconmedia_s
LIBXUL_LIBRARY = 1
EXPORTS = \
nsAudioAvailableEventManager.h \
nsMediaDecoder.h \
nsMediaStream.h \
nsMediaCache.h \
nsBuiltinDecoder.h \
nsBuiltinDecoderStateMachine.h \
nsBuiltinDecoderReader.h \
VideoFrameContainer.h \
VideoUtils.h \
nsAudioAvailableEventManager.h \
$(NULL)
CPPSRCS = \
nsAudioAvailableEventManager.cpp \
nsMediaDecoder.cpp \
nsMediaCache.cpp \
nsMediaStream.cpp \
nsBuiltinDecoder.cpp \
nsBuiltinDecoderStateMachine.cpp \
nsBuiltinDecoderReader.cpp \
VideoFrameContainer.cpp \
VideoUtils.cpp \
nsAudioAvailableEventManager.cpp \
$(NULL)
ifdef MOZ_SYDNEYAUDIO

View File

@ -0,0 +1,93 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "VideoFrameContainer.h"
#include "nsHTMLMediaElement.h"
#include "nsIFrame.h"
#include "nsDisplayList.h"
#include "nsSVGEffects.h"
using namespace mozilla::layers;
namespace mozilla {
void VideoFrameContainer::SetCurrentFrame(const gfxIntSize& aIntrinsicSize,
Image* aImage,
TimeStamp aTargetTime)
{
MutexAutoLock lock(mMutex);
if (aIntrinsicSize != mIntrinsicSize) {
mIntrinsicSize = aIntrinsicSize;
mIntrinsicSizeChanged = true;
}
gfxIntSize oldFrameSize = mImageContainer->GetCurrentSize();
TimeStamp lastPaintTime = mImageContainer->GetPaintTime();
if (!lastPaintTime.IsNull() && !mPaintTarget.IsNull()) {
mPaintDelay = lastPaintTime - mPaintTarget;
}
mImageContainer->SetCurrentImage(aImage);
gfxIntSize newFrameSize = mImageContainer->GetCurrentSize();
if (oldFrameSize != newFrameSize) {
mImageSizeChanged = true;
}
mPaintTarget = aTargetTime;
}
double VideoFrameContainer::GetFrameDelay()
{
MutexAutoLock lock(mMutex);
return mPaintDelay.ToSeconds();
}
void VideoFrameContainer::Invalidate()
{
NS_ASSERTION(NS_IsMainThread(), "Must call on main thread");
if (!mElement) {
// Element has been destroyed
return;
}
nsIFrame* frame = mElement->GetPrimaryFrame();
bool invalidateFrame = false;
{
MutexAutoLock lock(mMutex);
// Get mImageContainerSizeChanged while holding the lock.
invalidateFrame = mImageSizeChanged;
mImageSizeChanged = false;
if (mIntrinsicSizeChanged) {
mElement->UpdateMediaSize(mIntrinsicSize);
mIntrinsicSizeChanged = false;
if (frame) {
nsPresContext* presContext = frame->PresContext();
nsIPresShell *presShell = presContext->PresShell();
presShell->FrameNeedsReflow(frame,
nsIPresShell::eStyleChange,
NS_FRAME_IS_DIRTY);
}
}
}
if (frame) {
nsRect contentRect = frame->GetContentRect() - frame->GetPosition();
if (invalidateFrame) {
frame->Invalidate(contentRect);
} else {
frame->InvalidateLayer(contentRect, nsDisplayItem::TYPE_VIDEO);
}
}
nsSVGEffects::InvalidateDirectRenderingObservers(mElement);
}
}

View File

@ -0,0 +1,93 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef VIDEOFRAMECONTAINER_H_
#define VIDEOFRAMECONTAINER_H_
#include "ImageLayers.h"
#include "mozilla/Mutex.h"
#include "mozilla/TimeStamp.h"
#include "nsISupportsImpl.h"
#include "gfxPoint.h"
class nsHTMLMediaElement;
namespace mozilla {
/**
* This object is used in the decoder backend threads and the main thread
* to manage the "current video frame" state. This state includes timing data
* and an intrinsic size (see below).
* This has to be a thread-safe object since it's accessed by resource decoders
* and other off-main-thread components. So we can't put this state in the media
* element itself ... well, maybe we could, but it could be risky and/or
* confusing.
*/
class VideoFrameContainer {
public:
typedef mozilla::layers::ImageContainer ImageContainer;
typedef mozilla::layers::Image Image;
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VideoFrameContainer)
VideoFrameContainer(nsHTMLMediaElement* aElement,
already_AddRefed<ImageContainer> aContainer)
: mElement(aElement),
mImageContainer(aContainer), mMutex("nsVideoFrameContainer"),
mIntrinsicSizeChanged(false), mImageSizeChanged(false)
{
NS_ASSERTION(aElement, "aElement must not be null");
NS_ASSERTION(mImageContainer, "aContainer must not be null");
}
// Call on any thread
void SetCurrentFrame(const gfxIntSize& aIntrinsicSize, Image* aImage,
TimeStamp aTargetTime);
// Time in seconds by which the last painted video frame was late by.
// E.g. if the last painted frame should have been painted at time t,
// but was actually painted at t+n, this returns n in seconds. Threadsafe.
double GetFrameDelay();
// Call on main thread
void Invalidate();
ImageContainer* GetImageContainer() { return mImageContainer; }
void ForgetElement() { mElement = nsnull; }
protected:
// Non-addreffed pointer to the element. The element calls ForgetElement
// to clear this reference when the element is destroyed.
nsHTMLMediaElement* mElement;
nsRefPtr<ImageContainer> mImageContainer;
// mMutex protects all the fields below.
Mutex mMutex;
// The intrinsic size is the ideal size which we should render the
// ImageContainer's current Image at.
// This can differ from the Image's actual size when the media resource
// specifies that the Image should be stretched to have the correct aspect
// ratio.
gfxIntSize mIntrinsicSize;
// The time at which the current video frame should have been painted.
// Access protected by mVideoUpdateLock.
TimeStamp mPaintTarget;
// The delay between the last video frame being presented and it being
// painted. This is time elapsed after mPaintTarget until the most recently
// painted frame appeared on screen.
TimeDuration mPaintDelay;
// True when the intrinsic size has been changed by SetCurrentFrame() since
// the last call to Invalidate().
// The next call to Invalidate() will recalculate
// and update the intrinsic size on the element, request a frame reflow and
// then reset this flag.
bool mIntrinsicSizeChanged;
// True when the Image size has changed since the last time Invalidate() was
// called. When set, the next call to Invalidate() will ensure that the
// frame is fully invalidated instead of just invalidating for the image change
// in the ImageLayer.
bool mImageSizeChanged;
};
}
#endif /* VIDEOFRAMECONTAINER_H_ */

View File

@ -137,7 +137,6 @@ bool nsBuiltinDecoder::Init(nsHTMLMediaElement* aElement)
return false;
nsContentUtils::RegisterShutdownObserver(this);
mImageContainer = aElement->GetImageContainer();
return true;
}

View File

@ -514,7 +514,6 @@ class nsBuiltinDecoder : public nsMediaDecoder
// The new size must be between 512 and 16384.
virtual nsresult RequestFrameBufferLength(PRUint32 aLength);
public:
// Return the current state. Can be called on any thread. If called from
// a non-main thread, the decoder monitor must be held.
PlayState GetState() {
@ -618,7 +617,6 @@ class nsBuiltinDecoder : public nsMediaDecoder
// element. Called on the main thread.
virtual void NotifyAudioAvailableListener();
public:
// Notifies the element that decoding has failed.
void DecodeError();

View File

@ -392,6 +392,7 @@ class nsBuiltinDecoderReader : public nsRunnable {
public:
typedef mozilla::ReentrantMonitor ReentrantMonitor;
typedef mozilla::ReentrantMonitorAutoEnter ReentrantMonitorAutoEnter;
typedef mozilla::VideoFrameContainer VideoFrameContainer;
nsBuiltinDecoderReader(nsBuiltinDecoder* aDecoder);
~nsBuiltinDecoderReader();

View File

@ -1908,9 +1908,9 @@ void nsBuiltinDecoderStateMachine::RenderVideoFrame(VideoData* aData,
return;
}
nsRefPtr<Image> image = aData->mImage;
if (image) {
mDecoder->SetVideoData(aData->mDisplay, image, aTarget);
VideoFrameContainer* container = mDecoder->GetVideoFrameContainer();
if (container) {
container->SetCurrentFrame(aData->mDisplay, aData->mImage, aTarget);
}
}

View File

@ -136,6 +136,7 @@ public:
typedef mozilla::ReentrantMonitor ReentrantMonitor;
typedef mozilla::TimeStamp TimeStamp;
typedef mozilla::TimeDuration TimeDuration;
typedef mozilla::VideoFrameContainer VideoFrameContainer;
nsBuiltinDecoderStateMachine(nsBuiltinDecoder* aDecoder, nsBuiltinDecoderReader* aReader, bool aRealTime = false);
~nsBuiltinDecoderStateMachine();

View File

@ -72,13 +72,8 @@ static const PRInt64 CAN_PLAY_THROUGH_MARGIN = 10;
nsMediaDecoder::nsMediaDecoder() :
mElement(nsnull),
mRGBWidth(-1),
mRGBHeight(-1),
mVideoUpdateLock("nsMediaDecoder.mVideoUpdateLock"),
mFrameBufferLength(0),
mPinnedForSeek(false),
mSizeChanged(false),
mImageContainerSizeChanged(false),
mShuttingDown(false)
{
MOZ_COUNT_CTOR(nsMediaDecoder);
@ -94,6 +89,7 @@ nsMediaDecoder::~nsMediaDecoder()
bool nsMediaDecoder::Init(nsHTMLMediaElement* aElement)
{
mElement = aElement;
mVideoFrameContainer = aElement->GetVideoFrameContainer();
return true;
}
@ -118,47 +114,6 @@ nsresult nsMediaDecoder::RequestFrameBufferLength(PRUint32 aLength)
return NS_OK;
}
void nsMediaDecoder::Invalidate()
{
if (!mElement)
return;
nsIFrame* frame = mElement->GetPrimaryFrame();
bool invalidateFrame = false;
{
MutexAutoLock lock(mVideoUpdateLock);
// Get mImageContainerSizeChanged while holding the lock.
invalidateFrame = mImageContainerSizeChanged;
mImageContainerSizeChanged = false;
if (mSizeChanged) {
mElement->UpdateMediaSize(nsIntSize(mRGBWidth, mRGBHeight));
mSizeChanged = false;
if (frame) {
nsPresContext* presContext = frame->PresContext();
nsIPresShell *presShell = presContext->PresShell();
presShell->FrameNeedsReflow(frame,
nsIPresShell::eStyleChange,
NS_FRAME_IS_DIRTY);
}
}
}
if (frame) {
nsRect contentRect = frame->GetContentRect() - frame->GetPosition();
if (invalidateFrame) {
frame->Invalidate(contentRect);
} else {
frame->InvalidateLayer(contentRect, nsDisplayItem::TYPE_VIDEO);
}
}
nsSVGEffects::InvalidateDirectRenderingObservers(mElement);
}
static void ProgressCallback(nsITimer* aTimer, void* aClosure)
{
nsMediaDecoder* decoder = static_cast<nsMediaDecoder*>(aClosure);
@ -224,41 +179,6 @@ void nsMediaDecoder::FireTimeUpdate()
mElement->FireTimeUpdate(true);
}
void nsMediaDecoder::SetVideoData(const gfxIntSize& aSize,
Image* aImage,
TimeStamp aTarget)
{
MutexAutoLock lock(mVideoUpdateLock);
if (mRGBWidth != aSize.width || mRGBHeight != aSize.height) {
mRGBWidth = aSize.width;
mRGBHeight = aSize.height;
mSizeChanged = true;
}
if (mImageContainer && aImage) {
gfxIntSize oldFrameSize = mImageContainer->GetCurrentSize();
TimeStamp paintTime = mImageContainer->GetPaintTime();
if (!paintTime.IsNull() && !mPaintTarget.IsNull()) {
mPaintDelay = paintTime - mPaintTarget;
}
mImageContainer->SetCurrentImage(aImage);
gfxIntSize newFrameSize = mImageContainer->GetCurrentSize();
if (oldFrameSize != newFrameSize) {
mImageContainerSizeChanged = true;
}
}
mPaintTarget = aTarget;
}
double nsMediaDecoder::GetFrameDelay()
{
MutexAutoLock lock(mVideoUpdateLock);
return mPaintDelay.ToSeconds();
}
void nsMediaDecoder::PinForSeek()
{
nsMediaStream* stream = GetStream();

View File

@ -48,8 +48,8 @@
#include "nsITimer.h"
#include "ImageLayers.h"
#include "mozilla/ReentrantMonitor.h"
#include "mozilla/Mutex.h"
#include "nsIMemoryReporter.h"
#include "VideoFrameContainer.h"
class nsHTMLMediaElement;
class nsMediaStream;
@ -67,17 +67,17 @@ static const PRUint32 FRAMEBUFFER_LENGTH_MIN = 512;
static const PRUint32 FRAMEBUFFER_LENGTH_MAX = 16384;
// All methods of nsMediaDecoder must be called from the main thread only
// with the exception of GetImageContainer, SetVideoData and GetStatistics,
// with the exception of GetVideoFrameContainer and GetStatistics,
// which can be called from any thread.
class nsMediaDecoder : public nsIObserver
{
public:
typedef mozilla::ReentrantMonitor ReentrantMonitor;
typedef mozilla::TimeStamp TimeStamp;
typedef mozilla::TimeDuration TimeDuration;
typedef mozilla::layers::ImageContainer ImageContainer;
typedef mozilla::VideoFrameContainer VideoFrameContainer;
typedef mozilla::layers::Image Image;
typedef mozilla::ReentrantMonitor ReentrantMonitor;
typedef mozilla::Mutex Mutex;
typedef mozilla::layers::ImageContainer ImageContainer;
nsMediaDecoder();
virtual ~nsMediaDecoder();
@ -273,11 +273,6 @@ public:
PRUint32& mDecoded;
};
// Time in seconds by which the last painted video frame was late by.
// E.g. if the last painted frame should have been painted at time t,
// but was actually painted at t+n, this returns n in seconds. Threadsafe.
double GetFrameDelay();
// Return statistics. This is used for progress events and other things.
// This can be called from any thread. It's only a snapshot of the
// current state, since other threads might be changing the state
@ -306,7 +301,12 @@ public:
virtual void SetEndTime(double aTime) = 0;
// Invalidate the frame.
virtual void Invalidate();
void Invalidate()
{
if (mVideoFrameContainer) {
mVideoFrameContainer->Invalidate();
}
}
// Fire progress events if needed according to the time and byte
// constraints outlined in the specification. aTimer is true
@ -375,18 +375,6 @@ public:
// their nsMediaStream.
virtual void MoveLoadsToBackground()=0;
// Gets the image container for the media element. Will return null if
// the element is not a video element. This can be called from any
// thread; ImageContainers can be used from any thread.
ImageContainer* GetImageContainer() { return mImageContainer; }
// Set the video width, height, pixel aspect ratio, current image and
// target paint time of the next video frame to be displayed.
// Ownership of the image is transferred to the layers subsystem.
void SetVideoData(const gfxIntSize& aSize,
Image* aImage,
TimeStamp aTarget);
// Constructs the time ranges representing what segments of the media
// are buffered and playable.
virtual nsresult GetBuffered(nsTimeRanges* aBuffered) = 0;
@ -400,6 +388,12 @@ public:
virtual PRInt64 VideoQueueMemoryInUse() = 0;
virtual PRInt64 AudioQueueMemoryInUse() = 0;
VideoFrameContainer* GetVideoFrameContainer() { return mVideoFrameContainer; }
ImageContainer* GetImageContainer()
{
return mVideoFrameContainer ? mVideoFrameContainer->GetImageContainer() : nsnull;
}
protected:
// Start timer to update download progress information.
@ -422,22 +416,10 @@ protected:
// The decoder does not add a reference the element.
nsHTMLMediaElement* mElement;
PRInt32 mRGBWidth;
PRInt32 mRGBHeight;
// Counters related to decode and presentation of frames.
FrameStatistics mFrameStats;
// The time at which the current video frame should have been painted.
// Access protected by mVideoUpdateLock.
TimeStamp mPaintTarget;
// The delay between the last video frame being presented and it being
// painted. This is time elapsed after mPaintTarget until the most recently
// painted frame appeared on screen. Access protected by mVideoUpdateLock.
TimeDuration mPaintDelay;
nsRefPtr<ImageContainer> mImageContainer;
nsRefPtr<VideoFrameContainer> mVideoFrameContainer;
// Time that the last progress event was fired. Read/Write from the
// main thread only.
@ -450,17 +432,6 @@ protected:
// more data is received. Read/Write from the main thread only.
TimeStamp mDataTime;
// Lock around the video RGB, width and size data. This
// is used in the decoder backend threads and the main thread
// to ensure that repainting the video does not use these
// values while they are out of sync (width changed but
// not height yet, etc).
// Backends that are updating the height, width or writing
// to the RGB buffer must obtain this lock first to ensure that
// the video element does not use video data or sizes that are
// in the midst of being changed.
Mutex mVideoUpdateLock;
// The framebuffer size to use for audioavailable events.
PRUint32 mFrameBufferLength;
@ -468,20 +439,6 @@ protected:
// while seeking.
bool mPinnedForSeek;
// Set to true when the video width, height or pixel aspect ratio is
// changed by SetVideoData(). The next call to Invalidate() will recalculate
// and update the intrinsic size on the element, request a frame reflow and
// then reset this flag.
bool mSizeChanged;
// Set to true in SetVideoData() if the new image has a different size
// than the current image. The image size is also affected by transforms
// so this can be true even if mSizeChanged is false, for example when
// zooming. The next call to Invalidate() will call nsIFrame::Invalidate
// when this flag is set, rather than just InvalidateLayer, and then reset
// this flag.
bool mImageContainerSizeChanged;
// True if the decoder is being shutdown. At this point all events that
// are currently queued need to return immediately to prevent javascript
// being run that operates on the element and decoder during shutdown.

View File

@ -266,9 +266,12 @@ nsresult nsOggReader::ReadMetadata(nsVideoInfo* aInfo)
mInfo.mDisplay = displaySize;
mPicture = picture;
mDecoder->SetVideoData(gfxIntSize(displaySize.width, displaySize.height),
nsnull,
TimeStamp::Now());
VideoFrameContainer* container = mDecoder->GetVideoFrameContainer();
if (container) {
container->SetCurrentFrame(gfxIntSize(displaySize.width, displaySize.height),
nsnull,
TimeStamp::Now());
}
// Copy Theora info data for time computations on other threads.
memcpy(&mTheoraInfo, &mTheoraState->mInfo, sizeof(mTheoraInfo));

View File

@ -263,6 +263,7 @@ public:
* been created by this ImageContainer.
* Can be called on any thread. This method takes mReentrantMonitor
* when accessing thread-shared state.
* aImage can be null. While it's null, nothing will be painted.
*
* The Image data must not be modified after this method is called!
*/