## 2006-03-24

### PIE GRAPHICS WITH PURE VFP CODE

Did you know that it is possible to draw pie style graphics in vfp without the need of any active-x or api call ? With pure VFP code ? draw a circle with foxpro
To draw any circle, there are two main parameters : 1 point with the coordinates of the center of the circle (x,y) and the radius. having this, it is very simple to calculate the coordinates of any point in the circumference border.
Even if you already use gdi+ or any active-x to draw graphics, I think it's imteresting to know how a circle is built, and how to calculate the positions of each point.

For this purpose, I'll need to make you remember the concepts of sine and cosine :

In any right angled triangle, for any angle:

sine of the angle = length of the opposite side / length of the hypotenuse
cosine of the angle = length of the adjacent side / length of the hypotenuse

hypotenuse of a right angled triangle is the longest side, which is the one opposite the right angle. The adjacent side is the side which is between the angle in question and the right angle. The opposite side is opposite the angle in question.

So, imagine a right angled triangle inside a circumference, like in the picture below.

sine of angle = opposite side (height or y) / hypotenuse (radius) !!!

height = sine of angle * radius

cosine of angle = adjacent side (width or x) / hypotenuse (radius) !!!

width = cosine of angle * radius

Now we can create a loop starting from angle 0 (zero) and finishing at angle 360 degrees. At each step we can calculate the position of every point of the circle !

Then it becomes quite simple to draw the graphic. for that purpose, I'll use the line object, drawing lines from the center of the circle till the point x,y just calculated. and that's all !!!

There's one problem with the line object in VFP. To draw a line, we need to use the properties top, left, width, height and lineslant (specifies which way a line slants, from upper left to lower right or lower left to upper right), instead of simply passing the two points, x1y1 and x2y2. so, as you can see in the code below, depending on the angle and the resulting position in the quadrant (quarter of the circumference of a circle), I needed to make some extra code to deal with this. Pay attention to the "do case" command below.

Procedure DrawPie

```LPARAMETERS tnCenterX, tnCenterY, tnRadius, tnStart, tnEnd, tnColor

LOCAL lnLineWidth, N, x, Y, lcObj, lnPointLeft, lnPointTop, lcSlant
m.lnLineWidth = 3

FOR m.N = m.tnStart TO m.tnEnd STEP (1 * m.lnLineWidth)

m.lcObj = "line" + TRANSFORM(m.N * 100)

DO CASE
CASE m.N >= 0 AND m.N < 90 && 1st quadrant
m.lnPointLeft = m.tnCenterX
m.lnPointTop = m.tnCenterY – m.Y
m.lcSlant = "/"

CASE m.N >= 90 AND m.N < 180 && 2nd quadrant
m.lnPointLeft = m.tnCenterX + m.x
m.lnPointTop = m.tnCenterY – m.Y
m.lcSlant = "\"

CASE m.N >= 180 AND m.N < 270 && 3rd quadrant
m.lnPointLeft = m.tnCenterX + m.x
m.lnPointTop = m.tnCenterY
m.lcSlant = "/"

CASE m.N >= 270 AND m.N <= 360 && 4th quadrant
m.lnPointLeft = m.tnCenterX
m.lnPointTop = m.tnCenterY
m.lcSlant = "\"
ENDCASE

WITH THISFORM.container1.&lcObj.
.LINESLANT = m.lcSlant
.BORDERCOLOR = m.tnColor
.BORDERWIDTH = m.lnLineWidth
.WIDTH = ABS(m.x)
.HEIGHT = ABS(m.Y)
.LEFT = m.lnPointLeft
.TOP = m.lnPointTop
.VISIBLE = .T.
ENDWITH

ENDFOR
RETURN```

You may find this procedure slow, specially if you run it in slow machines, but it works ! the biggest problem is that it adds many objects in a form, in the case of a loop with step 1, at least 360 lines !
If we draw a line with borderwidth = 1, in some cases, the calculated points will create some holes between the lines. a wider border will resolve this problem. the bigger the step in the loop (from 0 to 360 degrees), the faster the procedure will run, and less objects will be added.
Run the form piegraphics.scx from the attached file, change the cursor, radius, step, and linewidth values, and check all procedures to better understand this post.

