Volume 8, Number 6 / June 2004

Chandler, Using GDI+, Part II: Bitmaps


Using GDI+, Part II: Bitmaps

By Damon Chandler

 

 

L

ast month, I demonstrated how to set up GDI+ for use in a VCL application. This month, I’ll show you how to work with bitmaps in GDI+ and how to use GDI+ drawing routines to effect output to a TBitmap object.

 

Raster- vs. vector-based rendering

Drawing anything to a digital device ultimately boils down to highlighting certain pixels and turning off others. A digital image is created by defining a particular pattern of pixels, one which our visual system interprets as a representation of a scene [1].

 In Windows, there are two main ways to render a digital image to an output device (e.g., a screen, printer). One approach is to load/create a bitmap and then transfer this bitmap to the device via, e.g., the BitBlt() GDI function or the Graphics::DrawImage() GDI+ method; this technique is typically called bitmapped or raster-based rendering. The other approach is to use a higher-level “language” to communicate to the output device the lines, shapes, and other graphics primitives that you want drawn; this latter technique is called vector-based rendering.

This month, I'll cover raster-based rendering with GDI+ bitmaps; and, next time, we'll tackle vector-based rendering (with metafiles). Raster-based rendering is typically faster than vector-based rendering, but this speed comes at a cost. Namely, bitmaps lack scalability—if you want to draw a larger version of a bitmapped image, you'll need to perform some form of interpolation.

 

GDI+ bitmaps

Recall that there are three types of bitmaps in the standard GDI: device-dependent bitmaps (DDBs); device-independent bitmaps (DIBs); and DIB section bitmaps.; see [2]. Also recall that whereas the pixel format—i.e., the number of bits with which each pixel is represented—of a DDB depends on the current screen settings, and whereas a DIB is not a true graphics object (meaning that you can’t select it into a device context), a DIB section bitmap suffers from neither of these limitations. Accordingly, when using the standard GDI, DIB section bitmaps are the preferred variety for caching and drawing raster-based graphics. Because GDI+ relies on the standard GDI as its backend, all bitmaps in GDI+ are effectively DIB section bitmaps.

Table 1: Commonly used methods of the Bitmap class

Method

Description

UINT GetWidth()

Returns the width of bitmap, in pixels.

UINT GetHeight()

Returns the height of the bitmap, in pixels.

PixelFormat GetPixelFormat()

Returns a PixelFormat-type constant [3] that identifies the number of bits used to represent each pixel.

Status LockBits(

  const Rect* rect,

  UINT flags,

  PixelFormat format, 

  BitmapData* lockedBitmapData

)

Retrieves/specifies a pointer to a buffer to the bitmap’s pixels; see “Accessing the pixels” later in this article.

Status UnlockBits(

  BitmapData* lockedBitmapData

 )

Effects to the bitmap any changes made to the buffer previously retrieved/specified via the LockBits() method; see “Accessing the pixels” later in this article.

Status Save(

  const WCHAR* filename,

  const CLSID* clsidEncoder,

  const EncoderParameters*

  encoderParams = NULL

 )

Saves the bitmap to a file specified by filename and in the file-format specified by clsidEncoder; the optional and file-format-specific encoderParams parameter can be used to specify attributes with which the file should be saved (e.g., compression quality for JPEGs).

You create a (DIB section) bitmap in GDI+ by using the Bitmap class. The Bitmap class is a descendant of the GDI+ Image class that provides bitmap-specific functionality such as creating a bitmap from a DDB or DIB, and accessing and modifying the bitmap’s pixels. Table 1 lists the Bitmap class’s key methods (many of which are inherited from the Image class); see [4] for a full listing.

Notice that Table 1 doesn’t list a method for loading a bitmap from a file. As I’ll discuss next, loading a bitmap is typically done by using the Bitmap constructor.

 

Bitmap constructors

