/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is mozilla.org code.
 *
 * The Initial Developer of the Original Code is
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 1998
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Daniel Switkin, Mathias Agopian and Sergei Dolgov
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

#include "nsImageBeOS.h"
#include "nsRenderingContextBeOS.h"
#include "nspr.h"
#include <Looper.h>
#include <Bitmap.h>
#include <View.h>

NS_IMPL_ISUPPORTS1(nsImageBeOS, nsIImage)

nsImageBeOS::nsImageBeOS()
  : mImage(nsnull)
  , mImageBits(nsnull)
  , mWidth(0)
  , mHeight(0)
  , mDepth(0)
  , mRowBytes(0)
  , mSizeImage(0)
  , mDecodedX1(PR_INT32_MAX)
  , mDecodedY1(PR_INT32_MAX)
  , mDecodedX2(0)
  , mDecodedY2(0)
  , mAlphaBits(nsnull)
  , mAlphaRowBytes(0)
  , mAlphaDepth(0)
  , mFlags(0)
  , mNumBytesPixel(0)
  , mImageCurrent(PR_FALSE)
  , mOptimized(PR_FALSE)
  , mTileBitmap(nsnull)
{
}

nsImageBeOS::~nsImageBeOS() 
{
	if (nsnull != mImage) 
	{
		delete mImage;
		mImage = nsnull;
	}
			
	if (mTileBitmap) 
	{
		delete mTileBitmap;
		mTileBitmap = nsnull;
	}

	if (nsnull != mImageBits) 
	{
		delete [] mImageBits;
		mImageBits = nsnull;
	}
	if (nsnull != mAlphaBits) 
	{
		delete [] mAlphaBits;
		mAlphaBits = nsnull;
	}
}

nsresult nsImageBeOS::Init(PRInt32 aWidth, PRInt32 aHeight, PRInt32 aDepth,
							nsMaskRequirements aMaskRequirements) 
{
	// Assumed: Init only gets called once by gfxIImageFrame
	// Only 24 bit depths are supported for the platform independent bits
	if (24 == aDepth) 
	{
		mNumBytesPixel = 3;
	}
	else 
	{
		NS_ASSERTION(PR_FALSE, "unexpected image depth");
		return NS_ERROR_UNEXPECTED;
	}
	
	mWidth = aWidth;
	mHeight = aHeight;
	mDepth = aDepth;
	mRowBytes = (mWidth * mDepth) >> 5;
	if (((PRUint32)mWidth * mDepth) & 0x1F) 
		mRowBytes++;
	mRowBytes <<= 2;
	mSizeImage = mRowBytes * mHeight;

	mImageBits = new PRUint8[mSizeImage];

	switch (aMaskRequirements) 
	{
		case nsMaskRequirements_kNeeds1Bit:
			mAlphaRowBytes = (aWidth + 7) / 8;
			mAlphaDepth = 1;
			// 32-bit align each row
			mAlphaRowBytes = (mAlphaRowBytes + 3) & ~0x3;
			mAlphaBits = new PRUint8[mAlphaRowBytes * aHeight];
			memset(mAlphaBits, 255, mAlphaRowBytes * aHeight);
			break;
		case nsMaskRequirements_kNeeds8Bit:
			mAlphaRowBytes = aWidth;
			mAlphaDepth = 8;
			// 32-bit align each row
			mAlphaRowBytes = (mAlphaRowBytes + 3) & ~0x3;
			mAlphaBits = new PRUint8[mAlphaRowBytes * aHeight];
			break;
	}
	
	return NS_OK;
}