What next ?

Of course, in one of my next posts I'll deal with pie style graphics using Gdi+ – Google for the keyword FOXCHARTS, for a great surprise made in pyure VFP9 code.
Some of the subjects discussed here will help us to create more interesting graphics.

## 2006-03-21

### BAR GRAPHICS WITHOUT COMPLICATIONS

I always see people asking how to create graphics without active-x controls.

In my opinion, the most simple way to do it is to use labels. labels are versatile, we can change their backcolor, caption, width, height. These four properties are all we need to create a simple graphic, like in this picture below.

The graphic is drawn in a container. we can set the label color to the bar color, in its caption we can store the values, its height is proportional to the value, and the width depends on the division between the container's width and the wuantity of bars.

The bars auto adjust themselves according to the data received, and the dimensions of the container.

Everything is done in a single method. in the form example, I've put this code in the refresh method of the container. a cursor is needed to store the data, where the first field, field(1) receives the values and the second field field(2) receives the caption. The name of the cursor is stored in the tag property of the container. and that's all !

Some extra code was added to allow the resizement of the form.

Put this code in the load event of the form :

create cursor sales (amount n(8,2), cname c(6))

insert into sales values (250,"jan")
insert into sales values (128,"feb")
insert into sales values ( 90,"mar")
insert into sales values (330,"apr")
insert into sales values (190,"may")
insert into sales values (250,"jun")
insert into sales values ( 50,"jul")
insert into sales values ( 80,"aug")
insert into sales values ( 50,"sep")
insert into sales values ( 19,"oct")
insert into sales values (160,"nov")
insert into sales values (199,"dec")

and this in the refresh event:

*!* create array with 12 colors to be used

dimension lacolors(12)
lacolors(1) = rgb(255,128,128) && red
lacolors(2) = rgb(0,255,0) && green
lacolors(3) = rgb(128,128,255) && blue
lacolors(4) = rgb(255,0,255) && pink
lacolors(5) = rgb(0,255,255) && cyan
lacolors(6) = rgb(255,255,0) && yellow
lacolors(7) = rgb(160,160,210) && blue2
lacolors(8) = rgb(255,160,30) && orange
lacolors(9) = rgb(200,140,140) &&
lacolors(10) = rgb(96,196,96) && green2
lacolors(11) = rgb(255,200,200) && rose
lacolors(12) = rgb(200,200,200) && grey

lnblack = rgb(28,28,28) && black
lnwhite = rgb(255,255,255) && white
lctable = this.tag

select "&lctable"

*!* calculate the width of each bar
lnlargura = int(this.width / reccount())
lnaltura = this.height

lcfldvalor = this.tag + "." + field(1)

if fcount() > 1
lcfldlegenda = this.tag + "." + field(2)
else
lcfldlegenda = ""
endif
calculate max(evaluate(lcfldvalor)) to lnmax

*!* add the labels that will be the bars and the captions
scan
n = recno()
lcobj = "label" + transform(n)
lcleg = "lname" + transform(n)
*!* check if object already exists to avoid errors
if type("this."+lcobj) <> "o"
endif

with this.&lcobj.
.backstyle = 1 && opaque
.backcolor = lacolors(iif(n>12,n-12,n))
.width = lnlargura
lnvalor = evaluate(lcfldvalor)
lnbarra = ( lnvalor / lnmax) * (this.height - 1 - 17)
.height = lnbarra
.left = ((n-1) * lnlargura) + 2
.top = lnaltura - this.&lcobj..height – 17
.tag = transform(lnvalor)
.caption = transform(lnvalor)
.fontsize = 8
.fontbold = .t.
.alignment = 2
.visible = .t.
endwith

with this.&lcleg.
.backstyle = 1 && opaque
.backcolor = lnwhite
.forecolor = lnblack
.width = lnlargura – 2
if not empty(lcfldlegenda)
.caption = alltrim(evaluate(lcfldlegenda))
endif
.left = ((n-1) * lnlargura) + 3
.top = this.height – 17
.height = 17
.fontsize = 8
.alignment = 2
.visible = .t.
endwith

