Image Decoding

This document gives you more information about the Image Decoding functionality.

Purpose

The image decoding class CImageDecoder provides functions to decode images held in descriptors or files from standard formats for use with devices such as screens, browsers and viewer applications.

Required Background

Image decoding features are provided through Imaging Frameworks and Imaging plugins. The standard formats supported by the decode plugins are shown in the table in Imaging Frameworks Overview.

Introduction

The decoding process comprises the following sections:

  • Creation - The creation of the CImageDecoder object and any requirements necessary.

  • Conversion - Covers the basic form of image decoding. More advanced features such as progressive and buffered decoding are described separately.

  • Enquiry features - Additional features that enable you to retrieve information stored in certain types of images, for example frame information and comments.

  • Streamed and progressive decoding - Reading partial image files and displaying image data before the entire image is read.

  • Buffered decoding - The decoding of an image using a buffered input rather than a file or descriptor.

Setup and Configuration Requirements

The CImageDecoder classes use synchronous methods to open an image and asynchronous methods to perform conversions or transformations. The asynchronous operations use the standard system of taking a pointer to a TRequestStatus object that is signalled on completion of the requested action. The assumption is that the client application or the calling DLL holds the TRequestStatus values within active objects. The structure of the active objects is dependent on the code that uses the Imaging Frameworks and its own requirements, particularly its internal architecture and how many images are opened simultaneously.

In addition to the use of active objects in the interfacing, many of the Imaging Frameworks internal functions make extensive use of them to provide asynchronous behaviour. As with any use of an active object it is necessary to have an active scheduler in the same thread as the application making use of the object. The Decoders can also be created so the plugin and framework runs in a separate thread. This is achieved by setting the EOptionAlwaysThread option when constructing the decoder objects. By running the object in its own thread, the application is shielded from any latency that can occur during image conversion, or possibly due to a badly written plugin.

Using Image Decoding

The Following tasks are covered in this tutorial:

Basic Procedure For Creation

The high level steps to create the object during decoding are as follows:

  1. CImageDecoder decodes images stored in files or in descriptors. The decoder object is owned by the client and must be deleted once decoding is finished. CImageDecoder objects cannot be reused to decode other images, each image requires its own instance of the decoder.

  2. CImageDecoder objects are created using the CImageDecoder::DataNewL() and CImageDecoder::FileNewL() factory functions for images held in descriptors or files respectively.

  3. When you create a CImageDecoder object, a suitable plugin must be associated with the image to be decoded. The Imaging plugin depends on the factory function you use to create the object and what parameters you specify. There are four alternatives:

    • MIME type The plugin is determined by looking up a specified MIME type against a list of known MIME type/plugin implementations. Use the following "File" factory function (or its "Data" equivalent).

      static CImageDecoder* FileNewL(RFs& aFs, const TDesC& aSourceFilename, const TDesC8& aMIMEType, const TOptions aOptions = EOptionNone);
      
    • Image type and sub-type The plugin is determined by looking up the specified image type and sub-types against a list of known type/sub-type plugin implementations. Use the following "File" factory function (or its "Data" equivalent).

      static CImageDecoder* FileNewL(RFs& aFs, const TDesC& aSourceFilename, const TOptions aOptions = EOptionNone, const TUid aImageType = KNullUid, const TUid aImageSubType = KNullUid, const TUid aDecoderUid = KNullUid);
      

      Note: For images types that do not have sub-types use KNullUid.

    • Implementation UID The plugin is determined by looking up its specific UID and the image type and sub-type. Use the same FileNewL() factory function shown in "Image type and sub-type", but in addition to the type and sub-type parameters, also specify the plugin UID using aDecoderUid.

      Note: Implementation UID is supported because it is possible to have more than one plugin for a particular image type. This method is recommended if the application or calling DLL needs to exploit features of a particular plugin.

    • Automatic detection - no MIME type, format type/sub-type or UIDs are specified. The plugin analyses the header information of the specified image. Use the same FileNewL() factory function shown in "Image type and sub-type", but do not specify any values for aImageType aImageSubType or aDecoderUid.

      If a suitable plugin cannot be found, the factory function leaves with KErrNotFound. If a suitable plugin is found, but that plugin cannot interpret the image data the factory function leaves with KErrCorrupt.

      Note: Some image formats cannot be automatically detected because they do not contain sufficient header information, for example, WBMP and OTA images. Under such circumstances, use one of the other three alternative for opening the image.