The Bitmap class provides 10 constructors which are used to create GDI+ bitmaps from various sources and/or with various attributes. For applications in which a bitmap is used as an in-memory drawing surface, you typically need a bitmap of a specific pixel format and of a specific size. Here’s the declaration of the Bitmap constructor that you’d use in that case:

 

void Bitmap(

  INT width,

  INT height,

  PixelFormat format

 );

 

The width and height parameters specify the dimensions of bitmap, in pixels; and the format parameter specifies the bitmap’s pixel format [3]. For example, the following code snippet demonstrates how to create a 512x512, 24-bits-per-pixel (bpp) bitmap:

 

// create a 24-bpp 512x512 bitmap

gdp::Bitmap bitmap(512, 512,

  PixelFormat24bppRGB);

GDPCheck(bitmap.GetLastStatus());

// other code here...

 

Note that all of the pixels of the newly created bitmap are initially set to zero. (Also, recall that the GDPCheck() function was defined in last month’s article.)

Another common requirement is the ability to load a bitmap from a file; here’s the Bitmap constructor to do that:

 

void Bitmap(

  const WCHAR* filename,

  BOOL useIcm = FALSE

 );

 

The filename parameter specifies the name of .BMP file to load, and the optional useIcm parameter that specifies whether or not the method should acknowledge the bitmap’s embedded color-profile information, if present. Listing A demonstrates how to load a bitmap from a file; the Bitmap object is created on the heap—so that it remains alive throughout the form’s lifetime—and it is destroyed in the form’s destructor before calling the GdiPlusShutdown() function.

The Bitmap class also provides a constructor to load a bitmap from a resource instead of from a file. To load a bitmap from a resource, you’d use the following Bitmap constructor:

 

void Bitmap(

  HINSTANCE hInstance,

  const WCHAR* bitmapName

 );

 

The hInstance parameter specifies the handle to the (application or li­brary) instance that contains the re­source identified by the bitmapName parameter. (This method is similar to the LoadFromResourceName() method of the TBitmap class.)

The Bitmap class also provides constructors that allow you to create a bitmap based on an existing DDB, DIB, or even a raw array of pixels:

 

// creation based on a DDB
// (or DIB section bitmap)

void Bitmap(

  HBITMAP hbm,

  HPALETTE hpal

 );

 

// creation based on a DIB

void Bitmap(

  const BITMAPINFO* gdiBitmapInfo,

  VOID* gdiBitmapData

 );

 

// creation based on a raw array

void Bitmap(

  INT width,

  INT height,

  INT stride,

  PixelFormat format,

  BYTE* scan0

 );    

 

For creation based on a DDB, the Bitmap constructor takes two parameters: a handle to the DDB (hbm; this can also be a handle to a DIB section bitmap) and a handle to the DDB’s palette (hpal, which can be NULL if the DDB doesn’t use a palette). The Bitmap object won’t take ownership of the DDB; rather, it creates it’s own bitmap representation (DIB section) based on the DDB.

Listing A: Loading a bitmap

//-----------------------------------------------

// in header...

//-----------------------------------------------

#include <memory>

class TForm1 : public TForm

{

// other stuff...

 

private:

  ULONG_PTR gdp_token_;

  std::auto_ptr<gdp::Bitmap> bitmap_;

  void __fastcall LoadBitmap(WideString fname);

};

 

//-----------------------------------------------

// in source...

//-----------------------------------------------

__fastcall TForm1::TForm1(

  TComponent* Owner) : TForm(Owner)

{

  // initialize the GDI+ library

  GDPCheck(

    gdp::GdiplusStartup(&gdp_token_,

      &gdp::GdiplusStartupInput(), NULL

      ));

}

 

__fastcall TForm1::~TForm1()

{

  // delete the Bitmap

  delete bitmap_.release();

 

  // close the GDI+ library

  gdp::GdiplusShutdown(gdp_token_);

}

 

void __fastcall TForm1::

  LoadBitmap(WideString fname)