endscan

this.width = ((n) * lnlargura) + 3

You may use this technique to create bar graphics at the horizontal, with very slight adaptations in the code.
In the attached file, bargraphics.zip, you'll find bargraphics.scx/sct from this example, and also 3 more files, callbar.prg that calls barras.scx/sct that create a modal form with bargraphics that can be resized.

## 2006-03-20

### CAPTURE SCREEN PORTIONS

This short but powerful example makes use of many API calls, for memory access and data conversion to the clipboard. Does not require any ocx and works with vfp from version 5 to 9. The author created it because many people have been looking for tools to capture images from webcams and other image accessories linked to the programs. This sample shows again that we can do many cool things with visual foxpro. Have a look at what he's done with a simple form !
This is one of my preferred codes from my colleague from the brazilian forum http://www.vfpbrasil.com.br/ , Gelson L. Bremm, from Florianopolis - Brazil, who gave me the authorization to publish here. I think it's really worth to share with the rest of the community.

```*!* IMAGECAPTURE.PRG
*!* Author : Gelson L. Bremm (Florianopolis - Brazil)
*!* Description : Captures portions of image
PUBLIC oCapturaImg
oCapturaImg = CREATEOBJECT("ImageCapture")
oCapturaImg.Show()

DEFINE CLASS ImageCapture AS Form
Height = 147
Width = 115
Desktop = .T.
ShowWindow = 2
DoCreate = .T.
ShowTips = .T.
AutoCenter = .T.
Caption = "Capture"
HalfHeightCaption = .T.
MaxButton = .F.
MinButton = .F.
MinHeight = 80
AlwaysOnTop = .T.
Name = "CAPTURE"

PROCEDURE Init
WITH THIS
.DeclareFunctions()
.Resize()
ENDWITH
ENDPROC

PROCEDURE SetTransparent
LOCAL lnControlBottom, lnControlRight, lnControlLeft, lnControlTop, lnBorderWidth, ;
lnTitleHeight, lnFormHeight, lnFormWidth, lnInnerRgn, lnOuterRgn, lnCombinedRgn, ;
lnControlRgn, lnControl, lnRgnDiff, lnRgnOr, llTrue

lnRgnDiff = 4
lnRgnOr = 2
llTrue = -1

WITH THIS
lnBorderWidth = SYSMETRIC(3)
lnTitleHeight = SYSMETRIC(9)-SYSMETRIC(4)
lnFormWidth = .Width + (lnBorderWidth * 2)
lnFormHeight = .Height + lnTitleHeight + lnBorderWidth
lnOuterRgn = CreateRectRgn(0, 0, lnFormWidth, lnFormHeight)
lnInnerRgn = CreateRectRgn(lnBorderWidth, lnTitleHeight, ;
lnFormWidth - lnBorderWidth, lnFormHeight - lnBorderWidth)
lnCombinedRgn = CreateRectRgn(0, 0, 0, 0)
CombineRgn(lnCombinedRgn, lnOuterRgn, lnInnerRgn, lnRgnDiff)
FOR EACH Control in .Controls
lnControlLeft = Control.Left + lnBorderWidth
lnControlTop = Control.Top + lnTitleHeight
lnControlRight = Control.Width + lnControlLeft
lnControlBottom = Control.Height + lnControlTop
lnControlRgn = CreateRectRgn(lnControlLeft, lnControlTop, lnControlRight, lnControlBottom)
CombineRgn(lnCombinedRgn, lnCombinedRgn, lnControlRgn, lnRgnOr)
ENDFOR
SetWindowRgn(.HWnd , lnCombinedRgn, llTrue)
ENDWITH
ENDPROC

PROCEDURE num2dword
LPARAMETERS lnValue

#DEFINE m0 256
#DEFINE m1 65536
#DEFINE m2 16777216

LOCAL b0, b1, b2, b3

b3 = INT(lnValue/m2)
b2 = INT((lnValue - b3*m2)/m1)
b1 = INT((lnValue - b3*m2 - b2*m1)/m0)
b0 = MOD(lnValue, m0)

RETURN(CHR(b0)+CHR(b1)+CHR(b2)+CHR(b3))
ENDPROC

PROCEDURE declarefunctions
DECLARE INTEGER CombineRgn in "gdi32" integer hDestRgn, integer hRgn1, integer hRgn2, integer nMode
DECLARE INTEGER CreateRectRgn in "gdi32" integer X1, integer Y1, integer X2, integer Y2
DECLARE INTEGER SetWindowRgn in "user32" integer hwnd, integer hRgn, integer nRedraw

DECLARE INTEGER SelectObject IN gdi32 integer hdc, integer hObject
DECLARE INTEGER ReleaseDC IN user32 INTEGER hwnd, INTEGER hdc
DECLARE INTEGER CreateCompatibleDC IN gdi32 INTEGER hdc
DECLARE INTEGER DeleteObject IN gdi32 INTEGER hObject
DECLARE INTEGER DeleteDC IN gdi32 INTEGER hdc
DECLARE INTEGER CloseClipboard IN user32
DECLARE INTEGER GetFocus IN user32
DECLARE INTEGER EmptyClipboard IN user32
DECLARE INTEGER GetWindowDC IN user32 INTEGER hwnd
DECLARE INTEGER OpenClipboard IN user32 INTEGER hwnd
DECLARE INTEGER SetClipboardData IN user32 INTEGER wFormat, INTEGER hMem
DECLARE INTEGER CreateCompatibleBitmap IN gdi32;
INTEGER hdc, INTEGER nWidth, INTEGER nHeight
DECLARE INTEGER BitBlt IN gdi32;
INTEGER hDestDC, INTEGER x, INTEGER y,;
INTEGER nWidth, INTEGER nHeight, INTEGER hSrcDC,;
INTEGER xSrc, INTEGER ySrc, INTEGER dwRop

DECLARE INTEGER GetActiveWindow IN user32
DECLARE INTEGER GetClipboardData IN user32 INTEGER uFormat
DECLARE INTEGER GlobalAlloc IN kernel32 INTEGER wFlags, INTEGER dwBytes
DECLARE INTEGER GlobalFree IN kernel32 INTEGER hMem

DECLARE INTEGER GetObject IN gdi32 AS GetObjectA;
INTEGER hgdiobj, INTEGER cbBuffer, STRING @lpvObject

DECLARE INTEGER GetObjectType IN gdi32 INTEGER h

DECLARE RtlZeroMemory IN kernel32 As ZeroMemory;
INTEGER dest, INTEGER numBytes

DECLARE INTEGER GetDIBits IN gdi32;
INTEGER hdc, INTEGER hbmp, INTEGER uStartScan,;
INTEGER cScanLines, INTEGER lpvBits, STRING @lpbi,;
INTEGER uUsage

DECLARE INTEGER CreateFile IN kernel32;
STRING lpFileName, INTEGER dwDesiredAccess,;
INTEGER dwShareMode, INTEGER lpSecurityAttr,;
INTEGER dwCreationDisp, INTEGER dwFlagsAndAttrs,;
INTEGER hTemplateFile

DECLARE INTEGER CloseHandle IN kernel32 INTEGER hObject

DECLARE Sleep IN kernel32 INTEGER dwMilliseconds
ENDPROC

PROCEDURE CopyToClipBoard
WITH THIS
.Caption = "Capturing"
.Command1.Left = .Width+.Command1.Width
.Cls()
.SetTransparent()
=Sleep(100)

#DEFINE CF_BITMAP 2
#DEFINE SRCCOPY 13369376

lnLeft = SYSMETRIC(3)
lnTop = SYSMETRIC(4)+(SYSMETRIC(20)-SYSMETRIC(11))
lnRight = 0
lnBottom = 0
lnWidth = .Width
lnHeight = .Height-1

*hwnd = GetFocus()
hdc = GetWindowDC(.HWnd)
hVdc = CreateCompatibleDC(hdc)
hBitmap = CreateCompatibleBitmap(hdc, lnWidth, lnHeight)

= SelectObject(hVdc, hBitmap)
= BitBlt(hVdc, 0, 0, lnWidth, lnHeight, hdc, lnLeft, lnTop, SRCCOPY)
= OpenClipboard(.HWnd)
= EmptyClipboard()
= SetClipboardData(CF_BITMAP, hBitmap)
= CloseClipboard()
= DeleteObject(hBitmap)
= DeleteDC(hVdc)
= ReleaseDC(.HWnd, hdc)

.Command1.Left = VAL(.Command1.Tag)
.SetTransparent()
.Caption = "Capture"
ENDWITH
ENDPROC

PROCEDURE CopyToFile
#DEFINE CF_BITMAP 2
#DEFINE SRCCOPY 13369376
#DEFINE OBJ_BITMAP 7
#DEFINE DIB_RGB_COLORS 0
#DEFINE BFHDR_SIZE 14
#DEFINE BHDR_SIZE 40
#DEFINE GENERIC_WRITE 1073741824
#DEFINE FILE_SHARE_WRITE 2
#DEFINE CREATE_ALWAYS 2
#DEFINE FILE_ATTRIBUTE_NORMAL 128
#DEFINE INVALID_HANDLE_VALUE -1
#DEFINE BITMAP_STRU_SIZE 24
#DEFINE BI_RGB 0
#DEFINE BHDR_SIZE 40
#DEFINE GMEM_FIXED 0

LOCAL cDefault, cNameFile, hClipBmp
LOCAL pnWidth, pnHeight, pnBitsSize, pnRgbQuadSize, pnBytesPerScan
LOCAL hFile, lnFileSize, lnOffBits, lcBFileHdr
LOCAL lpBitsArray, lcBInfo
LOCAL hdc, hMemDC, lcBuffer

cDefault = FULLPATH(SYS(5))
cNameFile = GETPICT("BMP")
SET DEFAULT TO (cDefault)
IF EMPTY(cNameFile)
RETURN
ENDIF

IF FILE(cNameFile)
IF MESSAGEBOX("There is already a file named '"+PROPER(JUSTFNAME(cNameFile))+;
"'"+CHR(13)+"Overwrite the existing file ?",36+256,"Confirm overwrite") = 7
RETURN
ENDIF
ENDIF
ERASE (cNameFile)

WITH THIS
.CopyToClipBoard()

= OpenClipboard (0)
hClipBmp = GetClipboardData (CF_BITMAP)
= CloseClipboard()

IF hClipBmp = 0 Or GetObjectType(hClipBmp) # OBJ_BITMAP
=MESSAGEBOX("There is no image in the clipboard.",48,"Error creating file")
RETURN
ENDIF

STORE 0 TO pnWidth, pnHeight, pnBytesPerScan, pnBitsSize, pnRgbQuadSize
lcBuffer = REPLI(CHR(0), BITMAP_STRU_SIZE)
IF GetObjectA (hClipBmp, BITMAP_STRU_SIZE, @lcBuffer) # 0
pnWidth = ASC(SUBSTR(lcBuffer, 5,1)) + ;
ASC(SUBSTR(lcBuffer, 6,1)) * 256 +;
ASC(SUBSTR(lcBuffer, 7,1)) * 65536 +;
ASC(SUBSTR(lcBuffer, 8,1)) * 16777216

pnHeight = ASC(SUBSTR(lcBuffer, 9,1)) + ;
ASC(SUBSTR(lcBuffer, 10,1)) * 256 +;
ASC(SUBSTR(lcBuffer, 11,1)) * 65536 +;
ASC(SUBSTR(lcBuffer, 12,1)) * 16777216
ENDIF

lnBitsPerPixel = 24
pnBytesPerScan = INT((pnWidth * lnBitsPerPixel)/8)
IF MOD(pnBytesPerScan, 4) # 0
pnBytesPerScan = pnBytesPerScan + 4 - MOD(pnBytesPerScan, 4)
ENDIF

lcBIHdr = .num2dword(BHDR_SIZE) + .num2dword(pnWidth) +;
.num2dword(pnHeight) + (CHR(MOD(1,256))+CHR(INT(1/256))) + (CHR(MOD(lnBitsPerPixel,256))+;
CHR(INT(lnBitsPerPixel/256))) + .num2dword(BI_RGB) + REPLI(CHR(0), 20)

IF lnBitsPerPixel <= 8
ELSE
ENDIF
pnBitsSize = pnHeight * pnBytesPerScan
lpBitsArray = GlobalAlloc (GMEM_FIXED, pnBitsSize)
= ZeroMemory (lpBitsArray, pnBitsSize)

*hwnd = GetActiveWindow()
hdc = GetWindowDC(.HWnd)
hMemDC = CreateCompatibleDC (hdc)
= ReleaseDC (.HWnd, hdc)
= GetDIBits (hMemDC, hClipBmp, 0, pnHeight, lpBitsArray, @lcBInfo, DIB_RGB_COLORS)

lnFileSize = BFHDR_SIZE + BHDR_SIZE + pnRgbQuadSize + pnBitsSize
lnOffBits = BFHDR_SIZE + BHDR_SIZE + pnRgbQuadSize
lcBFileHdr = "BM" + .num2dword(lnFileSize) + .num2dword(0) + .num2dword(lnOffBits)

hFile = CreateFile(cNameFile, GENERIC_WRITE, FILE_SHARE_WRITE, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0)

IF hFile # INVALID_HANDLE_VALUE
DECLARE INTEGER WriteFile IN kernel32;
INTEGER hFile, STRING @lpBuffer, INTEGER nBt2Write,;
INTEGER @lpBtWritten, INTEGER lpOverlapped
= WriteFile (hFile, @lcBFileHdr, Len(lcBFileHdr), 0, 0)
= WriteFile (hFile, @lcBInfo, Len(lcBInfo), 0, 0)

DECLARE INTEGER WriteFile IN kernel32;
INTEGER hFile, INTEGER lpBuffer, INTEGER nBt2Write,;
INTEGER @lpBtWritten, INTEGER lpOverlapped
= WriteFile (hFile, lpBitsArray, pnBitsSize, 0, 0)
= CloseHandle (hFile)
ELSE
= MESSAGEBOX("Error creating file: " + cNameFile, "Internal Error")
ENDIF

= GlobalFree(lpBitsArray)
= DeleteDC (hMemDC)
= DeleteObject (hClipBmp)
ENDWITH
ENDPROC

PROCEDURE Resize
WITH THIS
.Command1.Left = .Width-.Command1.Width
.Command1.Top = .Height-.Command1.Height
.Command1.Tag = ALLT(STR(.Command1.Left))

.SetTransparent()
ENDWITH
ENDPROC

PROCEDURE Destroy
oCapturaImg = .F.
RELEASE oCapturaImg
ENDPROC
ENDDEFINE

DEFINE CLASS myCmdButton AS Commandbutton
Top = 126
Left = 97
Height = 21
Width = 18
FontName = "Webdings"
Caption = "6"
ToolTipText = "Options"
Name = "Command1"

PROCEDURE Click
DEFINE POPUP _menu_clip SHORTCUT RELATIVE FROM MROW(), MCOL()

DO CASE
THISFORM.CopyToClipBoard()

THISFORM.CopyToFile()
ENDCASE
ENDPROC
ENDDEFINE```

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

