Progressive decoding support in a standard app is actually very simple - if the header scanning has insufficient data either on opening an image, it will leave with KErrUnderflow. This should be distinguished from errors within headers etc - which will be returned as KErrCorrupt. Other errors should be returned as appropriate.
When decoding, this is even simpler - the Framework detects when the codec says that things are not complete but it has no more data. This is again signalled back as KErrUnderflow.
CImageDecoder includes facilities to support the simultaneous decoding and display of images as they are being loaded. The decoder does not wait for the entire image to be loaded before processing it, rather it begins as soon as possible, stops when it runs out of data and then carries on when more data is available. The steps for progressive decoding are shown below:
- First of all we have to create the decoder. For this you can use either CImageDecoder::FileNewL() or CImageDecoder::DataNewL() (depending whether you have the image stored in a file or in a descriptor in memory). If there is insufficient data to work out the image format (plugin decoder to use) the constructor leaves with KErrUnderflow. Note that in order to allow the progressive decoding you have to pass the flag CImageDecoder::EOptionAllowZeroFrameOpen as argument to the constructor.
- As soon as the correct plugin decoder has been determined it is opened and whatever addition image data is available is scanned. The plugin decoder continues to decode image data as it arrives, updating FrameCount() whenever it becomes aware of a new frame within the data. The internal flag CImageDecoder::IsImageHeaderProcessingComplete() is maintained at EFalse until the entire image has been loaded.
- Frames can be decoded before the entire image is loaded, but the frame to be decoded must be at least partially loaded. If IsImageHeaderProcessingComplete() is set to EFalse and FrameCount() is equal to or less than the frame to decode, the application must wait for the relevant frame to load. In such circumstances a call to CImageDecoder::ContinueProcessingHeadersL() should be made that scans for any further headers. FrameCount() and IsImageHeaderProcessingComplete() should then be recalled to determine if the frame has arrived.
- Once FrameCount() is greater than the frame the application wants to decode, it is possible to create the start to decoding the frame using CImageDecoder::Convert(), but first you have to create a bitmap with the same size and display mode as the frame. Frame headers and their associated data do not always follow each other in some image formats. This has the implication that although FrameCount() has indicated that the frame is available, it may not yet be fully loaded. Under such circumstances as much decoding as possible is undertaken, and Convert() then completes with the error code KErrUnderflow. If the EPartialDecodeInvalid flag (from FrameInfo()) is not set, the partially decoded image can be displayed - for some image formats, a partially decoded image is not generally usable, but this facility is supported by most known formats. Obviously, if all of the image is present, Convert() completes with KErrNone as normal. A very important thing in this step is that you MUST use an active object for the conversion. User::WaitForRequest() will not work in conjunction with Convert() so if you want a synchronous behaviour you should create a dummy active object which RunL() just stops the active scheduler and pass its iStatus as argument to Convert().
- Where only a partial conversion has been completed, CImageDecoder::ContinueConvert() should be used to continue converting when new data arrives. ContinueConvert() continues to convert the frame data where the previous call left off. This function should continue to be called until it returns the error code KErrNone rather than KErrUnderflow. Note: The CFbsBitmap must never be resized during a conversion session using ContinueConvert(), if resizing does occur, a panic will be raised by the function. If resizing or any other parameter changes need to be made to the CFbsBitmap, frame decoding should be restarted by using Convert() rather than trying to continue an existing conversion session.
I wrote a simple state machine that implements all the process.
void CBitmapControl::StreamDecodeToBitmapViaDesL()
{
switch (iDecoderStatus)
{
case EDecoderStatusInit:
case EDecoderStatusOpening:
TRAPD(err, iDecoder = CImageDecoder::DataNewL(CCoeEnv::Static()->FsSession(), iBuffer,
CImageDecoder::EOptionAllowZeroFrameOpen));
if (err == KErrNone)
{
iDecoderStatus = EDecoderStatusOpened;
StreamDecodeToBitmapViaDesL();
}
else if(err == KErrUnderflow)
iDecoderStatus = EDecoderStatusOpening;
else
User::Leave(err);
break;
case EDecoderStatusOpened:
iDecoder->ContinueProcessingHeaderL();
if (iDecoder->IsImageHeaderProcessingComplete())
{
iDecoderStatus = EDecoderStatusFrameIsReady;
TFrameInfo frameInfo(iDecoder->FrameInfo());
TSize frameSize(frameInfo.iFrameCoordsInPixels.Width(),frameInfo.iFrameCoordsInPixels.Height());
User::LeaveIfError(iBitmap->Create(TSize(0,0),frameInfo.iFrameDisplayMode));
User::LeaveIfError(iBitmap->Resize(TSize(0,0)));
User::LeaveIfError(iBitmap->Resize(frameSize));
StreamDecodeToBitmapViaDesL();
}
break;
case EDecoderStatusFrameIsReady:
iActiveListener->InitialiseActiveListener();
iDecoder->Convert(&iActiveListener->iStatus,*iBitmap,0);
CActiveScheduler::Start();
if (iActiveListener->iStatus == KErrNone)
{
iDecoderStatus = EDecoderStatusConverted;
StreamDecodeToBitmapViaDesL();
}
else if (iActiveListener->iStatus == KErrUnderflow)
iDecoderStatus = EDecoderStatusConverting;
else
User::Leave(iActiveListener->iStatus.Int());
break;
case EDecoderStatusConverting:
iActiveListener->InitialiseActiveListener();
iDecoder->ContinueConvert(&iActiveListener->iStatus);
CActiveScheduler::Start();
if (iActiveListener->iStatus == KErrNone)
{
iDecoderStatus = EDecoderStatusConverted;
StreamDecodeToBitmapViaDesL();
}
else if (iActiveListener->iStatus == KErrUnderflow)
iDecoderStatus = EDecoderStatusConverting;
else
User::Leave(iActiveListener->iStatus.Int());
break;
case EDecoderStatusConverted:
iDecoderStatus = EDecoderStatusInit;
break;
default:
User::Leave(KErrArgument);
}
}
I have also coded a small application for S60 (based on the S60_Platform_HTTP_Client_API_Example_v2_0 by Nokia) that streams and decode an image from the Internet. You will find all the sources attached.