Partager via


Chapter 11: Using the Windows Imaging Component

The Windows 7 Imaging Component (WIC) allows you to load and manipulate images and their metadata. The WIC Application Programming Interface (API) has built-in component support for all standard formats. In addition, the images created by the WIC can be used to create Direct2D bitmaps so you can use Direct2D to change images. In this article you will learn how the Windows 7 Imaging Component is used in the Hilo Browser and Annotator applications.

Introducing the WIC

Both the Hilo Browser and Annotator display photos, and Annotator allows you to alter photos. For Hilo the definition of a photo is any image type so the Hilo applications have to be able to load and display a wide range of file types. The Windows 7 Imaging Component provides this functionality. The WIC can load images that are made up of multiple frames and it can access metadata in the image file. The WIC supports all the common image formats and even allows developers to develop codecs (coder-decoder components) for new formats.

To use the WIC you must include the wincodec.h header file, it contains the definitions for the various interfaces used by the WIC, definitions of structures and GUIDs for the WIC objects, and the standard pixel formats. The WIC is not just one component, instead there are several components used to encode and decode the different formats supported. Different image formats store image data in different ways, so to load an image file you need a decoder component to decode the data into a format your application can use. When you save image information you need an encoder component to encode the data into the format defined by the image file format. If you know the type of image you wish to load then you can create a decoder with a call to CoCreateInstanceEx and provide the Class ID (CLSID) of the decoder object. If you do not know the image type you can use the WIC API to examine the file and choose the appropriate object. To do this you create an instance of the WIC factory object.

Listing 1 shows the Direct2DUtility::GetWICFactory method that is used by the Hilo applications to create an instance of the factory object. Like all the other WIC objects, the factory object is a COM object and so this code must be called in a COM apartment. You can initialize either an STA or an MTA apartment.

Listing 1 Creating a WIC factory

HRESULT Direct2DUtility::GetWICFactory(IWICImagingFactory** factory)
{
   static ComPtr<IWICImagingFactory> m_pWICFactory;
   HRESULT hr = S_OK;

   if (nullptr == m_pWICFactory)
   {
      hr = CoCreateInstance(
            CLSID_WICImagingFactory, nullptr,
            CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&m_pWICFactory) );
   }

   if (SUCCEEDED(hr))
   {
      hr = AssignToOutputPointer(factory, m_pWICFactory);
   }

   return hr;
}

The IWICImagingFactory interface has methods that allow you to create decoders and encoders, bitmaps and streams; and it allows you to create objects to alter color palettes and metadata in an image file.

Handling Images

Conceptually bitmap graphics can be viewed as rows of pixels with the color of each pixel composed of color components such as red, blue, and green or cyan, magenta ,and yellow, and an alpha channel component to determine opacity. If these components have 256 values, involving 32-bits per pixel, this would mean that a 1000 by 1000 pixel image would take up a megabyte of storage. Since images of this resolution are fairly standard, and storage space is always at a premium, there is a need to compress the way that pixels are represented to reduce image size. In most images there are repetitions of pixels, and most images do not use the full gamut of colors, and so image compression techniques have been developed to reduce this redundancy. Some techniques are lossless, so that all the original pixels can be recreated during decompression and others are lossy meaning that to the human eye the decompressed image looks like the original but does not necessarily have the same pixels.

A codec is code that compresses and decompresses images, and code that compresses an image is called an encoder and the code that decompresses images is called a decoder. Your graphics card will have its own image format and so a codec must be able to convert the image format used by an image file to and from a form that Windows 7 can use. The WIC decoders can provide raw pixel information that can be used with GDI functions. In addition, Direct2D can create a bitmap object (with an ID2D1Bitmap interface) from a WIC image so you can use Direct2D to draw an image loaded by WIC. You can also create a Direct2D render target based on a WIC image, which means that you can use Direct2D drawing actions to change the WIC image.

