2006-03-18

DRAWING ON GIFS OR INDEXED PIXEL FORMAT IMAGES WITH GDI+

It's very common to need to draw some shapes, texts or to apply some effects to images.
But GDI+ has a limitation when working with images created in indexed pixel formats, such as 1bppindexed (0x00030101), 4bppindexed (0x00030402) and 8bppindexed (0x00030803). these are the pixel formats used by gif (graphics interchange format) files too. if you try to draw on these kind of images you may receive an error message like this :

A very simple solution is to load the GIF using gpImage, and retrieve some properties, like width and height. then create a new and empty bitmap object with the same size from the original gif, but using a non indexed pixel format. _gdiplus.vcx uses pixelformat_32bpppargb as default.

The next step is to "draw" the original gif image on the new created bitmap in the just created non indexed pixel format image using the procedure drawimage. now you can draw freely on this new image.

Finally, you can save the new image as gif or any other type.
An important detail that you must know is that when you tell gdi+ to save as gif, it will automatically convert the image again to 8bppindexed pixel format, that is the default for Gdi+.

Here's a sample code that draws 2 ellipses and a rectangle on a GIF.

#DEFINE gdiplus_pixelformat_1bppindexed 0x00030101
#DEFINE gdiplus_pixelformat_4bppindexed 0x00030402
#DEFINE gdiplus_pixelformat_8bppindexed 0x00030803
#DEFINE gdiplus_pixelformat_16bppgrayscale 0x00101004
#DEFINE gdiplus_pixelformat_16bpprgb555 0x00021005
#DEFINE gdiplus_pixelformat_16bpprgb565 0x00021006
#DEFINE gdiplus_pixelformat_16bppargb1555 0x00061007
#DEFINE gdiplus_pixelformat_24bpprgb 0x00021808
#DEFINE gdiplus_pixelformat_32bpprgb 0x00022009
#DEFINE gdiplus_pixelformat_32bppargb 0x0026200a
#DEFINE gdiplus_pixelformat_32bpppargb 0x000e200b
#DEFINE gdiplus_pixelformat_48bpprgb 0x0010300c

LOCAL lcSource, lcDest
LOCAL lcPixFormat, hImg, wImg
m.lcSource = GETPICT("gif")
IF EMPTY(m.lcSource)
    RETURN
ENDIF
m.lcDest = ADDBS(JUSTPATH(m.lcSource)) + "_" + JUSTSTEM(m.lcSource)

*** load image and check if indexed :
LOCAL loImage AS gpImage OF ffc / _GdiPlus.vcx
m.loImage = NEWOBJECT('gpimage',HOME() + 'ffc/_gdiplus.vcx')
m.loImage.CreateFromFile(m.lcSource)
m.wImg = m.loImage.ImageWidth
m.hImg = m.loImage.ImageHeight
m.lcPixFormat = GetPixFormatName(m.loImage.PixelFormat)
IF NOT "indexed" $ UPPER(m.lcPixFormat)
    MESSAGEBOX("draw directly on the image, cause it's not" + ;
          "of an indexed pixel format !",64, "use common technique")
    RETURN
ENDIF

*** create new bitmap with same dimensions :
LOCAL loBitmap AS gpBitmap OF ffc / _GdiPlus.vcx
m.loBitmap = NEWOBJECT("gpbitmap", HOME() + "ffc/_gdiplus.vcx")
LOCAL loGraph AS gpGraphics OF HOME() + ffc / _GdiPlus.vcx
m.loGraph = NEWOBJECT('gpgraphics', HOME() + "ffc/_gdiplus.vcx")
m.loBitmap.CREATE(m.wImg, m.hImg, gdiplus_pixelformat_16bpprgb555 )

*** paste original image to the new created :
m.loGraph.CreateFromImage(m.loBitmap)
m.loGraph.DrawImageScaled(m.loImage, 0, 0, m.wImg, m.hImg)

*** now we can draw anything we want to the image :
LOCAL loBlue, loRed, loGreen AS gpColor OF ffc / _GdiPlus.vcx
LOCAL loPen AS gpPen OF HOME() + ffc / _GdiPlus.vcx

m.loBlue = NEWOBJECT("gpcolor", HOME() + "ffc/_gdiplus.vcx","",40,40,240 )
m.loRed = NEWOBJECT("gpcolor", HOME() + "ffc/_gdiplus.vcx","",240,40,40 )
m.loGreen = NEWOBJECT("gpcolor", HOME() + "ffc/_gdiplus.vcx","",40,230,40 )

