diff -r 000000000000 -r 40261b775718 mmplugins/imagingplugins/codecs/JPEGCodec/Exif/ExifEditUtility.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mmplugins/imagingplugins/codecs/JPEGCodec/Exif/ExifEditUtility.cpp Tue Feb 02 01:56:55 2010 +0200 @@ -0,0 +1,875 @@ +// Copyright (c) 2004-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 "exiftransform.h" +#include "exiftransformdataaccessor.h" +#include "ExifEditUtility.h" +#include "ExifThumbnailGenerator.h" +#include "ExifGeneralConsts.h" +#include "JpegConsts.h" +#include "ImageUtils.h" + +#include + +const TUint8 KJpegMarkerByte = 0xff; +const TUint8 KDhtMarkerByte = 0xc4; +const TUint8 KDqtMarkerByte = 0xdb; +const TUint8 KSosMarkerByte = 0xda; + + +const TUint KTableTypeSize = 1; // in DHT and DQT the table type is one byte long +const TUint KEntriesInQTable = 64; // number of entries in a specific quantization table + +const TUint KDCTableSize = 28; // see Exif 2.2 specification +const TUint KACTableSize = 178; // see Exif 2.2 specification + +const TUint KDQTTypicalSize = (KTableTypeSize + KEntriesInQTable) * 3; +const TUint KDHTTypicalSize = (KTableTypeSize + KDCTableSize) * 2 + (KTableTypeSize + KACTableSize) * 2; + +const TInt KJpegBlockTypeUndefined = -1; + +_LIT(KDhtBlockHeader, "\xff\xc4\x00\x00"); // jpeg DHT block id followed by placeholder for block size +_LIT(KDqtBlockHeader, "\xff\xdb\x00\x00"); // jpeg DQT block id followed by placeholder for block size + +CExifEditUtility* CExifEditUtility::NewL(MExifSource* aSource, MExifDest* aDest, TBool aIgnoreExifMetadataProcessing) + { + CExifEditUtility * self= new (ELeave) CExifEditUtility(aIgnoreExifMetadataProcessing); + CleanupStack::PushL(self); + self->ConstructL(aSource, aDest); + CleanupStack::Pop(self); + + return self; + } + +CExifEditUtility::~CExifEditUtility() + { + Cancel(); + delete iScaledJpegGenerator; + delete iExifData; + iDHTTables.Close(); + iDQTTables.Close(); + iTempCopyBuffer.Close(); + } + +CExifEditUtility::CExifEditUtility(TBool aIgnoreExifMetadataProcessing) + : CActive(EPriorityStandard) + , iState(EEmpty) + , iIgnoreExifMetadataProcessing(aIgnoreExifMetadataProcessing) + { + iLookupSlackBuff.SetLength( 0 ); + } + +void CExifEditUtility::ConstructL(MExifSource* aSource, MExifDest* aDest) + { + ASSERT(aSource != NULL); + ASSERT(aDest != NULL); + iSource = aSource; + iDest = aDest; + CActiveScheduler::Add(this); + } + + +MExifMetadata* CExifEditUtility::ExifMetadata() + { + return iExifData; + } + + +// +// +// +// +// interface methods +// + +void CExifEditUtility::ReadSourceL() + { + if(iState!=EEmpty) + { + User::Leave(KErrInUse); + } + + InitReadL(); + if(!iIgnoreExifMetadataProcessing) + { + delete iExifData; + iExifData = NULL; + TRAPD(err, iExifData = iSource->ReadAndConvertExifDataL(iHeaderBlockSize + + (sizeof(KJpgSOISignature)+sizeof(KJpgMarker)) /*size of the app1 marker*/, + iExifBlockSize)); + if(err != KErrNone) + { + if(err == KErrNoMemory) + { + User::Leave(KErrNoMemory); + } + iIgnoreExifMetadataProcessing = ETrue; + iExifBlockSize = 0; + iConvertToJfif = ETrue; + } + + } + iState=EReadComplete; + } + + +void CExifEditUtility::WriteDestL(TBool aEncodeThumbnail, const TSize& aSize, TBool aPreserveImage, TBool aMaintainAspectRatio, TRequestStatus* aNotifier) + { + iNotifier=aNotifier; + iEncodeThumbnail=aEncodeThumbnail; + iSize= aSize; + iPreserveImage= aPreserveImage; + iMaintainAspectRatio = aMaintainAspectRatio; + if(iState !=EReadComplete) + { + RunError(KErrInUse); + return; + } + + TInt err=iDest->Init(); + if(err!=KErrNone) + { + RunError(err); + return; + } + + iState=EStartWrite; + ProcessCommandL(); + *iNotifier=KRequestPending; + } + + +// +// interface methods +// +// +// +// + + + + + + +// +// +// +// +// methods from CActive +// +void CExifEditUtility::DoCancel() + { + iState=ECancelled; + + if(iScaledJpegGenerator) + { + iScaledJpegGenerator->Cancel(); + } + + //Now reset everything to how it was before the transform. + iState = EReadComplete; + iSource->Cancel(); + iDest->Cancel(); + + //We need to cancel both the internal state machine request status + //and the client request status + if (iStatus == KRequestPending) + { + TRequestStatus* status=&iStatus; + User::RequestComplete(status, KErrCancel); + } + if (iNotifier)//but we do need to check iNotifier TRequestStatus + { + if (*iNotifier == KRequestPending) + { + User::RequestComplete(iNotifier,KErrCancel); + } + } + } + + +void CExifEditUtility::RunL() + { + if(iStatus.Int()==KErrNone) + { + ProcessCommandL(); + } + else + { + RunError(iStatus.Int()); + } + } + +TInt CExifEditUtility::RunError(TInt aError) + { + if(iStateCleanupAfterEarlyError(); + iDest->CleanupAfterEarlyError(); + } + else + { + iState=EReadComplete; + iSource->CleanupAfterLateError(); + iDest->CleanupAfterLateError(); + } + + User::RequestComplete(iNotifier, aError); + return KErrNone; + } +// +// interface methods +// +// +// +// + + +void CExifEditUtility::ProcessCommandL() + { + TBool doContinue=EFalse; + switch(iState) + { + // writer commands + case EStartWrite: + doContinue=DoCopyBlockL(0, iHeaderBlockSize, EWriteWriteHeader); + iState=ECopying; + break; + case EWriteWriteHeader: + if(!iIgnoreExifMetadataProcessing) + { + doContinue=DoWriteReadThumbnailL(); + } + else + { + //skip to next step. + iStatus=KRequestPending; + TRequestStatus* reqStat=&iStatus; + User::RequestComplete(reqStat, KErrNone); + doContinue = ETrue; + } + iState = EReadThumbnail; + break; + case EReadThumbnail: + doContinue=DoReadThumbnailL(); + if (iPreserveImage) + { + iState=EWriteReadThumbnail; + } + else + { + iState=EWriteWriteExif; + } + break; + case EWriteReadThumbnail: + doContinue=DoWriteConvertExifL(); + iState=EWriteConvertExif; + break; + case EWriteConvertExif: + if(iIgnoreExifMetadataProcessing && (iExifBlockSize >0)) + { + HBufC8* destBuffer = iIOBufferPtr.AllocL(); + iDest->SetDestBuffer(destBuffer); // Give iDest ownership of buffer + } + doContinue=DoWriteDestBufferL(); + + if(iPreserveImage) + { + iState=EWriteWriteExif; + } + else + { + iState=EWriteReadMainImage; + } + break; + case EWriteWriteExif: + if(iPreserveImage) + { + doContinue=DoCopyBlockL(iTrailerOffset, iTrailerBlockSize, EWriteTrailer, iDestIsExif); + iState=ECopying; + } + else + { + doContinue=DoCreateMainImageL(); + iCurrentWriteBufferIndex = 0; + iState=EWriteReadThumbnail; + } + break; + case EWriteTrailer: + DoWriteComplete(); + iState=EReadComplete; + break; + case EWriteReadMainImage: + doContinue=DoWriteScaledImageL(); + break; + case ECancelled: + return; + case ECopying: + iState=DoCopyNextBlockL(); + doContinue=ETrue; + break; + default: + break; + } + + if(doContinue) + { + SetActive(); + } + } + + + + +// +// +// +// +// Readers and writers +// each performs one step of the process + + +// +// Readers +// +void CExifEditUtility::InitReadL() + { + iSource->InitL(); + + TBool exifFound=EFalse; + + TUint16 jpegMarker=0; + TUint position=0; + TUint16 blockSize=0; + // we want to preserve the image data. + // so we parse the source file, and isolate the exif metadata + for(;;) + { + position+=blockSize; + jpegMarker=KJpgSOISignature; + User::LeaveIfError(iSource->SetReadPosition(position)); + do + { + jpegMarker=iSource->ReadUint16L(); + position+=sizeof(TUint16); + }while(jpegMarker==KJpgSOISignature); + + if(jpegMarker==KJpgApp0Signature || jpegMarker==KJpgApp1Signature) + { + blockSize=iSource->ReadUint16L(); + position+=sizeof(TUint16); + if (jpegMarker==KJpgApp1Signature) + { + exifFound=ETrue; + break; + } + } + else + { + break; + } + } + + User::LeaveIfError(iSource->Size(iTrailerBlockSize)); + iTrailerOffset=sizeof(KJpgSOISignature); + if(exifFound) + { + // Set our reading parameters + TUint sizeOfApp1Marker = sizeof(KJpgSOISignature)+sizeof(KJpgMarker); + if(position <= sizeOfApp1Marker) User::Leave(KErrCorrupt);//source file is corrupted. + iHeaderBlockSize=position - sizeOfApp1Marker; + iExifBlockSize=blockSize; + iTrailerOffset+=iHeaderBlockSize+blockSize; + } + else + { + iHeaderBlockSize=sizeof(KJpgSOISignature); + iExifBlockSize=0; + } + iTrailerBlockSize-= iTrailerOffset; + } + +// Readers +// +// + + +// +// Writers +// +TBool CExifEditUtility::DoWriteConvertExifL() + { + if(iIgnoreExifMetadataProcessing) + { + if(iExifBlockSize) + { + //copy the EXIF metadata en-block, without parsing it first. + iSource->ReadL(iHeaderBlockSize, iIOBufferPtr, iExifBlockSize + KBlockSizeLength, iStatus); + iDestIsExif = ETrue; + } + else + { // JUMP to next step... no EXIF metadata information, so nothing to ignore. + if (iConvertToJfif) + { + //Create a buffer to hold a default JFIF header + HBufC8* buffer=HBufC8::NewMaxL(KJfifApp0DataSize+sizeof(KJpgApp0Signature)); + TPtr8 buf(buffer->Des()); + TJpegUtilities::CreateJfifHeader(buf); + iDest->SetDestBuffer(buffer);// Give iDest ownership of buffer + } + + iStatus=KRequestPending; + TRequestStatus* reqStat=&iStatus; + User::RequestComplete(reqStat, KErrNone); + iDestIsExif = EFalse; + } + } + else + { + TInt err = KErrNone; + if(iExifBlockSize > 0 || iExifData->IsExifDataModified()) + { + HBufC8* buffer = NULL; + + UpdateImageSizeTagsL(); + iExifData->CheckUpdateMandatoryTagsL(); + + err= iExifData->CreateExifChunk(buffer); // buffer not owned by iExifData + if (err == KErrNone) + { + iDest->SetDestBuffer(buffer); // Give iDest ownership of buffer + } + iExifData->ResetExifDataModified(); + iDestIsExif = ETrue; + } + else + { + iDestIsExif = EFalse; + } + iStatus=KRequestPending; + TRequestStatus* reqStat=&iStatus; + User::RequestComplete(reqStat, err); + } + return ETrue; //we continue to the next step + } + +TBool CExifEditUtility::DoWriteReadThumbnailL() + { + if(iEncodeThumbnail) + { + HBufC8* buffer = iExifData->GetJpegThumbnailData(); // Does not transfer ownership of buffer + if(buffer==NULL) + { + delete iScaledJpegGenerator; + iScaledJpegGenerator=NULL; + // we have to generate the thumbnail + iScaledJpegGenerator=CScaledJpegGenerator::NewL(&iStatus, iSource); + iScaledJpegGenerator->StartL(TSize(KThumbnailWidth, KThumbnailHeight), CScaledJpegGenerator::EThumbnail); + } + else + { + iStatus=KRequestPending; + TRequestStatus* reqStat=&iStatus; + User::RequestComplete(reqStat, KErrNone); + } + } + else + { + iExifData->SetThumbnailData(NULL); + iStatus=KRequestPending; + TRequestStatus* reqStat=&iStatus; + User::RequestComplete(reqStat, KErrNone); + } + + return ETrue; + } + +TBool CExifEditUtility::DoReadThumbnailL() + { + + if(iEncodeThumbnail && iScaledJpegGenerator) + { + HBufC8* thumb=iScaledJpegGenerator->GetJpegDataL(); // thumb not owned by iScaledJpegGenerator + delete iScaledJpegGenerator; + iScaledJpegGenerator=NULL; + iExifData->SetThumbnailData(thumb); // Give iExifData ownership of thumb + } + iStatus=KRequestPending; + TRequestStatus* reqStat=&iStatus; + User::RequestComplete(reqStat, KErrNone); + return ETrue; + } + +TBool CExifEditUtility::DoCreateMainImageL() + { + if(!iScaledJpegGenerator) + { + iScaledJpegGenerator=CScaledJpegGenerator::NewL(&iStatus, iSource, iMaintainAspectRatio, + CImageDecoder::EOptionIgnoreExifMetaData); + } + iScaledJpegGenerator->StartL(iSize, CScaledJpegGenerator::EMainImage); + return ETrue; + } + +TBool CExifEditUtility::DoWriteScaledImageL() + { + HBufC8* buffer = iScaledJpegGenerator->JpegDataBufferL(iCurrentWriteBufferIndex); + if (buffer) + { + iCurrentWriteBufferIndex++; + iState = EWriteReadMainImage; + } + else + { + iState = EWriteTrailer; + } + iDest->SetDestBuffer(buffer); // Give iDest ownership of buffer + return DoWriteDestBufferL(); + } + +void CExifEditUtility::SetUpTablesL() + { + // set up the descriptors that store the combined tables + + iDHTTables.Zero(); + if (iDHTTables.MaxLength() < KDHTTypicalSize) + { + iDHTTables.ReAllocL(KDHTTypicalSize); + } + iDHTTables.Append(KDhtBlockHeader); // write the DHT block id and placeholder size + + iDQTTables.Zero(); + if (iDQTTables.MaxLength() < KDQTTypicalSize) + { + iDQTTables.ReAllocL(KDQTTypicalSize); + } + iDQTTables.Append(KDqtBlockHeader); // write the DQT block id and placeholder size + } + +void CExifEditUtility::AppendTableDataL(RBuf8& aTables, const TDesC8& aData) + { + if (aTables.Length() + aData.Length() > aTables.MaxLength()) + { + aTables.ReAllocL(aTables.Length() + aData.Length()); + } + aTables.Append(aData); + } + +TBool CExifEditUtility::DoCopyBlockL(TUint aStart, TInt aLength, TConvertState aNextStep, TBool aCombineTables) + { + iCurrentJpegBlockType = KJpegBlockTypeUndefined; + iCurrentJpegBlockSize = 0; + + TUint bytesToRead = 0; + if (aCombineTables) + { + // EXIF only supports one DHT table and one DQT table (JFIF supports multiple tables) + // When copying the data, keep track of jpeg blocks and store any DHT and DQT tables + // Write the combined tables out before the SOS block + + SetUpTablesL(); + + iCurCopyState = EReadJpegBlockInfo; + bytesToRead = KBlockIdAndSizeLength; + } + else + { + // copy the data in multiples of KIOBlockSize + iCurCopyState = ERead; + bytesToRead = Min(aLength, KIOBlockSize); + } + + iSource->ReadL(aStart, iIOBufferPtr, bytesToRead, iStatus); + iNextStep = aNextStep; + iCopyLength = aLength - bytesToRead; + return ETrue; //we continue to the next step + } + +void CExifEditUtility::DoReadL() + { + TUint bytesToRead = 0; + if (iCurrentJpegBlockType == KJpegBlockTypeUndefined) + { + // start/continue reading data without tracking jpeg block info + iCurCopyState = ERead; + bytesToRead = Min(iCopyLength, KIOBlockSize); + } + else if (iCurrentJpegBlockSize == 0) + { + // read the next jpeg block's info + iCurCopyState = EReadJpegBlockInfo; + bytesToRead = KBlockIdAndSizeLength - iLookupSlackBuff.Length(); + } + else + { + // start/continue reading data from this jpeg block + iCurCopyState = ERead; + bytesToRead = Min(iCurrentJpegBlockSize, KIOBlockSize); + iCurrentJpegBlockSize -= bytesToRead; // keep track of how much block data is left to read + } + iSource->NextReadL(iIOBufferPtr, bytesToRead, iStatus); + iCopyLength -= bytesToRead; + } + +void CExifEditUtility::HandleJpegBlockL(const TDesC8& aPrependedData) + { + switch (iCurrentJpegBlockType) + { + case KDhtMarkerByte: + case KDqtMarkerByte: + DoReadL(); + break; + case KSosMarkerByte: + { + // reached the SOS block so write out the tables + + // calculate the size of the combined DHT table data + TInt totalSize = iDHTTables.Length() - KBlockIdLength; + + // fill in the size placeholder + iDHTTables[2] = totalSize >> 8; + iDHTTables[3] = totalSize & 0xff; + + AppendTableDataL(iDHTTables, iIOBufferPtr); + + // calculate the size of the combined DQT table data + totalSize = iDQTTables.Length() - KBlockIdLength; + + // fill in the size placeholder + iDQTTables[2] = totalSize >> 8; + iDQTTables[3] = totalSize & 0xff; + + AppendTableDataL(iDQTTables, iDHTTables); + + iDest->WriteL(iDQTTables, iDQTTables.Length(), iStatus); + break; + } + case (KJpgCommentSignature & 0xFF): + case (KJpgApp0Signature & 0xFF): + if (iDestIsExif) + { + // According to the EXIF 2.2 spec. we can't have comments + // and shouldn't have APP0, so skip them. + // APP0 can be skipped entirely using the block length. + // In case of comment block, crawl slowly, as the comment blocks may be corrupted + if(iCurrentJpegBlockType == (KJpgApp0Signature & 0xFF)) + { + TInt fileSize = 0; + User::LeaveIfError(iSource->Size(fileSize)); + ASSERT(fileSize > 0); + TUint positionAfterCurBlock = fileSize - iCopyLength + iCurrentJpegBlockSize; + if(positionAfterCurBlock > fileSize) + { + User::Leave(KErrCorrupt); + } + User::LeaveIfError(iSource->SetReadPosition(positionAfterCurBlock)); + } + iCurrentJpegBlockType = 0; + iCurrentJpegBlockSize = 0; + DoReadL(); + break; + } + // if we'are not in EXIF mode fall to default block handling + default: + { + TInt len = aPrependedData.Length() + iIOBufferPtr.Length(); + if (iTempCopyBuffer.MaxLength() < len) + { + iTempCopyBuffer.ReAllocL(len); + } + iTempCopyBuffer = aPrependedData; + iTempCopyBuffer.Append( iIOBufferPtr ); + iDest->WriteL(iTempCopyBuffer, len, iStatus); + break; + } + } // switch + } + +TConvertState CExifEditUtility::DoCopyNextBlockL() + { + TConvertState result = ECopying; + + switch (iCurCopyState) + { + case EReadJpegBlockInfo: + { + iCurCopyState = EWrite; + + // iIOBuffer should contain in order: + // - the jpeg marker byte (0xff) + // - the block id byte + // - the high byte of the block size + // - the low byte of the block size + const TText8* dataPtr = iIOBufferPtr.Ptr(); + const TText8* const dataPtrLimit = dataPtr + iIOBufferPtr.Length(); + if (iLookupSlackBuff.Length()==0 ) + { + while (dataPtr < dataPtrLimit && *dataPtr != KJpegMarkerByte) + { + dataPtr++; + } + const TInt dataLeft = TUint(dataPtrLimit - dataPtr); + ASSERT(dataLeft >= 0 ); + if (dataLeft < KBlockIdAndSizeLength) + { + if (dataLeft > 0) // some partial marker, take a copy of that data + { + iLookupSlackBuff.Copy(dataPtr, dataLeft); + } + DoReadL(); + break; + } + + // we should have some marker and block length in the buffer + iCurrentJpegBlockType = dataPtr[1]; + iCurrentJpegBlockSize = (dataPtr[2] << 8) + dataPtr[3] - KBlockSizeLength; + } + else // we have some marker in a slack buffer, so combine it with current data + { + if (iLookupSlackBuff.Length() + iIOBufferPtr.Length() < KBlockIdAndSizeLength) + { + User::Leave(KErrCorrupt); // unexpected and of image data + } + TInt needToAppend = KBlockIdAndSizeLength - iLookupSlackBuff.Length(); + iLookupSlackBuff.Append( iIOBufferPtr.Left( needToAppend ) ); + iIOBufferPtr.Shift( needToAppend ); + iCurrentJpegBlockType = iLookupSlackBuff[1]; + iCurrentJpegBlockSize = (iLookupSlackBuff[2] << 8) + iLookupSlackBuff[3] - KBlockSizeLength; + } + //check validity of the block size + if(iCurrentJpegBlockSize > iCopyLength) + { + User::Leave(KErrCorrupt); + } + + HandleJpegBlockL(iLookupSlackBuff); + iLookupSlackBuff.SetLength( 0 ); + break; + } + case ERead: + { + iCurCopyState = EWrite; + + switch (iCurrentJpegBlockType) + { + case KDhtMarkerByte: + // store the DHT block data + AppendTableDataL(iDHTTables, iIOBufferPtr); + DoReadL(); + break; + case KDqtMarkerByte: + // store the DQT block data + AppendTableDataL(iDQTTables, iIOBufferPtr); + DoReadL(); + break; + default: + iDest->WriteL(iIOBufferPtr, iIOBufferPtr.Length(), iStatus); + ASSERT(iCopyLength >= 0); + if (iCopyLength == 0) + { + result = iNextStep; + } + break; + } + + break; + } + case EWrite: + { + if (iCurrentJpegBlockType == KSosMarkerByte) + { + iCurrentJpegBlockType = KJpegBlockTypeUndefined; // tables have been written out, no need to track the jpeg blocks now + } + DoReadL(); + break; + } + default: + break; + } + + return result; + } + + +TBool CExifEditUtility::DoWriteComplete() + { + iDest->WriteComplete(); + + User::RequestComplete(iNotifier, KErrNone); + return EFalse; + } + + +TBool CExifEditUtility::DoWriteDestBufferL() + { + iDest->WriteDestBufferL(iStatus); + return ETrue; + } +// Writers +// +// + +// +// Readers and writers +// each performs one step of the process +// +// +// + + +void CExifEditUtility::UpdateImageSizeTagsL() + { + if (iPreserveImage) + { + if (!iExifData->CheckImageSizeTags()) + { + TSize destSize; + // In this case, jpeg data blocks are copied from source to dest, + // so we need CImageDecoder to get the size of the image. + // NOTE: normally image size would be copied from valid Exif source + // but this is for the case where that data is missing from the source. + CImageDecoder* decoder = NULL; + TRAPD(err, decoder = iSource->CreateImageDecoderL(CImageDecoder::EOptionNone)); + if (err != KErrNone) + { + destSize = iSize; + } + else + { + destSize = decoder->FrameInfo().iFrameCoordsInPixels.Size(); + delete decoder; + } + iExifData->UpdateImageSizeTagsL(destSize); + } + } + else + { + TSize destSize; + if (iScaledJpegGenerator) + { + iScaledJpegGenerator->GetScaledImageSize(destSize); + } + else + { + destSize = iSize; + } + iExifData->UpdateImageSizeTagsL(destSize); + } + } + +