// This is a notification that the platform independent bits have changed. Therefore,
// the contents of the BBitmap are no longer up to date. By setting the mImageCurrent
// flag to false, we can be sure that the BBitmap will be updated before it gets blit.
// TO DO: It would be better to cache the updated rectangle here, and only copy the
// area that has changed in CreateImage().
void nsImageBeOS::ImageUpdated(nsIDeviceContext *aContext, PRUint8 aFlags, nsRect *aUpdateRect) 
{
	// This should be 0'd out by Draw()
	mFlags = aFlags;
	mImageCurrent = PR_FALSE;

	mDecodedX1 = PR_MIN(mDecodedX1, aUpdateRect->x);
	mDecodedY1 = PR_MIN(mDecodedY1, aUpdateRect->y);

	if (aUpdateRect->YMost() > mDecodedY2)
		mDecodedY2 = aUpdateRect->YMost();
	if (aUpdateRect->XMost() > mDecodedX2)
		mDecodedX2 = aUpdateRect->XMost();
} 

/** ---------------------------------------------------
 *  See documentation in nsIImage.h
 */
PRBool nsImageBeOS::GetIsImageComplete() {
  return mDecodedX1 == 0 &&
         mDecodedY1 == 0 &&
         mDecodedX2 == mWidth &&
         mDecodedY2 == mHeight;
}

// Draw the bitmap, this method has a source and destination coordinates
NS_IMETHODIMP nsImageBeOS::Draw(nsIRenderingContext &aContext, nsIDrawingSurface* aSurface,
	PRInt32 aSX, PRInt32 aSY, PRInt32 aSWidth, PRInt32 aSHeight,
	PRInt32 aDX, PRInt32 aDY, PRInt32 aDWidth, PRInt32 aDHeight) 
{
	// Don't bother to draw nothing		
	if (!aSWidth || !aSHeight || !aDWidth || !aDHeight)
		return NS_OK;
	if (mDecodedX2 < mDecodedX1 || mDecodedY2 < mDecodedY1)
		return NS_OK;

	// As we do all scaling with Be API (DrawBitmap), using float is safe and convenient
	float srcX = aSX, srcY = aSY, srcMostX = aSX + aSWidth, srcMostY = aSY + aSHeight;
	float dstX = aDX, dstY = aDY, dstMostX = aDX + aDWidth, dstMostY = aDY + aDHeight;
	float  scaleX = float(aDWidth)/float(aSWidth), scaleY = float(aDHeight)/float(aSHeight);

	if (!mImageCurrent || (nsnull == mImage)) 
		BuildImage(aSurface);
	if (nsnull == mImage || mImage->BitsLength() == 0) 
		return NS_ERROR_FAILURE;

	// Limit the image rectangle to the size of the image data which
	// has been validated. 
	if ((mDecodedY1 > 0)) 
	{
		srcY = float(PR_MAX(mDecodedY1, aSY));
	}
	if ((mDecodedX1 > 0))
	{
		srcX = float(PR_MAX(mDecodedX1, aSX));
	}
	// When targeting BRects, MostX/Y is more natural than Width/Heigh
	if ((mDecodedY2 < mHeight)) 
		srcMostY = float(PR_MIN(mDecodedY2, aSY + aSHeight));

	if ((mDecodedX2 < mWidth)) 
		srcMostX = float(PR_MIN(mDecodedX2, aSX + aSWidth));

	dstX = float(srcX - aSX)*scaleX + float(aDX);
	dstY = float(srcY - aSY)*scaleY + float(aDY);
	dstMostX = dstMostX - (float(aSWidth + aSX) - srcMostX)*scaleX;
	dstMostY =	dstMostY - (float(aSHeight + aSY) - srcMostY)*scaleY;

	nsDrawingSurfaceBeOS *beosdrawing = (nsDrawingSurfaceBeOS *)aSurface;
	BView *view;

	// LockAndUpdateView() sets proper clipping region here and elsewhere in nsImageBeOS.
	if (((nsRenderingContextBeOS&)aContext).LockAndUpdateView()) 
	{
		beosdrawing->AcquireView(&view);
		if (view) 
		{
			// TODO: With future locking update check if restricting clipping region 
			// to decoded values fastens things up - in this case clipping must be reset to 0
			// at unlocking or before getting native region in LockAndUpdateView().

			// Only use B_OP_ALPHA when there is an alpha channel present, as it is much slower
			if (0 != mAlphaDepth) 
			{
				view->SetDrawingMode(B_OP_ALPHA);
				view->DrawBitmap(mImage, BRect(srcX, srcY, srcMostX - 1, srcMostY - 1),
					BRect(dstX, dstY, dstMostX - 1, dstMostY - 1));
				view->SetDrawingMode(B_OP_COPY);
			}
			else 
			{
				view->DrawBitmap(mImage, BRect(srcX, srcY, srcMostX - 1, srcMostY - 1),
					BRect(dstX, dstY, dstMostX - 1, dstMostY - 1));
			}
			// view was locked by LockAndUpdateView() before it was aquired. So unlock.
		}
		((nsRenderingContextBeOS&)aContext).UnlockView();
		beosdrawing->ReleaseView();
	}
	
	mFlags = 0;
	return NS_OK;
}

