// Copyright (c) 1999-2009 Nokia Corporation and/or its subsidiary(-ies).
// All rights reserved.
// This component and the accompanying materials are made available
// under the terms of "Eclipse Public License v1.0"
// which accompanies this distribution, and is available
// at the URL "http://www.eclipse.org/legal/epl-v10.html".
//
// Initial Contributors:
// Nokia Corporation - initial contribution.
//
// Contributors:
//
// Description:
//

#include <icl/imageprocessor.h>
#include "ImageProcessorPriv.h"
#include "ImageUtils.h"
#include "ImageClientMain.h"

//Size of dynamically allocated buffers used by CPixelWriter & CMonochromePixelWriter
const TInt KPixelWriterBufferSize = 1024;
const TInt KPixelWriterBufferThreeQuarter = 768;

/**
Constructor for this class.
*/
EXPORT_C CImageProcessorExtension::CImageProcessorExtension()
:iOperation(EDecodeNormal)
	{
	}

/**
Destructor for this class.
*/
EXPORT_C CImageProcessorExtension::~CImageProcessorExtension()
	{
	}

/**
Sets the area of interest of the image to be decoded.

@param aRect
	   A reference to a TRect that specifies the location and size of the region to be decoded.

*/
EXPORT_C void CImageProcessorExtension::SetClippingRect(const TRect& aRect)
	{
	iClippingRect = aRect;
	}

/**
Sets the scaling coefficient for the decode.

@param aScalingCoeff
	   The scaling coefficient.

@see TImageConvScaler::SetScalingL
*/
EXPORT_C void CImageProcessorExtension::SetScaling(TInt aScalingCoeff)
	{
	iScalingCoeff = aScalingCoeff;
	}

/**
Sets the desired size of the destination image for the decode.

@param aDesiredSize
	   The desired size of the destination image.

@see TImageConvScaler::SetScalingL
*/
EXPORT_C void CImageProcessorExtension::SetScaling(const TSize& aDesiredSize)
	{
	iDesiredSize = aDesiredSize;
	}

/**
Sets the operation to be applied to the image.

@param aOperation
	   The operation to apply to the image.

@see TImageConvScaler::SetScalingL
*/
EXPORT_C void CImageProcessorExtension::SetOperation(TTransformOptions aOperation)
	{
	iOperation = aOperation;
	}

/**
Sets an initial one-off number of scanlines to be skipped.
This must be called prior to calling SetYPosIncrement(),
if it is to be used.

@param  aNumberOfScanlines
        The number of scanlines to skip.
        
@see CImageProcessor::SetYPosIncrement()
*/
EXPORT_C void CImageProcessorExtension::SetInitialScanlineSkipPadding(TInt aNumberOfScanlines)
	{
	iNumberOfScanlinesToSkip = aNumberOfScanlines;
	}

//
// ImageProcessorUtility
//

//
//	CColorImageProcessor
//

/**
 * @see CImageProcessor.
 * @internalComponent
 */
void CColorImageProcessor::CreateBlockBufferL(TInt aBlockArea)
	{
	delete[] iBlockBuffer;
	iBlockBuffer = NULL;

	if(aBlockArea)
		iBlockBuffer = new (ELeave) TRgb[aBlockArea];

	iBlockArea = aBlockArea;
	}

/**
 * Destructor.
 * @see CImageProcessor.
 * @internalComponent
 */
CColorImageProcessor::~CColorImageProcessor()
	{
	delete[] iBlockBuffer;
	}

/**
 * @see CImageProcessor.
 * @internalComponent
 */
TBool CColorImageProcessor::SetPixelRun(TRgb aColor,TInt aCount)
	{
	TBool returnValue = EFalse;

	while(aCount--)
		returnValue |= SetPixel(aColor);

	return returnValue;
	}

/**
 * @see CImageProcessor.
 * @internalComponent
 */
TBool CColorImageProcessor::SetPixels(TRgb* aColorBuffer,TInt aBufferLength)
	{
	TBool returnValue = EFalse;

	while(aBufferLength--)
		returnValue |= SetPixel(*aColorBuffer++);

	return returnValue;
	}

/**
 * @see CImageProcessor.
 * @internalComponent
 */
TBool CColorImageProcessor::SetMonoPixel(TInt aGray256)
	{
	return SetPixel(TRgb(aGray256,aGray256,aGray256));
	}

/**
 * @see CImageProcessor.
 * @internalComponent
 */
TBool CColorImageProcessor::SetMonoPixelRun(TInt aGray256,TInt aCount)
	{
	return SetPixelRun(TRgb(aGray256,aGray256,aGray256),aCount);
	}

/**
 * @see CImageProcessor.
 * @internalComponent
 */
TBool CColorImageProcessor::SetMonoPixels(TUint32* aGray256Buffer,TInt aBufferLength)
	{
	TBool returnValue = EFalse;

	while(aBufferLength--)
		{
		TUint32 gray256 = *aGray256Buffer++;
		returnValue = SetPixel(TRgb(gray256,gray256,gray256));
		}
	return returnValue;
	}

/**
 * @see CImageProcessor.
 * @internalComponent
 */
TBool CColorImageProcessor::SetMonoPixelBlock(TUint32* aGray256Buffer)
	{
	ASSERT(iBlockBuffer);

	TRgb* blockBufferPtr = iBlockBuffer;
	TRgb* blockBufferPtrLimit = blockBufferPtr+iBlockArea;

	while(blockBufferPtr<blockBufferPtrLimit)
		{
		TUint32 gray256 = *aGray256Buffer++;
		*blockBufferPtr++ = TRgb(gray256,gray256,gray256);
		}

	return SetPixelBlock(iBlockBuffer);
	}

//
//	CMonochromeImageProcessor
//

/**
 * @see CImageProcessor.
 */
void CMonochromeImageProcessor::CreateBlockBufferL(TInt aBlockArea)
	{
	delete[] iBlockBuffer;
	iBlockBuffer = NULL;

	iBlockBuffer = new (ELeave) TUint32[aBlockArea];
	iBlockArea = aBlockArea;
	}

/**
 * Destructor.
 * @see CImageProcessor.
 */
CMonochromeImageProcessor::~CMonochromeImageProcessor()
	{
	delete []iBlockBuffer;
	}

/**
 * @see CImageProcessor.
 */
TBool CMonochromeImageProcessor::SetMonoPixelRun(TInt aGray256,TInt aCount)
	{
	TBool returnValue = EFalse;

	while(aCount--)
		returnValue = SetMonoPixel(aGray256);

	return returnValue;
	}

/**
 * @see CImageProcessor.
 */
TBool CMonochromeImageProcessor::SetMonoPixels(TUint32* aGray256Buffer,TInt aBufferLength)
	{
	TBool returnValue = EFalse;

	while(aBufferLength--)
		returnValue = SetMonoPixel(*aGray256Buffer++);

	return returnValue;
	}

/**
 * @see CImageProcessor.
 */
TBool CMonochromeImageProcessor::SetPixel(TRgb aColor)
	{
	return SetMonoPixel(TColorConvertor::RgbToMonochrome(aColor));
	}

/**
 * @see CImageProcessor.
 */
TBool CMonochromeImageProcessor::SetPixelRun(TRgb aColor,TInt aCount)
	{
	return SetMonoPixelRun(TColorConvertor::RgbToMonochrome(aColor),aCount);
	}

/**
 * @see CImageProcessor.
 */
TBool CMonochromeImageProcessor::SetPixels(TRgb* aColorBuffer,TInt aBufferLength)
	{
	TBool returnValue = EFalse;

	while(aBufferLength--)
		returnValue = SetMonoPixel(TColorConvertor::RgbToMonochrome(*aColorBuffer++));

	return returnValue;
	}

/**
 * @see CImageProcessor.
 */
TBool CMonochromeImageProcessor::SetPixelBlock(TRgb* aColorBuffer)
	{
	ASSERT(iBlockBuffer);

	TUint32* blockBufferPtr = iBlockBuffer;
	TUint32* blockBufferPtrLimit = blockBufferPtr+iBlockArea;

	while(blockBufferPtr<blockBufferPtrLimit)
		*blockBufferPtr++ = TColorConvertor::RgbToMonochrome(*aColorBuffer++);

	return SetMonoPixelBlock(iBlockBuffer);
	}

//
// CPixelWriter
//

/**
 *
 * Static factory function to create CPixelWriter objects.
 *
 * @return  Pointer to a fully constructed CPixelWriter object. 
 */
CPixelWriter* CPixelWriter::NewL()
	{
	return new(ELeave) CPixelWriter;
	}

/**
 *
 * Default constructor for this class.
 */
CPixelWriter::CPixelWriter():
	iYInc(1)
	{}	
	
/**
 *
 * Destructor.
 */
CPixelWriter::~CPixelWriter()
	{
	Reset();
	ASSERT(iColorConv==NULL);
	ASSERT(iRgbBuffer==NULL);
	ASSERT(iIndexBuffer==NULL);
	}

/**
 *
 * @see CImageProcessor
 */
void CPixelWriter::PrepareL(CFbsBitmap& aBitmap,const TRect& aImageRect)
	{
	DoPrepareL(aBitmap,aImageRect,NULL);
	}

/**
 *
 * @see CImageProcessor
 */
void CPixelWriter::PrepareL(CFbsBitmap& aBitmap,const TRect& aImageRect,const TSize& aRgbBlockSize)
	{
	DoPrepareL(aBitmap,aImageRect,&aRgbBlockSize);
	}

/**
 *
 * @see CImageProcessor.
 */
void CPixelWriter::SetYPosIncrement(TInt aYInc)
	{
	iYInc = aYInc - iNumberOfScanlinesToSkip;
	}

/**
 *
 * @see CImageProcessor.
 */
void CPixelWriter::SetLineRepeat(TInt aLineRepeat)
	{
	ASSERT(aLineRepeat>=0);
	iLineRepeat = aLineRepeat;
	}

/**
 *
 * @see CImageProcessor.
 */
void CPixelWriter::SetPixelPadding(TInt aNumberOfPixels)
	{
	iPixelPadding = aNumberOfPixels;
	}

/**
 *
 * @see CImageProcessor.
 */
