GDIPlusX: Problem with FromVarBinary

Topics: General
Developer
Nov 19, 2007 at 8:08 PM
I have a variable m.lcBitmapData that has the complete raw data for a bitmap in bmp format 256 colors.

STRTOFILE(m.lcBitmapData, "c:\strtofile.bmp") gives me a bitmap file that i can see and use.

m.loBitmap = _Screen.System.Drawing.Bitmap.FromVarBinary(m.lcBitmapData)

m.loBitmap.Save("c:\bitmap.bmp", _Screen.System.Drawing.Imaging.ImageFormat.bmp)

gives me an empty bmp file, correct format, correct size, correct color table, but empty and black.

Any ideas Cesar?


Nov 19, 2007 at 8:22 PM
Carlos,

The BitmapData does not contain the image header, only the bitmap information, more exactly the colors used in the bitmap. If you want to use "FromVarBinary" for this case, you'll need to create the file header manually.
Developer
Nov 19, 2007 at 9:10 PM
OK, lets eliminate that variable from the way, try this:

DO system.prg
m.lcBitmapData = Filetostr(Getpic())
m.loBitmap = _Screen.System.Drawing.Bitmap.FromVarBinary(m.lcBitmapData)
m.loBitmap.Save("c:\varbinary_test.bmp", _Screen.System.Drawing.Imaging.ImageFormat.Bmp)

And test it with bmp files that you know are good. I am getting very strange results, something is wrong. If a file has a width that is not a mutiple of 8 i get garbage for example. If I use png files and _Screen.System.Drawing.Imaging.ImageFormat.Png all seems to work OK

Can we load for example a bmp and save it as png in this way?
Developer
Nov 19, 2007 at 9:36 PM
Edited Nov 19, 2007 at 10:21 PM
Update: something is wrong in this methods, I am seeing problems similar to what I saw while trying to read ico files and got wrong the size of the pixel data area, I guess the stride is not being taken into account or something.

I get strange results depending on the bitmap used, sometimes nothing, a black image, sometimes garbage, sonetimes some small artifacts.
Developer
Nov 19, 2007 at 11:51 PM
I uploaded a zip so this can be checked. it contains a prg and some bitmaps, run it and check the resulting bitmaps.

http://www.codeplex.com/Project/Download/FileDownload.aspx?ProjectName=VFPX&DownloadId=22332
Developer
Nov 20, 2007 at 5:58 AM
New findings: something is wrong with FromvarBinary, if I try to get an alpha mask bitmap from a png, if i create the bitmap like this:

Strtofile(This.ImageData1, "c:\xxx.png")
m.loBitmap1 = .Bitmap.New("c:\xxx.png")

I get the alpha mask bitmap ok. But if I create the bitmap like this:

m.lcBitmap = This.ImageData1
m.loBitmap1 = .Bitmap.Fromvarbinary(m.lcBitmap)

I get an all white alpha mask bitmap.

Code to reproduce problem, test it with pngs with some transparent background, comment uncomment lines to check.

**********
m.lcFileName = Getfile("png")

If Empty(m.lcFileName) Then
Return
Endif

m.lcImageData = Filetostr(m.lcFileName)

With _Screen.System.Drawing

*** Get PNG raw data

*** This way of creating the bitmap gives me an all white mask
m.lcbitmap = m.lcImageData
m.loBitmap1 = .Bitmap.Fromvarbinary(m.lcbitmap)

*** This works
*** Strtofile(m.lcImageData, "c:\xxxxxxxx.png")
*** m.loBitmap1 = .Bitmap.New("c:\xxxxxxxx.png")

m.loBitmap2 = .Bitmap.New(m.loBitmap1.Width, m.loBitmap1.Height, 0, .Imaging.PixelFormat.Format24bppRGB)

m.loGraphics = .Graphics.FromImage(m.loBitmap2)

*** Clear the new bitmap with white
m.loGraphics.Clear(.Color.White)

*** This colormatrix will apply the value of the alpha channel to the RGB channels,
*** creating a grayscale representation of the alpha channel
m.loColorMatrix = .Imaging.ColorMatrix.New( ;
0, 0, 0, 0, 0, ;
0, 0, 0, 0, 0, ;
0, 0, 0, 0, 0, ;
-1, -1, -1, 1, 0, ;
0, 0, 0, 0, 1)