// Draw the bitmap, this draw just has destination coordinates
NS_IMETHODIMP nsImageBeOS::Draw(nsIRenderingContext &aContext, nsIDrawingSurface* aSurface,
	PRInt32 aX, PRInt32 aY, PRInt32 aWidth, PRInt32 aHeight) 
{
	//Don't bother to draw nothing		
	if (!aWidth || !aHeight)
		return NS_OK;
	if (mDecodedX2 < mDecodedX1 || mDecodedY2 < mDecodedY1)
		return NS_OK;

	if (!mImageCurrent || (nsnull == mImage)) 
		BuildImage(aSurface);
	if (nsnull == mImage) 
		return NS_ERROR_FAILURE;

	PRInt32 validX = 0, validY = 0, validMostX = mWidth, validMostY = mHeight;

	// XXX kipp: this is temporary code until we eliminate the
	// width/height arguments from the draw method.
	aWidth = PR_MIN(aWidth, mWidth);
	aHeight = PR_MIN(aHeight, mHeight);

	if ((mDecodedY2 < aHeight)) 
		validMostY = mDecodedY2;

	if ((mDecodedX2 < aWidth)) 
		validMostX = mDecodedX2;

	if ((mDecodedY1 > 0)) 
		validY = mDecodedY1;
	if ((mDecodedX1 > 0)) 
		validX = mDecodedX1;
			
	nsDrawingSurfaceBeOS *beosdrawing = (nsDrawingSurfaceBeOS *)aSurface;
	BView *view;

	if (((nsRenderingContextBeOS&)aContext).LockAndUpdateView()) 
	{
		beosdrawing->AcquireView(&view);
		if (view) 
		{
			// See TODO clipping comment above - code ready for use:
			// BRegion tmpreg(BRect(aX + validX, aY + validY, aX + validMostX - 1, aY + validMostY - 1));
			// view->ConstrainClippingRegion(&tmpreg);
			
			// Only use B_OP_ALPHA when there is an alpha channel present, as it is much slower
			if (0 != mAlphaDepth) 
			{
				view->SetDrawingMode(B_OP_ALPHA);
				view->DrawBitmap(mImage, BRect(validX, validY, validMostX - 1, validMostY - 1), 
								BRect(aX + validX, aY + validY, aX + validMostX - 1, aY + validMostY - 1));
				view->SetDrawingMode(B_OP_COPY);
			} 
			else 
			{
				view->DrawBitmap(mImage, BRect(validX, validY, validMostX - 1, validMostY - 1), 
								BRect(aX + validX, aY + validY, aX + validMostX - 1, aY + validMostY - 1));
			}
		}
		((nsRenderingContextBeOS&)aContext).UnlockView();
		beosdrawing->ReleaseView();
	}

	mFlags = 0;
	return NS_OK;
}

