// Copyright (c) 1997-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 "PngScanlineEncoder.h"

// CPngWriteSubCodec
CPngWriteSubCodec* CPngWriteSubCodec::NewL(const TPngImageInformation& aInfo, const CFbsBitmap* aSource)
	{
	CPngWriteSubCodec* self = NULL;

	switch (aInfo.iBitDepth)
		{
		case 1:
			self = new(ELeave) CBitDepth1Encoder;
			break;
		case 2:
			self = new(ELeave) CBitDepth2Encoder;
			break;
		case 4:
			self = new(ELeave) CBitDepth4Encoder;
			break;
		case 8:
			switch (aInfo.iColorType)
				{
				case TPngImageInformation::EGrayscale:
				case TPngImageInformation::EIndexedColor:
					self = new(ELeave) CBitDepth8Encoder;
					break;
				case TPngImageInformation::EDirectColor:
					self = new(ELeave) CBitDepth8ColorType2Encoder;
					break;
				default:
					User::Leave(KErrNotSupported);
					break;
				}
			break;
		default:
			User::Leave(KErrNotSupported);
			break;
		}
	ASSERT(self);
	CleanupStack::PushL(self);
	self->ConstructL(aInfo, aSource);
	CleanupStack::Pop(self);
	return self;
	}

CPngWriteSubCodec::CPngWriteSubCodec()
	: iScanlineDes(NULL, 0)
	{
	}

CPngWriteSubCodec::~CPngWriteSubCodec()
	{
	delete iScanlineBuffer;
	delete iPalette;
	}

void CPngWriteSubCodec::ConstructL(const TPngImageInformation& aInfo, const CFbsBitmap* aSource)
	{
	iInfo = aInfo;
	iSource = aSource;
	iScanlineBufferSize = ScanlineBufferSize(iInfo.iSize.iWidth);
	iScanlineBuffer = HBufC8::NewMaxL(iScanlineBufferSize);
	iScanlineDes.Set(&(iScanlineBuffer->Des())[0], iScanlineBufferSize, iScanlineBufferSize);

	// Init stuff specific to derived class
	DoConstructL();
	}

void CPngWriteSubCodec::DoConstructL()
	{
	}

TDes8& CPngWriteSubCodec::Buffer()
	{
	iScanlineDes.FillZ();
	return iScanlineDes;
	}

TDes8& CPngWriteSubCodec::EncodeL(const TInt aScanline)
	{
	if (aScanline < iInfo.iSize.iHeight)
		{
		TUint8* dataPtr = const_cast<TUint8*>(iScanlineDes.Ptr());
		const TUint8* dataPtrLimit = dataPtr + iScanlineBufferSize;

		DoEncode(iSource, aScanline, dataPtr, dataPtrLimit);
		}
	else
		{
		iScanlineDes.Set(NULL, 0, 0);
		}
	return iScanlineDes;
	}

TUint8 CPngWriteSubCodec::ReverseBits(const TUint8 aValue) const
	{
	TUint value = aValue;
	TUint reverseVal = 0;
	for (TInt i = 0; i < 8; i++)
		{
		reverseVal <<= 1;
		reverseVal |= value & 1;
		value >>= 1;
		}
	return TUint8(reverseVal);
	}

void CPngWriteSubCodec::EncodePalettedScanline(TUint8* aDataPtr, const CFbsBitmap* aSource, const TInt aScanline,
											   const TInt aPixelsPerByte, const TInt aShiftValue)
	{
	// Encode a single scanline with indexes into the current palette
	ASSERT(iInfo.iPalettePresent);
	TPoint pos(0, aScanline);
	const TInt scanLength = iInfo.iSize.iWidth;
	for (TInt i=0; i < scanLength; i += aPixelsPerByte)
		{
		// Pack each byte with 'aPixelsPerByte' index values
		TUint8 pixels = 0;
		for (TInt j=0; j < aPixelsPerByte; j++)
			{
			pixels <<= aShiftValue;
			TRgb rgb;
			aSource->GetPixel(rgb, pos);
			pixels |= TUint8(iPalette->NearestIndex(rgb));
			pos.iX++;
			}
		*aDataPtr = pixels;
		aDataPtr++;
		}
	}