Images can be provided in several formats. Typically an image will be a file on a hard disk so WIC provides methods to load an image from a file name or a file handle. Images may also be stored as streams within other files (OLE documents) so WIC provides methods to load from an OLE stream (the IStream interface). Images may be stored as resources as part of executables and the WIC API provide methods to load images through HBITMAP or HICON data.

In addition to pixel information most image formats contain metadata. Digital cameras use metadata to store information such as the camera settings (ISO, shutter speed, aperture, flash) and information about the photo (time, date, GPS reading). There are several standard metadata formats: Exif, XMP, IPTC, are three of the common formats supported by WIC. These formats determine how the metadata are stored in an image file and also define standard metadata items. It is clearly the responsibility of a codec to handle the metadata formats used by the image type.

Some image formats support storing more than one image in a file, for example GIF images and some TIFF images. Many digital cameras also provide a thumbnail image as well as the full size image. WIC provides methods to get each of the images in a file as a frame, and it supports loading global thumbnails and frame-based thumbnails. The standard decoders do not support accessing global image data, so even if the image file only contains one image you still have to load that as a single frame and then use a frame decoder to get information about that frame.

Using Bitmap Sources

The WIC API provides a polymorphic collection of objects to give access to bitmap images. The base interface of these objects is IWICBitmapSource, which provides the pixel format, the size and resolution of the image, and allows you to make a copy of the pixels. The other interfaces are for objects that add additional functionality, for example to resize the image, to change the pixel format, or rotate the image. These interfaces are shown in Figure 1.

Figure 1 WIC Bitmap interfaces

Ff973956.c6f125ce-545e-4e84-b002-04d55a623862-thumb(en-us,MSDN.10).png

The interface hierarchy means that wherever an IWICBitmapSource interface is required an instance of any of the other interfaces can be used. This means that you can pass an IWICBitmapScaler interface, implemented by a scaler object, to a function that requires an IWICBitmapSource. As the function reads information about the bitmap through the interface, the scaler object will perform scaling automatically on the information. Most of the child interfaces shown in Figure 1 simply add an Initialize method to the IWICBitmapSource interface to enable you to provide information about how the object works, for example for the IWICBitmapScalar interface the Initialize method provides the new height and width to which the image will be scaled.

Loading Images with the WIC

To load an image you have to use a decoder object that implements the IWICBitmapDecoder interface. The decoder object will decode only a specific image format and before you can use the decoder you must provide it with a stream that contains the image of the correct format. To do this you call the IWICBitmapDecoder::Initialize method. Once the decoder has been initialized you can call the IWICBitmapDecoder::GetFrameCount method to obtain the number of frames in the image and you can call the IWICBitmapDecoder::GetFrame method with an index to get a specific decoded frame which is returned through an object with the IWICBitmapFrameDecode interface.

The IWICBitmapFrameDecode interface derives from the IWICBitmapSource interface and so has methods to obtain information about the image. For example, you can call the IWICBitmapSource::GetPixelFormat to get information about the pixel format (one of the GUIDs given on the Native Pixel Formats Overview page) and then obtain the actual pixels by calling the IWICBitmapSource::CopyPixels method. You can also call the IWICBitmapFrameDecode::GetMetadataQueryReader method to obtain a metadata query object with the IWICMetadataQueryReader interface through which you can obtain metadata about the frame.

Images can have different pixel formats varying by the number of bits used for each color component, the order of the color components, whether there is an alpha channel ,and whether a palette is used. Direct2D requires bitmap sources to be in the 32bppPBGRA format which means that each pixel is 4 bytes with one byte each for blue, green, red, and the alpha channel, in that order. To render an image on a Direct2D render target you must convert an image to this format and make the image through an object with an ID2D1Bitmap interface.

Listing 2 Loading an image as a Direct2D bitmap

ComPtr<IWICBitmapDecoder> decoder;
ComPtr<IWICBitmapFrameDecode> bitmapSource;
ComPtr<IWICFormatConverter> converter;

ComPtr<IWICImagingFactory> wicFactory;
GetWICFactory(&wicFactory);