TBool CPixelWriter::SetPixel(TRgb aColor)
	{
	*iRgbBufferPtr++ = aColor;

	if (iRgbBufferPtr == iRgbBufferPtrLimit)
		return FlushPixels();

	return EFalse;
	}

/**
 *
 * @see CImageProcessor.
 */
TBool CPixelWriter::SetPixelRun(TRgb aColor,TInt aCount)
	{
	while(aCount)
		{
		TRgb* ptr = iRgbBufferPtr;
		TRgb* limit = ptr+aCount;
		if(limit>iRgbBufferPtrLimit)
			limit = iRgbBufferPtrLimit;

		TInt n = limit-ptr;
		aCount -= n;

		if(n&1)
			*ptr++ = aColor;
		if(n&2)
			{
			*ptr++ = aColor;
			*ptr++ = aColor;
			}
		if(n&4)
			{
			*ptr++ = aColor;
			*ptr++ = aColor;
			*ptr++ = aColor;
			*ptr++ = aColor;
			}
		while(ptr<limit)
			{
			*ptr++ = aColor;
			*ptr++ = aColor;
			*ptr++ = aColor;
			*ptr++ = aColor;
			*ptr++ = aColor;
			*ptr++ = aColor;
			*ptr++ = aColor;
			*ptr++ = aColor;
			}

		iRgbBufferPtr = ptr;

		if(ptr!=iRgbBufferPtrLimit)
			break;

		if(FlushPixels())
			return ETrue;
		}

	return EFalse;
	}

/**
 *
 * @see CImageProcessor.
 */
TBool CPixelWriter::SetPixels(TRgb* aColorBuffer, TInt aBufferLength)
	{
	if (aBufferLength >= KPixelWriterBufferThreeQuarter)
		{
		TRgb* rgbBuffer = iRgbBuffer;

		if (iRgbBufferPtr != rgbBuffer) 
			{
			// flush rest of the pixels 
			if(FlushPixels())
				{
				return ETrue;
				}
			}

		// use external buffer without copying data
		TBool rValue = EFalse;

		while (aBufferLength && !rValue)
			{
			TInt bufferLength = (aBufferLength>KPixelWriterBufferSize)?KPixelWriterBufferSize:aBufferLength;
			iRgbBuffer = aColorBuffer;
			iRgbBufferPtr = aColorBuffer+bufferLength;
			iRgbBufferPtrLimit = aColorBuffer+bufferLength;

			rValue = FlushPixels();
			aBufferLength -= bufferLength;
			aColorBuffer += bufferLength;
			}
		
		// restore pointers to inner buffer
		iRgbBuffer = rgbBuffer;
		iRgbBufferPtr = rgbBuffer;
		iRgbBufferPtrLimit = rgbBuffer+KPixelWriterBufferSize;
		
		return rValue;
		}

	while(aBufferLength)
		{
		TRgb* ptr = iRgbBufferPtr;
		TRgb* limit = ptr+aBufferLength;
		if(limit>iRgbBufferPtrLimit)
			limit = iRgbBufferPtrLimit;

		TInt n = limit-ptr;
		aBufferLength -= n;

		if(n&1)
			*ptr++ = *aColorBuffer++;
		if(n&2)
			{
			*ptr++ = *aColorBuffer++;
			*ptr++ = *aColorBuffer++;
			}
		while(ptr<limit)
			{
			*ptr++ = *aColorBuffer++;
			*ptr++ = *aColorBuffer++;
			*ptr++ = *aColorBuffer++;
			*ptr++ = *aColorBuffer++;
			}

		iRgbBufferPtr = ptr;

		if(ptr!=iRgbBufferPtrLimit)
			break;

		if(FlushPixels())
			return ETrue;
		}

	return EFalse;
	}

/**
 *
 * @see CImageProcessor.
 */
TBool CPixelWriter::SetPixelBlock(TRgb* aColorBuffer)
	{
	ASSERT(aColorBuffer);

	TUint32* indexBufferPtr = iIndexBuffer;
	if (iDisplayMode==EColor16M || iDisplayMode == EColor16MU || iDisplayMode == EColor16MA)
		{
		indexBufferPtr = reinterpret_cast<TUint32*>(aColorBuffer);
		}
	else
		{
		SetPixelBlockIndex(aColorBuffer);
		}

	TInt ySkip = 0;
	if(iNumberOfScanlinesToSkip > 0)
		{
		ySkip = iNumberOfScanlinesToSkip * iBlockSize.iWidth;
		indexBufferPtr += ySkip;
		ySkip = iNumberOfScanlinesToSkip;
		iNumberOfScanlinesToSkip = 0; // Only call this conditional once.
		}
	
	TInt imageWidth = iImageRegion.iBr.iX;
	TInt imageHeight = iImageRegion.iBr.iY;
	TInt endOfImage = iDrawBottomUp ? -1 : imageHeight;
	
	// The minimum number of pixels to render horizontally
	TInt minWidth = Min(iBlockSize.iWidth, imageWidth - iPos.iX);
	
	// The next vertical position.  Note that this is usually the height of the block, but 
	// in the case of the first block when clipping is required, this will be reduced by ySkip.
	TInt nextYPos = iDrawBottomUp ?	(iPos.iY - iBlockSize.iHeight) + ySkip :
									(iPos.iY + iBlockSize.iHeight) - ySkip;
	
	TInt endPosition = iDrawBottomUp ? Max(nextYPos, endOfImage) : Min(nextYPos, endOfImage);
	
	// Once the first block has been processed, iYInc is set to block height
	iYInc = iDrawBottomUp ? -iBlockSize.iHeight + ySkip : iBlockSize.iHeight - ySkip;
				
	// Skip unnecessary pixels (for cropping, or padding when rotated)
	indexBufferPtr += iPixelPadding;
	
	TPoint pos(iPos);
	iUtil.Begin();
	for(;iDrawBottomUp ? pos.iY > endPosition : pos.iY < endPosition; iDrawBottomUp ? pos.iY-- : pos.iY++)
		{
		iUtil.SetPos(pos);
		iUtil.SetPixels(indexBufferPtr, minWidth);
		indexBufferPtr += iBlockSize.iWidth; // next line in block
		}
	iUtil.End();

	iPos.iX += iBlockSize.iWidth;
	
	if (iPos.iX >= imageWidth)
		{
		return NewLine();
		}

	return EFalse;
	}

/**
 *
 * @see CImageProcessor.
 */
TBool CPixelWriter::FlushPixels()
	{
	TRgb* rgbBufferPtrLimit = iRgbBufferPtr;
	iRgbBufferPtr = iRgbBuffer;

	if(iPos.iY < iImageRegion.iTl.iY || iPos.iY >= iImageRegion.iBr.iY)
		return ETrue;

	iUtil.Begin();

	TBool finished = EFalse;
	for (TRgb* rgbBufferPtr = iRgbBuffer; rgbBufferPtr < rgbBufferPtrLimit; )
		{
		TInt pixelsToSkip = Min(rgbBufferPtrLimit - rgbBufferPtr,iPixelsToSkip);
		rgbBufferPtr += pixelsToSkip;
		iPixelsToSkip -= pixelsToSkip;

		if(iPixelsToSkip)
			break;

		TInt pixelsToFlush = Min(rgbBufferPtrLimit - rgbBufferPtr,iImageRegion.iBr.iX - iPos.iX);

		if(!pixelsToFlush)
			break;

		SetPixelBufferIndex(rgbBufferPtr,pixelsToFlush);
		rgbBufferPtr += pixelsToFlush;

		TBool fillDown = iYInc > 0;
		TPoint pos(iPos);
		TInt posYLimit;
		if(fillDown)
			posYLimit = Min(pos.iY + iLineRepeat + 1 ,iImageRegion.iBr.iY);
		else
			posYLimit = Max(pos.iY - iLineRepeat - 1 ,iImageRegion.iTl.iY-1);

		for(;fillDown ? pos.iY < posYLimit : pos.iY > posYLimit; fillDown ? pos.iY++ : pos.iY--)
			{
			if(!iUtil.SetPos(pos-iImageRegion.iTl))
				{
				iUtil.End();
				return ETrue;
				}
			iUtil.SetPixels(iIndexBuffer,pixelsToFlush);
			}

		iPos.iX += pixelsToFlush;
		if (iPos.iX >= iImageRegion.iBr.iX)
			{
			finished = NewLine();
			if(finished)
				break;
			}
		}

	iUtil.End();

	return finished;
	}

/**
 *
 * @see CImageProcessor.
 */
TBool CPixelWriter::SetPos(const TPoint& aPosition)
	{
	if(iImageRegion.Contains(aPosition))
		{
		FlushPixels();
		iPixelsToSkip = 0;
		iPos = aPosition;
		return ETrue;
		}

	return EFalse;
	}

void CPixelWriter::Reset()
	{
	delete iColorConv;
	iColorConv = NULL;

	delete[] iRgbBuffer;
	iRgbBuffer = NULL;

	delete[] iIndexBuffer;
	iIndexBuffer = NULL;

	iPos.SetXY(0,0);
	iPixelsToSkip = 0;
	iImageRegion.SetRect(0,0,0,0);
	iBlockSize.SetSize(0,0);
	
	iDrawBottomUp = EFalse;
	}