NS_IMETHODIMP nsImageBeOS::DrawTile(nsIRenderingContext &aContext, nsIDrawingSurface* aSurface,
	PRInt32 aSXOffset, PRInt32 aSYOffset, PRInt32 aPadX, PRInt32 aPadY, const nsRect &aTileRect) 
{
	// Don't bother to draw nothing	
	if (!aTileRect.width || !aTileRect.height)
		return NS_OK;
	if (mDecodedX2 < mDecodedX1 || mDecodedY2 < mDecodedY1)
		return NS_OK;

	if (!mImageCurrent || (nsnull == mImage)) 
		BuildImage(aSurface);
	if (nsnull == mImage || mImage->BitsLength() == 0) 
		return NS_ERROR_FAILURE;

	PRInt32 validX = 0, validY = 0, validMostX = mWidth, validMostY = mHeight;

	// Limit the image rectangle to the size of the image data which
	// has been validated.
	if ((mDecodedY2 < mHeight)) 
		validMostY = mDecodedY2;

	if ((mDecodedX2 < mWidth)) 
		validMostX = mDecodedX2;

	if ((mDecodedY1 > 0)) 
		validY = mDecodedY1;

	if ((mDecodedX1 > 0)) 
		validX = mDecodedX1;

	nsDrawingSurfaceBeOS *beosdrawing = (nsDrawingSurfaceBeOS *)aSurface;
	BView *view = 0;
	if (((nsRenderingContextBeOS&)aContext).LockAndUpdateView()) 
	{
		beosdrawing->AcquireView(&view);
		if (view) 
		{
	        BRegion rgn(BRect(aTileRect.x, aTileRect.y,
	        			aTileRect.x + aTileRect.width - 1, aTileRect.y + aTileRect.height - 1));
    	    view->ConstrainClippingRegion(&rgn);

			// Force transparency for bitmap blitting in case of padding even if mAlphaDepth == 0
			if (0 != mAlphaDepth || aPadX || aPadY) 
				view->SetDrawingMode(B_OP_ALPHA);
			// Creating temporary bitmap, compatible with mImage and  with size of area to be filled with tiles
			// Reuse existing if possible
			if (!mTileBitmap || mTileBitmap->Bounds().IntegerWidth() + 1 != aTileRect.width || mTileBitmap->Bounds().IntegerHeight() + 1 != aTileRect.height)
			{
				if (mTileBitmap)
				{
					delete mTileBitmap;
					mTileBitmap = nsnull;
				}
				mTileBitmap = new BBitmap(BRect(0, 0, aTileRect.width - 1, aTileRect.height -1), mImage->ColorSpace(), false);
			}
			
			int32 tmpbitlength = mTileBitmap->BitsLength();

			if (!mTileBitmap || tmpbitlength == 0)
			{
				// Failed. Cleaning things a bit.
				((nsRenderingContextBeOS&)aContext).UnlockView();
				if (mTileBitmap)
				{
					delete mTileBitmap;
					mTileBitmap = nsnull;
				}
				beosdrawing->ReleaseView();
				return NS_ERROR_FAILURE;
			}

			uint32 *dst0 = (uint32 *)mTileBitmap->Bits();
			uint32 *src0 = (uint32 *)mImage->Bits();
			uint32 *dst = dst0;
			uint32 dstRowLength = mTileBitmap->BytesPerRow()/4;
			uint32 dstColHeight = tmpbitlength/mTileBitmap->BytesPerRow();

			// Filling mTileBitmap with transparent color to preserve padding areas on destination 
			uint32 filllength = tmpbitlength/4;
			if (0 != mAlphaDepth  || aPadX || aPadY) 
			{
				for (uint32 i=0, *dst = dst0; i < filllength; ++i)
					*(dst++) = B_TRANSPARENT_MAGIC_RGBA32;
			}

			// Rendering mImage tile to temporary bitmap
			uint32 *src = src0; dst = dst0;
			for (uint32 y = 0, yy = aSYOffset; y < dstColHeight; ++y) 
			{					
				src = src0 + yy*mWidth;
				dst = dst0 + y*dstRowLength;
				// Avoid unnecessary job outside update rect
				if (yy >= validY && yy <= validMostY)
				{
					for (uint32 x = 0, xx = aSXOffset; x < dstRowLength; ++x) 
					{
						// Avoid memwrite if outside update rect
						if (xx >= validX && xx <= validMostX)
							dst[x] = src[xx];
						if (++xx == mWidth)
						{
							// Width of source reached. Adding horizontal paddding.
							xx = 0;
							x += aPadX;
						}
					}
				}
				if (++yy == mHeight)
				{
					// Height of source reached. Adding vertical paddding.
					yy = 0;
					y += aPadY;
				}
			}
			// Flushing tile bitmap to proper area in drawable BView	
			view->DrawBitmap(mTileBitmap, BPoint(aTileRect.x , aTileRect.y ));
			view->SetDrawingMode(B_OP_COPY);
			view->Sync();
		}
		((nsRenderingContextBeOS&)aContext).UnlockView();
		beosdrawing->ReleaseView();
	}
	mFlags = 0;
	return NS_OK;
}