It is possible for more than one valid plugin decoder to be available for a specific image format. If a plugin determined by MIME type or type/sub-type fails to open an image due to KErrCorrupt, it will continue to try with subsequent valid plugins if available. This mechanism is carried out in the system and is not visible to the application. If you require a specific function available within a specific plugin, specify that plugin using aDecoderUid.

Basic Procedure For Conversion

The high level steps to convert encoded data to decoded data are as follow:

  1. As part of the CImageDecoder creation basic information about the image is pre-read from the image headers. This information is used to support certain enquiry methods such as CImageDecoder::FrameCount() and CImageDecoder::FrameInfo() (For more information about the retrieve information stored in certain types of images is supplied in Retrieve Information features).

  2. You can decode any subset of individual frames, in any order, and to repeat the decoding if necessary. To decode an image frame use the asynchronous conversion method CImageDecoder::Convert(), as follows.


void Convert(TRequestStatus* aRequestStatus, CFbsBitmap& aDestination, TInt aFrameNumber = 0);

void ContinueConvert(TRequestStatus* aRequestStatus);

Note: The use of CImageDecoder::ContinueConvert() is described in Streamed and progressive decoding.

There are different type of sub-procedures which have to be followed before decode conversion, they are:

Basic Procedure For Bitmap Masks conversion

The high level steps to Bitmap Masks conversion are as follows:

  1. There is a second variant of the Convert() function intended for use when decoding images with bitmap masks.

    
    void Convert(TRequestStatus* aRequestStatus, CFbsBitmap& aDestination, CFbsBitmap& aDestinationMask, TInt aFrameNumber = 0);
    
  2. Within the Symbian platform it is normal to store the bitmap mask separately from the main image data. Calls such as BitBltMasked() expect this data to be provided separately. This separation is true for both CFbsBitmap objects and MBM files.

  3. To determine whether an image contains a bitmap mask use FrameInfo(), the presence or absence of a mask will be indicated the ETransparencyPossible flag.

    The following two types of mask are available:

    • 8-bit alpha blend - indicated by the EAlphaChannel flag of CImageDecoder::FrameInfo() iFlags. The destination mask bitmap must be of type EGray256.

    • Simple on/off mask - if no EAlphaChannel flag is set. The destination bitmap mask can be either EGray2 or EGray256.

Note: Images that contain bitmap masks do not have to have those masks decoded if they are not required.

Basic Procedure For Scaling and display modes

The high level steps to Scale and display are shown here:

  1. Before Convert() is used, the destination CFbsBitmap objects for image data and bitmap masks are created. The simplest way to do this is to use a 1 to 1 approach, that is, create the CFbsBitmap object with the same properties as the source image. This is achieved by using something similar to:

    iFrameInfo = &imageDecoder->FrameInfo(FrameNum);
    
    iBitmap->Create(iFrameInfo->iOverallSizeInPixels, iFrameInfo->iFrameDisplayMode );
    
  2. If the ECanDither flag of CImageDecoder::FrameInfo() iFlags is set then the destination display mode can be adjusted. It is recommended that the window display mode is used as it is more efficient.

    Note: The mask bitmap must have the same size in pixels as the main image, even though the display mode is generally different.

  3. The size of the image bitmap is available via FrameInfo() (iOverallSizeInPixels). However, in addition to a direct mapping of the size of an image, ReducedSize() function can be used to calculate the reduced size of the decoded bitmap based on the input parameters.

  4. If the EFullyScaleable flag of FrameInfo() iFlags is set, you can specify any size for the CFbsBitmap and the image will be resized accordingly.

Basic Procedure For Animations

The high level steps to decode a GIF multi-frame image are as follow:

  1. Included in the CImageDecoder::FrameInfo() result are flags that enable the support of GIF animation. These flags are iDelay, ELeaveInPlace, ERestoreToBackground and ERestoreToPrevious. ICL provides these flags to enable the application to implement the animation; ICL does not provide functions to display the animation directly.

  2. Processing the information from these flags will inevitably require a state machine, and additional timers, within the application. Requirements will vary depending on the architecture of the client application itself.

Basic Procedure For Background colours

  1. Some image formats contain background colour information. This is information is presented through CImageDecoder::FrameInfo() iBackgroundColor.

  2. If a non-masked image with background colour is decoded, the colour is included in the image data. If the decoded image also contains a bitmap mask, then the background colour will be a component of the mask CFbsBitmap object. Depending on your application, you may want to choose your own background colour rather than use the one provided by the source image.

Basic Procedure To Retrieve Information From An Image