void CPixelWriter::DoPrepareL(CFbsBitmap& aBitmap,const TRect& aImageRect,const TSize* aBlockSize)
	{
	if( (aImageRect.iTl.iX<0) || (aImageRect.iTl.iY<0) || (aImageRect.Size().iWidth>aBitmap.SizeInPixels().iWidth) || (aImageRect.Size().iHeight>aBitmap.SizeInPixels().iHeight) )
		{
		User::Leave(KErrArgument);
		}

	Reset();
	
	iDisplayMode = aBitmap.DisplayMode();

	iImageRegion = aImageRect;

	ASSERT(iColorConv==NULL);
	iColorConv = TColorConvertor::NewL(aBitmap.DisplayMode());
	iUtil.SetBitmapL(&aBitmap);

	if (aBlockSize)
		{
		if (aBlockSize->iWidth <= 0 || aBlockSize->iHeight <= 0)
			{
			User::Leave(KErrArgument);
			}

		iBlockSize = *aBlockSize;
		iBlockArea = iBlockSize.iWidth * iBlockSize.iHeight;
		
		ASSERT(iIndexBuffer == NULL);
		iIndexBuffer = new(ELeave) TUint32[iBlockArea];

		iIndexBufferPtrLimit = iIndexBuffer + iBlockArea;
		CreateBlockBufferL(iBlockArea);
		
		switch(iOperation)
			{
			case EDecodeRotate180:
			case EDecodeRotate270:
			case EDecodeHorizontalFlip:
			case EDecodeVerticalFlipRotate90:
				iDrawBottomUp = ETrue;
				break;
			default:
				iDrawBottomUp = EFalse;
			}
		
		iYInc = iDrawBottomUp ? -iBlockSize.iHeight : iBlockSize.iHeight;
		iStartPosition.SetXY(iImageRegion.iTl.iX, iDrawBottomUp ? iImageRegion.iBr.iY - 1 : 0);
		iEndPosition.SetXY(iImageRegion.iBr.iX, iDrawBottomUp ?
							iImageRegion.iTl.iY - 1 : iImageRegion.iBr.iY);
		iPos = iStartPosition;
		}
	else
		{
		iPos = iImageRegion.iTl;
		iStartPosition = iPos;
		iEndPosition = aImageRect.iBr;
		
		ASSERT(iRgbBuffer == NULL);
		iRgbBuffer = new(ELeave) TRgb[KPixelWriterBufferSize];

		iRgbBufferPtr = iRgbBuffer;
		iRgbBufferPtrLimit = iRgbBuffer + KPixelWriterBufferSize;

		ASSERT(iIndexBuffer == NULL);
		iIndexBuffer = new(ELeave) TUint32[KPixelWriterBufferSize];

		iIndexBufferPtrLimit = iIndexBuffer + KPixelWriterBufferSize;
		}
	}

TBool CPixelWriter::NewLine()
	{
	iPos.iX = iStartPosition.iX;
	iPos.iY += iYInc;

	if(iPos.iY < iStartPosition.iY || iPos.iY >= iEndPosition.iY)
		{
		return ETrue;
		}
	
	iPixelsToSkip = iPixelPadding;
	return EFalse;
	}

void CPixelWriter::SetPixelBufferIndex(TRgb* aColorBuffer,TInt aCount)
	{
	iColorConv->ColorToIndex(REINTERPRET_CAST(TInt*,iIndexBuffer),aColorBuffer,aCount);
	}

void CPixelWriter::SetPixelBlockIndex(TRgb* aColorBuffer)
	{
	iColorConv->ColorToIndex(REINTERPRET_CAST(TInt*,iIndexBuffer),aColorBuffer,iIndexBufferPtrLimit-iIndexBuffer);
	}

//
//	CMonochromePixelWriter
//

/**
 *
 * Static factory function to create CMonochromePixelWriter objects.
 *
 * @return  Pointer to a fully constructed CMonochromePixelWriter object. 
 */
CMonochromePixelWriter* CMonochromePixelWriter::NewL()
	{
	return new(ELeave) CMonochromePixelWriter;
	}

/**
 *
 * Default constructor for this class.
 */
CMonochromePixelWriter::CMonochromePixelWriter():
	iYInc(1)
	{}

/**
 *
 * Destructor
 */
CMonochromePixelWriter::~CMonochromePixelWriter()
	{
	Reset();
	}

/**
 *
 * @see CImageProcessor
 */
void CMonochromePixelWriter::PrepareL(CFbsBitmap& aBitmap,const TRect& aImageRect)
	{
	DoPrepareL(aBitmap,aImageRect,NULL);
	}

/**
 *
 * @see CImageProcessor
 */
void CMonochromePixelWriter::PrepareL(CFbsBitmap& aBitmap,const TRect& aImageRect,const TSize& aRgbBlockSize)
	{
	DoPrepareL(aBitmap,aImageRect,&aRgbBlockSize);
	}

/**
 *
 * @see CImageProcessor.
 */
void CMonochromePixelWriter::SetYPosIncrement(TInt aYInc)
	{
	iYInc = aYInc - iNumberOfScanlinesToSkip;
	}

/**
 *
 * @see CImageProcessor.
 */
void CMonochromePixelWriter::SetPixelPadding(TInt aNumberOfPixels)
	{
	iPixelPadding = aNumberOfPixels;
	}

/**
 *
 * @see CImageProcessor.
 */
void CMonochromePixelWriter::SetLineRepeat(TInt aLineRepeat)
	{
	ASSERT(aLineRepeat>=0);
	iLineRepeat = aLineRepeat;
	}

/**
 *
 * @see CImageProcessor.
 */
TBool CMonochromePixelWriter::SetMonoPixel(TInt aGray256)
	{
	*iGray256BufferPtr++ = aGray256;

	if (iGray256BufferPtr != iGray256BufferPtrLimit)
		return EFalse;

	return FlushPixels();
	}

/**
 *
 * @see CImageProcessor.
 */
TBool CMonochromePixelWriter::SetMonoPixelRun(TInt aGray256,TInt aCount)
	{
	while(aCount)
		{
		TUint32* ptr = iGray256BufferPtr;
		TUint32* limit = ptr+aCount;
		if(limit>iGray256BufferPtrLimit)
			limit = iGray256BufferPtrLimit;

		TInt n = limit-ptr;
		aCount -= n;

		if(n&1)
			*ptr++ = aGray256;
		if(n&2)
			{
			*ptr++ = aGray256;
			*ptr++ = aGray256;
			}
		if(n&4)
			{
			*ptr++ = aGray256;
			*ptr++ = aGray256;
			*ptr++ = aGray256;
			*ptr++ = aGray256;
			}
		while(ptr<limit)
			{
			*ptr++ = aGray256;
			*ptr++ = aGray256;
			*ptr++ = aGray256;
			*ptr++ = aGray256;
			*ptr++ = aGray256;
			*ptr++ = aGray256;
			*ptr++ = aGray256;
			*ptr++ = aGray256;
			}

		iGray256BufferPtr = ptr;

		if(ptr!=iGray256BufferPtrLimit)
			break;

		if(FlushPixels())
			return ETrue;
		}

	return EFalse;
	}

/**
 *
 * @see CImageProcessor.
 */
TBool CMonochromePixelWriter::SetMonoPixels(TUint32* aGray256Buffer,TInt aBufferLength)
	{

	if (aBufferLength >= KPixelWriterBufferThreeQuarter)
		{
		TUint32* gray256Buffer = iGray256Buffer;

		if (iGray256BufferPtr != gray256Buffer) 
			{
			// flush rest of the pixels 
			if(FlushPixels())
				{
				return ETrue;
				}
			}

		// use external buffer without copying data
		TBool rValue = EFalse;
		
		while (aBufferLength && !rValue)
			{
			TInt bufferLength = (aBufferLength>KPixelWriterBufferSize)?KPixelWriterBufferSize:aBufferLength;
			iGray256Buffer = aGray256Buffer;
			iGray256BufferPtr = aGray256Buffer+bufferLength;
			iGray256BufferPtrLimit = aGray256Buffer+bufferLength;

			rValue = FlushPixels();
			aBufferLength -= bufferLength;
			aGray256Buffer += bufferLength;
			}
		
		// restore pointers to inner buffer
		iGray256Buffer = gray256Buffer;
		iGray256BufferPtr = gray256Buffer;
		iGray256BufferPtrLimit = gray256Buffer+KPixelWriterBufferSize;
		
		return rValue;
		}

	while(aBufferLength)
		{
		TUint32* ptr = iGray256BufferPtr;
		TUint32* limit = ptr+aBufferLength;
		if(limit>iGray256BufferPtrLimit)
			limit = iGray256BufferPtrLimit;

		TInt n = limit-ptr;
		aBufferLength -= n;

		if(n&1)
			*ptr++ = *aGray256Buffer++;
		if(n&2)
			{
			*ptr++ = *aGray256Buffer++;
			*ptr++ = *aGray256Buffer++;
			}
		while(ptr<limit)
			{
			*ptr++ = *aGray256Buffer++;
			*ptr++ = *aGray256Buffer++;
			*ptr++ = *aGray256Buffer++;
			*ptr++ = *aGray256Buffer++;
			}

		iGray256BufferPtr = ptr;

		if(ptr!=iGray256BufferPtrLimit)
			break;

		if(FlushPixels())
			return ETrue;
		}

	return EFalse;
	}

/**
 *
 * @see CImageProcessor.
 */
TBool CMonochromePixelWriter::SetMonoPixelBlock(TUint32* aGray256Buffer)
	{
	SetPixelBlockIndex(aGray256Buffer);
	
	TUint32* indexBufferPtr = iIndexBuffer;

	TInt ySkip = 0;
	if(iNumberOfScanlinesToSkip > 0)
		{
		ySkip = iNumberOfScanlinesToSkip * iBlockSize.iWidth;
		indexBufferPtr += ySkip;
		ySkip = iNumberOfScanlinesToSkip;
		iNumberOfScanlinesToSkip = 0; // Only call this conditional once.
		}
	
	TInt imageWidth = iImageRegion.iBr.iX;
	TInt imageHeight = iImageRegion.iBr.iY;
	TInt endOfImage = iDrawBottomUp ? -1 : imageHeight;
	
	// The minimum number of pixels to render horizontally
	TInt minWidth = Min(iBlockSize.iWidth, imageWidth - iPos.iX);
	
	// The next vertical position.  Note that this is usually the height of the block, but 
	// in the case of the first block when clipping is required, this will be reduced by ySkip.
	TInt nextYPos = iDrawBottomUp ?	(iPos.iY - iBlockSize.iHeight) + ySkip :
										(iPos.iY + iBlockSize.iHeight) - ySkip;
	
	TInt endPosition = iDrawBottomUp ? Max(nextYPos, endOfImage) : Min(nextYPos, endOfImage);
	
	// Once the first block has been processed, iYInc is set to block height
	iYInc = iDrawBottomUp ? -iBlockSize.iHeight + ySkip : iBlockSize.iHeight - ySkip;
				
	// Skip unnecessary pixels (for cropping, or padding when rotated)
	indexBufferPtr += iPixelPadding;
	
	TPoint pos(iPos);
	iUtil.Begin();
	for(;iDrawBottomUp ? pos.iY > endPosition : pos.iY < endPosition; iDrawBottomUp ? pos.iY-- : pos.iY++)
		{
		iUtil.SetPos(pos);
		iUtil.SetPixels(indexBufferPtr, minWidth);
		indexBufferPtr += iBlockSize.iWidth; // next line in block
		}
	iUtil.End();

	iPos.iX += iBlockSize.iWidth;
	
	if (iPos.iX >= imageWidth)
		{
		return NewLine();
		}

	return EFalse;
	}