// If not yet optimized, delete mImageBits and mAlphaBits here to save memory,
// since the image will never change again and no one else will need to get the
// platform independent bits.
nsresult nsImageBeOS::Optimize(nsIDeviceContext *aContext) 
{
	if (!mOptimized) 
	{
		// Make sure the BBitmap is up to date
		CreateImage(NULL);
		
		// Release Mozilla-specific data
		if (nsnull != mImageBits) 
		{
			delete [] mImageBits;
			mImageBits = nsnull;
		}
		if (nsnull != mAlphaBits) 
		{
			delete [] mAlphaBits;
			mAlphaBits = nsnull;
		}
		
		mOptimized = PR_TRUE;
	}
	return NS_OK;
}

// Not implemented at the moment. It's unclear whether this is necessary for
// the BeOS port or not. BBitmap::Lock/UnlockBits()  may be used if necessary
NS_IMETHODIMP nsImageBeOS::LockImagePixels(PRBool aMaskPixels) 
{
	// we may need some sort of syncing here in future
	return NS_OK;
}

// Same as above.
NS_IMETHODIMP nsImageBeOS::UnlockImagePixels(PRBool aMaskPixels) 
{
	return NS_OK;
}

nsresult nsImageBeOS::BuildImage(nsIDrawingSurface* aDrawingSurface) 
{
	CreateImage(aDrawingSurface);
	return NS_OK;
}