// CBitDepth1Encoder
void CBitDepth1Encoder::DoConstructL()
	{
	if (iInfo.iColorType == TPngImageInformation::EIndexedColor)
		{
		// Setup palette
		iPalette = CPalette::NewL(2);
		iPalette->SetEntry(0, KRgbBlack);
		iPalette->SetEntry(1, KRgbWhite);
		iInfo.iPalettePresent = ETrue;
		}
	}

TInt CBitDepth1Encoder::ScanlineBufferSize(TInt aPixelLength)
	{
	// 8 pixels per byte
	return ((aPixelLength + KPngDepth1RoundUpValue) / KPngDepth1PixelsPerByte) + KPngScanlineFilterTypeLength;
	}

void CBitDepth1Encoder::DoEncode(const CFbsBitmap* aSource, const TInt aScanline,
								 TUint8* aDataPtr, const TUint8* aDataPtrLimit)
	{
	// Filter method
	PtrWriteUtil::WriteInt8(aDataPtr, iInfo.iFilterMethod);
	aDataPtr++;

	// Pixel data
	const TInt scanLength = iInfo.iSize.iWidth;
	if (iInfo.iColorType == TPngImageInformation::EIndexedColor)
		{
		// Write palette indexes
		EncodePalettedScanline(aDataPtr, aSource, aScanline, KPngDepth1PixelsPerByte, KPngDepth1ShiftValue);
		}
	else
		{
		// Write RGB data
		TInt dataLength = (scanLength + KPngDepth1RoundUpValue) / KPngDepth1PixelsPerByte;
		TPtr8 dataPtr(aDataPtr, dataLength, dataLength);

		aSource->GetScanLine(dataPtr, TPoint(0, aScanline), scanLength, EGray2);

		// Reverse the order of the bits
		while (aDataPtr < aDataPtrLimit)
			{
			aDataPtr[0] = ReverseBits(aDataPtr[0]);
			aDataPtr++;
			}
		}
	}

// CBitDepth2Encoder
void CBitDepth2Encoder::DoConstructL()
	{
	if (iInfo.iColorType == TPngImageInformation::EIndexedColor)
		{
		// Setup palette entries
		iPalette = CPalette::NewL(4);
		iPalette->SetEntry(0, KRgbBlack);
		iPalette->SetEntry(1, KRgbDarkGray);
		iPalette->SetEntry(2, KRgbGray);
		iPalette->SetEntry(3, KRgbWhite);
		iInfo.iPalettePresent = ETrue;
		}
	}

TInt CBitDepth2Encoder::ScanlineBufferSize(TInt aPixelLength)
	{
	return ((aPixelLength + KPngDepth2RoundUpValue) / KPngDepth2PixelsPerByte) + KPngScanlineFilterTypeLength;
	}

void CBitDepth2Encoder::DoEncode(const CFbsBitmap* aSource, const TInt aScanline,
								 TUint8* aDataPtr, const TUint8* aDataPtrLimit)
	{
	// Filter method
	PtrWriteUtil::WriteInt8(aDataPtr, iInfo.iFilterMethod);
	aDataPtr++;

	// Pixel data
	const TInt scanLength = iInfo.iSize.iWidth;
	if (iInfo.iColorType == TPngImageInformation::EIndexedColor)
		{
		// Write palette indexes
		EncodePalettedScanline(aDataPtr, aSource, aScanline, KPngDepth2PixelsPerByte, KPngDepth2ShiftValue);
		}
	else
		{
		// RGB values
		TPtr8 dataPtr(aDataPtr, (scanLength + KPngDepth2RoundUpValue) / KPngDepth2PixelsPerByte);
		aSource->GetScanLine(dataPtr, TPoint(0, aScanline), scanLength, EGray4);

		// Reverse the order of the bits
		while (aDataPtr < aDataPtrLimit)
			{
			TUint8 value = aDataPtr[0];
			TUint8 reverse = 0;
			for (TInt i=0; i < KPngDepth2PixelsPerByte; i++)
				{
				reverse <<= 2;	// advance the bits for the reverse value
				reverse |= value & 0x3;	// mask off the 2 bits, then OR with existing reverse value
				value >>= 2;	// advance the bits for the actual value
				}
			aDataPtr[0] = reverse;
			aDataPtr++;
			}
		}
	}

// CBitDepth4Encoder
void CBitDepth4Encoder::DoConstructL()
	{
	if (iInfo.iColorType == TPngImageInformation::EIndexedColor)
		{
		// Calculate palette for image
		iPalette = CPalette::NewDefaultL(EColor16);
		iInfo.iPalettePresent = ETrue;
		}
	}