m.loAttributes = .Imaging.ImageAttributes.New()
m.loAttributes.SetColorMatrix(loColorMatrix)

m.loRectangle = .Rectangle.New(0, 0, m.loBitmap1.Width, m.loBitmap1.Height)

loGraphics.DrawImage(m.loBitmap1, m.loRectangle, m.loRectangle, .GraphicsUnit.Pixel, m.loAttributes)

loBitmap2.Save(Forceext(m.lcFileName, ".msk.bmp"), .Imaging.ImageFormat.Bmp)

m.loBitmap1.Dispose()
m.loBitmap2.Dispose()
m.loGraphics.Dispose()
m.loAttributes.Dispose()
ERASE "c:\xxxxxxxx.png"

Endwith
**********************

Developer
Nov 20, 2007 at 6:11 AM
Update regarding the code in previous post. If I run this:

m.lcFileName = Getfile("png")

If Empty(m.lcFileName) Then
Return
Endif

m.lcImageData = Filetostr(m.lcFileName)

With _Screen.System.Drawing

*** Get PNG raw data

*** This way of creating the bitmap gives me an all white mask
m.lcbitmap = m.lcImageData
m.loBitmap1 = .Bitmap.Fromvarbinary(m.lcbitmap)
m.loBitmap1.Save(Addbs(Justpath(m.lcFileName)) + "xxx1.png")

*** This works
Strtofile(m.lcImageData, "c:\xxxxxxxx.png")
m.loBitmap1 = .Bitmap.New("c:\xxxxxxxx.png")
m.loBitmap1.Save(Addbs(Justpath(m.lcFileName)) + "xxx2.png")

Endwith

the 2 resulting files xxx1.png and xxx2.png are identical.
Nov 22, 2007 at 3:53 AM
Thanks. I managed to reproduce this.
I'll try to investigate what's going wrong there.
Developer
Nov 23, 2007 at 9:02 AM
Edited Nov 23, 2007 at 9:04 AM
SOLUTION FOUND

I think the problem is a "Feature" of GDI+, and this is how I found the solution. Since I could not use fromvarbinary, I was saving the bitmap raw data to a file and doing Bitmap.New(filename)

This way I discovered that I could not reuse the file name, as I remembered reading someone complain about GDI locking the file as long as the bitmap object exists. That is the "feature" of GDI, it blocks the source of the bitmap object.

So I check the code of FromVarbinary, I see it is just creating a stream object and passing it to FromStream, all very nice, but as soon as the FromVarBinary returns, m.loStream goes out of scope, and our Bitmap object dies of a sudden heart attack.

So it seems like the source stream of the bitmap object has to exist for the lifetime of the bitmap, the same way the source file is locked until the bitmap object dies?

I tried creating m.loStream as a property of _Screen, for a quick check:

_screen.AddProperty("stream")
screen.Stream = NEWOBJECT("xfcMemoryStream", XFCCLASSIO, "", m.tqBinary, .T.)
m.loImage = This.FromStream(_screen.Stream, m.tlUseEmbeddedColorManagement)

And it worked! So it seems you have to keep your stream around until the Bitmap object dies, you will have to store it as a property of the bitmap object.

You will do that in FromStream I guess, saving a copy of the stream passed as a Bitmap object property, and using that copy to create the Bitmap.

How nice it is to deal with this "features"!!

If you can send me a fixed version so I can change the code of the icon class, that is almost 90% finished
Nov 23, 2007 at 12:20 PM
Interesting.
How do you explain that the original test worked with images that have dimensions multiple of 8 then ?
Nov 24, 2007 at 5:47 AM
This discussion has been copied to a work item. Click here to go to the work item and continue the discussion.
Developer
Nov 24, 2007 at 2:53 PM
What I really meant when I said "If a file has a width that is not a mutiple of 8" I really was talking about the width of the pixeldata of a row of the bitmap. Bob Powell has a great explanation here: http://www.bobpowell.net/lockingbits.htm

The original test worked because we now usually work with 32bpp images, so the size of a row of pixel data is always = stride

With images less than 32bpp, a row of pixel data is not always equal to the stride, so if you ignore the stride, you get into problems.

But I will make more tests regarding this corruption when I get the new version that fixes this issue of the stream object going out of scope. Maybe the corruption was caused by that.