wicFactory->CreateDecoderFromFilename(
   uri, // name of the file
   nullptr, GENERIC_READ, WICDecodeMetadataCacheOnLoad, &decoder);

decoder->GetFrame(0, &bitmapSource);
wicFactory->CreateFormatConverter(&converter);
converter->Initialize(
   bitmapSource, GUID_WICPixelFormat32bppPBGRA,
   WICBitmapDitherTypeNone, nullptr, 0.f,
   WICBitmapPaletteTypeMedianCut);
// Create a Direct2D bitmap from the WIC bitmap.
hr = renderTarget->CreateBitmapFromWicBitmap(converter, nullptr, bitmap);

Listing 2 is code taken from the Direct2DUtility::LoadBitmapFromFile method in the Hilo Common project. This method is used by the Browser and Annotator applications to load image files. First the code obtains the WIC factory object and then it loads the decoder for the image by calling the IWICImagingFactory::CreateDecoderFromFilename method where the uri variable is the path to the image file. This method activates the appropriate decoder for the image type, loads the image file, and initializes the decoder with the image, so you do not need to call the Initialize method on the IWICBitmapDecoder interface returned from this method. The code in Listing 2 then calls GetFrame for the first frame in the bitmap to get the decoded image. The IWICBitmapFrameDecode interface uses the decoder object whenever information is read from the image.

The pixel format of this image is unlikely to be the 32bppPBGRA format so Listing 2 creates a converter object. The IWICFormatConverter interface derives from IWICBitmapSource and in essence, it delegates calls to this interface to the raw image, performing conversions where necessary. Finally, the code calls the ID2D1RenderTarget::CreateBitmapFromWicBitmap method to provide an object with the ID2D1Bitmap interface using data from the converted IWICBitmapSource object. The returned variable (in Listing 2 this is the bitmap variable) can be rendered on an ID2D1RenderTarget object and a Direct2D render target can be created from this bitmap to allow you to change the image using the Direct2D drawing operations. When you call the Direct2D bitmap object, the call is passed to the format converter, which calls the frame decoder, which calls the decoder object. At each stage the data is converted.

Manipulating Images with the WIC

Listing 2 shows that you can create a WIC object to change the format of the bitmap data that is supplied by an image, you can use the same technique to alter other aspects of the image. For example, you can use a scalar object to automatically resize an image. This is illustrated by the Direct2DUtility::LoadBitmapFromFile method. The code shown in Listing 2 loads the image without scaling, the code in Listing 3 shows the additional code to provide scaling.

Listing 3 Providing scaling

ComPtr<IWICBitmapScaler> scaler;

if (destinationWidth != 0 || destinationHeight != 0)
{
   unsigned int originalWidth, originalHeight;
   bitmapSource->GetSize(&originalWidth, &originalHeight);
   if (destinationWidth == 0)
   {
      float scalar = static_cast<float>(destinationHeight) / static_cast<float>(originalHeight);
      destinationWidth = static_cast<unsigned int>(scalar * static_cast<float>(originalWidth));
   }
   else if (destinationHeight == 0)
   {
      float scalar = static_cast<float>(destinationWidth) / static_cast<float>(originalWidth);
      destinationHeight = static_cast<unsigned int>(scalar * static_cast<float>(originalHeight));
   }

   wicFactory->CreateBitmapScaler(&scaler);
   scaler->Initialize(
         bitmapSource, destinationWidth, destinationHeight, WICBitmapInterpolationModeCubic);
   converter->Initialize(
         scaler, GUID_WICPixelFormat32bppPBGRA, WICBitmapDitherTypeNone,
         nullptr, 0.f, WICBitmapPaletteTypeMedianCut);
}
else // Don't scale the image.
{
   converter->Initialize(
      bitmapSource, GUID_WICPixelFormat32bppPBGRA, WICBitmapDitherTypeNone,
      nullptr, 0.f, WICBitmapPaletteTypeMedianCut);
}

