diff -r 000000000000 -r d6fe6244b863 screengrabber/src/SGGifAnimator.cpp --- /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 +#include +#include +#include +#include +#include +#include +#include + +_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; iCount(); 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; iEntries(); 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; + } + +/*************************************************************************************************/