## 2006-03-02

Did you know that all the jpegs from your digital camera contain a lot of extra information ?

We can easily retrieve some interesting information such as : title, equipment make, camera model, shutter speed, lens aperture, flash mode, date of picture, and much more ! these metadata "tags" are stored in a jpeg file to indicate various camera settings and picture taking conditions that occurred while creating the photo. several image file formats enable you to store metadata along with an image, such as jpeg, tiff and png.

Again GDI+ makes our lives easier, providing to us a function to get these information : getpropertyitem, stored in the gpimage class from _gdiplus.vcx .

Download and execute this file, and select an image from any digital camera, and you'll see all metadata stored in it.

On the first part of this code, i get the most common properties, from the gpimage class, like imagewidth, imageheight, horizontalresolution, verticalresolution and pixelformat.

On the rest i retrieve the metadata from the image file, using getpropertyidlist and getpropertyitem. it's important to notice that getpropertyidlist receives an array as a parameter, and returns that array populated with the metadata.

lcsource = getpict()
local loimage as gpimage of ffc/_gdiplus.vcx
loimage = newobject("gpimage", home() + "ffc/_gdiplus.vcx")
loimage.createfromfile(lcsource)

dimension rapropidlist(1)
local ncount, n, lctagname, lnprop, luprop

ncount = loimage.getpropertyidlist(@rapropidlist)
for n = 1 to ncount
lnprop = rapropidlist(n)
luprop = loimage.getpropertyitem(lnprop)