{

  // load the bitmap from a file

  bitmap_.reset(

    new gdp::Bitmap(fname.c_bstr())

    );

 

  // if the bitmap did not

  // load successfully...

  gdp::Status const res =

    bitmap_->GetLastStatus();

  if (res != gdp::Ok)

  {

    // delete the invalid bitmap

    delete bitmap_.release();

    // throw an exception

    throw EGDIPlusError(res);

  }

 

  // NOTE: You should check that

  // bitmap_.get() != NULL before

  // trying to access the bitmap

  // elsewhere in your code

For creation based on a DIB, the constructor also takes two parameters: a pointer to a DIB’s header and color table (gdiBitmapInfo), and a pointer to the DIB’s pixels (gdiBitmapData). Again, the Bitmap object doesn’t assume ownership of the DIB.

For creation based on a raw array of pixels, the Bitmap constructor takes five parameters: integers that specify the width and height of the image, an integer that specifies how many bytes of the array should be used for each row (stride), a PixelFormat-type variable [3] that specifies the pixel-format of the array (format), and a pointer to the beginning of the array (scan0).

The Bitmap class also provides four other constructors which are used to create a bitmap based on an IStream, based on a DirectDraw surface, based on the attributes of a GDI+ Graphics object, or based on an icon. I won’t discuss these constructors here, but you can see [5] for more information.

 

Drawing GDI+ bitmaps to the screen

Now that you know how to create GDI+ bitmaps, let me show you draw them to the screen. As I mentioned last month, this is accomplished by using the DrawImage() method of the GDI+ Graphics class [6]. There are actually 16 variants of the DrawImage() method, which allow you to draw the bitmap at (integer and floating-point) locations, using (integer and floating-point) scaling factors, and using a callback function.

For example, to draw a bitmap in its original size at location (x_pos, y_pos) in your form’s client area, you’d do the following:

 

// create a Graphics object

// associated with the form

gdp::Graphics graphics(Handle);

GDPCheck(graphics.GetLastStatus());

 

// draw the bitmap to the form
// at location (10, 20)

int const x_pos = 10;

int const y_pos = 20;

graphics.DrawImage(

  &bitmap, x_pos, y_pos

  );

 

Here, bitmap is assumed to be a pre-created Bitmap object. Similarly, here’s the code to draw a scaled version of the bitmap:

 

// create a Graphics object associated

// with the form

gdp::Graphics graphics(Handle);

GDPCheck(graphics.GetLastStatus());

 

// draw the bitmap to the form

// at location (10, 20) and in

// 20% of its original size

int const x_pos = 10;

int const y_pos = 20;

graphics.DrawImage(

  &bitmap, x_pos, y_pos,

  // new width

  bitmap.GetWidth() / 5,

  // new height

  bitmap.GetHeight() / 5

  );

 

And, to draw just a portion of the bitmap, you’d use the following approach:

 

// create a Graphics object associated

// with the form

gdp::Graphics graphics(Handle);

GDPCheck(graphics.GetLastStatus());

 

// draw the first 75 rows of the bitmap

// to the form at location (10, 20) and

// in 150% of its original size

gdp::Rect const RDest(

  // target (x, y)

  10, 20,

  // target width 

  1.5f * bitmap.GetWidth(),

  // target height

  1.5f * bitmap.GetHeight()

  );

graphics.DrawImage(

  &bitmap,

  // target (destination) rectangle

  RDest,

  // source coordinates (chunk of

  // the bitmap to draw)

  0, 0, bitmap.GetWidth(), 75,

  // source chunk is in pixel coords

  gdp::UnitPixel

  );

 

This latter version of the DrawImage() method operates in a fashion similar to the StretchBlt() GDI function. Note, however, that although I’ve used integer-valued coordinates in these examples, the DrawImage() method also allows you to specify floating-point-valued coordinates.

 

Drawing to GDI+ bitmaps

As with DDBs and DIB section bitmaps (but not DIBs), you can render output to a GDI+ bitmap by using normal drawing routines. In the standard GDI, you’d effect output to a bitmap object by selecting the bitmap into a memory device context (DC); you’d then call your drawing function, specifying a handle to that memory DC as the “target” DC. In GDI+, you use a similar approach. Instead of creating a memory DC, you create a Graphics object that’s associated with the bitmap.

Figure A

 

By creating a GDI+ Graphics object that’s associated with a GDI+ Bitmap, you can first draw to the bitmap, and then (using another Graphics object) draw the bitmap to the screen.

The following code snippet demonstrates how to create a Graphics object that’s associated with a bitmap and how to use that Graphics object to render output to the bitmap (and then to the screen); the result of this code is depicted in Figure A:

 

// create a 24-bpp 128x256 bitmap

int const bmp_cx = 128;

int const bmp_cy = 256;

gdp::Bitmap bitmap(bmp_cx, bmp_cy,

  PixelFormat24bppRGB);

GDPCheck(bitmap.GetLastStatus());

 

// create a Graphics object that's

// associated with the bitmap

gdp::Graphics bmp_graphics(&bitmap);

GDPCheck(bmp_graphics.GetLastStatus());

 

// create a brush with a horizontal

// white-to-black gradient that

// repeats after bmp_cx units

gdp::LinearGradientBrush brush(

  // gradient starting point

  gdp::Point(0, 0),

  // gradient ending/restarting point

  gdp::Point(bmp_cx, 0),

  // white to start

  gdp::Color(255, 255, 255),

  // black to end

  gdp::Color(0, 0, 0)

  );

GDPCheck(brush.GetLastStatus());

 

// fill the bitmap using the brush

bmp_graphics.FillRectangle(&brush,

  0, 0, bmp_cx, bmp_cy);

 

// create a Graphics object that's

// associated with the form

// and then draw the bitmap to the form

gdp::Graphics graphics(Handle);

GDPCheck(graphics.GetLastStatus());

graphics.DrawImage(&bitmap, 0, 0);

 

Note that this technique of first rendering output to a bitmap and then rendering the bitmap to the screen is known as double buffering; the bitmap serves as a so-called “back buffer” and the screen serves as the front or “primary buffer.”  Double buffering is useful for situations in which repetitive, complex rendering is required:  Instead of placing the time-consuming drawing code within, for example, the form’s OnPaint event handler, it makes more sense (assuming your drawing code doesn’t change) to execute the expensive code only once, storing the output in a bitmap, and then drawing this bitmap to the screen.

As note that the Graphics constructor which takes a Bitmap pointer as it’s single parameter (as used in this code) will fail—yielding a generic out-of-memory error message—if the Bitmap’s pixel format is PixelFormatUndefined, PixelFormatDontCare, PixelFormat1bppIndexed, PixelFormat4bppIndexed, PixelFormat8bppIndexed, PixelFormat16bppGrayScale, or PixelFormat16bppARGB1555. This is a documented limitation [6].

 

Accessing the bitmap’s pixels

One of the main advantages of using a DIB section bitmap over a DDB is that the standard GDI allows direct access to the DIB section’s pixels. Accordingly, GDI+ also permits direct access to a Bitmap’s pixel via the Bitmap::LockBits() method.

To use the LockBits() method, you specify: (1) the portion of the bitmap’s pixels to which you want access, (2) the type of access desired (reading, writing, or both), (3) the format of the pixels (more on this shortly), and (4) a pointer to a BitmapData object, which the LockBits() method will fill with information about the bitmap’s pixels. And, once you’re done examining and/or modifying the pixels, you use the Bitmap::UnlockBits() method to effect the changes.

For example, to convert a 32-bpp bitmap to grayscale, you’d use the Bitmap::LockBits() and UnlockBits() methods as follows:

 

// load the bitmap from a file

gdp::Bitmap bitmap(L"c:/pict_32bpp.bmp");

GDPCheck(bitmap.GetLastStatus());

 

// define the area of the bitmap to lock

UINT const bmp_cx = bitmap.GetWidth();

UINT const bmp_cy = bitmap.GetHeight();

gdp::Rect bmp_rect(0, 0, bmp_cx, bmp_cy);

 

// grab a pointer to the pixels

gdp::BitmapData bmp_data;

GDPCheck(bitmap.LockBits(

  // portion of the bitmap to lock

  &bmp_rect,

  // read-/write-access flags

  gdp::ImageLockModeRead |

  gdp::ImageLockModeWrite,

  // desired format of output pixels

  bitmap.GetPixelFormat(),

  // filled with bitmap data (including

  // a pointer to the pixels)

  &bmp_data

  ));

unsigned char* p_pixels =

  static_cast<unsigned char*>

    (bmp_data.Scan0);

 

for (UINT y = 0; y < bmp_cy; ++y)

{

  // grab an RGBQUAD* to each row

  RGBQUAD* p_scanline =

    reinterpret_cast<RGBQUAD*>(

      p_pixels + (y * bmp_data.Stride)

      );

  for (UINT x = 0; x < bmp_cx; ++x)

  {

    // compute grayscale

    unsigned short const gray_val =

      0.299f * p_scanline[x].rgbRed +

      0.587f * p_scanline[x].rgbGreen +

      0.114f * p_scanline[x].rgbBlue;

 

    // change the pixel

    p_scanline[x].rgbRed =

      p_scanline[x].rgbGreen =

        p_scanline[x].rgbBlue =

          (gray_val > 255) ?

            255 : gray_val;

  }

}

 

// unlock the bitmap (commits any

// changes made to the pixels)

bitmap.UnlockBits(&bmp_data);

 

Note that the desired pixel format, which is specified via the third parameter to LockBits(), doesn’t have to be the same as the pixel format of the bitmap. However, for 16-bpp, 24-bpp, and 32-bpp bitmaps, LockBits() will fail if you specify an output pixel-format that requires a color table (PixelFormat1bppIndexed, PixelFormat4bppIndexed, or PixelFormat8bppIndexed). Furthermore, if you request the data in a different format than that of the bitmap (e.g., if you request PixelFormat24bppRGB for a 32-bpp bitmap), GDI+ will have to create a temporary array to accommodate your new format. Although the documentation for the LockBits() method states that a temporary array is always created, I’ve found that this is not the case when you request the pixels in the same format as that of the bitmap. In short, it’s generally best to avoid format conversions when using LockBits()—i.e., pass the return value of the Bitmap::GetPixelFormat() as the third parameter to LockBits().

 

Listing B: Saving a bitmap

void GetEncoderCLSID(

   CLSID& enc_clsid,

   WideString mime_type

  )

{

  UINT num_encs, size_encs;

  // get the number of installed encoders and

  // the size in bytes required to hold the

  // ImageCodecInfo data for these encoders

  GDPCheck(

    gdp::GetImageEncodersSize(&num_encs, &size_encs)

    );

  if (num_encs < 1)

  {

    throw EGDIPlusError(

      "No GDI+ encoders installed."

      );

  }

 

  // create a buffer to hold the encoders' data

  unsigned char* const p_buffer = new

    unsigned char[size_encs];

  try

  {

    // cast the buffer to ImageCodecInfo*

    gdp::ImageCodecInfo* const p_encs =

      reinterpret_cast<gdp::ImageCodecInfo*>

        (p_buffer);

    // get the ImageCodecInfo for the encoders

    GDPCheck(gdp::GetImageEncoders(

      num_encs, size_encs, p_encs

      ));

 

    // run through the array of ImageCodecInfo,

    // looking for the CLSID of the desired encoder

    UINT idx;

    for (idx = 0; idx < num_encs; ++idx)

    {

      if (WideString(p_encs[idx].MimeType) ==

          mime_type)

      {

        enc_clsid = p_encs[idx].Clsid;

        break;

      }

    }

 

    // check that we found the encoder for

    // the specific MIME type

    if (idx == num_encs)

    {     

      throw EGDIPlusError(

        "The specified encoder is not installed."

        );

    }

  }

  __finally

  {

    // free the buffer

    delete [] p_buffer;

  }

}

 

void SaveBitmap(

   gdp::Bitmap& bitmap,

   WideString fname,

   WideString mime_type,

   gdp::EncoderParameters* p_save_options = NULL

  )

{

  // get the CLSID of the encoder

  CLSID enc_clsid;

  GetEncoderCLSID(enc_clsid, mime_type);

 

  // save the bitmap using the encoder

  // (see [8] for info on using save_options)

  GDPCheck(

    bitmap.Save(fname.c_bstr(), &enc_clsid,

      p_save_options)

    );

}

Saving bitmaps

After you’ve created and manipulated a GDI+ bitmap, you can save the output to a file by using the Bitmap::Save() method. The Save() method is actually inherited from the Image class, which allows you to save the bitmap to any file-format for which GDI+ provides an encoder (BMPs, GIFs, JPEGs, PNGs, and TIFFs). In order to save a bitmap to specific format, however, you need to specify the CLSID of the encoder; this is accomplished by using the GetImageEncoders() GDI+ function. Listing B provides utility functions for retrieving the CLSID of an encoder given its MIME-type string, and for saving a bitmap using that encoder.

 

Using GDI+ with TBitmap objects

Now that you know how to use the GDI+ Bitmap class, let’s switch gears and discuss how to use GDI+ to draw to a TBitmap object. In many ways, the TBitmap class is easier to use than GDI+’s Bitmap class. On the other hand, because the VCL relies on the standard GDI, rendering fancy lines, shapes, and text and performing basic image-processing operations (namely, geometric transformations) is much easier in GDI+.

Fortunately, as C++Builder developers, we get the best of both worlds—you can easily instruct GDI+ to render its output to a TBitmap object.

 

Drawing to a TBitmap using GDI+

The use GDI+ to draw to a TBitmap object, you simply use the Graphics constructor that takes an HDC (handle to a device context) as one of its parameters. Typically, this constructor is used to create a Graphics object that sends its output to the DC of a window; however, the DC doesn’t have to be a window’s DC, it can also be a memory DC that’s associated with a TBitmap object (i.e., Bitmap->Canvas->Handle).

The following code snippet demonstrates how to use GDI+ to render a gradient background and some text to the TBitmap object that’s held in a TImage (Image1); the output of this code is depicted in Figure B:

 

// grab a reference to the

// TBitmap that's held within

// Image1 and then resize it

Graphics::TBitmap& Bitmap =

  *Image1->Picture->Bitmap;

Bitmap.PixelFormat = pf24bit;

Bitmap.Width = 256;

Bitmap.Height = 256;

 

// local scope

{

  // create a Graphics object

  // associated with the

  // TBitmap

  gdp::Graphics bmp_graphics(

    Bitmap.Canvas->Handle

    );

  GDPCheck(

    bmp_graphics.

      GetLastStatus()

    );

 

  // create a brush with a

  // horizontal red-to-blue

  // gradient

  gdp::LinearGradientBrush

    brush(

      // gradient starting point

Figure B

 

By creating a GDI+ Graphics object that’s associated with the memory DC of a TBitmap object, you can use GDI+ to render output to a TBitmap object. Here, the TBitmap is simply displayed via a TImage control.

      gdp::Point(0, 0),

      // gradient ending/restarting

      // point

      gdp::Point(Bitmap.Width, 0),

      // red to start

      gdp::Color(255, 0, 0),

      // blue to end

      gdp::Color(0, 0, 255)

      );

  GDPCheck(brush.GetLastStatus());

 

  // fill the TBitmap using the brush

  bmp_graphics.FillRectangle(&brush,

    0, 0, Bitmap.Width, Bitmap.Height);

 

  // create a 25pt Tahoma font

  gdp::Font const font(

    L"Tahoma",            // font name

    25,                   // font size

    gdp::FontStyleRegular // attributes

    );

  GDPCheck(font.GetLastStatus());

 

  // draw some rotated green text

  // to the TBitmap

  bmp_graphics.RotateTransform(

    45.0f, gdp::MatrixOrderAppend

    );

  bmp_graphics.DrawString(

    L"GDI+ on a TBitmap!", -1,

    &font, gdp::PointF(25, -30),

    &gdp::SolidBrush(

      gdp::Color(0, 255, 0))

    );

}

 

// redraw the TBitmap

Image1->Refresh();

 

Note that I declared bmp_graphics in a local-scope sub-block so that the Graphics object is destroyed before the TBitmap object is assigned to the TImage. This isn’t strictly required for this example because the TImage class simply draws the bitmap to the screen; it doesn’t draw to the bitmap. In general, however, you need to make sure that all GDI+ Graphics objects associated with the TBitmap’s Canvas are destroyed before calling a GDI routine that may alter the bitmap (see [7] for more information on mixing GDI and GDI+ code).

 

Converting a TBitmap to a GDI+ Bitmap

As I discussed earlier, a very useful feature in GDI+ is the ability to save a bitmap to various formats (see Listing B). Although the TPicture class is not too far behind in this regard (with the proper third-party libraries), it’s often more convenient to use GDI+.

In order to save a TBitmap to a specific image-file format using GDI+, you need to create a Bitmap object based on the TBitmap object; here’s the code to do that:

 

gdp::Bitmap* TBitmap2Bitmap(

   Graphics::TBitmap& VCLBitmap

  )

{ 

  // use the GDI+ Bitmap constructor

  // that takes a handle to a DDB or 

  // DIB section bitmap and a handle to

  // the DDB or DIB section's palette

  gdp::Bitmap* p_bitmap =

    new gdp::Bitmap(

      // handle to the GDI bitmap

      VCLBitmap.Handle,

      // handle to the GDI palette

      VCLBitmap.Palette

    );

 

  // validate the GDI+ bitmap

  gdp::Status const res =

    p_bitmap->GetLastStatus();

  if (res != gdp::Ok)

  {

    delete p_bitmap;

    throw EGDIPlusError(res);

  }

 

  // return a pointer the GDI+ bitmap

  // (caller assumes ownership)

  return p_bitmap;

}

 

After the Bitmap object is created, you can simply use the technique presented in Listing B to save the Bitmap to the desired format.

 

Conclusion

In this article I’ve demonstrated how to work with GDI+ Bitmaps and how to use GDI+ to draw to a TBitmap. The source code for this article is available for download from http://www.bcbjournal.org. Next time, we’ll switch gears and examine GDI+ metafiles and GDI+ vector-based drawing routines.

 

References

1.  D. Marr and E. Hildreth, "Theory of edge detection," Proc. R. Soc. Lond. B, 207, 1980.

2.  D. Chandler, "Printing Bitmaps, Part I," C++Builder Dev. Journal, 5 (1), 2001. [link]

3.  http://tinyurl.com/2v8zc

4.  http://tinyurl.com/222mb

5.  http://tinyurl.com/2zg9q

6.  http://tinyurl.com/3bxqv

7.  http://tinyurl.com/32bvo

8.  http://tinyurl.com/27z75

 

 

Version 2.0 of our popular archive CD is now available! We’ve added more content, a new article title index, and HTML versions of all articles in addition to the PDF files. (Subscribers can take advantage of a special discounted price; contact sales@bcbjournal.org.)

For more information, please visit http://bcbjournal.org/archive_cd.htm.

Not yet a subscriber? We've got a special package just for you: A 12-month subscription to the Journal plus an archive CD for $77 (save $17).

For more information on this package, please visit http://bcbjournal.org/subscriptions.htm.


C++Builder Developer's Journal

www.bcbjournal.org

 

Copyright © 2004, EnCoded Communications Group.  All Rights Reserved.

 

C++Builder Developer’s Journal is an independently produced publication of EnCoded Communications Group. All rights reserved. Reproduction in whole or in part in any form or medium without express written permission of EnCoded Communications Group is prohibited.