m.loPen = NEWOBJECT("gppen", HOME() + "ffc/_gdiplus.vcx")
m.loPen.CREATE(m.loBlue, 12)
m.loGraph.drawellipse( m.loPen, 0, 0, m.wImg, m.hImg)

m.loPen.pencolor = m.loRed
m.loGraph.drawellipse( m.loPen, 0 + 12, 0 + 12 , m.wImg - 24 , m.hImg - 24)

m.loPen.pencolor = m.loGreen
m.loGraph.drawrectangle( m.loPen, 0 + 30, 0 + 30 , m.wImg - 60 , m.hImg - 60)

*** save image in gif and jpg :
m.loBitmap.SaveToFile(m.lcDest + ".jpg","image/jpeg","quality=100")
m.loBitmap.SaveToFile(m.lcDest + ".gif","image/gif")

RETURN

PROCEDURE GetPixFormatName(npix)
    DO CASE
        CASE m.npix = 0x00030101
            RETURN "1bppindexed"
        CASE m.npix = 0x00030402
            RETURN "4bppindexed"
        CASE m.npix = 0x00030803
            RETURN "8bppindexed"
        CASE m.npix = 0x00101004
            RETURN "16bppgrayscale"
        CASE m.npix = 0x00021005
            RETURN "16bpprgb555"
        CASE m.npix = 0x00021006
            RETURN "16bpprgb565"
        CASE m.npix = 0x00061007
            RETURN "16bppargb1555"
        CASE m.npix = 0x00021808
            RETURN "24bpprgb"
        CASE m.npix = 0x00022009
            RETURN "32bpprgb"
        CASE m.npix = 0x0026200a
            RETURN "32bppargb"
        CASE m.npix = 0x000e200b
            RETURN "32bpppargb"
        CASE m.npix = 0x0010300c
            RETURN "48bpprgb"
        CASE m.npix = 0x001c400e
            RETURN "64bpppargb"
        OTHERWISE
            RETURN "unidentified"
    ENDCASE
ENDPROC


This procedure will cause a big inconvenient, because in this automatic conversion, gdi+ writes the file by using a halftone palette to which the image object's bits have been color reduced. gdi+ does a color conversion from 32 bits-per-pixel (32 bpp) when it writes the image to the file. 

According to szaak priester, "when gdi+ saves a bitmap in gif format, it performs a very crude form of color quantization. it always uses the same color palette, mostly filled with the 216 'web-safe colors.' in the early years of the internet, these colors were the only ones to be displayed consistently by most browsers; hence the name. ... on top of that, the 216 web-safe colors were chosen purely on the basis of their technical merits (they uniformly divide the rgb color space), and not because of their visual qualities. as a consequence, the web-safe palette (also called the 'halftone palette') contains many almost indiscernible purples and a lot of muddy greenish and brownish colors, whereas some more useable parts of the spectrum are seriously underpopulated."
Below you can see the result obtained using the technique discussed here.



gif - original image

gif - 8bppindexed image

jpeg - 32bppargb

 

Pay attention to the differences of quality between the three images.
This technique can be used also to resize any image, including gifs. for resizing purposes, all you need to change in the previous code are the instructions that deal with the image size, lobitmap.create(newwidth, newheight, pixelformat) and when pasting the original image to the new created bitmap, lograph.drawimageportionat(looriginalimage, 0, 0, newwidth, newheight).
You just need to know the gifs limitations, and decide if you will save it as gif or as another image format.

 

Here are some differences between gifs and jpegs, according to MSDN :

GIF vs. JPEG

Should you store images in gif format or jpeg format? although this question doesn't relate directly to palette management, it confuses a lot of people, and it is somewhat relevant to our subject matter.

There are two major differences between gif and jpeg images:

gif images are compressed in a way that preserves all data, while jpeg images are compressed in a way that loses some data.
gif images are limited to a maximum of 256 colors, while jpeg images aren't limited in the number of colors they use.

 

But is there a way to convert an image to gif format without using a better distributed color pallete instead of the the halftone pallete that deteriorates the pictures ? Of course, but that's for another post...

1 comment:

  1. Versión en Español de este artículo en / Spanish version at http://www.portalfox.com/article.php?sid=2244

    ReplyDelete