If the caller of the LoadBitmapFromFile method provides either a new width or a new height then a scalar object is created. If just one dimension is specified then the new image is scaled maintaining the aspect ratio of the image. If you want to change the aspect ratio then you can provide both the new height and the new width. Once the code has determined the new height and width, it creates the scalar object through the WIC factory object by calling the IWICImagingFactory::CreateBitmapScalar method. This scalar object is then initialized by calling the IWICBitmapScalar::Initialize method providing the bitmap source (in this case the frame decoder) and the scaling parameters. The final parameter of this method indicates how the scaling is performed and in this case bicubic interpolation is used. Finally, the pixel format converter is initialized with the scalar, rather than the frame decoder, to add scaling when the image is accessed.

Drawing Images with the WIC

The WIC allows you to perform basic manipulation of an image: scaling, cropping, rotation, and mirroring. If you want to do more complex manipulations then you have to change the individual pixels yourself. Hilo does this by creating a Direct2D render target based on the image you load with the WIC. When you draw on an image, crop it, rotate, or mirror it, the action will be carried out on the Direct2D render target rather than the WIC bitmap. In Hilo, when you save the changes a copy is made of the original and then the changed image is saved to disk.

When started, Annotator locates the image folder and the specified photo in the folder. Information about the photo being edited is stored in an instance of the class SimpleImage. This class holds an instance of the IShellItem reference to the shell item, so that it knows the location of the original file. It has an IWICBitmap reference on the object that the WIC loaded, which is the original image, and an ID2D1Bitmap reference to the Direct2D converted from the WIC bitmap which is where changes are made.

In addition there are two vectors, m_imageOperations and m_redoStack. Each item in these vectors is an interface pointer of the type IImageOperation and there are three classes that implement this interface: DrawGeometryOperation, ImageClippingOperation, and ImageTransformationOperation. When you perform a mutable operation on the image, an instance of one of these classes is created and initialized with details of the operation. and stored in the m_imageOperations vector so that Annotator has a list of operations that have been performed.

When you draw on an image, this information is stored in the m_imageOperations vector as a DrawGeometryOperation object. This object has information about the color and the pen width, and a vector of the points visited during the operation. To draw the image, the location information is used to create an instance of the Direct2D geometry object in the DrawGeometryOperation::UpdateGeometry method by drawing a Bézier segment between each point and adding these Bézier curves to an ID2D1PathGeometry object.

What you see on screen is the result of applying the operations in the m_imageOperations vector, in order, on the IWICBitmap object. When you click the undo button the last item is removed from the m_imageOperations vector and placed at the top of the m_redoStack vector. The m_redoStack vector is cleared whenever you perform another operation, so it means that only during the time between undoing an operation and performing another operation can you redo an operation.

When the image is drawn on screen, for example, in response to the user resizing the Annotator window, the method SimpleImage::DrawImage is called. This method first draws the ID2D1Bitmap object, which is converted from the IWICBitmap image loaded by WIC, to the render target of the image editor. Then the DrawImage method iterates through each of the drawing operations and applies each one to the render target, and pen drawing operations are applied by calling the ID2D1RenderTarget::DrawGeometry method using the ID2D1PathGeometry object.

Saving Images with the WIC

In Hilo there are two steps to saving images. The first step is to create a WIC bitmap with the changes that you have performed in the image editor. The second step is to save the WIC bitmap to disk.

Creating a WIC Bitmap

When you save an image, the command handler calls the SimpleImage::Save method. The first action of this method is to create a WIC bitmap that will eventually be saved to disk. Listing 4 shows the code to do this. First the code determines the size of the new image from the dimensions of the cropped image and from information about any rotations that have been applied. Second, the code calls the IWICImagingFactory::CreateBitmap method using this size. It is important to note that the WIC bitmap is created with the same pixel format (GUID_WICPixelFormat32bppBGR) as the Direct2D bitmap that is used.

Listing 4 Creating a new WIC bitmap

ComPtr<IWICImagingFactory> wicFactory;
ComPtr<IWICBitmap> wicBitmap;

Direct2DUtility::GetWICFactory(&wicFactory);