/**
 *
 * @see CImageProcessor.
 */
TBool CMonochromePixelWriter::FlushPixels()
	{
	TUint32* gray256BufferPtrLimit = iGray256BufferPtr;
	iGray256BufferPtr = iGray256Buffer;

	if(iPos.iY < iImageRegion.iTl.iY || iPos.iY >= iImageRegion.iBr.iY)
		return ETrue;

	iUtil.Begin();

	TBool finished = EFalse;
	for (TUint32* gray256BufferPtr = iGray256Buffer; gray256BufferPtr < gray256BufferPtrLimit; )
		{
		TInt pixelsToSkip = Min(gray256BufferPtrLimit - gray256BufferPtr,iPixelsToSkip);
		gray256BufferPtr += pixelsToSkip;
		iPixelsToSkip -= pixelsToSkip;

		if(iPixelsToSkip)
			break;

		TInt pixelsToFlush = Min(gray256BufferPtrLimit - gray256BufferPtr,iImageRegion.iBr.iX - iPos.iX);

		if(!pixelsToFlush)
			break;

		SetPixelBufferIndex(gray256BufferPtr,pixelsToFlush);
		gray256BufferPtr += pixelsToFlush;

		TBool fillDown = iYInc > 0;
		TPoint pos(iPos);
		TInt posYLimit;
		if(fillDown)
			posYLimit = Min(pos.iY + iLineRepeat + 1 ,iImageRegion.iBr.iY);
		else
			posYLimit = Max(pos.iY - iLineRepeat - 1 ,iImageRegion.iTl.iY-1);

		for(;fillDown ? pos.iY < posYLimit : pos.iY > posYLimit; fillDown ? pos.iY++ : pos.iY--)
			{
			if(!iUtil.SetPos(pos-iImageRegion.iTl))
				{
				iUtil.End();
				return ETrue;
				}
			iUtil.SetPixels(iIndexBuffer,pixelsToFlush);
			}

		iPos.iX += pixelsToFlush;
		if (iPos.iX >= iImageRegion.iBr.iX)
			{
			finished = NewLine();
			if(finished)
				break;
			}
		}

	iUtil.End();

	return finished;
	}

/**
 *
 * @see CImageProcessor.
 */
TBool CMonochromePixelWriter::SetPos(const TPoint& aPosition)
	{
	if(iImageRegion.Contains(aPosition))
		{
		FlushPixels();
		iPixelsToSkip = 0;
		iPos = aPosition;
		return ETrue;
		}

	return EFalse;
	}

void CMonochromePixelWriter::Reset()
	{
	delete iColorConv;
	iColorConv = NULL;

	delete[] iGray256Buffer;
	iGray256Buffer = NULL;

	delete[] iIndexBuffer;
	iIndexBuffer = NULL;

	iPos.SetXY(0,0);
	iPixelsToSkip = 0;
	iImageRegion.SetRect(0,0,0,0);
	iBlockSize.SetSize(0,0);
	
	iDrawBottomUp = EFalse;
	}

void CMonochromePixelWriter::DoPrepareL(CFbsBitmap& aBitmap,const TRect& aImageRect,const TSize* aBlockSize)
	{
	if( (aImageRect.iTl.iX<0) || (aImageRect.iTl.iY<0) || (aImageRect.Size().iWidth>aBitmap.SizeInPixels().iWidth) || (aImageRect.Size().iHeight>aBitmap.SizeInPixels().iHeight) )
		{
		User::Leave(KErrArgument);
		}

	Reset();

	iImageRegion = aImageRect;

	iColorConv = TColorConvertor::NewL(aBitmap.DisplayMode());
	iUtil.SetBitmapL(&aBitmap);

	if (aBlockSize)
		{
		if (aBlockSize->iWidth <= 0 || aBlockSize->iHeight <= 0)
			{
			User::Leave(KErrArgument);
			}

		iBlockSize = *aBlockSize;
		iBlockArea = iBlockSize.iWidth * iBlockSize.iHeight;
		
		ASSERT(iIndexBuffer == NULL);
		iIndexBuffer = new(ELeave) TUint32[iBlockArea];

		iIndexBufferPtrLimit = iIndexBuffer + iBlockArea;
		CreateBlockBufferL(iBlockArea);
		
		switch(iOperation)
			{
			case EDecodeRotate180:
			case EDecodeRotate270:
			case EDecodeHorizontalFlip:
			case EDecodeVerticalFlipRotate90:
				iDrawBottomUp = ETrue;
				break;
			default:
				iDrawBottomUp = EFalse;		
			}
		
		iYInc = iDrawBottomUp ? -iBlockSize.iHeight : iBlockSize.iHeight;
		iStartPosition.SetXY(iImageRegion.iTl.iX, iDrawBottomUp ? iImageRegion.iBr.iY - 1 : 0);
		iEndPosition.SetXY(iImageRegion.iBr.iX, iDrawBottomUp ?
							iImageRegion.iTl.iY - 1 : iImageRegion.iBr.iY);
		iPos = iStartPosition;
		}
	else
		{
		iPos = iImageRegion.iTl;
		iStartPosition = iPos;
		iEndPosition = aImageRect.iBr;

		ASSERT(iGray256Buffer == NULL);
		iGray256Buffer = new(ELeave) TUint32[KPixelWriterBufferSize];

		iGray256BufferPtr = iGray256Buffer;
		iGray256BufferPtrLimit = iGray256Buffer + KPixelWriterBufferSize;

		ASSERT(iIndexBuffer == NULL);
		iIndexBuffer = new(ELeave) TUint32[KPixelWriterBufferSize];

		iIndexBufferPtrLimit = iIndexBuffer + KPixelWriterBufferSize;
		}
	
	for(TInt i=0; i<256; i++)
		{
		iIndexLookup[i] = iColorConv->ColorIndex(TRgb(i,i,i));	
		}
	}

TBool CMonochromePixelWriter::NewLine()
	{
	iPos.iX = iStartPosition.iX;
	iPos.iY += iYInc;

	if(iPos.iY < iStartPosition.iY || iPos.iY >= iEndPosition.iY)
		{
		return ETrue;
		}
	
	iPixelsToSkip = iPixelPadding;
	return EFalse;
	}

void CMonochromePixelWriter::SetPixelBlockIndex(TUint32* aGray256Buffer)
	{
	CMonochromePixelWriter::SetPixelBufferIndex(aGray256Buffer,iBlockArea);
	}

void CMonochromePixelWriter::SetPixelBufferIndex(TUint32* aGray256Buffer,TInt aCount)
	{
	TUint32* indexBufferPtr = iIndexBuffer;
	TUint32* indexBufferPtrLimit = indexBufferPtr+aCount;
	TUint32* indexLookup = iIndexLookup;

	if(aCount&1)
		*indexBufferPtr++ = indexLookup[*aGray256Buffer++];
	if(aCount&2)
		{
		*indexBufferPtr++ = indexLookup[*aGray256Buffer++];
		*indexBufferPtr++ = indexLookup[*aGray256Buffer++];
		}
	while (indexBufferPtr < indexBufferPtrLimit)
		{
		*indexBufferPtr++ = indexLookup[*aGray256Buffer++];
		*indexBufferPtr++ = indexLookup[*aGray256Buffer++];
		*indexBufferPtr++ = indexLookup[*aGray256Buffer++];
		*indexBufferPtr++ = indexLookup[*aGray256Buffer++];
		}
	}

//
// CErrorDiffuser::TColorError
//

inline CErrorDiffuser::TColorError::TColorError():
	iRedError(0),
	iGreenError(0),
	iBlueError(0)
	{}

inline CErrorDiffuser::TColorError::TColorError(TInt aRedError,TInt aGreenError,TInt aBlueError):
	iRedError(aRedError),
	iGreenError(aGreenError),
	iBlueError(aBlueError)
	{}

inline void CErrorDiffuser::TColorError::AdjustColor(TRgb& aColor) const
	{
	TInt red = ColorCcomponent::ClampColorComponent((iRedError >> 4) + aColor.Red());
	TInt green = ColorCcomponent::ClampColorComponent((iGreenError >> 4) + aColor.Green());
	TInt blue = ColorCcomponent::ClampColorComponent((iBlueError >> 4) + aColor.Blue());
	aColor = TRgb(red,green,blue);
	}

inline void CErrorDiffuser::TColorError::SetError(TRgb aIdealColor,TRgb aActualColor)
	{
	iRedError = aIdealColor.Red() - aActualColor.Red();
	iGreenError = aIdealColor.Green() - aActualColor.Green();
	iBlueError = aIdealColor.Blue() - aActualColor.Blue();
	}

inline CErrorDiffuser::TColorError CErrorDiffuser::TColorError::operator+(const TColorError& aColorError) const
	{
	TInt redError = iRedError + aColorError.iRedError;
	TInt greenError = iGreenError + aColorError.iGreenError;
	TInt blueError = iBlueError + aColorError.iBlueError;
	return TColorError(redError,greenError,blueError);
	}

inline CErrorDiffuser::TColorError CErrorDiffuser::TColorError::operator-(const TColorError& aColorError) const
	{
	TInt redError = iRedError - aColorError.iRedError;
	TInt greenError = iGreenError - aColorError.iGreenError;
	TInt blueError = iBlueError - aColorError.iBlueError;
	return TColorError(redError,greenError,blueError);
	}

inline CErrorDiffuser::TColorError CErrorDiffuser::TColorError::operator<<(TInt aShift) const
	{
	TInt redError = iRedError << aShift;
	TInt greenError = iGreenError << aShift;
	TInt blueError = iBlueError << aShift;
	return TColorError(redError,greenError,blueError);
	}

inline CErrorDiffuser::TColorError& CErrorDiffuser::TColorError::operator+=(const TColorError& aColorError)
	{
	iRedError += aColorError.iRedError;
	iGreenError += aColorError.iGreenError;
	iBlueError += aColorError.iBlueError;
	return *this;
	}