? transform(lnprop), transform(luprop)
endfor

It is possible to get some other really cool information from pictures. Take a look at the 2 last items in the picture, exiflightsource and exifflash. in both cases we have a zero value. check this table, to see what each possible value could mean :
tagid : 0x9208 (37384) - lightsource int16u exififd
1 = daylight
2 = fluorescent
3 = tungsten
4 = flash
9 = fine weather
10 = cloudy
12 = daylight fluorescent
13 = day white fluorescent
14 = cool white fluorescent
15 = white fluorescent
17 = standard light a
18 = standard light b
19 = standard light c
20 = d55
21 = d65
22 = d75
23 = d50
24 = iso studio tungsten
255 = other

tagid : 0x9209 (37385) - flash int16u exififd
0x0 = no flash
0x1 = fired
0x5 = fired, return not detected
0x7 = fired, return detected
0x9 = on
0xd = on, return not detected
0xf = on, return detected
0x10 = off
0x18 = auto, did not fire
0x19 = auto, fired
0x1d = auto, fired, return not detected
0x1f = auto, fired, return detected
0x20 = no flash function
0x41 = fired, red-eye reduction
0x45 = fired, red-eye reduction, return not detected
0x47 = fired, red-eye reduction, return detected
0x49 = on, red-eye reduction
0x4d = on, red-eye reduction, return not detected
0x4f = on, red-eye reduction, return detected
0x59 = auto, fired, red-eye reduction
0x5d = auto, fired, red-eye reduction, return not detected
0x5f = auto, fired, red-eye reduction, return detected