// Adjust height and width based on current orientation and clipping rectangle
float width = m_isHorizontal ? 
   Direct2DUtility::GetRectWidth(m_clipRect) : Direct2DUtility::GetRectHeight(m_clipRect);
float height = m_isHorizontal ? 
   Direct2DUtility::GetRectHeight(m_clipRect) : Direct2DUtility::GetRectWidth(m_clipRect);

wicFactory->CreateBitmap(
   static_cast<unsigned int>(width), static_cast<unsigned int>(height),
   GUID_WICPixelFormat32bppBGR,
   WICBitmapCacheOnLoad, &wicBitmap);

The bitmap created in Listing 4 is empty, so the SimpleImage::Save method must now construct the bitmap from the original and the drawing operations. The first step is to create a render target based on the WIC bitmap and then load the original image into that render target. This is shown in Listing 5. First, this code calls the ID2D1Factory::CreateWicBitmapRenderTarget method, passing the WIC bitmap that was created in Listing 4. The render target returned is like any other render target: you can draw in it using Direct2D resources. The difference is that the pixels that are changed are in the WIC bitmap, rather than the screen. The final part of Listing 5 loads the original image file into the render target so that the drawing operations will be applied to this image.

Listing 5 Creating a render target based on a WIC bitmap

ComPtr<ID2D1Factory> d2dFactory;
ComPtr<ID2D1RenderTarget> wicRenderTarget;
Direct2DUtility::GetD2DFactory(&d2dFactory);

d2dFactory->CreateWicBitmapRenderTarget(
   wicBitmap, D2D1::RenderTargetProperties(), &wicRenderTarget);
Direct2DUtility::LoadBitmapFromFile(
      wicRenderTarget, m_imageInfo.fileName.c_str(), 0, 0, &m_bitmap);

The next part of the SimpleImage::Save method performs the drawing operations that were stored in the m_imageOperations vector, Listing 6. The first part creates a transformation so that any rotations performed on the image will be applied to the center of the cropped image. The significant code in Listing 6 is at the end. The Save method changes the instance variable m_currentRenderTarget to be the render target based on the WIC bitmap and calls the SimpleImage::DrawImage method. This means that the drawing operations are applied to the WIC render target and also on the WIC bitmap.

Listing 6 Applying the drawing operations on the WIC bitmap

// Get the original bitmap rectangle in terms of the current crop
D2D1_RECT_F originalBitmapRect = D2D1::RectF(
   0, 0, Direct2DUtility::GetRectWidth(m_clipRect), 
   Direct2DUtility::GetRectHeight(m_clipRect));

// When rotating images make sure that the point around which rotation occurs lines
// up with the center of the rotated render target
if (false == m_isHorizontal)
{
   float offsetX;
   float offsetY;

   if (width > height)
   {
      offsetX = (width - height) / 2;
      offsetY = -offsetX;
   }
   else
   {
      offsetY = (height - width) / 2;
      offsetX = - offsetY;
   }

   D2D1_MATRIX_3X2_F translation = D2D1::Matrix3x2F::Translation(offsetX, offsetY);
   wicRenderTarget->SetTransform(translation);
}

// Update current render target to point to WIC render target
m_currentRenderTarget = wicRenderTarget;

// Draw updated image to WIC render target
wicRenderTarget->BeginDraw();
DrawImage(originalBitmapRect, m_clipRect, true);
wicRenderTarget->EndDraw();

The remainder of the Save method makes a backup copy of the original file in a subfolder called AnnotatorBackup and then calls the Direct2DUtility::SaveBitmapToFile method to save the WIC bitmap.

Using WIC Encoders

Writing image data is a little more complicated than reading it. You need to have an encoder so that the data is written to the file in the correct format, but since you will be potentially be writing large amounts of data you will also need to initialize the encoder to use block transfers of data, both of the pixels and any metadata that needs to be copied.