CErrorDiffuser::CErrorDiffuser()
	{
	}

CErrorDiffuser::~CErrorDiffuser()
	{
	Reset();
	}

void CErrorDiffuser::DoPrepareL(CFbsBitmap& aBitmap,const TRect& aImageRect,const TSize* aBlockSize)
	{
	CPixelWriter::DoPrepareL(aBitmap,aImageRect,aBlockSize);

	TInt scanlineErrorBufferLength = iImageRegion.iBr.iX+2;

	if (iBlockArea > 0)
		{
		ASSERT(iEdgeErrorBuffer == NULL);
		iEdgeErrorBuffer = new(ELeave) TColorError[iBlockSize.iHeight];

		scanlineErrorBufferLength += iBlockSize.iWidth;
		}

	ASSERT(iScanlineErrorBuffer == NULL);
	iScanlineErrorBuffer = new(ELeave) TColorError[scanlineErrorBufferLength];

	if (iDisplayMode == EColor64K) 
		{
			ASSERT(iRedErrorLookupTable == NULL);
			iRedErrorLookupTable = new(ELeave) TInt8[256];
			ASSERT(iGreenErrorLookupTable == NULL);
			iGreenErrorLookupTable = new(ELeave) TInt8[256];
			
			for (TInt i=0;i<256;i++) 
				{
					TInt tmp = i & 0xf8;
					iRedErrorLookupTable[i] = i - (tmp | (tmp >> 5));
					tmp = i & 0xfc;
					iGreenErrorLookupTable[i] = i - (tmp | (tmp >> 6));
				}
		}
	}

void CErrorDiffuser::SetPixelBufferIndex(TRgb* aColorBuffer,TInt aCount)
	{
	// use optimized function for EColor64K mode
	if (iDisplayMode == EColor64K) 
		{
		SetPixelBufferColor64KIndex(aColorBuffer, aCount);
		return;
		}

	TInt clearX = iPos.iX;

	TInt yDiff = iPos.iY - iLastPos.iY;
	if(yDiff != 0)									// On a new line?
		{
		new(&iNextError) TColorError;
		clearX = iImageRegion.iBr.iX;				// To clear to end of line

		if(yDiff == -1 || yDiff == 1)				// Now on ajacent line?
			{
			clearX -= iLastPos.iX;					// Clear end of previous line
			if(clearX)
				{
				Mem::FillZ(iScanlineErrorBuffer + iLastPos.iX + 2, clearX * sizeof(TColorError));
				}
			clearX = iPos.iX;						// To clear up to current position
			}
		iLastPos.iX = iImageRegion.iTl.iX;			// Start of this line
		}

	clearX -= iLastPos.iX;
	if(clearX > 0)									// Treat any skipped pixels as if they produced no error
		{
		new(&iNextError) TColorError;
		Mem::FillZ(iScanlineErrorBuffer + iLastPos.iX + 2, clearX * sizeof(TColorError));
		}

	iLastPos.iY = iPos.iY;
	iLastPos.iX = iPos.iX+aCount;

	TUint32* indexBufferPtr = iIndexBuffer;
	TUint32* indexBufferPtrLimit = indexBufferPtr+aCount;
	TColorError* scanlineErrorBufferPtr = iScanlineErrorBuffer + iPos.iX;
	TColorError error;
	TRgb color;

	while(indexBufferPtr<indexBufferPtrLimit)
		{
		color = *aColorBuffer++;

		iNextError.AdjustColor(color);
		TUint32 index = iColorConv->ColorIndex(color);
		*indexBufferPtr++ = index;

		error.SetError(color, iColorConv->Color(index));

		iNextError = (error << 3) - error; // Set right error for this pixel

		*scanlineErrorBufferPtr++ += error + (error << 1); // Set left-down error for this pixel

		*scanlineErrorBufferPtr += error + (error << 2); // Set down error for this pixel

		iNextError += *(scanlineErrorBufferPtr+1);

		*(scanlineErrorBufferPtr+1) = error; // Set right-down error for this pixel
		}
	}

// faster function (see listing) then Bitmap Util ClampColorComponent
inline TInt CErrorDiffuser::ClipColorComponent(TInt value)
    {
    if (TUint(value) > 0xFF)
        {
        value = value < 0 ? 0 : 0xFF;
        }
    return value;
    }

void CErrorDiffuser::SetPixelBufferColor64KIndex(TRgb* aColorBuffer,TInt aCount)
	{
	TInt clearX = iPos.iX;

	TInt yDiff = iPos.iY - iLastPos.iY;
	if(yDiff != 0)									// On a new line?
		{
		iNextRedError = 0;
		iNextGreenError = 0;
		iNextBlueError = 0;
		clearX = iImageRegion.iBr.iX;				// To clear to end of line

		if(yDiff == -1 || yDiff == 1)				// Now on ajacent line?
			{
			clearX -= iLastPos.iX;					// Clear end of previous line
			if(clearX > 0) 
				{
				Mem::FillZ(iScanlineErrorBuffer + iLastPos.iX + 2, clearX * sizeof(TColorError));
				}
			clearX = iPos.iX;						// To clear up to current position
			}
		iLastPos.iX = iImageRegion.iTl.iX;			// Start of this line
		}

	clearX -= iLastPos.iX;
	if(clearX > 0)									// Treat any skipped pixels as if they produced no error
		{
		iNextRedError = 0;
		iNextGreenError = 0;
		iNextBlueError = 0;
		Mem::FillZ(iScanlineErrorBuffer + iLastPos.iX + 2, clearX * sizeof(TColorError));
		}

	iLastPos.iY = iPos.iY;
	iLastPos.iX = iPos.iX+aCount;

	TUint32* indexBufferPtr = iIndexBuffer;
	TUint32* indexBufferPtrLimit = indexBufferPtr+aCount;

	TColorError* scanlineErrorBufferPtr = iScanlineErrorBuffer + iPos.iX;

	TInt redError = iNextRedError;
	TInt greenError = iNextGreenError;
	TInt blueError = iNextBlueError;
	
	while(indexBufferPtr<indexBufferPtrLimit)
		{
		// red			
		register TInt red = aColorBuffer->Red();
		red = ClipColorComponent(red + (redError >> 4));

		register TInt error = iRedErrorLookupTable[red];
		
		// Set right error for red component
		scanlineErrorBufferPtr->iRedError += error + (error << 1); // Set left-down error for this pixel
		(scanlineErrorBufferPtr+1)->iRedError += error + (error << 2); // Set down error for this pixel
		redError = (scanlineErrorBufferPtr+2)->iRedError + (error << 3) - error; // Set right error for this pixel
		(scanlineErrorBufferPtr+2)->iRedError = error; // Set right-down error for this pixel

		// green			
		register TInt green = aColorBuffer->Green();
			
		green = ClipColorComponent(green + (greenError >> 4));

		error = iGreenErrorLookupTable[green];
		
		// Set right error for green component
		scanlineErrorBufferPtr->iGreenError += error + (error << 1); // Set left-down error for this pixel
		(scanlineErrorBufferPtr+1)->iGreenError += error + (error << 2); // Set down error for this pixel
		greenError = (scanlineErrorBufferPtr+2)->iGreenError + (error << 3) - error; // Set right error for this pixel
		(scanlineErrorBufferPtr+2)->iGreenError = error; // Set right-down error for this pixel

		// blue			
		register TInt blue = aColorBuffer->Blue();
			
		blue = ClipColorComponent(blue + (blueError >> 4));
		
		*indexBufferPtr++ = ((red & 0xf8) << 8) | ((green & 0xfc) << 3) | ((blue & 0xf8) >> 3);
		
		error = iRedErrorLookupTable[blue];// use the same lookup table for blue color

		// Set right error for blue component
		scanlineErrorBufferPtr->iBlueError += error + (error << 1); // Set left-down error for this pixel
		(scanlineErrorBufferPtr+1)->iBlueError += error + (error << 2); // Set down error for this pixel
		blueError = (scanlineErrorBufferPtr+2)->iBlueError + (error << 3) - error; // Set right error for this pixel
		(scanlineErrorBufferPtr+2)->iBlueError = error; // Set right-down error for this pixel

		scanlineErrorBufferPtr++;
		aColorBuffer++;
		}

		iNextRedError = redError;
		iNextGreenError = greenError;
		iNextBlueError = blueError;


	}

void CErrorDiffuser::SetPixelBlockIndex(TRgb* aColorBuffer)
	{
	if(iPos.iY!=iLastPos.iY)
		{
		Mem::FillZ(iEdgeErrorBuffer,sizeof(TColorError) * iBlockSize.iHeight);
		}

	TUint32* indexBufferPtr = iIndexBuffer;

	TColorError error;
	TColorError* edgeErrorBuffer = iEdgeErrorBuffer;

	for (TInt row = 0; row < iBlockSize.iHeight; row++)
		{
		TColorError* errorValue = iScanlineErrorBuffer + iPos.iX;
		TColorError nextError = *edgeErrorBuffer + *errorValue;
		*edgeErrorBuffer = error;

		for (TInt col = 0; col < iBlockSize.iWidth; col++)
			{
			TRgb bufferColor = *aColorBuffer++;
			nextError.AdjustColor(bufferColor);

			TUint32 index = iColorConv->ColorIndex(bufferColor);
			*indexBufferPtr++ = index;

			error.SetError(bufferColor,iColorConv->Color(index));

			if (col > 0)
				*(errorValue - 1) += error + (error << 1); // Set left-down error for this pixel
			else
				*errorValue = error + (error << 1);

			*errorValue += error + (error << 2); // Set down error for this pixel
			errorValue++;

			nextError = (error << 3) - error; // Set right error for this pixel

			if (col < iBlockSize.iWidth)
				{
				nextError += *errorValue;
				*errorValue = error; // Set right-down error for this pixel
				}

			}

		*edgeErrorBuffer++ += nextError;
		}

	iLastPos.iY = iPos.iY;
	iLastPos.iX = iPos.iX+iBlockSize.iWidth;
	}

void CErrorDiffuser::Reset()
	{
	CPixelWriter::Reset();

	delete[] iScanlineErrorBuffer;
	iScanlineErrorBuffer = NULL;

	delete[] iEdgeErrorBuffer;
	iEdgeErrorBuffer = NULL;
	
	delete iRedErrorLookupTable;
	iRedErrorLookupTable = NULL;
	
	delete iGreenErrorLookupTable;
	iGreenErrorLookupTable = NULL;

	}

