Image Manipulation

Image manipulations are performed using the Series 60 multimedia architecture. Dedicated APIs are provided for image rotation, image scaling and converting to and from a variety of industry-standard file types, such as JPEG and GIF. Between Series 60 1.x and 2.x the architecture was revised and improved upon, although the key functionality remains similar.

Under Series 60 1.x the architecture is referred to as the Multi Media Server, while for Series 60 2.x it is the Multi Media Framework. Code written for Series 60 1.x is still compatible with Series 60 2.x, but you cannot take advantage of any enhancements, such as multithreading and the ability to write new conversion plug-ins. As most operations take place asynchronously, when using the multimedia APIs, an understanding of Active Objects is imperative.

From an implementation perspective, the key difference between the alternative Series 60 versions is handling the asynchronous nature of the image manipulations. Under Series 60 1.x an observer mechanism, based on the MMdaImageUtilObserver class, is used. By deriving from this class and implementing its virtual functions, applications are informed of the completion of a manipulation and can respond appropriately.

When developing for Series 60 2.x, a CActive -derived class should be created and the trequestStatus member iStatus passed into the function performing the manipulation. This will result in the RunL() of the CActive -derived class being called when the manipulation is finished.

The example code shown in this section is taken from two example applications: ImageManip for Series 60 1.x code and MultiMediaF for Series 60 2.x code.

Image Conversion

Conversion can take place to and from images stored in files or descriptors. Following a decode operation, if an image is to be displayed onto the device screen, a bitmap object of type CFbsBitmap must be created.

Conversely, if the image is already displayed on screen ”for example, within a camera application ”and needs to be saved as a JPEG, its data will currently be contained within a CFbsBitmap object. Consequently, the CFbsBitmap class plays an important role in the conversion process, acting as a bridging point between both encoding and decoding.

Decoding

The principles for decoding an image are the same, regardless of the Series 60 version being developed for. First, a channel to the data store, such as a GIF file, is opened, and this provides essential image attributes such as the image size .

When this is complete, a CFbsBitmap object can be instantiated with respect to the attributes obtained; however, it is still lacking the image data that will be displayed. The conversion operation takes place, resulting in the image data being stored inside the bitmap object, ready for use by application code.

Series 60 1.x

CMdaImageFileToBitmapUtility and CMdaImageDescToBitmapUtility are used to decode from files and descriptors, respectively. Both derive from CMdaImageUtility and provide two asynchronous functions, OpenL() and ConvertL() , which will be used in the decoding process. At the time of its creation, the decoding class is passed a reference to an MMdaImageUtilObserver object to inform applications that the asynchronous functions have completed.

 void CImageManipAdapter::DecodeOpenL()    {    iFileToBitmap->OpenL(KPathOfGifFile);    } 

OpenL() will invoke MMdaImageUtilObserver::MiuoOpenComplete():

 void CImageManipAdapter::MiuoOpenComplete(TInt aError)    {    if (aError == KErrNone)       {       switch (iManipulationState)          {          ...          case EDecode:             TRAPD(err, DecodeConvertL());             break;          ...          }       }    } 

If the manipulation state is set to EDecode , then DecodeConvertL() is called. From this function, a call to CMdaImageUtility::FrameInfo() is made, and this provides the vital image data to construct an appropriate bitmap:

 void CImageManipAdapter::DecodeConvertL()    {    TFrameInfo frmInfo;    iFileToBitmap->FrameInfo(KGifIndex, frmInfo);    iImage->Create(frmInfo.iOverallSizeInPixels, iDeviceDisplayMode);    iFileToBitmap->ConvertL(*iImage, KGifIndex);    } 

ConvertL() will call MMdaImageUtilObserver::MiuoConvertComplete() :

 void CImageManipAdapter::MiuoConvertComplete(TInt aError)    {    if (aError == KErrNone)       {       switch (iManipulationState)          {          case EDecode:          case EEncode:          case ERotating:          case EScaling:             iManipulationState = EDoNothing;             iMultimediaController.RedrawView();             break;          default:             break;          }       }    } 

Series 60 2.x

Unlike Series 60 1.x, there is a single decoding class in Series 60 2.x, CImageDecoder ; however, it has two synchronous functions to perform the "opening" step. CImageDecoder::FileNewL() is used for files and CImageDecoder::DataNewL() for descriptors.

The Multi Media Framework has an extensible plug-in architecture for image codecs ( coder /decoders), and both functions are overloaded so that the correct codec can be selected to decode the image. These overloaded functions have the option to perform codec selection in one of four ways: automatically, via a MIME type, using an image type UID, or using a plug-in UID. A plug-in UID is passed into the "opening" function if two plug-ins are available that can decode the same image type, but a preference exists as to which one should perform the decoding.