The IWICBitmapEncoder interface is the starting point for encoding, and the WIC API provides implementations for all the common file formats. To create an encoder you call the IWICImagingFactory::CreateEncoder passing a GUID identifying the required encoder. You must then initialize the encoder with a call to the IWICBitmapEncoder::Initialize method and provide a stream to the file where the data will be saved. After this you can call the various Set methods on the encoder to write data such as the palette or a thumbnail for the image. When you have finished writing to the encoder and want to close the stream and the file it is based upon, you must call the IWICBitmapEncoder::Commit method.

The actual pixel data is provided as one or more frame objects, so you must call IWICBitmapEncoder::CreateNewFrame for each frame to obtain an IWICBitmapFrameEncode interface for the specific frame. This interface has a collection of Set methods to set data for the frame such as the size of the image, the resolution, or the pixel format. You write the actual pixel data through calls to either the IWICBitmapFrameEncode::WritePixels method (to write an individual scan line with a buffer of bytes) or the IWICBitmapFrameEncode::WriteSource method (to write the entire frame from an IWICBitmapSource object). When you have finished writing a frame you must call IWICBitmapFrameEncode::Commit method to indicate that the frame can be written to the stream.

As mentioned earlier, a frame may have metadata and you can obtain a IWICMetadataQueryWriter object by calling the IWICBitmapFrameEncode::GetMetadataQueryWriter method. However, if you already have a bitmap source object with metadata you can copy the metadata as a block by using metadata block readers and writers. The frame decoder supports block metadata reading through the IWICMetadataBlockReader interface. Normally you would not use this interface directly; instead you use it to initialise a metadata block writer. A frame encoder implements the IWICMetadataBlockWriter interface and you can initialize the block writer by calling the IWICMetadataBlockWriter::InitializeFromBlockReader method passing the block reader object. This method will then read all the metadata and write them to the image file stream when the frame is committed.

Saving a WIC Bitmap in Annotator

In Hilo the Common project provides the Direct2DUtility::SaveBitmapToFile method to obtain an encoder and save an image to disk. The first part of this method needs to get information about the file that it is creating, so first it calls IWICImagingFactory::CreateDecoderFromFilename to get a decoder for the original file and through this object the SaveBitmapToFile method can determine how many frames there are in the original image file. Annotator only allows you to edit the first frame, and an image file may contain more than one frame, so when saving the altered file the first frame will be the image altered in Annotator and any other frames are simply copied from the original file.

The next part of the method is to create an encoder, and this code is shown in Listing 7. To create an encoder using the factory object you have to supply the encoder type and the SaveBitmapToFile method determines the type from the file extension.

Listing 7 Creating an encoder

GUID containerFormat = GUID_ContainerFormatBmp;
ComPtr<IWICImagingFactory> factory;
ComPtr<IWICBitmapEncoder> encoder;
Direct2DUtility::GetWICFactory(&factory);

// File extension to determine which container format to use for the output file
std::wstring fileExtension(outputFilePath.substr(outputFilePath.find_last_of('.')));
// Convert all characters to lower case
std::transform(fileExtension.begin(), fileExtension.end(), fileExtension.begin (), tolower);

// Default value is bitmap encoding
if (fileExtension.compare(L".jpg") == 0 ||
    fileExtension.compare(L".jpeg") == 0 ||
    fileExtension.compare(L".jpe") == 0 ||
    fileExtension.compare(L".jfif") == 0)
{
   containerFormat = GUID_ContainerFormatJpeg;
}
else if (fileExtension.compare(L".tif") == 0 ||
          fileExtension.compare(L".tiff") == 0)
{
   containerFormat = GUID_ContainerFormatTiff;
}
// Other code omitted

factory->CreateEncoder(containerFormat, nullptr, &encoder);

After creating the encoder you must initialize it with a stream that indicates where the data is to be written. Listing 8 shows the code to do this. The factory object is used to create a stream and the stream is initialized with the path to the output file. The stream is then used to initialize the encoder object.

Listing 8 Initializing the encoder