TInt CBitDepth4Encoder::ScanlineBufferSize(TInt aPixelLength)
	{
	return ((aPixelLength + KPngDepth4RoundUpValue) / KPngDepth4PixelsPerByte) + KPngScanlineFilterTypeLength;
	}

void CBitDepth4Encoder::DoEncode(const CFbsBitmap* aSource, const TInt aScanline,
								 TUint8* aDataPtr, const TUint8* aDataPtrLimit)
	{
	// Filter method
	PtrWriteUtil::WriteInt8(aDataPtr, iInfo.iFilterMethod);
	aDataPtr++;

	// Pixel data
	const TInt scanLength = iInfo.iSize.iWidth;
	if (iInfo.iColorType == TPngImageInformation::EIndexedColor)
		{
		// Write palette indexes
		EncodePalettedScanline(aDataPtr, aSource, aScanline, KPngDepth4PixelsPerByte, KPngDepth4ShiftValue);
		}
	else
		{
		TPtr8 dataPtr(aDataPtr, (scanLength + KPngDepth4RoundUpValue) / KPngDepth4PixelsPerByte);
		aSource->GetScanLine(dataPtr, TPoint(0, aScanline), scanLength,
			(iInfo.iColorType == TPngImageInformation::EDirectColor) ? EColor16 : EGray16);

		// Swap order of the low/high bits
		while (aDataPtr < aDataPtrLimit)
			{
			TUint value = aDataPtr[0];
			TUint low = value << KPngDepth4ShiftValue;
			TUint high = value >> KPngDepth4ShiftValue;
			aDataPtr[0] = TUint8(low | high);
			aDataPtr++;
			}
		}
	}

// CBitDepth8ColorType2Encoder
TInt CBitDepth8ColorType2Encoder::ScanlineBufferSize(TInt aPixelLength)
	{
	return (aPixelLength * KPngDepth8RgbBytesPerPixel) + KPngScanlineFilterTypeLength;
	}

void CBitDepth8ColorType2Encoder::DoEncode(const CFbsBitmap* aSource, const TInt aScanline,
										   TUint8* aDataPtr, const TUint8* aDataPtrLimit)
	{
	// Filter method
	PtrWriteUtil::WriteInt8(aDataPtr, iInfo.iFilterMethod);
	aDataPtr++;

	// Pixel data
	TPtr8 dataPtr(aDataPtr, iInfo.iSize.iWidth * KPngDepth8RgbBytesPerPixel);
	aSource->GetScanLine(dataPtr, TPoint(0, aScanline), iInfo.iSize.iWidth, EColor16M);

	while (aDataPtr < aDataPtrLimit)
		{
		// Swap the red and blue components of the image data
		TUint8 temp = aDataPtr[0];	// temp = Red
		aDataPtr[0] = aDataPtr[2];	// Red = Blue
		aDataPtr[2] = temp;			// Blue = temp
		aDataPtr += KPngDepth8RgbBytesPerPixel;
		}
	}

// CBitDepth8Encoder
void CBitDepth8Encoder::DoConstructL()
	{
	if (iInfo.iColorType == TPngImageInformation::EIndexedColor)
		{
		// Calculate palette for image
		iPalette = CPalette::NewDefaultL(EColor256);
		iInfo.iPalettePresent = ETrue;
		}
	}

TInt CBitDepth8Encoder::ScanlineBufferSize(TInt aPixelLength)
	{
	return aPixelLength + KPngScanlineFilterTypeLength;
	}

void CBitDepth8Encoder::DoEncode(const CFbsBitmap* aSource, const TInt aScanline,
								 TUint8* aDataPtr, const TUint8* /*aDataPtrLimit*/)
	{
	// Filter method
	PtrWriteUtil::WriteInt8(aDataPtr, iInfo.iFilterMethod);
	aDataPtr++;

	const TInt scanLength = iInfo.iSize.iWidth;
	if (iInfo.iColorType == TPngImageInformation::EIndexedColor)
		{
		// Write palette indexes
		EncodePalettedScanline(aDataPtr, aSource, aScanline, KPngDepth8PixelsPerByte, KPngDepth8ShiftValue);
		}
	else
		{
		// Pixel data
		TPtr8 dataPtr(aDataPtr, scanLength);
		aSource->GetScanLine(dataPtr, TPoint(0, aScanline), scanLength, EGray256);
		}
	}
