I see what you are talking about now. Honestly, there are only a few things that can be done
1. Process ALL 32bpp bitmaps as 24bpp
2. Build a better algo that can guess better whether alpha is in use or not
3. Live with it
4. Pass optional parameters to prevent the pvProcessAlphaBitmap routine from running
The problem is as I described. The print-screen created a 32bpp DIB that wrote values in the alpha channel. Even though the image is obviously not an alpha bitmap and is meant to be a 24bpp bitmap, the alpha channel contains non-zero values. The alpha bitmap processing algo sees this and must assume the bitmap uses the alpha channel.
When running this for multiple screenshots, I consistently got over 2500 pixels with the alpha channel filled in with values that range from 2 to 253. The unfortunate fact is that the bitmap does populate the alpha channel and therefore, by definition, is an alpha bitmap. Now in real life, we know this is not true, but there is no way for the routines to know that.
Insomnia is just a byproduct of, "It can't be done"
I see what you are talking about now. Honestly, there are only a few things that can be done
1. Process ALL 32bpp bitmaps as 24bpp
2. Build a better algo that can guess better whether alpha is in use or not
3. Live with it
4. Pass optional parameters to prevent the pvProcessAlphaBitmap routine from running
The problem is as I described. The print-screen created a 32bpp DIB that wrote values in the alpha channel. Even though the image is obviously not an alpha bitmap and is meant to be a 24bpp bitmap, the alpha channel contains non-zero values. The alpha bitmap processing algo sees this and must assume the bitmap uses the alpha channel.
When running this for multiple screenshots, I consistently got over 2500 pixels with the alpha channel filled in with values that range from 2 to 253. The unfortunate fact is that the bitmap does populate the alpha channel and therefore, by definition, is an alpha bitmap. Now in real life, we know this is not true, but there is no way for the routines to know that.
Because <Print Screen> will capture whole screen,including Windows Captionbar,Start Menu,Task bar and System tray whose are alpha unfortunately.
I have made a funny test:
Code:
For B = X - 3& To X - 1&
If inArray(B) > inArray(X) Then
' pARGB can never have a R,G,B value > alpha value so this must be ARGB
lColorFormat = PixelFormat32bppARGB ' ARGB format; done checking
X = X + scanWidth ' exit X loop
Y = Height ' exit Y loop
alphaNum = alphaNum + 1
Debug.Print inArray(B), inArray(X)
'Exit For
End If
Next
1.Windows <Start Menu> bar,Alpha number is 3
2.Windows <Task bar>,Alpha number is 2
3.Windows <System Tray>,Alpha number is 1
4.Windows <Desktop>, Alpha number is 2
5.Windows Caption bar,Alpha number is 0 (exclude the min/max/close button)
6.Windows Caption bar,Alpha number is 1 (including the min/max/close button)
Makes sense, but doesn't help. I think the only way to "fix" this is to not process alpha bitmaps automatically, rather provide a parameter for 32bpp images that either skips or enters the pvProcessAlphaBitmap.
Thinking out loud, this may be a better option. The routine that processes stdPicture objects takes a shortcut, for stdPicture bitmaps, it simply passes the .Handle off to the routine that processes DIB handles. The odds of someone trying to load a real 32bpp alpha bitmap into a stdPicture would be rare I would think. Therefore the routine could be modified to not take that shortcut and process them a bit differently as either paletted or 24bpp bitmaps, ignoring any alpha channels. A thought.
Continuing with that thought... If someone does load a true alphablended bitmap into a stdPicture object (possible), to process it as alphablended, they would call the routine that loads DIBhandles instead, passing the .Handle property. This new process/option takes some of the automated picture type recognition out for stdPictures (bitmaps only), but provides the coder with both options.
Last edited by LaVolpe; Jun 5th, 2010 at 12:52 AM.
Insomnia is just a byproduct of, "It can't be done"
Makes sense, but doesn't help. I think the only way to "fix" this is to not process alpha bitmaps automatically, rather provide a parameter for 32bpp images that either skips or enters the pvProcessAlphaBitmap.
Thinking out loud, this may be a better option. The routine that processes stdPicture objects takes a shortcut, for stdPicture bitmaps, it simply passes the .Handle off to the routine that processes DIB handles. The odds of someone trying to load a real 32bpp alpha bitmap into a stdPicture would be rare I would think. Therefore the routine could be modified to not take that shortcut and process them a bit differently as either paletted or 24bpp bitmaps, ignoring any alpha channels. A thought.
Continuing with that thought... If someone does load a true alphablended bitmap into a stdPicture object (possible), to process it as alphablended, they would call the routine that loads DIBhandles instead, passing the .Handle property. This new process/option takes some of the automated picture type recognition out for stdPictures (bitmaps only), but provides the coder with both options.
Put an extra Optional ByVal IgnoreAlpha As Boolean to force LoadPicture_stdPicture to ignore Alpha channel when copy picture from clipboard or screen-capture. I guess you will come out a better solution again.
Public Function LoadPicture_DIBhandle(DibHandle As Long, TokenClass As cGDIpToken, Optional ByVal ClearAttributes As Boolean = True, Optional ByVal IgnoreAlpha As Boolean = False) As Boolean
Public Function LoadPicture_stdPicture(Picture As StdPicture, TokenClass As cGDIpToken, Optional ClearAttributes As Boolean = True, Optional IgnoreAlpha As Boolean) As Boolean
Private Function pvProcessAlphaBitmap(inArray() As Byte, Width As Long, Height As Long, resetFlag As pvCleanUpEnum, Optional IgnoreAlpha As Boolean = False) As Boolean
In other classes/projects I've written, I have a more generic LoadPicture routine that accepts a variant. That routine tests the variant to see if its an array, long handle, stdPic, ClipBoard, Data (drag/drop) object, filename, or IStream/IUnknown object.
But that is just a tip. I do not plan on making this thread's project commercial quality. It was an attempt to get some people thinking GDI+ that were intimidated by it and I think it can do that.
There are many advanced functions that I have not included in this project and you are aware of a few of them. I don't mind giving you hints/tips to improve this project, but I don't plan on supersizing this one.
Insomnia is just a byproduct of, "It can't be done"
Because <Print Screen> will capture whole screen,including Windows Captionbar,Start Menu,Task bar and System tray whose are alpha unfortunately.
I have made a funny test:
1.Windows <Start Menu> bar,Alpha number is 3
2.Windows <Task bar>,Alpha number is 2
3.Windows <System Tray>,Alpha number is 1
4.Windows <Desktop>, Alpha number is 2
5.Windows Caption bar,Alpha number is 0 (exclude the min/max/close button)
6.Windows Caption bar,Alpha number is 1 (including the min/max/close button)
One thought:
According to above trail,Why GDI+ display non-Alpha area with <BLACK> color except of <Start Menu> and <Task bar> and <Desktop> and Windows Caption bar?
I couldn't tell you. Except that the alpha values are probably non-zero and low values like 3, 5, etc. Alpha of zero means 100% transparency. So since low alpha values, they show up nearly transparent. When alpha value are used I think any 32bpp-aware graphics engine is going to assume they are indeed alpha. And if any alpha values are used, must assume all alpha channel values are used as alpha, nothing else.
Insomnia is just a byproduct of, "It can't be done"
Resizing a picturebox? Use Picture1.Move x, y, w, h
Maintain aspect ratio? Lots of examples in this forum & on the net.
I've also included 2 functions in that project that you can use: GetScaledImageSizes & GetScaledCanvasSize
Insomnia is just a byproduct of, "It can't be done"
In the sample project, the form's RenderTheImage sub has an example. Otherwise, you might want to post what you are trying and exactly which you want to do:
1) size the image to the container's dimensions, keeping aspect ratio
2) size the container to the image's dimensions, keeping aspect ratio
Insomnia is just a byproduct of, "It can't be done"
I want to do number 1, resize the image when the container is resizing, for example when I resize the form (dragging the edges of the windows) the container resizes, I want also the image inside the container resize to its dimensions.
Real short simple example. Note that you'll see flicker. If you want to prevent flicker, you'll want a backbuffer to render to then render the backbuffer to the form. Tons of examples about.
Code:
Option Explicit
Dim cImage As cGDIpImage
Dim cToken As cGDIpToken
Private Sub Form_Load()
Me.ScaleMode = vbPixels ' cGDIpImage class expects pixel values, not twips
Set cToken = New cGDIpToken
If cToken.Token = 0 Then
MsgBox "GDI+ failed to load", vbExclamation + vbOKOnly
Unload Me
Exit Sub
End If
Set cImage = New cGDIpImage
' load your image into cImage now...
End Sub
Private Sub Form_UnLoad(Cancel As Integer)
Set cImage = Nothing
Set cToken = Nothing
End Sub
Private Sub Form_Resize()
Dim newCx As Long, newCy As Long
Dim cRenderer As New cGDIpRenderer
cImage.GetScaledImageSizes Me.ScaleWidth, Me.ScaleHeight, newCx, newCy
Me.Cls
cRenderer.RenderImageClassToDC cImage, Me.hDC, (Me.ScaleWidth - newCx) \ 2, (Me.ScaleHeight - newCy) \ 2, newCx, newCy
' ^^ drawing from center of form. If not desired, simply change to 0,0 or whatever coords you want
Set cRenderer = Nothing
End Sub
You may be interested in my Alpha Image Control, linked in my signature below. It takes the idea of this project and combines it into a usercontrol that can produce very professional results, flicker-free...
If using my Alpha Image Control, you can load an image into the control during form design.
Then the code would be as simple as...
Code:
Private Sub Form_Load()
AlphaImgCtl1.Aspect = lvicScaled
' if wanting to draw the image from top/left of the control vs. centered within, then unrem following line
'AlphaImgCtl1.AlignCenter = False
'^^ both above properties can be set during design time & not required to be set here in Form_Load
End Sub
Private Sub Form_Resize()
AlphaImgCtl1.Move 0, 0, Me.ScaleWidth, Me.ScaleHeight
End Sub
Last edited by LaVolpe; Jul 29th, 2011 at 10:12 AM.
Insomnia is just a byproduct of, "It can't be done"
thanks on the code, it was very helpful, on Alpha Image Control how to put a checkered board on the background just like on the sample on the first page
thanks on the code, it was very helpful, on Alpha Image Control how to put a checkered board on the background just like on the sample on the first page
Two immediate options come to play
1) Simply create a checkerboard PNG or jpg/bmp in Paint or something else. Load that image into the control at design time or runtime
2) Runtime: Create your own checkerboard pattern to a hidden picturebox (AutoRedraw=True & sized appropriately) or offscreen memory DC/bitmap. Then send the image to LoadPictureGDIplus(), passing the Picturebox.Image property or memory bitmap handle, depending which method you used.
Last edited by LaVolpe; Jul 31st, 2011 at 03:44 PM.
Insomnia is just a byproduct of, "It can't be done"
Hi
Today just came across a wired bug. The Class fails to load a 8bit 256color JPEG as soon it is compiled. In the IDE it works fine.
After some debugging, I found that the variable Depth in pvCreateSourcelessImage is reseted to 0 by the following Line when running as .exe and not in die IDE:
This makes then GdipBitmapLockBits fail.
The work around is to make a copy of the variable Depth and to use this in GdipBitmapLockBits.
Below the modified function:
Code:
Private Function pvCreateSourcelessImage(hImage As Long, Width As Long, Height As Long, flipFlag As Long) As Long
' function creates a stand-alone GDI+ image from a linked image
' The routine here follows the instructions given at this MS KB article link
' http://support.microsoft.com/kb/814675
Dim tSize As RECTF, tSizeI As RECTI, BHI As BITMAPINFO
Dim tBMPsrc As BitmapData, tBMPdst As BitmapData
Dim cRender As cGDIpRenderer, pal() As Byte
Dim oldImage As Long, newImage As Long, hObj As Long
Dim dDC As Long, tDC As Long, Depth As Long, hGraphics As Long, dibPtr As Long
Dim DepthSave As Long
If Width = 0& Or Height = 0& Then ' get size of image
Call GdipGetImageBounds(hImage, tSize, UnitPixel)
Width = tSize.nWidth: Height = tSize.nHeight
End If
Call GdipGetImagePixelFormat(hImage, Depth)
DepthSave = Depth 'Save as CopyMemory BHI.bmiColors(0), pal(8), .biClrImportant resets Depth to 0
Select Case Depth
Case PixelFormat1bppIndexed, PixelFormat4bppIndexed, PixelFormat8bppIndexed
With BHI.bmiHeader
If Depth = PixelFormat1bppIndexed Then
.biBitCount = 1
ElseIf Depth = PixelFormat4bppIndexed Then
.biBitCount = 4
Else
.biBitCount = 8
End If
Call GdipGetImagePaletteSize(hImage, .biClrImportant)
ReDim pal(0 To .biClrImportant + 7&)
GdipGetImagePalette hImage, pal(0), .biClrImportant
CopyMemory BHI.bmiColors(0), pal(8), .biClrImportant ' Does reset Depths to 0 when running compiled
.biClrImportant = .biClrImportant \ 4&
.biClrUsed = 2 ^ .biBitCount
Erase pal()
End With
Case PixelFormat24bppRGB, PixelFormat16bppGrayScale, PixelFormat16bppRGB555, PixelFormat16bppRGB565, PixelFormat48bppRGB
BHI.bmiHeader.biBitCount = 24
Case Else
Set cRender = New cGDIpRenderer
cRender.AttachTokenClass m_Token
oldImage = m_Image(m_SourceIndex) ' using the cGDIpRenderer class
m_Image(m_SourceIndex) = hImage ' which requires this class to reflect correct image handle
hGraphics = cRender.CreateGraphicsFromImageClass(Me) ' create a generic graphics object
If hGraphics Then ' then create new GDI+ bitmap
If GdipCreateBitmapFromGraphics(Width, Height, hGraphics, newImage) = 0& Then
cRender.DestroyHGraphics hGraphics
Call GdipGetImageBounds(hImage, tSize, UnitPixel)
m_Image(m_SourceIndex) = newImage ' create graphics object around new bitmap
hGraphics = cRender.CreateGraphicsFromImageClass(Me)
If hGraphics Then ' draw the passed bitmap onto the new bitmap & clean up
Call cRender.RenderToHGraphics(hImage, hGraphics, 0&, 0&, Width, Height, tSize.nLeft, tSize.nTop, tSize.nWidth, tSize.nHeight)
cRender.DestroyHGraphics hGraphics
If flipFlag Then GdipImageRotateFlip newImage, flipFlag
pvCreateSourcelessImage = newImage
End If
Else
cRender.DestroyHGraphics hGraphics
End If
End If
m_Image(m_SourceIndex) = oldImage
End Select
If BHI.bmiHeader.biBitCount Then ' handle paletted & 24bit bitmaps here
tSizeI.nHeight = Height: tSizeI.nWidth = Width
With BHI.bmiHeader
.biHeight = Height
.biPlanes = 1
.biSize = 40
.biWidth = Width
If .biBitCount = 24 Then
If GdipBitmapLockBits(hImage, tSizeI, ImageLockModeRead, PixelFormat24bppRGB, tBMPsrc) Then .biBitCount = 0& 'flag meaning failure
Else
If GdipBitmapLockBits(hImage, tSizeI, ImageLockModeRead, DepthSave, tBMPsrc) Then .biBitCount = 0& 'Use DepthSave as Depth is 0 here when running compiled.
End If
End With
If BHI.bmiHeader.biBitCount Then
dDC = GetDC(GetDesktopWindow())
hObj = CreateDIBSection(dDC, BHI, 0&, dibPtr, 0&, 0&)
ReleaseDC GetDesktopWindow(), dDC
If hObj Then
CopyMemory ByVal dibPtr, ByVal tBMPsrc.Scan0Ptr, tBMPsrc.Stride * Height
GdipBitmapUnlockBits newImage, tBMPsrc
GdipCreateBitmapFromHBITMAP hObj, 0&, newImage
DeleteObject hObj
GdipImageRotateFlip newImage, (flipFlag Xor 6&)
pvCreateSourcelessImage = newImage
End If
End If
End If
GdipDisposeImage hImage
End Function
I'm not sure what is causing this behavior. Attached the Image that triggers this bug.
Good catch. I don't use that function any longer as I've written a more efficient and much faster routine to create such sourceless GDI+ bitmaps. I won't be updating this project with that routine which can be found in my AlphaImage Control project linked in my signature below.
The problem appears to be writing memory beyond intended bounds. That offending line should be changed to:
CopyMemory BHI.bmiColors(0), pal(8), .biClrImportant - 8
What was happening is that by copying 8 more bytes of data to the .bmiColors memory location than that location could hold, corruption of the following 8 bytes of memory on the stack occurs. This could be where the Depth variable resided? Anyway, I think you'll find the above patch suitable.
Insomnia is just a byproduct of, "It can't be done"
This project is a teaser to get one's feet wet. For a fully functional usercontrol that is based almost entirely on GDI+, see my alpha image control project.
If you have questions on how to do something with GDI+ that the attached project does not do, please post your question in the Graphics portion of the forum. I do not want, nor intend that this posting become a proxy "GDI+ Forum"
If you have questions about this project then by all means ask away. There may be a few logic errors in the project; as we find those I'll continue to update. Jump to bottom to determine when last updated.
GDI+ is a very handy tool and many coders stay away from it because it appears difficult, at first. So, the classes in this project are aimed at easing you along a bit. GDI+ has 100's of functions and the project herein only touches a small percentage of them. But should be enough for you to get your feet wet without being overwhelmed.
I do plan on updating this project from time to time. Things I have knowledge on and have not yet added to the project include GDI+ paths, strings, and regions. The project does include a very complete image processing class, a rendering class, and a pens/brushes class to get you started.
If you want the absolute minimal code to read and display an image at actual size, see post #8 below
Some advantages of using GDI+
:: Load/Save PNGs and TIFFs, even multi-page TIFFs
:: Load and display animated GIFs
:: Alphablending - images, lines, shapes, everything. GDI+ does not use standard RGB colors. All colors are ARGB which includes level of alphablending.
:: Built-in niffty options like rotation, mirroring, on-the-fly pixel transformations and more
:: No manual premultiplication of alpha values
Some disadvantages of GDI+
:: Crashes easy if not used properly. Objects created around other objects (i.e., images) are not actually stand-alone. GDI+ requires the source data to remain present until the GDI+ object is destroyed.
-- Here is the MS KB Article link: http://support.microsoft.com/kb/814675
-- The workaround is to create a DIBSection, then create a GDI+ bitmap from it and destroy the DIBSection
-- But that workaround won't allow access to other images in a multi-image formats (GIFs, TIFFs) and 32bpp become pARGB.
:: In IDE, can crash easily too -- like subclassing, you cannot just hit End or terminate your app from the debug window while GDI+ is running.
:: As mentioned above, a little complicated for new users
:: Does not support all older GDI functions. For example, you can't create an XOR pen with GDI+
:: Requires extra steps to setup for drawing; but like anything new to you, just takes a little getting used to
___________________________________________________________________________ Last Edited: Jan 21, 2010 8:50 PM GMT/Zulu
:: Modified primarily for TIFF saving
-- Can now save multiple images with/without attributes applied
-- TIFF images can be saved in 1,4,8,24,32 bit depths - dependent upon source image depth
:: Added another optional rendering quality setting when drawing: PixelOffsetMode
:: Removed requirement of cGDIpRenderer to be passed token class during its function calls.
-- Now entire class can be passed it one time via its AttachTokenClass method
:: Single page TIFFs no longer cache their original source data; more memory savings
Jan 19, 2010 2:20 AM GMT/Zulu
:: Revamped quite a bit
-- To reduce memory usage, PNG, BMP, & JPG no longer cache original data
-- CloneImage routine still had some shortcomings when trying to clone without applying image attributes; fixed
-- Fixed logic error reported by Jonney when cloning rotated images
:: Added additional image types: imageIconPNG & imageCursorPNG to distinguish between PNGs loaded from ico/cur resources
Jan 14, 2010 19:10 PM GMT/Zulu
:: Found 2 memory leaks, one each in 2 different functions; fixed
:: Changed logic when loading new images
-- If new image then all attributes are reset automatically (were not before). Optional load flag prevents this.
-- If multi-image format displays another image within same source; attributes remain (no change from previous logic)
:: CreateCloneWithAttributes renamed to CreateClone. WithAttributes now an optional parameter
:: ImageType property distinguishes between icons and cursors now
:: Reorganized and cleaned up some routines
:: Demo reworked a tad
-- You can now apply multiple attributes to the same image and see results
-- Included sample of saving a multi-page TIFF
Jan 12, 2010 01:10 AM GMT/Zulu
:: Added a little more functionality to the cGDIpImage class
-- GetPixel, SetPixel, ExtraTransparentColor (similar functionality of TransparentBLT)
:: Mirroring was off, had my constants swapped around, fixed
:: When SaveAsOriginalFormat was applied for 32bpp alphablended bitmaps, was saved as 24bpp vs 32pp, fixed
:: Updated demo a bit
-- included SaveAsOriginalFormat, TransparentBLT examples
-- included option to view any image in a multiple image format
-- included option to view image info: size, depth, type
Jan 11, 2010 01:30 AM GMT/Zulu
:: Jonney helped identify a logic error when deciding whether 32bpp images use the Alpha channel and how it is used. Thanx Jonney
...
AWESOME!
Too bad you don't in your online documentation explain how to use HGraphics. Render Class can create HGraphics from Image Class and a number of other sources. And while the Render Class can directly Render the Image Class to a DC for viewing, what it can't do is render an HGraphics to DC. Sure it can convert a DC to HGraphics, but not the other way. So after getting an HGraphics and using that for the Draw Rectangle command, I have NO WAY TO DISPLAY THE DRAWN RECTANGLE! Please help. Thanks in advance.
Sorry for my English, I translate with google!
Good evening everyone, I'm new to this forum and do not know English well.
I wanted to complete my little project for my collection of coins and I would, at this point, transfer images with a transparent background on word 2010.
I would have my picture (picture3.hdc) in transparency would save it in png transparent and then transfer it into word (only know the way through file:
where Filename = image.png).
How do I save a picture3.hdc file.png?
I could give a hand please?
I tried but I can not save the picture, I am only the background color!!
To explain better, I hope, I have a picturebox with an image with a white background and I would like to send a word in transparency ('cause word on the sheet there' a background image and you should see below, I have transformed my picturebox in a transparent picturebox.hdc (with mask), and now I would like to save it in png with transparent background.
Can you help me with the command line that I include in my project?
thank you very much
Last edited by egits75; Feb 20th, 2013 at 11:49 AM.
Hello; I am not so clever person. So I need your help: I want to display PNG data from a vCard in a picturebox. I already decoded the base64 data into an byte array. I would prefer to convert the data into a StdPicture to load it easily into the picturebox or imagebox.
Please can you provide me the minimal code for this? I need a function like this:
Private Function GetPictureFromPNGArray(PNG() as Byte) as StdPicture
Ciao LaVolpe,
first of all, thanks for this well done work, you saved me tons of hours about writing code to manage GDI+ from VB6!!
Your classes works very well for my pourposes. However I need something it seems to be not present at all in this classes: create a Bitmap/Image in memory.
At the moment, sources for creating cGDIpImage objec are: File, Stream, StdPicture and Dib handle. But there is no way to create a in-memory-bitmap like in C# i can do with the code:
Code:
Bitmap bitmap = new Bitmap(100, 100);
Suppose I've a .png with a round button shape inside (100x100px), and a .png with the floppy image(48x48px). Both .png are 32bbp and have transparence and AlphaChannel for a drop shadow effect. I need to obtain a single image, with the result of a paste operation between the two .pngs. This I can do with your class in this way:
1) load the two .pngs in two cGDIpImage objects via .LoadPicture_FileName() method
2) create a cGDIpRenderer object via .CreateGraphicsFromImageClass() method from first cGDIpImage object (the button image)
3) use the .RenderImageClassToHGraphics() method to paste then floppy object over the button object
4) save to new .png or use it with a PictureBox standard control
This works fine. But: if I need to get a 300x300px image as result of the merge operation, with button and floppy centered, I need a new image (not existing as .png file!!) of 300x300px, wich I use as starting point for cGDIpRenderer object, where I can paste over the button and floppy objects. Well, this new image of 300x300px should be created as new memory bitmap, then saved to .png file using .SaveAsPNG() method, preserving transparence and AlphaChannel.
I've been able to create a in-memory bitmap using this code:
Code:
Public Function Create(Width As Integer, Height As Integer)
Dim pbmi As BITMAPINFO
'Memorizza dimensioni
m_Width = Width
m_Height = Height
'Prepara informazioni per la generazione del bitmap
With pbmi.bmiHeader
.biBitCount = 32
.biCompression = BI_RGB
.biPlanes = 1
.biSize = Len(pbmi.bmiHeader)
.biWidth = m_Width
.biHeight = m_Height
.biSizeImage = m_Height * m_Width * 4&
End With
'Crea in memoria un DC compatibile con lo schermo
m_hDC = CreateCompatibleDC(0)
'Crea un BMP compatibile con il DC creato
m_hBMP = CreateDIBSection(m_hDC, pbmi, DIB_RGB_COLORS, m_hDIBS, ByVal 0&, ByVal 0&)
'Azzera contenuto
FillMemory ByVal m_hDIBS, (m_Width * m_Height * 4&), 0
End Function
Public Sub Dispose()
'Scarica oggetti
If (m_hBMP <> 0) Then DeleteObject m_hBMP
If (m_hDC <> 0) Then DeleteDC m_hDC
End Sub
and after creating the in-memory 300x300 surface bitmap I can use the
Code:
.LoadPicture_DIBhandle(m_hBMP , mclsToken)
method to create theh cGDIpImage object I can work with, starting from a bitmap created in memory and not loaded from file.
The problem is that this in-memory bitmap has the background setted to black (I mean due to the fact all BMP bytes are filled to 0). So, when I display the result in a PictureBox or save to a .png file, the background around the button is not transparent, but black. If I FillMemory with &H255 the background becomes white, of course.
At the moment my workaround is to load a file .png that is completely transparent in a "surface" cGDIpImage object, stretch it to the size I need, load others .png files, .RenderImageClassToHGraphics() to paste loaded .png over the "surface" object, then finally save it back to .png file (preserving transparence and AlphaChannel) or paste into Picturebox (with alphablend to the PictureBox background color).
It would be perfect if you can suggest me the code to obtain an ARBG, completely transparent, cGDIpImage object created directly in memory, giving only then Width and Height parameters.
With GDIP you can create a 32bit blank image with this line of code:
GdipCreateBitmapFromScan0 Width, Height, 0&, PixelFormat32bppPARGB, ByVal 0&, hImage
In the cGDIpImage class, you may want to create a new function where you pass the width,height and assign the hImage of the GdipCreateBitmapFromScan0 to the m_Image(0) variable in that class. Once you have a class you can render from GDIP to GDIP via the RenderImageClassToHGraphics after first calling CreateGraphicsFromImageClass and passing the destination class.
So logic could be:
1) modify then cGDIpImage class to add new function to create a blank 32bit bitmap as suggested above. Set that class' m_ImageType to imageBitmap and other properties as appropriate, i.e, m_Size, m_OrigColorType
2) call cGDIpRenderer.CreateGraphicsFromImageClass to get a graphics handle for that blank bitmap
3) call cGDIpRenderer.RenderImageClassToHGraphics, passing the source image class and the hGraphics handle
4) call cGDIpRenderer.DestroyHGraphics when done drawing
These classes were designed to get one started with GDI+, just intended as an outline to be learned from & eventually moved on to one's own classes & implementation.
Insomnia is just a byproduct of, "It can't be done"
With GDIP you can create a 32bit blank image with this line of code:
GdipCreateBitmapFromScan0 Width, Height, 0&, PixelFormat32bppPARGB, ByVal 0&, hImage
In the cGDIpImage class, you may want to create a new function where you pass the width,height and assign the hImage of the GdipCreateBitmapFromScan0 to the m_Image(0) variable in that class. Once you have a class you can render from GDIP to GDIP via the RenderImageClassToHGraphics after first calling CreateGraphicsFromImageClass and passing the destination class.
So logic could be:
1) modify then cGDIpImage class to add new function to create a blank 32bit bitmap as suggested above. Set that class' m_ImageType to imageBitmap and other properties as appropriate, i.e, m_Size, m_OrigColorType
2) call cGDIpRenderer.CreateGraphicsFromImageClass to get a graphics handle for that blank bitmap
3) call cGDIpRenderer.RenderImageClassToHGraphics, passing the source image class and the hGraphics handle
4) call cGDIpRenderer.DestroyHGraphics when done drawing
These classes were designed to get one started with GDI+, just intended as an outline to be learned from & eventually moved on to one's own classes & implementation.
Great!!! You are the man LaVolpe!!!
So, new function to create in-memory bitmap could be:
Code:
Public Function LoadPicture_Memory(Width As Long, Height As Long, ImageFormat As ImageColorFormatConstants, TokenClass As cGDIpToken) As Boolean
If TokenClass Is Nothing Then Exit Function
If TokenClass.Token = 0& Then Exit Function
If Width = 0 Then Exit Function
If Height = 0 Then Exit Function
Dim lResult As Long
Dim hImage As Long
If m_Token Is Nothing Then
Set m_Token = TokenClass
TokenClass.AddUser Me
End If
'Tenta creazione del Bitmap in memoria
GdipCreateBitmapFromScan0 Width, Height, 0&, ImageFormat, ByVal 0&, hImage
If hImage <> 0 Then
m_Image(0) = hImage
m_ImageType = imageBitmap
m_Size.nWidth = Width
m_Size.nHeight = Height
m_Attr = 0
m_Alpha = 0
m_TransColor = 0
m_Angle = 0
m_Lightness = 0
m_GrayScale = attrGrayNone
m_Mirror = attrMirrorNone
m_OrigColorType = ImageFormat
End If
LoadPicture_Memory = (hImage <> 0)
End Function
Image created by GdipCreateBitmapFromScan0 (handle in m_Image(0)) will be automatically disposed by
Code:
GdipDisposeImage m_Image(0)
in pvCleanUp function.
So, what's the difference to use PixelFormat32bppPARGB and PixelFormat32bppARGB values as PixelFormat for the new in-memory bitmap?? The resulting .png file seems to be the same..
Thanks once more for this huge work you've made and released as free usable code!
So, what's the difference to use PixelFormat32bppPARGB and PixelFormat32bppARGB values as PixelFormat for the new in-memory bitmap?? The resulting .png file seems to be the same..
Very slight pixel modification due to rounding during premultiplication. However, theoretically faster internal GDI+ usage, especially if intending to render to an old-fashioned GDI device context
Enjoy.
Edited: One flaw with your new function. ImageFormat should be restricted to 24 bit or higher. Honestly can't recall whether GDI+ allows creating of blank 16bit or not? However, anything less than 16bit requires a palette and your code doesn't currently support that. If GDI+ doesn't support creation (via Scan0 method) of specific bit depths, it uses the next higher depth it does support, or defaults to 32bit as a last resort
Last edited by LaVolpe; Jan 14th, 2015 at 11:11 AM.
Insomnia is just a byproduct of, "It can't be done"
Very slight pixel modification due to rounding during premultiplication. However, theoretically faster internal GDI+ usage, especially if intending to render to an old-fashioned GDI device context
Enjoy.
Edited: One flaw with your new function. ImageFormat should be restricted to 24 bit or higher. Honestly can't recall whether GDI+ allows creating of blank 16bit or not? However, anything less than 16bit requires a palette and your code doesn't currently support that. If GDI+ doesn't support creation (via Scan0 method) of specific bit depths, it uses the next higher depth it does support, or defaults to 32bit as a last resort
Ok, I mean.
Last (stupid) question, regarding plain BMP (24 bit) and VB6 StdPicture object.
If I use the code
Code:
Dim thePic as StdPicture
set thePic = LoadPicture("background.bmp")
I get the bitmap inside StdPicture object; then I can pass it to control's .Picture property to show it. Fine.
But if bmp is a set of contiguos icons of 32x32 size, and I'd like to create an array of n 32x32 StdPicture objects, each one with his own n-icon graphics from original bmp, how can I do this without any .hDC available??
I'm trying to use the .Render method of the StdPicture object, but it requires .hDC. More, if I create a new instance of StdPictures, Width, Height, Handle all equals 0.
I tried to create in-memory bitmap 32x32 and convert it to StdPicture via OleCreatePictureIndirect() and I get correct properties values, but Render method fails.
I'm surprised that .Render method of stdPicture would fail on a valid stdPicture object?
As far as an array of stdPicture objects, that can be done a few ways. Using a VB control called PictureClip I believe? You could also do this with these classes or using a hidden picturebox (autoredraw=true & sized to 32x32).
The logic for these classes, looks like this:
1) Use your new function to create a memory 32x32 (24 bit) bitmap
2) Load the bitmap that has the strip of 32x32 images into a separate image class
3) Using cGDIpRenderer, draw the current 32x32 block, offsetting x,y as needed, to the blank image
4) in that image class, Save the 32x32 image to a bitmap array that you can send to OleCreatePictureIndirect
5) now blank out that memory bitmap using cGDIpRenderer.EraseHGraphics
6) repeat steps 3-5 for each 32x32 block
7) destroy the created Graphics object & unload your classes
The logic for VB's picbox is very similar
1) PicboxBuffer: autoredraw=true, sized to 32x32 inside width (or 32x32 without borders)
2) Set PicboxBuffer.Picture = Nothing
3) BitBlt or stdPicture.Render a 32x32 block, offsetting x,y as needed, to PicboxBuffer.hDC
4) Set PicboxBuffer.Picture = PicboxBuffer.Image
5) cache PicboxBuffer.Picture to a new stdPicture object
6) repeat steps 2-5 for each 32x32 block
7) Clean up: Set PicboxBuffer.Picture = Nothing: PicboxBuffer.AutoRedraw = False, etc
The above is air-code. Hopefully I didn't miss an obvious (or subtle) step anywhere. Sounds like you are a good coder & can fill in any blanks I may have missed.
Edited: I just glanced over a comment you made and shouldn't have. You don't want to use an .hDC? Sounds like this is a class-based issue then? Just FYI: You always have an hDC to render to, just need to create one via GDI API calls. Of course that usually means you have to create a bitmap (GDI calls) & move it in & out of the hDC as needed. Using the 1st idea above avoids those extra steps since the classes have the necessary code to get what you want.
Last edited by LaVolpe; Jan 14th, 2015 at 03:08 PM.
Insomnia is just a byproduct of, "It can't be done"
Hello; I am not so clever person. So I need your help: I want to display PNG data from a vCard in a picturebox. I already decoded the base64 data into an byte array. I would prefer to convert the data into a StdPicture to load it easily into the picturebox or imagebox.
Please can you provide me the minimal code for this? I need a function like this:
Private Function GetPictureFromPNGArray(PNG() as Byte) as StdPicture
Don't be mad. I took over a year off from this forum. When I came back I was ignoring all posts that were 1+ years old
You have two issues:
1) VB6 does not support PNG as a stdPicture. The PNG will have to be converted to a bitmap/jpg for use in the stdPicture object
2) The classes in this project do not directly support saving any images to a stdPicture object
So, workarounds.
1) Consider using my AlphaImage control. It accepts base64 and can also save to various formats, including saving to a stdPicture object. However, that control was an attempt to replace VB's image control. So all you have to do is simply add it to a form and use it as is.
2) There are several examples of sending a byte array to the OleCreatePictureIndirect API. If you search for that API in the VB forum, you will find examples. So, what you can do is send the PNG array to the cGDIpImage class' LoadPicture_Stream function to create an image. Then call the SaveAsBMP or SaveAsJPG method and pass the array returned from those functions to the OleCreatePictureIndirect API to create a stdPicture
Insomnia is just a byproduct of, "It can't be done"
I'm surprised that .Render method of stdPicture would fail on a valid stdPicture object?
You can try by yourself.. creating a new StdPicture you won't get a valid bitmap behind the object.. you have to get a reference from an existing .Picture from PictureBox or use LoadImage() function.. really strange!
Originally Posted by LaVolpe
As far as an array of stdPicture objects, that can be done a few ways. Using a VB control called PictureClip I believe? You could also do this with these classes or using a hidden picturebox (autoredraw=true & sized to 32x32).
Oh, that was only an example.. I don't really need a PictureClip control, or, at least, I don't need only that kind of solution.. For example, I'm implementing some code for 9patch images: starting from 256x256 .png button I've to create some other buttons in different sizes (100x80, 180x120, 90x45..) and each one, once created with your classes and pasted over a graphic background (a photo for example), I need to keep it in memory, in the form of StdPicture object, so is very easy to assign it to the corresponding PictureBox control on the form.
Originally Posted by LaVolpe
The logic for these classes, looks like this:
1) Use your new function to create a memory 32x32 (24 bit) bitmap
2) Load the bitmap that has the strip of 32x32 images into a separate image class
3) Using cGDIpRenderer, draw the current 32x32 block, offsetting x,y as needed, to the blank image
4) in that image class, Save the 32x32 image to a bitmap array that you can send to OleCreatePictureIndirect
5) now blank out that memory bitmap using cGDIpRenderer.EraseHGraphics
6) repeat steps 3-5 for each 32x32 block
7) destroy the created Graphics object & unload your classes
The logic for VB's picbox is very similar
1) PicboxBuffer: autoredraw=true, sized to 32x32 inside width (or 32x32 without borders)
2) Set PicboxBuffer.Picture = Nothing
3) BitBlt or stdPicture.Render a 32x32 block, offsetting x,y as needed, to PicboxBuffer.hDC
4) Set PicboxBuffer.Picture = PicboxBuffer.Image
5) cache PicboxBuffer.Picture to a new stdPicture object
6) repeat steps 2-5 for each 32x32 block
7) Clean up: Set PicboxBuffer.Picture = Nothing: PicboxBuffer.AutoRedraw = False, etc
The above is air-code. Hopefully I didn't miss an obvious (or subtle) step anywhere. Sounds like you are a good coder & can fill in any blanks I may have missed.
Edited: I just glanced over a comment you made and shouldn't have. You don't want to use an .hDC? Sounds like this is a class-based issue then? Just FYI: You always have an hDC to render to, just need to create one via GDI API calls. Of course that usually means you have to create a bitmap (GDI calls) & move it in & out of the hDC as needed. Using the 1st idea above avoids those extra steps since the classes have the necessary code to get what you want.
However the code to create 9-path images and all the other graphics will be the best to be keept in common classes/modules, without the need of a form and a hidden PictureBox control. That's the reason why I said "no .dHC", I don't (want to) have a PictureBox to use for graphics pourposes, and if I can load sources images from files, I need a "destination surface" in memory, and I've to create it from scratch.. Your classes can manage this (now with GdipCreateBitmapFromScan0) but I don't want to bother GDI+ to create a plain 24bit bitmap for raster operation, so I developed the following code to create an in-memory bitmap using the standard GDI APIs, and convert it back to StdPicture object I cas store in a Collection for faster later use.
Code:
Option Explicit
Private Declare Function OleCreatePictureIndirect Lib "olepro32.dll" (lpPictDesc As PICTDESC, RefIID As GUID, ByVal fPictureOwnsHandle As Long, IPic As Any) As Long
Private Declare Function CreateCompatibleDC Lib "gdi32.dll" (ByVal hDC As Long) As Long
Private Declare Function DeleteDC Lib "gdi32.dll" (ByVal hDC As Long) As Long
Private Declare Function DeleteObject Lib "gdi32.dll" (ByVal hObject As Long) As Long
Private Declare Function GetObjectType Lib "gdi32.dll" (ByVal hGDIObj As Long) As Long
Private Declare Function GetObject Lib "gdi32.dll" (ByVal hObject As Long, ByVal nCount As Long, lpObject As Any) As Long
Private Declare Function SelectObject Lib "gdi32.dll" (ByVal hDC As Long, ByVal hObject As Long) As Long
Private Declare Function CreateDIBSection Lib "gdi32.dll" (ByVal hDC As Long, pBitmapInfo As BitmapInfo, ByVal un As Long, ByRef lplpDIBS As Long, ByVal handle As Long, ByVal dw As Long) As Long
Private Declare Function FillMemory Lib "kernel32.dll" Alias "RtlFillMemory" (ByRef Destination As Any, ByVal Length As Long, ByVal Fill As Byte) As Long
Private Declare Function FillRect Lib "user32.dll" (ByVal hDC As Long, lpRC As RECT, ByVal hBR As Long) As Long
Private Declare Function CreateSolidBrush Lib "gdi32.dll" (ByVal crColor As Long) As Long
Private Const OBJ_BITMAP As Long = &H7
Private Const PICTYPE_BITMAP As Long = &H1
Private Const BI_RGB = 0
Private Const DIB_RGB_COLORS = 0
Private Type GUID
Data1 As Long
Data2 As Integer
Data3 As Integer
Data4(7) As Byte
End Type
Private Type PICTDESC
cbSize As Long
picType As Long
hImage As Long
xExt As Long
yExt As Long
End Type
Private Type BitmapInfoHeader '40 bytes
biSize As Long
biWidth As Long
biHeight As Long
biPlanes As Integer
biBitCount As Integer
biCompression As Long
biSizeImage As Long
biXPelsPerMeter As Long
biYPelsPerMeter As Long
biClrUsed As Long
biClrImportant As Long
End Type
Private Type RGBQUAD
rgbBlue As Byte
rgbGreen As Byte
rgbRed As Byte
rgbReserved As Byte
End Type
Private Type BitmapInfo
bmiHeader As BitmapInfoHeader
bmiColors As RGBQUAD
End Type
Private Type RECT
nLeft As Long
nTop As Long
nWidth As Long
nHeight As Long
End Type
Private Type BITMAP
bmType As Long
bmWidth As Long
bmHeight As Long
bmWidthBytes As Long
bmPlanes As Integer
bmBitsPixel As Integer
bmBits As Long
End Type
Public Function CreateStdPicture(Width As Long, Height As Long, Optional BackgroundColor As OLE_COLOR = vbBlack) As IPicture
Dim pbmi As BitmapInfo
Dim hDC As Long
Dim hBMP As Long
Dim hDIBS As Long
Dim hOldBMP As Long
Dim hBrush As Long
Dim tRECT As RECT
Dim lResult As Long
'Prepara informazioni per la generazione del bitmap
With pbmi.bmiHeader
.biSize = Len(pbmi.bmiHeader)
.biBitCount = 24
.biCompression = BI_RGB
.biPlanes = 1
.biWidth = Width
.biHeight = Height
.biSizeImage = ((((Width * .biBitCount) + &H1F) And Not &H1F) \ &H8) * Height
End With
'Crea in memoria un DC compatibile con lo schermo
hDC = CreateCompatibleDC(0)
'Crea un BMP compatibile con il DC creato
hBMP = CreateDIBSection(hDC, pbmi, DIB_RGB_COLORS, hDIBS, ByVal 0&, ByVal 0&)
'Azzera contenuto
FillMemory ByVal hDIBS, pbmi.bmiHeader.biSizeImage, 0
'Se colore diverso da nero, effettua un fill dell'area del BMP
If (BackgroundColor <> vbBlack) Then
'Seleziona il BMP nel DC
hOldBMP = SelectObject(hDC, hBMP)
'Colora il bitmap con il colore di sfondo
With tRECT
.nTop = 0
.nLeft = 0
.nWidth = Width
.nHeight = Height
End With
'Crea un brush solido
hBrush = CreateSolidBrush(BackgroundColor)
'Riempie il bitmap con il brush creato
lResult = FillRect(hDC, tRECT, hBrush)
'Elimina il brush
lResult = DeleteObject(hBrush)
'Rilascia il BMP
SelectObject hDC, hOldBMP
End If
'Distrugge il DC generato, mentre il BMP rimane valido e verrà automaticamente distrutto al momento della distruzione dell'oggetto
'OLE generato tramite la GDIToPicture(), grazie all'automazione offerta dall'interfaccia IDispatch
lResult = DeleteDC(hDC)
'Converte in IPicture e ritorna l'oggetto
Set CreateStdPicture = GDIToPicture(hBMP)
End Function
Public Function GDIToPicture(ByVal hGDIObj As Long) As IPicture
Dim PicDesc As PICTDESC
Dim IPict As IPicture
Dim lObjType As Long
Dim lResult As Long
'Recupera tipologia di oggetto presente nell'handle passato
lObjType = GetObjectType(hGDIObj)
Select Case lObjType
Case OBJ_BITMAP
PicDesc.cbSize = 16
PicDesc.picType = PICTYPE_BITMAP
Case Else
'Oggetto non riconosciuto
Exit Function
End Select
'Imposta l'handle del DC
PicDesc.hImage = hGDIObj
'Tenta la conversione da BMP a StdPicture
lResult = OleCreatePictureIndirect(PicDesc, IPictureGUID(), 1, IPict)
If (lResult = 0) Then Set GDIToPicture = IPict
'Scarica riferimento all'oggetto IPucture locale
Set IPict = Nothing
End Function
Private Function IPictureGUID() As GUID
'IPicture GUID {7BF80980-BF32-101A-8BBB-00AA00300CAB}
With IPictureGUID
.Data1 = &H7BF80980
.Data2 = &HBF32
.Data3 = &H101A
.Data4(0) = &H8B
.Data4(1) = &HBB
.Data4(2) = &H0
.Data4(3) = &HAA
.Data4(4) = &H0
.Data4(5) = &H30
.Data4(6) = &HC
.Data4(7) = &HAB
End With
End Function
Public Sub RenderStdPicture(SourcePic As IPicture, DestPic As IPicture, Optional X As Long = -1, Optional Y As Long = -1, Optional Width As Long = -1, Optional Height As Long = -1, Optional xSrc As Long = -1, Optional ySrc As Long = -1, Optional WidthSrc As Long = -1, Optional HeightSrc As Long = -1)
Dim hDC As Long
Dim hBMP As Long
Dim hOldBMP As Long
Dim tBITMAP As BITMAP
Dim lResult As Long
'Effettua render delle due StdPicuture
'Verifica i valori x,y,width,height. I parametri devono essere espressi tutti in Pixel
If (X = -1) Then X = 0
If (Y = -1) Then Y = 0
If (Width = -1) Then Width = HimetricToPixelX(SourcePic.Width)
If (Height = -1) Then Height = HimetricToPixelY(SourcePic.Height)
If (xSrc = -1) Then xSrc = 0
If (ySrc = -1) Then ySrc = 0
If (WidthSrc = -1) Then WidthSrc = HimetricToPixelX(SourcePic.Width)
If (HeightSrc = -1) Then HeightSrc = HimetricToPixelY(SourcePic.Height)
'Crea un DC per la StdPicture di destinazione
hDC = CreateCompatibleDC(0)
'Seleziona il BMP nel DC
hOldBMP = SelectObject(hDC, DestPic.handle)
'Effettua il render
SourcePic.Render hDC, X, Y, Width, Height, PixelToHimetricX(xSrc), SourcePic.Height - PixelToHimetricY(ySrc), PixelToHimetricX(WidthSrc), -PixelToHimetricY(HeightSrc), 0&
'Rilascia il BMP
SelectObject hDC, hOldBMP
'Distrugge il DC generato
lResult = DeleteDC(hDC)
End Sub
Private Function HimetricToPixelY(Value As Long) As Long
HimetricToPixelY = (CDbl(Value) / 1000) * 567 / Screen.TwipsPerPixelY
End Function
Private Function PixelToHimetricY(Value As Long) As Long
PixelToHimetricY = (CDbl(Value) * 1000) / 567 * Screen.TwipsPerPixelY
End Function
Private Function HimetricToPixelX(Value As Long) As Long
HimetricToPixelX = (CDbl(Value) / 1000) * 567 / Screen.TwipsPerPixelX
End Function
Private Function PixelToHimetricX(Value As Long) As Long
PixelToHimetricX = (CDbl(Value) * 1000) / 567 * Screen.TwipsPerPixelX
End Function
What do you think about this way to deal with graphics resources?!?
In case you want to handle your graphics without any Controls - and store them in a
VB-Collection as StdPictures as a kind of global ImageCache...
I've managed that already in a combination of GDI+ based PNG-Loading - and then storing
the resulting DIBs (as 32Bit ones, including the AlphaChannel) in normal StdPictures.
That is managed in a small wrapper-class (cPngCacheGDIP), which when declared globally
(and then filled with PNG-resources at App-Startup) - would then be reachable throughout
your Application, to render its preloaded Alpha-Image-StdPictures "per Key".
Not sure if that is more like, what you are after?
Here's a variation of the above, which is using the base-mechanism (the cPngCacheGDIP),
in lightweight UserControl-encapsulations - but that's not as "pure VBClass-driven" anymore,
as the example in the above link. http://www.vbforums.com/showthread.p...mage-Sprite%29
What do you think about this way to deal with graphics resources?!?
There are various ways to deal with graphics. In my alpha image control, I don't use stdPictures at all, but since VB uses them, that control has a SaveAs option to create them if the user needs them.
I must stress that these classes were not designed to be a graphics factory for any serious projects. There were designed to get people comfortable with using GDI+, more or less: "Graphics 101 with GDI+", borrowing college terminology as a play on words.
Insomnia is just a byproduct of, "It can't be done"
Hey LaVolpe, just a note of appreciation - these classes are awesome for my purposes and are very much appreciated as they saved me tons of time. I've been using Freeimage in this particular application for several years now but I've decided to streamline the code and that my program doesn't really need to support 23+ image formats. And using GDI+ internally dumps the dependence on the Freeimage dll as well. Anyway, thanks again for the fine work!