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

#include <s32file.h>
#include <fbs.h>
#include <gdi.h>
#include <imageconversion.h>
#include <aknenv.h>
#include <AknGlobalProgressDialog.h>
#include <AknQueryDialog.h> 
#include <avkon.rsg>

_LIT(KSavingText, "Saving");
    
// ---------------------------------------------------------------------------

TInt CGifAnimator::CreateGifAnimation(const TDesC& aFileName, TSize aDimensions, CVideoFrameArray* aVideoFrameArray)
    {
	CGifAnimator* self = new(ELeave) CGifAnimator;
	CleanupStack::PushL(self);
	TRAPD(err, self->StartL(aFileName, aDimensions, aVideoFrameArray));
	CleanupStack::PopAndDestroy();
	return err;
    }

// ---------------------------------------------------------------------------

CGifAnimator::CGifAnimator() : CActive(EPriorityStandard)
    {
    }

// ---------------------------------------------------------------------------

void CGifAnimator::StartL(const TDesC& aFileName, const TSize& aDimensions, CVideoFrameArray* aVideoFrameArray)
    {
    __ASSERT_ALWAYS(aFileName.Length() > 0, User::Panic(_L("GifAnim"), 100));
    __ASSERT_ALWAYS(aDimensions.iHeight > 0, User::Panic(_L("GifAnim"), 101));
    __ASSERT_ALWAYS(aDimensions.iWidth > 0, User::Panic(_L("GifAnim"), 102));
    __ASSERT_ALWAYS(aVideoFrameArray != NULL, User::Panic(_L("GifAnim"), 103));
    
    CActiveScheduler::Add(this);
    
    // show a progress dialog
    iSavingProgressDialog = CSavingProgressDialog::NewL(this);
    iSavingProgressDialog->StartDisplayingL(KSavingText, aVideoFrameArray->Count()-1);

    // open the file for writing
    User::LeaveIfError(iFs.Connect());
    User::LeaveIfError(iOutFile.Replace(iFs, aFileName, EFileWrite));
    
    // write header
    WriteHeaderL(aDimensions);
    
    // process each frame of the animation
    for (TInt i=0; i<aVideoFrameArray->Count(); i++)
        {
        // write headers and raster block
        TVideoFrame frame = aVideoFrameArray->At(i);
        WriteGraphicControlL(frame);
        CFbsBitmap* bitmap = GetBitmapLC(frame, aDimensions);
        WriteImageDescriptorL(frame);
        WriteRasterDataL(bitmap);
        CleanupStack::PopAndDestroy(); //bitmap

        // update the progress bar
        iSavingProgressDialog->IncreaseProgressValueWithOne();
        }

    // write footer and finish
    WriteFooterL();
    FinishL();
    
    // remove the progress dialog from the screen
    iSavingProgressDialog->ProcessFinished();
    }    

// ---------------------------------------------------------------------------

CGifAnimator::~CGifAnimator()
    {
    Cancel();
    
    if (iImageEncoder)
        delete iImageEncoder;
    
    if (iGIFImageData)
        delete iGIFImageData;
    
    delete iSavingProgressDialog;
    }

// ---------------------------------------------------------------------------

void CGifAnimator::WriteHeaderL(const TSize& aDimensions)
    {
    WriteInt8L('G');
    WriteInt8L('I');
    WriteInt8L('F');
    WriteInt8L('8');
    WriteInt8L('9');
    WriteInt8L('a');

    WriteInt16L(aDimensions.iWidth);  // width of animation
    WriteInt16L(aDimensions.iHeight); // height of animation
    
    // logical screen descriptor
    TUint8 packedFlags = 0;
	packedFlags |= 8 - 1; // size of colour table is number of bits in each color table minus one (bits 0-2)
	packedFlags |= (8 - 1) << 4; // colour resolution ie maximum size of the original colour palette (bits 4-6)
	packedFlags |= 0x80; // use global colour table (bit 7)
    
    WriteInt8L(packedFlags);
    
    WriteInt8L(TRANSPARENCY_INDEX); // background color index
    
    WriteInt8L(0); // pixel aspect ratio, 0=not used
    
    // write the Symbian default palette since that's what is used
    CPalette* palette = CPalette::NewDefaultL(EColor256);
    CleanupStack::PushL(palette);
    
    for (TInt i=0; i<palette->Entries(); i++)
        {
        TRgb entry = palette->GetEntry(i);

        WriteInt8L(entry.Red());
        WriteInt8L(entry.Green());
        WriteInt8L(entry.Blue());
        }
    
    CleanupStack::PopAndDestroy(); //palette
    }