//
// CMonochromeErrorDiffuser
//


/**
 *
 * Static factory function to create CMonochromeErrorDiffuser objects.
 *
 * @return  Pointer to a fully constructed CMonochromeErrorDiffuser object. 
 */
CMonochromeErrorDiffuser* CMonochromeErrorDiffuser::NewL()
	{
	return new(ELeave) CMonochromeErrorDiffuser;
	}

CMonochromeErrorDiffuser::CMonochromeErrorDiffuser()
	{}

CMonochromeErrorDiffuser::~CMonochromeErrorDiffuser()
	{
	Reset();
	}

void CMonochromeErrorDiffuser::DoPrepareL(CFbsBitmap& aBitmap,const TRect& aImageRect,const TSize* aBlockSize)
	{
	CMonochromePixelWriter::DoPrepareL(aBitmap,aImageRect,aBlockSize);

	TInt scanlineErrorBufferLength = iImageRegion.iBr.iX+2;

	if (iBlockArea > 0)
		{
		ASSERT(iEdgeErrorBuffer == NULL);
		iEdgeErrorBuffer = new(ELeave) TInt[iBlockSize.iHeight];

		Mem::FillZ(iEdgeErrorBuffer,sizeof(TInt) * iBlockSize.iHeight);
		scanlineErrorBufferLength += iBlockSize.iWidth;
		}

	ASSERT(iScanlineErrorBuffer == NULL);
	iScanlineErrorBuffer = new(ELeave) TInt[scanlineErrorBufferLength];

	Mem::FillZ(iScanlineErrorBuffer,sizeof(TInt) * scanlineErrorBufferLength);
	}

void CMonochromeErrorDiffuser::SetPixelBufferIndex(TUint32* aGray256Buffer,TInt aCount)
	{
	TInt clearX = iPos.iX;

	TInt yDiff = iPos.iY - iLastPos.iY;
	if(yDiff != 0)									// On a new line?
		{
		iNextError = 0;
		clearX = iImageRegion.iBr.iX;				// To clear to end of line

		if(yDiff == -1 || yDiff == 1)				// Now on ajacent line?
			{
			clearX -= iLastPos.iX;					// Clear end of previous line
			if(clearX)
				Mem::FillZ(iScanlineErrorBuffer + iLastPos.iX + 2, clearX * sizeof(TInt));
			clearX = iPos.iX;						// To clear up to current position
			}
		iLastPos.iX = iImageRegion.iTl.iX;			// Start of this line
		}

	clearX -= iLastPos.iX;
	if(clearX > 0)									// Treat any skipped pixels as if they produced no error
		{
		iNextError = 0;
		Mem::FillZ(iScanlineErrorBuffer + iLastPos.iX + 2, clearX * sizeof(TInt));
		}

	iLastPos.iY = iPos.iY;
	iLastPos.iX = iPos.iX+aCount;

	TUint32* indexBufferPtr = iIndexBuffer;
	TUint32* indexBufferPtrLimit = indexBufferPtr+aCount;

	TInt* scanlineErrorBufferPtr = iScanlineErrorBuffer + iPos.iX;
	TInt nextError = iNextError;

	while(indexBufferPtr<indexBufferPtrLimit)
		{
		TInt gray256 = *aGray256Buffer++;

		TInt error = gray256 + (nextError >> 4);
		TUint32 index = iIndexLookup[ColorCcomponent::ClampColorComponent(error)];
		*indexBufferPtr++ = index;

		error -= TColorConvertor::RgbToMonochrome(iColorConv->Color(index));

		nextError = (error << 3) - error; // Set right error for this pixel

		*scanlineErrorBufferPtr++ += error + (error << 1); // Set left-down error for this pixel

		*scanlineErrorBufferPtr += error + (error << 2); // Set down error for this pixel

		nextError += *(scanlineErrorBufferPtr+1);

		*(scanlineErrorBufferPtr+1) = error; // Set right-down error for this pixel
		}

	iNextError = nextError;
	}

void CMonochromeErrorDiffuser::SetPixelBlockIndex(TUint32* aGray256Buffer)
	{
	if(iPos.iY!=iLastPos.iY)
		{
		Mem::FillZ(iEdgeErrorBuffer,sizeof(TInt) * iBlockSize.iHeight);
		}

	TUint32* indexBufferPtr = iIndexBuffer;

	TInt error = 0;
	TInt* edgeErrorBuffer = iEdgeErrorBuffer;

	for (TInt row = 0; row < iBlockSize.iHeight; row++)
		{
		TInt* errorValue = iScanlineErrorBuffer + iPos.iX;
		TInt nextError = *edgeErrorBuffer + *errorValue;
		*edgeErrorBuffer = error;

		for (TInt col = 0; col < iBlockSize.iWidth; col++)
			{
			TInt gray256 = *aGray256Buffer++;

			error = gray256 + (nextError >> 4); // Same as /16
			
			TUint32 index = iIndexLookup[ColorCcomponent::ClampColorComponent(error)];
			*indexBufferPtr++ = index;

			error -= TColorConvertor::RgbToMonochrome(iColorConv->Color(index));

			if (col > 0)
				*(errorValue - 1) += error + (error << 1); // Set left-down error for this pixel
			else
				*errorValue = error + (error << 1);

			*errorValue += error + (error << 2); // Set down error for this pixel
			errorValue++;

			nextError = (error << 3) - error; // Set right error for this pixel

			if (col < iBlockSize.iWidth)
				{
				nextError += *errorValue;
				*errorValue = error; // Set right-down error for this pixel
				}
			}

		*edgeErrorBuffer++ += nextError;
		}

	iLastPos.iY = iPos.iY;
	iLastPos.iX = iPos.iX+iBlockSize.iWidth;
	}

void CMonochromeErrorDiffuser::Reset()
	{
	CMonochromePixelWriter::Reset();

	delete[] iScanlineErrorBuffer;
	iScanlineErrorBuffer = NULL;

	delete[] iEdgeErrorBuffer;
	iEdgeErrorBuffer = NULL;
	}

//
// CThumbnailProcessor
//

/**
 *
 * Static factory function to create CThumbnailProcessor objects.
 *
 * @param	"aImageProc"
 *          A pointer to an externally constructed CImageProcessorExtension object.
 *          This will be deleted when the CThumbnailProcessor object is deleted.
 * @param	"aReductionFactor"
 *          The reduction factor to use.
 * @return  Pointer to a fully constructed CThumbnailProcessor object. 
 */
CThumbnailProcessor* CThumbnailProcessor::NewL(CImageProcessorExtension* aImageProc,TInt aReductionFactor)
	{
	return new(ELeave) CThumbnailProcessor(aImageProc,aReductionFactor);
	}

CThumbnailProcessor::CThumbnailProcessor(CImageProcessorExtension* aImageProc,TInt aReductionFactor):
	iImageProc(aImageProc),
	iYInc(1),
	iReductionFactor(aReductionFactor)
		{}

CThumbnailProcessor::~CThumbnailProcessor()
	{
	delete iImageProc;
	delete[] iReducedPixelBuffer;
	delete[] iReducedSumBuffer;
	}

void CThumbnailProcessor::PrepareL(CFbsBitmap& aBitmap,const TRect& aImageRect)
	{
	PrepareCommonL(aImageRect);
	iYInc = 1;

	TInt bufferSize = (iImageRegion.iBr.iX + (1<<iReductionFactor) -1 ) >> iReductionFactor;

	ASSERT(iReducedSumBuffer == NULL);
	iReducedSumBuffer = new(ELeave) TColorSum[bufferSize];
	Mem::FillZ(iReducedSumBuffer,bufferSize * sizeof(TColorSum));

	iImageProc->PrepareL(aBitmap,iReducedImageRegion);

	ASSERT(iReducedPixelBuffer == NULL);
	iReducedPixelBuffer = new(ELeave) TRgb[iReducedImageRegion.iBr.iX];
	}

void CThumbnailProcessor::PrepareL(CFbsBitmap& aBitmap,const TRect& aImageRect,const TSize& aRgbBlockSize)
	{
	PrepareCommonL(aImageRect);

	CreateBlockBufferL(aRgbBlockSize.iWidth*aRgbBlockSize.iHeight);

	iOriginalBlockSize = aRgbBlockSize;
	iYInc = iDrawBottomUp ? -iOriginalBlockSize.iHeight : iOriginalBlockSize.iHeight;

	iReducedBlockSize = aRgbBlockSize;
	iReducedBlockSize.iWidth >>= iReductionFactor;
	iReducedBlockSize.iHeight >>= iReductionFactor;

	iImageProc->SetInitialScanlineSkipPadding(iNumberOfScanlinesToSkip >> iReductionFactor);
	iImageProc->SetPixelPadding(iPixelPadding >> iReductionFactor);
	iImageProc->PrepareL(aBitmap,iReducedImageRegion,iReducedBlockSize);

	ASSERT(iReducedPixelBuffer == NULL);
	iReducedPixelBuffer = new(ELeave) TRgb[iReducedBlockSize.iWidth * iReducedBlockSize.iHeight];
	}

void CThumbnailProcessor::PrepareCommonL(const TRect& aImageRect)
	{
	ASSERT(iReductionFactor > 0);
	iImageRegion = aImageRect;

	TInt roundUp = (1<<iReductionFactor)-1;
	iReducedImageRegion.iTl.iX = aImageRect.iTl.iX >> iReductionFactor;
	iReducedImageRegion.iTl.iY = aImageRect.iTl.iY >> iReductionFactor;
	
	TSize size = aImageRect.Size();
	size.iWidth = (size.iWidth + roundUp) >> iReductionFactor;
	size.iHeight = (size.iHeight + roundUp) >> iReductionFactor;
	iReducedImageRegion.iBr = iReducedImageRegion.iTl + size;

	switch(iOperation)
		{
		case EDecodeRotate180:
		case EDecodeRotate270:
		case EDecodeHorizontalFlip:
		case EDecodeVerticalFlipRotate90:
			iDrawBottomUp = ETrue;
			iImageProc->SetOperation(iOperation);
			break;
		default:
			iDrawBottomUp = EFalse;
		}
	iStartPosition.SetXY(iImageRegion.iTl.iX, iDrawBottomUp ? aImageRect.iBr.iY - 1 : 0);
	iEndPosition.SetXY(aImageRect.iBr.iX, iDrawBottomUp ? aImageRect.iTl.iY - 1 : aImageRect.iBr.iY);
	iPos = iStartPosition;

	iPositionChanged = ETrue;

	iEndOfLineX = iEndPosition.iX + iPixelPadding;

	delete[] iReducedPixelBuffer;
	iReducedPixelBuffer = NULL;

	delete[] iReducedSumBuffer;
	iReducedSumBuffer = NULL;
	}