http://www.sno.phy.queensu.ca/~phil/exiftool/tagnames/exif.html

It is obviously possible to remove, change or set the metadata property items from images with gdi+ too, but for this task, it is necessary use some extra code, once _gdiplus.vcx doesn't bring this feature.

I wrote a much more detailed article on this subject to the universalthread magazine published in the april/2006 issue:

It's really worth to have a look there, where you will find a subclass for gpimage that permits to you to read, write or remove image tags.

## 2006-03-01

### GETTING IMAGE PROPERTIES WITH GDI+

```LOCAL lcSource, lcInfo, lnWidth, lnHeight, lnHorRes, lnVerRes, lnPixForm
LOCAL lcpixform
m.lcSource = GETPICT()

LOCAL loImage AS gpImage OF FFC / _GdiPlus.vcx
m.loImage = NEWOBJECT("gpimage", HOME() + "ffc/_gdiplus.vcx")
m.loImage.CreateFromFile(m.lcSource)

m.lnWidth = m.loImage.ImageWidth
m.lnHeight = m.loImage.ImageHeight
m.lnHorRes = m.loImage.HorizontalResolution
m.lnVerRes = m.loImage.VerticalResolution
m.lnPixForm = m.loImage.PixelFormat
m.lcpixform = GetPixFormatName(m.lnPixForm)

m.lcInfo = "image : " + m.lcSource + CHR(13) + CHR(13) + ;
"width : " + TRANSFORM(m.lnWidth) + " pixels" + CHR(13) + ;
"height : " + TRANSFORM(m.lnHeight) + " pixels" + CHR(13) + ;
"pixel format : " + m.lcpixform + CHR(13) + ;
"hor. resol : " + TRANSFORM(m.lnHorRes) + " pixels/inch" + CHR(13) + ;
"ver. resol : " + TRANSFORM(m.lnVerRes) + " pixels/inch" + CHR(13)

MESSAGEBOX(m.lcInfo, 64, "image information")
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```