// This function is responsible for copying the platform independent bits (mImageBits
// and mAlphaBits) into a BBitmap so it can be drawn using the Be APIs. Since it is
// expensive to create and destroy a BBitmap for this purpose, we will keep this bitmap
// around, which also prevents the need to copy the bits if they have not changed.
void nsImageBeOS::CreateImage(nsIDrawingSurface* aSurface) 
{
	PRInt32 validX = 0, validY = 0, validMostX = mWidth, validMostY = mHeight;

	if (mImageBits) 
	{
		if (24 != mDepth) 
		{
			NS_ASSERTION(PR_FALSE, "unexpected image depth");
			return;
		}
		
		// If the previous BBitmap is the right dimensions and colorspace, then reuse it.
		const color_space cs = B_RGBA32;
		if (nsnull != mImage) 
		{
			BRect bounds = mImage->Bounds();
			if (bounds.IntegerWidth() < validMostX - 1 || bounds.IntegerHeight() < validMostY - 1 ||
				mImage->ColorSpace() != cs) 
			{
				
				delete mImage;
				mImage = new BBitmap(BRect(0, 0, validMostX - 1, validMostY - 1), cs, false);
			} 
			else 
			{
				// Don't copy the data twice if the BBitmap is up to date
				if (mImageCurrent) return;
			}
		} 
		else 
		{
			// No BBitmap exists, so create one and update it
			mImage = new BBitmap(BRect(0, 0, mWidth - 1, mHeight - 1), cs, false);
		}
		
		// Only the data that has changed (the rectangle supplied to ImageUpdated())
		// needs to be copied to the BBitmap. 
		
		if ((mDecodedY2 < mHeight)) 
			validMostY = mDecodedY2;

		if ((mDecodedX2 < mWidth)) 
			validMostX = mDecodedX2;

		if ((mDecodedY1 > 0)) 
			validY = mDecodedY1;
		if ((mDecodedX1 > 0)) 
			validX = mDecodedX1;
		
		if (mDecodedX2 < mDecodedX1 || mDecodedY2 < mDecodedY1)
			return;

		// Making up a 32 bit double word for the destination is much more efficient
		// than writing each byte separately on some CPUs. For example, BeOS does not
		// support write-combining on the Athlon/Duron family.
		
		// Using mRowBytes as source stride due alignment of mImageBits array to 4.
		// Destination stride is mWidth, as mImage always uses RGB(A)32 
		if (mImage && mImage->IsValid()) 
		{
			uint32 *dest, *dst0 = (uint32 *)mImage->Bits() + validX;
			uint8 *src, *src0 = mImageBits + 3*validX; 
			if (mAlphaBits) 
			{
				uint8 a, *alpha = mAlphaBits + validY*mAlphaRowBytes;;
				for (int y = validY; y < validMostY; ++y) 
				{
					dest = dst0 + y*mWidth;
					src = src0 + y*mRowBytes;
					for (int x = validX; x < validMostX; ++x) 
					{
						if(1 == mAlphaDepth)
							a = (alpha[x / 8] & (1 << (7 - (x % 8)))) ? 255 : 0;
						else
							a = alpha[x];
						*dest++ = (a << 24) | (src[2] << 16) | (src[1] << 8) | src[0];
						src += 3;
					}
					alpha += mAlphaRowBytes;
				}
			} 
			else 
			{
				// Fixed 255 in the alpha channel to mean completely opaque
				for (int y = validY; y < validMostY; ++y) 
				{
					dest = dst0 + y*mWidth;
					src = src0 + y*mRowBytes;
					for (int x = validX; x < validMostX; ++x) 
					{
						*dest++ = 0xff000000 | (src[2] << 16) | (src[1] << 8) | src[0];
						src += 3;
					}
				}
			}
			
			// The contents of the BBitmap now match mImageBits (and mAlphaBits),
			// so mark the flag up to date to prevent extra copies in the future.
			mImageCurrent = PR_TRUE;
		}
	}
}