TBool CThumbnailProcessor::SetPixel(TRgb aColor)
	{
	TInt x = iPos.iX;

	if (x < iImageRegion.iBr.iX)
		{
		TColorSum* sumPtr = iReducedSumBuffer + (x >> iReductionFactor);
		sumPtr->iRed += aColor.Red();
		sumPtr->iGreen += aColor.Green();
		sumPtr->iBlue += aColor.Blue();
		sumPtr->iCount++;
		}

	x++;
	iPos.iX = x;

	if (x == iEndOfLineX)
		return NewLine();

	return EFalse;
	}

TBool CThumbnailProcessor::NewLine()
	{
	TInt newY = iPos.iY + iYInc;

	TBool finished = (newY < iStartPosition.iY || newY >= iEndPosition.iY);
	TBool outsideOfBuffer = ((newY ^ iPos.iY) >> iReductionFactor) != 0;

	if(finished || outsideOfBuffer)
		{
		DoFlushPixels();
		}

	iPos.iX = iStartPosition.iX;
	iPos.iY = newY;

	if(iPositionChanged && outsideOfBuffer)
		{
		iImageProc->SetPos(TPoint(iPos.iX >> iReductionFactor,iPos.iY >> iReductionFactor));
		iPositionChanged = EFalse;
		}

	return finished;
	}

TBool CThumbnailProcessor::SetPixelBlock(TRgb* aColorBuffer)
	{
	if ((iPos.iX >> iReductionFactor) < iReducedImageRegion.iBr.iX)
		{
		ASSERT(aColorBuffer);

		if(iPositionChanged)
			{
			iImageProc->SetPos(TPoint(iPos.iX >> iReductionFactor,iPos.iY >> iReductionFactor));
			iPositionChanged = EFalse;
			}

		TInt xOuterStop = iReducedBlockSize.iWidth<<iReductionFactor;
		TInt yOuterStop = iReducedBlockSize.iHeight<<iReductionFactor;

		TInt outerStep = 1<<iReductionFactor;
		TInt divisionFactor = 2*iReductionFactor;

		TRgb* reducedPixelBuffer = iReducedPixelBuffer;

		for (TInt yOuter = 0; yOuter < yOuterStop; yOuter += outerStep)
			{
			for (TInt xOuter = 0; xOuter < xOuterStop; xOuter += outerStep)
				{
				TRgb* colorBuffer = &aColorBuffer[yOuter * iOriginalBlockSize.iWidth + xOuter];
				TInt red = 0;
				TInt green = 0;
				TInt blue = 0;

				for (TInt yInner = 0; yInner < outerStep; yInner++)
					{
					for (TInt xInner = 0; xInner < outerStep; xInner++)
						{
						red += colorBuffer[xInner].Red();
						green += colorBuffer[xInner].Green();
						blue += colorBuffer[xInner].Blue();
						}
					colorBuffer += iOriginalBlockSize.iWidth;
					}

				red >>= divisionFactor;
				green >>= divisionFactor;
				blue >>= divisionFactor;

				*reducedPixelBuffer++ = TRgb(red,green,blue);
				}
			}

		iImageProc->SetPixelBlock(iReducedPixelBuffer);
		}

	iPos.iX += iOriginalBlockSize.iWidth;
	if (iPos.iX >= iEndOfLineX)
		{
		iPos.iX = iStartPosition.iX;
		iPos.iY += iYInc;
		if(iPos.iY < iStartPosition.iY || iPos.iY >= iEndPosition.iY)
			{
			return ETrue;
			}
		}

	return EFalse;

	}

TBool CThumbnailProcessor::FlushPixels()
	{
	DoFlushPixels();
	iImageProc->FlushPixels();

	iPositionChanged = ETrue;

	if(iPos.iY < iStartPosition.iY || iPos.iY >= iEndPosition.iY)
		{
		return ETrue;
		}

	return EFalse;
	}

void CThumbnailProcessor::DoFlushPixels()
	{
	if(!iReducedSumBuffer)
		return;

	TColorSum* reducedSumPtr = iReducedSumBuffer + iReducedImageRegion.iTl.iX;
	TColorSum* reducedSumPtrLimit = iReducedSumBuffer + iReducedImageRegion.iBr.iX;

	while(reducedSumPtr < reducedSumPtrLimit)
		{

		while(reducedSumPtr->iCount==0)
			{
			reducedSumPtr++;
			if(reducedSumPtr==reducedSumPtrLimit)
				return;
			}

		if(iPositionChanged)
			iImageProc->SetPos(TPoint(reducedSumPtr - iReducedSumBuffer,iPos.iY >> iReductionFactor));

		TRgb* reducedPixelBufferPtr = iReducedPixelBuffer;
		TInt fullCountFactor = 2*iReductionFactor;

		while(reducedSumPtr < reducedSumPtrLimit)
			{
			TInt count = reducedSumPtr->iCount;
			TUint32 red;
			TUint32 green;
			TUint32 blue;

			if(count == (1<<fullCountFactor))
				{
				red = reducedSumPtr->iRed >> fullCountFactor;
				green = reducedSumPtr->iGreen >> fullCountFactor;
				blue = reducedSumPtr->iBlue >> fullCountFactor;
				}
			else if(count!=0)
				{
				red = reducedSumPtr->iRed / count;
				green = reducedSumPtr->iGreen / count;
				blue = reducedSumPtr->iBlue / count;
				}
			else
				break;

			*reducedPixelBufferPtr++ = TRgb(red,green,blue);

			reducedSumPtr++;
			}

		TInt numPixels = reducedPixelBufferPtr-iReducedPixelBuffer;
		iImageProc->SetPixels(iReducedPixelBuffer,numPixels);

		Mem::FillZ(reducedSumPtr-numPixels,numPixels * sizeof(TColorSum));
		}

	}

TBool CThumbnailProcessor::SetPos(const TPoint& aPosition)
	{
	if(iImageRegion.Contains(aPosition)==EFalse)
		return EFalse;

	if((aPosition.iY ^ iPos.iY) >> iReductionFactor)
		DoFlushPixels();

	iPositionChanged = ETrue;
	iPos = aPosition;

	return ETrue;
	}

void CThumbnailProcessor::SetYPosIncrement(TInt aYInc)
	{
	iYInc = aYInc;

	TInt reducedYInc = aYInc >> iReductionFactor;
	if(reducedYInc==0)
		reducedYInc = 1;

	iImageProc->SetYPosIncrement(reducedYInc);
	}

void CThumbnailProcessor::SetLineRepeat(TInt aLineRepeat)
	{
	TInt reducedLineRepeat = aLineRepeat >> iReductionFactor;
	iImageProc->SetLineRepeat(reducedLineRepeat);
	}

void CThumbnailProcessor::SetPixelPadding(TInt aNumberOfPixels)
	{
	iPixelPadding = aNumberOfPixels;
	iEndOfLineX = iEndPosition.iX + iPixelPadding;
	}

//
// CMonochromeThumbnailProcessor
//

/**
 *
 * Static factory function to create CMonochromeThumbnailProcessor objects.
 *
 * @param	"aImageProc"
 *          A pointer to an externally constructed CImageProcessorExtension object.
 *          This will be deleted when the CMonochromeThumbnailProcessor object is deleted.
 * @param	aReductionFactor"
 *          The reduction factor to use.
 * @return  Pointer to a fully constructed CMonochromeThumbnailProcessor object. 
 */
CMonochromeThumbnailProcessor* CMonochromeThumbnailProcessor::NewL(CImageProcessorExtension* aImageProc,TInt aReductionFactor)
	{
	return new(ELeave) CMonochromeThumbnailProcessor(aImageProc,aReductionFactor);
	}

CMonochromeThumbnailProcessor::CMonochromeThumbnailProcessor(CImageProcessorExtension* aImageProc,TInt aReductionFactor):
	iImageProc(aImageProc),
	iYInc(1),
	iReductionFactor(aReductionFactor)
		{}

CMonochromeThumbnailProcessor::~CMonochromeThumbnailProcessor()
	{
	delete iImageProc;
	delete[] iReducedPixelBuffer;
	delete[] iReducedSumBuffer;
	}

void CMonochromeThumbnailProcessor::PrepareL(CFbsBitmap& aBitmap,const TRect& aImageRect)
	{
	PrepareCommonL(aImageRect);
	iYInc = 1;
	
	TInt bufferSize = (iImageRegion.iBr.iX + (1<<iReductionFactor) -1 ) >> iReductionFactor;

	ASSERT(iReducedSumBuffer == NULL);
	iReducedSumBuffer = new(ELeave) TMonochromeSum[bufferSize];
	Mem::FillZ(iReducedSumBuffer,bufferSize * sizeof(TMonochromeSum));

	iImageProc->PrepareL(aBitmap,iReducedImageRegion);

	ASSERT(iReducedPixelBuffer == NULL);
	iReducedPixelBuffer = new(ELeave) TUint32[iReducedImageRegion.iBr.iX];
	}

void CMonochromeThumbnailProcessor::PrepareL(CFbsBitmap& aBitmap,const TRect& aImageRect,const TSize& aRgbBlockSize)
	{
	PrepareCommonL(aImageRect);

	CreateBlockBufferL(aRgbBlockSize.iWidth*aRgbBlockSize.iHeight);

	iOriginalBlockSize = aRgbBlockSize;
	iYInc = iDrawBottomUp ? -iOriginalBlockSize.iHeight : iOriginalBlockSize.iHeight;


	iReducedBlockSize = aRgbBlockSize;
	iReducedBlockSize.iWidth >>= iReductionFactor;
	iReducedBlockSize.iHeight >>= iReductionFactor;

	iImageProc->SetInitialScanlineSkipPadding(iNumberOfScanlinesToSkip >> iReductionFactor);
	iImageProc->SetPixelPadding(iPixelPadding >> iReductionFactor);
	iImageProc->PrepareL(aBitmap,iReducedImageRegion,iReducedBlockSize);

	ASSERT(iReducedPixelBuffer == NULL);
	iReducedPixelBuffer = new(ELeave) TUint32[iReducedBlockSize.iWidth * iReducedBlockSize.iHeight];
	}