ComPtr<IWICStream> stream;
// Create a stream for the encoder
factory->CreateStream(&stream);
// Initialize the stream using the output file path
stream->InitializeFromFilename(outputFilePath.c_str(), GENERIC_WRITE);
// Create encoder to write to image file
encoder->Initialize(stream, WICBitmapEncoderNoCache);

At this point you have three objects: the decoder with the original data, the WIC bitmap with the changed bitmap, and the encoder that is initialized with a stream to the location to store the data. The final part of the SaveBitmapToFile method is to write the WIC bitmap as the first frame to the encoder and if the original file has more than one frame then all the other frames are read from the decoder and written to the encoder. Each time the SaveBitmapToFile method writes a frame it must write the metadata for that frame and the simplest way to do this is through metadata block readers and writers.

Listing 9 shows the first part of the loop to write a frame. The first part of this code obtains the frame decoder for the specified frame and creates a new frame in the encoder, it then sets the size of the new frame and if this is the first frame the size is taken from the WIC bitmap. Otherwise it is taken from the frame read from the decoder from the original file. Next, the code reads the pixel format of the frame from the original file and uses this to set the frame encoder.

Listing 9 Creating a frame using an encoder

ComPtr<IWICMetadataBlockWriter> blockWriter;
ComPtr<IWICMetadataBlockReader> blockReader;

for (unsigned int i = 0; i < frameCount && SUCCEEDED(hr); i++)
{
   //Frame variables
   ComPtr<IWICBitmapFrameDecode> frameDecode;
   ComPtr<IWICBitmapFrameEncode> frameEncode;

   //Get and create image frame
   decoder->GetFrame(i, &frameDecode);
   encoder->CreateNewFrame(&frameEncode, nullptr);

   //Initialize the encoder
   frameEncode->Initialize(NULL);

   //Get and set size
   if (i == 0)
   {
      updatedBitmap->GetSize(&width, &height);
   }
   else
   {
      frameDecode->GetSize(&width, &height);
   }
   frameEncode->SetSize(width, height);

   //Set pixel format
   frameDecode->GetPixelFormat(&pixelFormat);
   frameEncode->SetPixelFormat(&pixelFormat);

The final part of the loop reads the data for the frame and writes the data through the encoder, as shown in Listing 10. First the code obtains the metadata block reader from the decoder and uses this to initialize the metadata block writer from the encoder so that the frame’s metadata is written in one action. Then the frame pixel data is written in one action with a call to IWICBitmapFrameEncode::WriteSource and passing either the WIC bitmap or the frame decoder object. The final part of the loop is to commit the frame, and then when all frames have been written, there is a call to IWICBitmapEncoder::Commit to write the entire image to the file.

Listing 10 Writing a frame with an encoder

   //Check that the destination format and source formats are the same
   bool formatsEqual = false;
   GUID srcFormat;
   GUID destFormat;

   decoder->GetContainerFormat(&srcFormat);
   encoder->GetContainerFormat(&destFormat);
   formatsEqual = (srcFormat == destFormat) ? true : false;

   if (formatsEqual)
   {
      //Copy metadata using metadata block reader/writer
      frameDecode->QueryInterface(&blockReader);
      frameEncode->QueryInterface(&blockWriter);

      if (nullptr != blockReader && nullptr != blockWriter)
      {
         blockWriter->InitializeFromBlockReader(blockReader);
      }
   }

   if (i == 0)
   {
      // Copy updated bitmap to output
      frameEncode->WriteSource(updatedBitmap, nullptr);
   }
   else
   {
      // Copy existing image to output
      frameEncode->WriteSource(static_cast<IWICBitmapSource*> (frameDecode), nullptr);
   }

   //Commit the frame   
   frameEncode->Commit();
}
encoder->Commit();

Conclusion

The Windows 7 Imaging Component allows you to load images with standard image formats from files, resources, and streams. It will decode the image data into a format that you can use with GDI or Direct2D. Hilo uses the WIC in both the Browser application and the Annotator application to display images, and the Annotator application uses the WIC to save edited photos to disk. In the next chapter we will introduce the Hilo Share application, which allows you to share your images online.

Next | Previous | Home