// Since this function does not touch either the source or destination BBitmap,
// there is no need to call CreateImage(). The platform independent bits will get
// copied to the BBitmap if and when it gets blit. Code is derived from GTK version,
// but some wordy constant math is moved out of loops for performance
NS_IMETHODIMP nsImageBeOS::DrawToImage(nsIImage* aDstImage, 
										nscoord aDX, nscoord aDY, 
										nscoord aDWidth, nscoord aDHeight)
{
	nsImageBeOS *dest = static_cast<nsImageBeOS *>(aDstImage);

	if (!dest)
		return NS_ERROR_FAILURE;

	if (aDX >= dest->mWidth || aDY >= dest->mHeight)
		return NS_OK;

	PRUint8 *rgbPtr=0, *alphaPtr=0;
	PRUint32 rgbStride, alphaStride;

	rgbPtr = mImageBits;
	rgbStride = mRowBytes;
	alphaPtr = mAlphaBits;
	alphaStride = mAlphaRowBytes;

	PRInt32 y;
	PRInt32 ValidWidth = ( aDWidth < ( dest->mWidth - aDX ) ) ? aDWidth : ( dest->mWidth - aDX ); 
	PRInt32 ValidHeight = ( aDHeight < ( dest->mHeight - aDY ) ) ? aDHeight : ( dest->mHeight - aDY );

	// now composite the two images together
	switch (mAlphaDepth)
	{
		case 1:
		{
			PRUint8 *dst = dest->mImageBits + aDY*dest->mRowBytes + 3*aDX;
			PRUint8 *dstAlpha = dest->mAlphaBits + aDY*dest->mAlphaRowBytes;
			PRUint8 *src = rgbPtr;
			PRUint8 *alpha = alphaPtr;
			PRUint8 offset = aDX & 0x7; // x starts at 0
			PRUint8 offset_8U = 8U - offset;
			int iterations = (ValidWidth+7)/8; // round up
			PRUint32  dst_it_stride = dest->mRowBytes - 3*8*iterations;
			PRUint32  src_it_stride = rgbStride - 3*8*iterations;
			PRUint32  alpha_it_stride = alphaStride - iterations;

			for (y=0; y < ValidHeight; ++y)
			{
				for (int x=0; x < ValidWidth; x += 8, dst += 24, src += 24)
				{
					PRUint8 alphaPixels = *alpha++;
					PRInt32  VW_x = ValidWidth-x;
					if (alphaPixels == 0)
						continue; // all 8 transparent; jump forward

					// 1 or more bits are set, handle dstAlpha now - may not be aligned.
					// Are all 8 of these alpha pixels used?
					if (x+7 >= ValidWidth)
					{
						alphaPixels &= 0xff << (8 - VW_x); // no, mask off unused
						if (alphaPixels == 0)
							continue;  // no 1 alpha pixels left
					}
					if (offset == 0)
					{
						dstAlpha[(aDX+x)>>3] |= alphaPixels; // the cheap aligned case
					}
					else
					{
						dstAlpha[(aDX+x)>>3] |= alphaPixels >> offset;
						// avoid write if no 1's to write - also avoids going past end of array
						// compiler should merge the common sub-expressions
						if (alphaPixels << offset_8U)
							dstAlpha[((aDX+x)>>3) + 1] |= alphaPixels << offset_8U;
					}
          
					if (alphaPixels == 0xff)
					{
						// fix - could speed up by gathering a run of 0xff's and doing 1 memcpy
						// all 8 pixels set; copy and jump forward
						memcpy(dst,src,24);
						continue;
					}
					else
					{
						// else mix of 1's and 0's in alphaPixels, do 1 bit at a time
						// Don't go past end of line!
						PRUint8 *d = dst, *s = src;
						for (PRUint8 aMask = 1<<7, j = 0; aMask && j < VW_x; aMask >>= 1, ++j)
						{
							// if this pixel is opaque then copy into the destination image
							if (alphaPixels & aMask)
							{
								// might be faster with *d++ = *s++ 3 times?
								d[0] = s[0];
								d[1] = s[1];
								d[2] = s[2];
								// dstAlpha bit already set
							}
							d += 3;
							s += 3;
						}
					}
				}
				// at end of each line, bump pointers. 
				dst = dst + dst_it_stride;
				src = src + src_it_stride;
				alpha = alpha + alpha_it_stride;
				dstAlpha += dest->mAlphaRowBytes;
			}
		}
		break;
		case 0:
		default:
			for (y=0; y < ValidHeight; ++y)
				memcpy(dest->mImageBits + (y+aDY)*dest->mRowBytes + 3*aDX, 
						rgbPtr + y*rgbStride, 3*ValidWidth);
	}
	// ImageUpdated() won't be called in this case, so we need to mark the destination
	// image as changed. This will cause its data to be copied in the BBitmap when it
	// tries to blit. The source has not been modified, so its status has not changed.
	nsRect rect(aDX, aDY, ValidWidth, ValidHeight);
	dest->ImageUpdated(nsnull, 0, &rect);
	mImageCurrent = PR_TRUE;

	return NS_OK;
}
