screengrabber/src/SGGifAnimator.cpp
changeset 0 d6fe6244b863
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/screengrabber/src/SGGifAnimator.cpp	Tue Feb 02 00:17:27 2010 +0200
@@ -0,0 +1,413 @@
+/*
+* 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;
+    }
+
+/*************************************************************************************************/