The high level steps to retrieve the information stored in certain types of images are shown here:

  1. CImageDecoder::FrameData() Provides access to additional chunks of data within the images, for example, palette and similar lookup tables, copyright information and other strings. This information should be used with care, the returned data is merely a reference to the original data within the CImageDecoder object, and is only valid for the lifetime of the object.

  2. CImageDecoder::NumberOfImageComments() and CImageDecoder::ImageCommentL() Images can have comments embedded in them. These comments are usually either embedded as a single repository which can be retrieved on an image level, or multiple repositories attached to individual frames within the image. The two functions above return the number of comments attached to the entire image and provide a mechanism for retrieving any of those comments.

  3. CImageDecoder::NumberOfFrameComments() and CImageDecoder::FrameCommentL() Similar functionality to above, but these functions return the number of comments attached to individually specified frames and provide a mechanism for retrieving any of those comments.

  4. CImageDecoder::FrameInfoStringsLC and CImageDecoder::FrameInfoStringsL() Provide a list of data for a specified frame, including data items such as format, plugin description, size and so on, in a readable format. Although the property strings can be accessed by name, it is recommended that they be generally treated as a list of strings.

Basic Procedure Streamed and Progressive Decoding

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 high level steps to perform the streaming and the progressive during decoding are shown here:

  1. If there is insufficient data to work out the image format (plugin decoder to use) CImageDecoder::FileNewL() (or its "Data" equivalent) leaves with KErrUnderflow. This is only applicable to instances where automatic detection of the plugin decoder is used. For more information see, "Automatic detection" in Creation.

  2. As soon as the correct plugin decoder has been determined it is opened and whatever addition image data 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::IsHeaderProcessingComplete() is maintained at EFalse until the entire image has been loaded.

  3. Frames can be decoded before the entire image is loaded, but the frame to be decoded must be at least partially loaded. If IsHeaderProcessingComplete() 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 IsHeaderProcessingComplete() should then be recalled to determine if the frame has arrived.

  4. Once FrameCount() is greater than the frame the application wants to decode, it is possible to start to decoding the frame using CImageDecoder::Convert(). 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.

  5. 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.

There is no explicit decoder call to say "all of the data is now present". This could be done via inference, continuously checking IsHeaderProcessingComplete() until it returns ETrue. However, this is not advisable as it is possible that IsHeaderProcessingComplete() could continue to return EFalse even after the application has finished sending image data. Your state machine should be written so that it takes this possibility into account.

Basic Procedure For Buffered Decoding

CBufferedImageDecoder is a wrapper that encapsulates not only a CImageDecoder but also the descriptor used to store the image, and in some circumstances can replace the use of CImageDecoder itself. CBufferedImageDecoder can always be created, even if there is no data available at the time of creation. Another key feature of CBufferedImageDecoder is that it can be reused to decode multiple images.

The high level steps to perform the buffered decoding are shown here:

  1. The decoder is created using the CBufferedImageDecoder::NewL() factory function, and the decoding process is started with one of the two CBufferedImageDecoder::OpenL() calls, specifying either MIME-type or image type/sub-type and UID, which are similar to CImageDecoder::DataNewL(). If not enough data is available to create an internal decoder a call to CBufferedImageDecoder::ValidDecoder() returns EFalse. The decoder keeps an internal copy of the data provided so the client can discard its own copy.

    Warning: The decoding and enquiry functions must not be used until an internal decoder has been created, otherwise calls will panic with EDecoderNotCreated.

  2. Additional data can be supplied to the decoder, when it becomes available, by using CBufferedImageDecoder::AppendDataL() and CBufferedImageDecoder::ContinueOpenL(), which will copy the data and try to find an appropriate plugin. This step should be repeated until enough data is available to create the internal decoder, indicated by ValidDecoder() returning ETrue. OpenL() and ContinueOpenL() will leave with KErrCorrupt if a suitable decoder is found, but that decoder was unable to interpret the supplied image data.

    Note: As with OpenL(), data provided by AppendDataL() is kept in an internal buffer by the decoder so the client can discard its own copy.

  3. The process for decoding the image header and frames are the same as for CImageDecoder with IsHeaderProcessingComplete(), FrameCount(), Convert() and ContinueConvert() calls except that the data is not appended to a descriptor, but supplied to the decoder with AppendDataL().

  4. The decoder can be reused by calling a CBufferedImageDecoder::Reset(), which destroys the internal data buffer and decoder. A new decoder plugin can then be started using OpenL().