void CMonochromeThumbnailProcessor::PrepareCommonL(const TRect& aImageRect)
	{
	ASSERT(iReductionFactor > 0);
	iImageRegion = aImageRect;

	TInt roundUp = (1<<iReductionFactor)-1;
	iReducedImageRegion.iTl.iX = aImageRect.iTl.iX >> iReductionFactor;
	iReducedImageRegion.iTl.iY = aImageRect.iTl.iY >> iReductionFactor;

	TSize size = aImageRect.Size();
	size.iWidth = (size.iWidth + roundUp) >> iReductionFactor;
	size.iHeight = (size.iHeight + roundUp) >> iReductionFactor;
	iReducedImageRegion.iBr = iReducedImageRegion.iTl + size;
	
	switch(iOperation)
		{
		case EDecodeRotate180:
		case EDecodeRotate270:
		case EDecodeHorizontalFlip:
		case EDecodeVerticalFlipRotate90:
			iDrawBottomUp = ETrue;
			iImageProc->SetOperation(iOperation);
			break;
		default:
			iDrawBottomUp = EFalse;
		}
	iStartPosition.SetXY(iImageRegion.iTl.iX, iDrawBottomUp ? aImageRect.iBr.iY - 1 : 0);
	iEndPosition.SetXY(aImageRect.iBr.iX, iDrawBottomUp ? aImageRect.iTl.iY - 1 : aImageRect.iBr.iY);
	iPos = iStartPosition;
	
	iPositionChanged = ETrue;

	iEndOfLineX = iEndPosition.iX + iPixelPadding;

	delete[] iReducedPixelBuffer;
	iReducedPixelBuffer = NULL;

	delete iReducedSumBuffer;
	iReducedSumBuffer = NULL;
	}

TBool CMonochromeThumbnailProcessor::SetMonoPixel(TInt aGray256)
	{
	TInt x = iPos.iX;

	if (x < iImageRegion.iBr.iX)
		{
		TMonochromeSum* sumPtr = iReducedSumBuffer + (x >> iReductionFactor);
		sumPtr->iLevel += aGray256;
		sumPtr->iCount++;
		}

	x++;
	iPos.iX = x;

	if (x == iEndOfLineX)
		return NewLine();

	return EFalse;
	}

TBool CMonochromeThumbnailProcessor::SetMonoPixelRun(TInt aGray256,TInt aCount)
	{
	while (aCount != 0)
		{
		TInt x		= iPos.iX;
		TInt xLimit = x+aCount;

		iPos.iX = xLimit;

		if (xLimit > iImageRegion.iBr.iX)
			xLimit = iImageRegion.iBr.iX;

		if (xLimit > x)
			{
			TInt numPixels = xLimit-x;

			TInt reductionFactor = iReductionFactor;
			TInt reductionCount = 1<<reductionFactor;	//number of horizontal pixel in a TMonochromeSum

			TMonochromeSum* sumPtr = iReducedSumBuffer + (x >> reductionFactor);

			TInt n = reductionCount-(x&(reductionCount-1));	//number of pixels to complete current TMonochromeSum

			if(numPixels > n)
				{
				sumPtr->iCount += n;					//Complete first TMonochromeSum in run
				sumPtr->iLevel += n * aGray256;
				sumPtr++;
				numPixels -= n;

				while(numPixels > reductionCount)			//Complete middle TMonochromeSum(s) in run
					{
					sumPtr->iCount += reductionCount;
					sumPtr->iLevel += aGray256 << reductionFactor;
					sumPtr++;
					numPixels -= reductionCount;
					}
				}

			sumPtr->iCount += numPixels;				//Update last/only TMonochromeSum in run
			sumPtr->iLevel += numPixels * aGray256;
			}

		if (iPos.iX < iEndOfLineX)
			break;

		aCount = iPos.iX - iEndOfLineX;

		if(NewLine())
			return ETrue;
		}

	return EFalse;
	}

TBool CMonochromeThumbnailProcessor::NewLine()
	{
	TInt newY = iPos.iY + iYInc;

	TBool finished = (newY < iStartPosition.iY || newY >= iEndPosition.iY);
	TBool outsideOfBuffer = ((newY ^ iPos.iY) >> iReductionFactor) != 0;

	if(finished || outsideOfBuffer)
		{
		DoFlushPixels();
		}

	iPos.iX = iStartPosition.iX;
	iPos.iY = newY;

	if(iPositionChanged && outsideOfBuffer)
		{
		iImageProc->SetPos(TPoint(iPos.iX >> iReductionFactor,iPos.iY >> iReductionFactor));
		iPositionChanged = EFalse;
		}

	return finished;
	}

TBool CMonochromeThumbnailProcessor::SetMonoPixelBlock(TUint32* aGray256Buffer)
	{
	if ((iPos.iX >> iReductionFactor) < iReducedImageRegion.iBr.iX)
		{
		ASSERT(aGray256Buffer);

		if(iPositionChanged)
			{
			iImageProc->SetPos(TPoint(iPos.iX >> iReductionFactor,iPos.iY >> iReductionFactor));
			iPositionChanged = EFalse;
			}

		TInt xOuterStop = iReducedBlockSize.iWidth<<iReductionFactor;
		TInt yOuterStop = iReducedBlockSize.iHeight<<iReductionFactor;

		TInt outerStep = 1<<iReductionFactor;
		TInt divisionFactor = 2*iReductionFactor;

		TUint32* reducedPixelBuffer = iReducedPixelBuffer;

		for (TInt yOuter = 0; yOuter < yOuterStop; yOuter += outerStep)
			{
			for (TInt xOuter = 0; xOuter < xOuterStop; xOuter += outerStep)
				{
				TUint32* gray256Buffer = &aGray256Buffer[yOuter * iOriginalBlockSize.iWidth + xOuter];
				TInt level = 0;

				for (TInt yInner = 0; yInner < outerStep; yInner++)
					{
					for (TInt xInner = 0; xInner < outerStep; xInner++)
						level += gray256Buffer[xInner];

					gray256Buffer += iOriginalBlockSize.iWidth;
					}

				level >>= divisionFactor;
				*reducedPixelBuffer++ = level;
				}
			}

		iImageProc->SetMonoPixelBlock(iReducedPixelBuffer);
		}

	iPos.iX += iOriginalBlockSize.iWidth;
	if (iPos.iX >= iEndOfLineX)
		{
		iPos.iX = iStartPosition.iX;
		iPos.iY += iYInc;
		if(iPos.iY < iStartPosition.iY || iPos.iY >= iEndPosition.iY)
			{
			return ETrue;
			}
		}

	return EFalse;

	}

TBool CMonochromeThumbnailProcessor::FlushPixels()
	{
	DoFlushPixels();
	iImageProc->FlushPixels();

	iPositionChanged = ETrue;

		if(iPos.iY < iStartPosition.iY || iPos.iY >= iEndPosition.iY)
			{
			return ETrue;
			}

	return EFalse;
	}

void CMonochromeThumbnailProcessor::DoFlushPixels()
	{
	if(!iReducedSumBuffer)
		return;

	TMonochromeSum* reducedSumPtr = iReducedSumBuffer + iReducedImageRegion.iTl.iX;
	TMonochromeSum* reducedSumPtrLimit = iReducedSumBuffer + iReducedImageRegion.iBr.iX;

	while(reducedSumPtr < reducedSumPtrLimit)
		{

		while(reducedSumPtr->iCount==0)
			{
			reducedSumPtr++;
			if(reducedSumPtr==reducedSumPtrLimit)
				return;
			}

		if(iPositionChanged)
			iImageProc->SetPos(TPoint(reducedSumPtr - iReducedSumBuffer,iPos.iY >> iReductionFactor));

		TUint32* reducedPixelBufferPtr = iReducedPixelBuffer;
		TInt fullCountFactor = 2*iReductionFactor;
		TInt fullCount = 1<<fullCountFactor;

		do
			{
			TInt level = reducedSumPtr->iLevel;
			TInt count = reducedSumPtr->iCount;

			if(count==fullCount)
				level >>= fullCountFactor;
			else if(count!=0)
				level /= count;
			else
				break;

			*reducedPixelBufferPtr++ = level;

			reducedSumPtr++;
			}
		while(reducedSumPtr < reducedSumPtrLimit);

		TInt numPixels = reducedPixelBufferPtr-iReducedPixelBuffer;
		iImageProc->SetMonoPixels(iReducedPixelBuffer,numPixels);

		Mem::FillZ(reducedSumPtr-numPixels,numPixels * sizeof(TMonochromeSum));
		}

	}

TBool CMonochromeThumbnailProcessor::SetPos(const TPoint& aPosition)
	{
	if(iImageRegion.Contains(aPosition)==EFalse)
		return EFalse;

	if((aPosition.iY ^ iPos.iY) >> iReductionFactor)
		DoFlushPixels();

	iPositionChanged = ETrue;
	iPos = aPosition;

	return ETrue;
	}

void CMonochromeThumbnailProcessor::SetYPosIncrement(TInt aYInc)
	{
	iYInc = aYInc;

	TInt reducedYInc = aYInc >> iReductionFactor;
	if(reducedYInc==0)
		reducedYInc = 1;

	iImageProc->SetYPosIncrement(reducedYInc);
	}

void CMonochromeThumbnailProcessor::SetLineRepeat(TInt aLineRepeat)
	{
	TInt reducedLineRepeat = aLineRepeat >> iReductionFactor;
	iImageProc->SetLineRepeat(reducedLineRepeat);
	}

void CMonochromeThumbnailProcessor::SetPixelPadding(TInt aNumberOfPixels)
	{
	iPixelPadding = aNumberOfPixels;
	iEndOfLineX = iEndPosition.iX + iPixelPadding;
	}