// ---------------------------------------------------------------------------

void CGifAnimator::WriteGraphicControlL(const TVideoFrame& aFrame)
    {
    TInt packedFlags(0);
    
    // enable transparency if needed
    if (aFrame.iEnableTransparency)
        packedFlags |= 0x01; 

    // set disposal method:
    // 0 = disposal method not specified, 1 = do not dispose of graphic,
    // 2 = overwrite graphic with background color, 3 = overwrite graphic with previous graphic
    TInt disposalMethod = 1;
    packedFlags |= ((disposalMethod << 2) & 0x1c);
    
    WriteInt8L(0x21); // GIF extension
    WriteInt8L(0xf9); // GIF graphic control block
    WriteInt8L(0x04); // block size
    WriteInt8L(packedFlags); // packed
    WriteInt16L(aFrame.iDelay); // delay
    WriteInt8L(TRANSPARENCY_INDEX); // transparent color index
    WriteInt8L(0); // block terminator, always 0
    }

// ---------------------------------------------------------------------------

void CGifAnimator::WriteImageDescriptorL(const TVideoFrame& aFrame)
    {
    WriteInt8L(0x2c); // GIF image descriptor
    WriteInt16L(aFrame.iXPos);
    WriteInt16L(aFrame.iYPos);
    WriteInt16L(aFrame.iWidth);
    WriteInt16L(aFrame.iHeight);
    WriteInt8L(0); // packed flags, none specified in this case
    }
    
// ---------------------------------------------------------------------------

CFbsBitmap* CGifAnimator::GetBitmapLC(TVideoFrame& aFrame, const TSize& aDimensions)
    {
    CFbsBitmap* bitmap = new(ELeave) CFbsBitmap;
    CleanupStack::PushL(bitmap);
    
    // read the bitmap from the temporary file
    RFile bitmapFile;
    User::LeaveIfError( bitmapFile.Open(iFs, aFrame.iFileStorePath, EFileRead) );
    RFileReadStream readStream(bitmapFile);
    bitmap->InternalizeL(readStream);
    readStream.Close();
    bitmapFile.Close();

    // delete the temporary file since it's not needed anymore
    iFs.Delete(aFrame.iFileStorePath);
    
    // resize the bitmap to match the video dimensions if it is a full screen one
    if (aFrame.iFillsWholeScreen && (aFrame.iWidth != aDimensions.iWidth || aFrame.iHeight != aDimensions.iHeight))
        {
        if (bitmap->Resize(aDimensions) == KErrNone)
            {
            // also update dimensions of this frame to match the dimensions of the video            
            aFrame.iWidth = aDimensions.iWidth;
            aFrame.iHeight = aDimensions.iHeight;
            }
        }
    
    return bitmap;
    }
    
// ---------------------------------------------------------------------------

void CGifAnimator::WriteRasterDataL(CFbsBitmap* aBitmap)
    {
    // reset the encoder
    if (iImageEncoder)
        {
	    delete iImageEncoder;
        iImageEncoder = NULL;
        }
    
    // make sure the buffer for conversion is empty    
    if (iGIFImageData)
        {
	    delete iGIFImageData;
        iGIFImageData = NULL;
        }        
        
    // init & convert the bitmap to GIF format
    iImageEncoder = CImageEncoder::DataNewL(iGIFImageData, CImageEncoder::EOptionNone, KImageTypeGIFUid);
    iImageEncoder->Convert( &iStatus, *aBitmap );

    // execute the async operation and wait till it's finished    
    SetActive();
    iWait.Start();
    
    // check any erros in active object
    User::LeaveIfError( iStatus.Int() );
    
    // check if we have valid data
    if (iGIFImageData == NULL || iGIFImageData->Des().Length()<793)
        User::Leave(KErrNoMemory);
    
    // in GIF files generated by Symbian, the raster data always starts at offset 791,
    // initial code size in GIF encoding should be 8 since we have a 8bpp image,
    // also check that the second last byte is the terminator 0,
    // if this check fails in newer releases of S60, proper parsing of GIF format is probably needed
    TUint8* imagePtr = &iGIFImageData->Des()[0];
    if (imagePtr[791] != 8 || imagePtr[iGIFImageData->Des().Length()-2] != 0)
        User::Leave(KErrNotSupported);
    
    // write the raster data block to the file
    TUint writeLength = iGIFImageData->Des().Length() - 1 - 791;
    imagePtr+=791;
    TPtr8 writePtr(imagePtr, writeLength, writeLength);
    User::LeaveIfError( iOutFile.Write(writePtr) );
    }