To discuss all of the approaches in detail is beyond the scope of this chapter, but further information can be found in the SDK documentation, where each approach is extensively illustrated . In most cases, automatic detection will suffice, as many industry-standard file formats are already provided for.

 void CMultiMediaFAdapter::DecodeOpenAndConvertL()    {    delete iDecoder;    iDecoder = 0;    iDecoder = CImageDecoder::FileNewL(iFs, KPathOfGifFile);    ... 

Once a handle to the image store has been acquired , the bitmap object has to be set up correctly using data obtained from a call to the CImageDecoder::FrameInfo() function. This is analogous to CMdaImageUtility::FrameInfo() from Series 60 1.x. CImageDecoder also has a number of other frame interrogation functions, such as FrameData() , which returns image quality and copyright information.

 ... TFrameInfo frmInfo = iDecoder->FrameInfo(KGifIndex); TRect rectOfImage = frmInfo.iFrameCoordsInPixels; delete iImage; iImage = 0; iImage = new (ELeave) CFbsBitmap(); iImage->Create(rectOfImage.Size(), iDeviceDisplayMode); ... 

As CImageDecoder::FileNewL() and CImageDecoder::DataNewL() are synchronous, there is no need for specific management of the completion of either operation. The conversion step, however, is asynchronous and is performed by CImageDecoder::Convert() .

Convert() takes a trequestStatus object as a parameter, and uses this to inform the calling class that the conversion has completed. By ensuring the calling class is an Active Object (derived from CActive) , its trequestStatus member, iStatus , can be passed into the Convert() method, and when the conversion is finished the RunL() method of the calling class will be invoked. It is worth remembering that RunL() will be called only if the Active Object is "active" ”in other words, its iActive member is Etrue . This requires SetActive() to be called immediately after CImageDecoder::Convert() :

 ... // Decode to the bitmap asynchronously. // RunL will be called when decoding is completed. iDecoder->Convert(&iStatus, *iImage, KGifIndex); SetActive(); } 

Encoding

Encoding, like decoding, is a three-stage process. The data store, be it a file or descriptor, must be created as the destination for the encoding operation. An image data object is then instantiated with the correct data settings relating to the file type. Finally, the conversion takes place with respect to the data settings passed into the conversion function.

Series 60 1.x

CMdaImageBitmapToFileUtility is used to encode to files, while CMdaImageBitmapToDescUtility encodes to descriptors. Both have a CreateL() function in order to construct the data store.

[View full width]
 
[View full width]
void CImageManipAdapter::EncodeOpenL() { // set up the jpg saving information that is required iJpgFormat->iSettings.iQualityFactor = KJpgQuality; iJpgFormat->iSettings.iSampleScheme = TMdaJpgSettings::TColorSampling(TMdaJpgSettings: :EColor420); TMdaPackage* codec = NULL; iBitmapToFile->CreateL(KPathOfSaveJpgFile, iJpgFormat, codec, NULL); }

MMdaImageUtilObserver::MiuoCreateComplete() is used to notify applications that creation has completed.

Like their decoding equivalents, CMdaImageBitmapToFileUtility and CMdaImageBitmapToDescUtility derive from CMdaImageUtility and have an asynchronous ConvertL() method. ConvertL() can be passed image format data ”for example, the compression rate if the image is to be a JPEG, and codec information. The image format is contained within the TMdaClipFormat class, while TMdaPackage encapsulates the codec.

 void CImageManipAdapter::MiuoCreateComplete(TInt aError)    {    if (aError == KErrNone)       {       switch (iManipulationState)          {          case EEncode:             TRAPD(err, EncodeConvertL());             break;          default:             break;          }       }    } 

Once the conversion is complete, the observer function MiuoConvertComplete() is called:

 void CImageManipAdapter::MiuoConvertComplete(TInt aError)    {    if (aError == KErrNone)       {       switch (iManipulationState)          {          case EDecode:          case EEncode:          case ERotating:          case EScaling:             iManipulationState = EDoNothing;             iMultimediaController.RedrawView();             break;          default:             break;          }       }    } 

Series 60 2.x

CImageEncoder is employed to encode bitmaps and has two overloaded functions for creating the data store that will receive the converted image. Like CImageDecoder , DataNewL() is used if a descriptor is the destination, while FileNewL() is used if a file is.

Both functions are overloaded to give the same flexibility previously seen when decoding ”this allows encoding to take place via a supplied MIME type, image type UID, or encoder UID. The creation of the data store is a synchronous operation, so explicit completion handling is unnecessary:

[View full width]
 
[View full width]
void CMultiMediaFAdapter::EncodeOpenAndConvertL() { // Create a new file ready for encoding. delete iEncoder; iEncoder = 0; iEncoder = CImageEncoder::FileNewL(iFs, KPathOfSaveJpgFile(), KJpgMime(), CImageEncoder ::EOptionNone); ...

Before converting through the CImageEncoder::Convert() method, it is possible to set certain attributes relating to the image frame data using the CImageFrameData class. An example of this might be setting the number of colors in the image palette of a JPEG. Once the desired elements have been set up, the CImageFrameData can be passed in to CImageEncoder::Convert() .

 ... // Set up the jpg saving information that is required. TJpegImageData* jpgData = new (ELeave) TJpegImageData(); // jpgData will be deleted by the Multi Media Framework CleanupStack::PushL(jpgData); jpgData->iQualityFactor = KJpgQuality; jpgData->iSampleScheme = TJpegImageData::EColor420; // Create the new image data. delete iJpgImageData; iJpgImageData = 0; iJpgImageData = CFrameImageData::NewL(); // Set the saving information. // Passes ownership if successful. User::LeaveIfError(iJpgImageData->AppendImageData(jpgData)); // jpgData now owned by iJpgImageData. CleanupStack::Pop(jpgData); // Begin conversion process // RunL will be called when encoding is completed. iEncoder->Convert(&iStatus, *iImage, iJpgImageData); SetActive(); } 

CImageEncoder::Convert() is also called within an Active Object class ”as with CImageDecoder::Convert() , the RunL() method of the Active Object is called when the asynchronous processing completes:

 void CMultiMediaFAdapter::RunL()    {    switch (iManipulationState)       {       ...       case EEncode:          {          iManipulationState = EDoNothing;          iMultimediaController.RedrawView();          break;          }       ...       }    } 

A key enhancement of the Series 60 2.x Multi Media Framework is the ability for each encoding and decoding operation to take place within its own thread, as previously only an Active Object-based implementation existed.

The encoding or decoding object is notified of the desire to use threads by passing in the enumeration value CImageDecoder::EOptionAlwaysThread to the DataNewL() or FileNewL() function. Plug-ins themselves can be designed to run in their own thread, and the flag will be ignored should a plug-in be selected where this is the case. Because the resources of a Series 60 device are limited, it is not always best to use multithreading, as there is an overhead in switching between threads. However, the nonpreemptive nature of Active Objects means that there is a potential loss of responsiveness while an Active Object executes. It is also worth noting that each new image to be encoded or decoded requires a new CImageEncoder or CImageDecoder object to be created.

Image Rotation

The image rotation classes are very similar on both versions of Series 60. Specific rotation angles cannot be user defined ”however, the TRotationAngle enumeration provides a selection of valid values.

Series 60 1.x

The CMdaBitmapRotator class performs image rotation through its asynchronous RotateL() function. CMdaBitmapRotator::RotateL() is overloaded to allow for two types of rotation: one to a separate target bitmap object, and the other to the original bitmap object itself.

[View full width]
 
[View full width]
void RotateL(MMdaImageUtilObserver& aObserver, CFbsBitmap& aSrcBitmap, CFbsBitmap& aTgtBitmap, TRotationAngle aAngle); void RotateL(MMdaImageUtilObserver& aObserver, CFbsBitmap& aSrcBitmap, TRotationAngle aAngle);

TRotationAngle allows images to be rotated clockwise in 90-, 180- and 270-degree increments .

 iRotator->RotateL(*this, *iImage, CMdaBitmapRotator::ERotation90DegreesClockwise); iRotator->RotateL(*this, *iImage, CMdaBitmapRotator::ERotation180DegreesClockwise); iRotator->RotateL(*this, *iImage, CMdaBitmapRotator::ERotation270DegreesClockwise); 

Application code is informed of the completion of a rotation through the MMdaImageUtilObserver mechanism with a resulting call to MMdaImageUtilObserver::MiuoConvertComplete() .

Series 60 2.x

CBitmapRotator carries out image rotation via the asynchronous Rotate() function. Once more a trequestStatus object is used to inform the calling class that the operation is complete. As with CMdaBitmapRotator::RotateL() , there are two versions of the CBitmapRotator::Rotate() function available, depending on whether or not the resultant rotation is placed into a separate bitmap object.

[View full width]
 
[View full width]
void Rotate(TRequestStatus* aRequestStatus, CFbsBitmap& aSrcBitmap, CFbsBitmap& aTgtBitmap , TRotationAngle aAngle); void Rotate(TRequestStatus* aRequestStatus, CFbsBitmap& aBitmap, TRotationAngle aAngle);

The trotationAngle enumeration has been enhanced to also include EMirrorHorizontalAxis and EMirrorVerticalAxis , which, as their names suggest, will mirror the image along the horizontal and vertical axis, respectively.

 iRotator->Rotate(&iStatus, *iImage, CBitmapRotator::ERotation90DegreesClockwise); iRotator->Rotate(&iStatus, *iImage, CBitmapRotator::ERotation180DegreesClockwise); iRotator->Rotate(&iStatus, *iImage, CBitmapRotator::ERotation270DegreesClockwise); iRotator->Rotate(&iStatus, *iImage, CBitmapRotator::EMirrorHorizontalAxis); iRotator->Rotate(&iStatus, *iImage, CBitmapRotator::EMirrorVerticalAxis); 

Image Scaling

Scaling an image is programmatically similar to rotation.

Series 60 1.x

CMdaBitmapScaler executes scaling using the asynchronous ScaleL() function. Again, MMdaImageUtilObserver::MiuoConvertComplete() is called on completion. Like rotation, scaling can result in a new bitmap object, or merely be performed on an existing bitmap.

 void CImageManipAdapter::ScaleImageL()    {    if (iScaleState == EScaleDown)       {       iScaler->ScaleL(*this, *iImage, TSize(KScaledWidthOne, KScaledHeightOne));       iScaleState = EScaleUp;       }    else       {       iScaler->ScaleL(*this, *iImage, TSize(KScaledWidthTwo, KScaledHeightTwo));       iScaleState = EScaleDown;       }    } 

Series 60 2.x

The CBitmapScaler class is used to scale bitmaps by calling its Scale() method. Scale() is overloaded to allow for scaling to a separate CFbsBitmap object. As Scale() is asynchronous, it should be called by an Active Object, which provides the trequestStatus parameter, as required.

 void CMultiMediaFAdapter::ScaleImage()    {    if (iScaleState == EScaleDown)       {       iScaler->Scale(&iStatus, *iImage, TSize(KScaledWidthOne, KScaledHeightOne));       iScaleState = EScaleUp;       }    else       {       iScaler->Scale(&iStatus, *iImage, TSize(KScaledWidthTwo, KScaledHeightTwo));       iScaleState = EScaleDown;       }    SetActive();    } 

As has been discussed, there are subtle differences between the multimedia architectures of Series 60 1.x and Series 60 2.x. There is a facility to use multithreading on Series 60 2.x, while handling the asynchronous nature of the manipulation APIs is performed by an observer on Series 60 1.x, and a user-defined Active Object on Series 60 2.x. Table 11-12 summarizes the main differences between platform versions.

Table 11-12. Overview of Differences between Series 60 1.x and 2.x

Operation

Series 60 1.x

Series 60 2.x

Execution

Active Objects only

Threads or Active Objects

Asynchronous Function Handling

MMdaImageUtilObserver mechanism

CActive::RunL() approach

Decoding

CMdaImageDescToBitmapUtility

CMdaImageFileToBitmapUtility

CImageDecoder

Encoding

CMdaImageBitmapToDescUtility

CMdaImageBitmapToFileUtility

CImageEncoder

Rotation

CMdaBitmapRotator

CBitmapRotator

Scaling

CMdaBitmapScaler

CBitmapScaler

Import Library

MediaClientImage.lib

ImageConversion.lib (encode/decode)

BitmapTransforms.lib (rotation/scaling)

Include Header

MediaClientImage.h

ImageConversion.h (encode/decode)

BitmapTransforms.h (rotation/scaling)


You should use an image-manipulation "Adapter" class to contain all of the platform-dependent manipulation code needed ”application code then only has to interact with this adapter. For Series 60 1.x your adaptor should derive from MMdaImageUtilObserver , while on Series 60 2.x it should derive from CActive . The adapter class will probably need to be implemented as some form of state machine to keep track of the current operation ”an enumeration value is often used to represent the current state.




Developing Series 60 Applications. A Guide for Symbian OS C++ Developers
Developing Series 60 Applications: A Guide for Symbian OS C++ Developers: A Guide for Symbian OS C++ Developers
ISBN: 0321227220
EAN: 2147483647
Year: 2003
Pages: 139

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net