// ---------------------------------------------------------------------------

void CGifAnimator::WriteFooterL()
    {
    WriteInt8L(0x3b); // GIF trailer
    }
    
// ---------------------------------------------------------------------------

void CGifAnimator::FinishL()
    {
    iOutFile.Close();
    iFs.Close();
    }

// ---------------------------------------------------------------------------

void CGifAnimator::WriteInt8L(TInt aValue)
    {
    HBufC8* buf = HBufC8::NewMaxLC(1);

    TUint8* ptr = &buf->Des()[0];
    ptr[0] = TUint8(aValue);
    
    User::LeaveIfError( iOutFile.Write(buf->Des()) );
    
    CleanupStack::PopAndDestroy(); // buf
    }

// ---------------------------------------------------------------------------

void CGifAnimator::WriteInt16L(TInt aValue)
    {
    HBufC8* buf = HBufC8::NewMaxLC(2);

    TUint8* ptr = &buf->Des()[0];
    ptr[0] = TUint8(aValue);
    ptr[1] = TUint8(aValue>>8);
        
    User::LeaveIfError( iOutFile.Write(buf->Des()) );
    
    CleanupStack::PopAndDestroy(); // buf
    }

// ---------------------------------------------------------------------------

void CGifAnimator::DoCancel()
    {
    CAknEnv::StopSchedulerWaitWithBusyMessage( iWait );
    }

// ---------------------------------------------------------------------------

void CGifAnimator::RunL()
    {
    CAknEnv::StopSchedulerWaitWithBusyMessage( iWait );
    }

// ---------------------------------------------------------------------------

void CGifAnimator::DialogDismissedL(TInt aButtonId)
    {
    // check if cancel button was pressed
    if (aButtonId == EAknSoftkeyCancel)
        {
        }
    }

// ---------------------------------------------------------------------------

/*************************************************************************************************/
// a helper class to display the progress dialog on the screen

CSavingProgressDialog::CSavingProgressDialog() : CActive(EPriorityLow)
    {
	CActiveScheduler::Add(this);
    }

void CSavingProgressDialog::ConstructL(MDialogCallback* aDialogCallback)
    {
    iGlobalProgressDialog = CAknGlobalProgressDialog::NewL();
    iDialogCallback = aDialogCallback;
    }

CSavingProgressDialog* CSavingProgressDialog::NewL(MDialogCallback* aDialogCallback)
    {
    CSavingProgressDialog* self = new(ELeave) CSavingProgressDialog();
    CleanupStack::PushL(self);
    self->ConstructL(aDialogCallback);
    CleanupStack::Pop(self);
    return self;
    }

CSavingProgressDialog::~CSavingProgressDialog()
    {
    Cancel();
    delete iGlobalProgressDialog;
    }

void CSavingProgressDialog::StartDisplayingL(const TDesC &aText, TInt aFinalValue)
    {
    if (!iVisible)
        {
        iFinalValue = aFinalValue;
        iCurrentValue = 0;
        
        iGlobalProgressDialog->ShowProgressDialogL(iStatus, aText, R_AVKON_SOFTKEYS_EMPTY,
            aFinalValue, CAknNoteDialog::ENoTone);
            
        iVisible = ETrue;
        SetActive();
        }
    }

void CSavingProgressDialog::DoCancel()
    {
    if (iGlobalProgressDialog)
        {
        iGlobalProgressDialog->CancelProgressDialog();
        }
    iVisible = EFalse;
    }

void CSavingProgressDialog::RunL()
    {
    if (iGlobalProgressDialog && iStatus.Int() == KErrNotFound)
        {
        // user dismissed the dialog
        iDialogCallback->DialogDismissedL(EAknSoftkeyCancel);
        }
    }

void CSavingProgressDialog::IncreaseProgressValueWithOne()
    {
    iCurrentValue++;
    iGlobalProgressDialog->UpdateProgressDialog(iCurrentValue, iFinalValue);
    }

void CSavingProgressDialog::ProcessFinished()
    {
    iGlobalProgressDialog->ProcessFinished();
    iVisible = EFalse;
    }

/*************************************************************************************************/
