# VBForums CodeBank > CodeBank - Visual Basic 6 and earlier >  [VB6] GDI+ Usage & Samples

## LaVolpe

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.

A great site for descriptions of the GDI+ API functions.  

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

----------


## Jonney

There's problem with opening 32bpp picture. 
Offset = LBound(inArray)  instead of Offset = 14& in function ProcessAlphaBitmap.

----------


## Jonney

Another place in LoadPicture_DIBSection
"If ProcessAlphaBitmap(bData(), 14&, BHI.bmiHeader.biWidth, BHI.bmiHeader.biHeight) = True Then"
should be "If ProcessAlphaBitmap(bData(), 0&, BHI.bmiHeader.biWidth, BHI.bmiHeader.biHeight) = True Then"

----------


## LaVolpe

> Another place in LoadPicture_DIBSection
> "If ProcessAlphaBitmap(bData(), 14&, BHI.bmiHeader.biWidth, BHI.bmiHeader.biHeight) = True Then"
> should be "If ProcessAlphaBitmap(bData(), 0&, BHI.bmiHeader.biWidth, BHI.bmiHeader.biHeight) = True Then"


Jonney, you'd have to send me a sample bitmap that it doesn't work with. I suspect you were sending the arrays that contain BitmapInfo + PixelData not a proper bitmap file format.  Note my final comments in last paragraph below.

Regarding your 2 comments

1. When passing offset as 14 vs 0, this is required because the array being passed to that function has a BITMAPFILEHEADER attached, where the 1st 14 bytes are an additional header in front of the typical BITMAPINFO.  We are passing bitmap data in a file format, not DIBSECTION format.  If you noticed a couple lines before that call, you will see the pixel data is being added 54 bytes + color table, into the array.  The 54 bytes are the 40 for the bitmapinfo and 14 for fileheader.  So 14, is correct, not 0.


```
If GetDIBits(dDC, DIBhandle, 0&, BHI.bmiHeader.biHeight, bData(54& + clrUsed * 4&), BHI, 0&) Then
```

2. And in your previous post, again, for same reasons, offset cannot be LBound(Array) because the routine should be being passed an array that contains a bitmap/DIB with the BITMAPFILEHEADER in play.  

If anything I may have misled you in the function name. It probably would be better named as LoadPicture_DIBHandle vs LoadPicture_DIBSection.  The ProcessAlphaBitmap routine was not designed to pass some VB array that contains just the bitmapinfoheader and pixel data, it was designed to process bitmaps file formats.  If you are using it for a custom purpose, simply add an additional 14 bytes in front of your VB array before sending it to be parsed/loaded. But the 1st 2 bytes of that 14 byte buffer you may add must contain this value: &H4D42.  That is the magic number for a bitmap file. You should also populate bytes the final 8 bytes with real values and there are examples on what those values are suppose to be in several of the routines.  Bottom line: All loading functions in the classes assume proper file format of whatever was passed, not portions of that file format.

----------


## Jonney

Attached a 32bpp BMP file

----------


## LaVolpe

Jonney, thanx for the bitmap.  The issue isn't with offsets, the ProcessAlphaBitmap is identifying yours as 32bpp ARGB bitmap but GDI+ says it is 32bpp RGB (no alpha channel used).  I'll have to re-examine my ARGB, pARGB loop to see what happened and why.

Thanx again for the bug report.

*Edited*: Fixed and updated zip. Note that logic was flawed twice, not only did it fail when all alpha bytes were zero, it would have failed if all alpha bytes were 255. I threw that part of the project in as a final thought. I was always annoyed that GDI+ doesn't seem to want to process 32bpp alphablended bitmaps; though in the real-world, they are hard to find..

FYI. Why your tweaks worked. Because my routine was flawed it told GDI+ that it used the alpha channel. Since the alpha channel was all zeros, GDI+ interpretted it as a 100&#37; transparent bitmap. The reason why changing the offsets worked for you is that the routine failed on purpose because the information retrieved was offset by -14 bytes and basically retrieved garbage. Because it failed, it simply passed it off to GDI+ to see if it could load it, which it did because the image was fine, just my logic was flawed. However, those tweaks would have prevented an alphablended bitmap from loading correctly.

----------


## Jonney

Your fixes are working well with 32bpp BMP and alphablended bitmap now.

----------


## LaVolpe

A little bonus code. If you only want to render a PNG, TIFF to a DC of your choosing and render it full size, this is the minimal code you need.  You could also render jpg, bmp, wmf, emf and first frame of a gif.  Not all icons will be loaded by GDI+.  To render at a size other than full size, you will need more GDI+ API calls.

*Edited*: See post #19 & post #21 below for potentially weird behavior using GdipDrawImage.


```
Private Declare Function GdiplusStartup Lib "gdiplus" (Token As Long, inputbuf As GdiplusStartupInput, Optional ByVal outputbuf As Long = 0) As Long
Private Declare Function GdipLoadImageFromFile Lib "GdiPlus.dll" (ByVal mFilename As Long, ByRef mImage As Long) As Long
Private Declare Function GdipDeleteGraphics Lib "GdiPlus.dll" (ByVal mGraphics As Long) As Long
Private Declare Function GdipCreateFromHDC Lib "gdiplus" (ByVal hDC As Long, hGraphics As Long) As Long
Private Declare Function GdipDrawImage Lib "GdiPlus.dll" (ByVal mGraphics As Long, ByVal mImage As Long, ByVal mX As Single, ByVal mY As Single) As Long
Private Declare Function GdipDisposeImage Lib "gdiplus" (ByVal Image As Long) As Long
Private Declare Sub GdiplusShutdown Lib "gdiplus" (ByVal Token As Long)
Private Type GdiplusStartupInput
    GdiplusVersion           As Long
    DebugEventCallback       As Long
    SuppressBackgroundThread As Long
    SuppressExternalCodecs   As Long
End Type

Private Function RenderPNG(FileName As String, hDC As Long, X As Long, Y As Long) As Boolean
    On Error Resume Next
    Dim GDIsi As GdiplusStartupInput, gToken As Long, hGraphics As Long, hBitmap As Long
    GDIsi.GdiplusVersion = 1&
    GdiplusStartup gToken, GDIsi
    If Err Then
          Err.Clear
          Exit Function
    ElseIf gToken = 0& Then
         Exit Function
    End If
    On Error Goto 0
    Call GdipCreateFromHDC(hDC, hGraphics)
    If hGraphics Then
        Call GdipLoadImageFromFile(StrPtr(FileName), hBitmap)
        If hBitmap Then
            GdipDrawImage hGraphics, hBitmap, X, Y
            GdipDisposeImage hBitmap
            RenderPNG = True
        End If
        GdipDeleteGraphics hGraphics
    End If
    GdiplusShutdown gToken
End Function
```

----------


## VBClassicRocks

Keith, thanks for sharing this fine work.  Over the years I've
learned a lot from your PSC projects and this is no exception.
This really takes a lot of the mystery out of GDIP.

----------


## coolcurrent4u

hello,

in the site u listed , some of them use code in c++ like this one

http://www.com.it-berater.org/gdiplu..._functions.htm

how do i translate this to vb


thanks

----------


## LaVolpe

> hello, if the site to listed , some of them use code in c++ like this one http://www.com.it-berater.org/gdiplu..._functions.htm how do i translate this to vb
> thanks


Just gotta know how  :Wink: 
Look at the PowerBasic declarations, not the C decs.  They are far easier, DWORD is Long.  Also use declarations in the project as a guide to help.

----------


## Jonney

I didn't see Attributes being changed with a Rotation (m_Angle) in pvModifyAttributes. Does a Rotation cause Image Attribute being modified? It seems yes. A Rotation could cause changes of Image Width or Height.


```
' Return the GDI+ image angle
' Attribute permanent until Destroy is called
Public Property Get Rotation() As Single
    Rotation = m_Angle
End Property
Public Property Let Rotation(newVal As Single)
    If m_Angle <> newVal Then
        m_Angle = newVal
        pvModifyAttributes   'Called,but no Rotation code inside
    End If
End Property
```

----------


## LaVolpe

Nice to have 2nd set of eyes.  

No, attributes are not modified when rotation is in play or not.  What handles rotation is the RenderImageClassToDC function in cGDIpRenderer class. Rotation is done via a world transformation on the DC.

So, the call to pvModifyAttributes is useless in that one spot & should be removed from that property Let statement & will do next update (a result of copy & pasting one property to the next and to the next).  Will re-think a bit to see if having Rotation property non-zero has any ill effects elsewhere in the routines.  If it does, then I will update sooner than later. I am thinking the answer is no, otherwise I would have seen those ill effects by now; but will think about it in my sleep tonight.

*Edited*: I didn't answer both of your questions.  The one regarding the size, you are very correct in that it can affect the size.

When rendering to a DC, it isn't the code's responsibility to ensure the rotated image fits the DC area, much like any rendering routine.  A simpler example is if you have a 64x64 drawing surface and load a 126x126 image and render it, it will not fit the surface. It is the coder's responsibility to dictate what dimensions the rendering should be done at.

Now in the CloneImage routine where the image is actually rotated and/or resized, it matters very much because I am taking the responsibility of ensuring the end result is large enough for the rotated image. I admit I did take a shortcut where I resized the image to SQR(W*W + H*H) which is the maximum size any dimension can have and be able to rotate the image at any angle without chopping it off.  Because that size may be larger than the actual rotated image, a fixer-upper section in that routine trims excess transparency.  Now, I could have taken the time to calculate the true bounds of the non-alpha pixels and then calculated their rotated dimensions to create the end result exactly sized without the fixer-upper stuff and wouldn't require a 2nd temp image because the 1st one would have been correct from the start.  But 1) I really hate trig, 2) pretty much the same amount of code to determine opacity bounds, and 3) I just wanted to draw the line on how advanced some of the functions were going to be.  I took out hundreds of lines of more advanced code from this project that no-one has seen, simply because, in my opinion, it was too advanced for a "get your feet wet" type of project.

----------


## Jonney

Since What I indicated previously,rotation don't change attributes, so I can't figure out how to save a rotated picture directly using one of the cGDIpImage functions.
   Here is a way how to save a rotated picture based on your demo:

1. Change DoRotationExample:


```
Private Sub DoRotationExample()

    If cImage.Handle = 0& Then LoadResourceImage "Spider"
    cImage.Rotation = cboSample.ListIndex * 5
    RenderTheImage
    cImage.ExtraTransparentColor = cImage.GetPixel(0, 0)
    RenderTheImage 'Apply m_TransColor so that we can modify m_Attr so that we can call below codes
           '(
            'If m_Attr Then  ' any attributes to apply?
               'Set cNewImage = New cGDIpImage
               'CloneImage cNewImage, , , True
               'hImage = cNewImage.Handle
             'Else
                'hImage = m_Image(m_SourceIndex)
             'End If
             ')

End Sub
```

2.  Save the rotated picture using DoSaveExample. 
eg.Case 0: bOk = cImage.SaveAsBMP(bData(), True) to save as bmp file.

3.If the original Boundary is 'bigger' than Boundary of after-rotated picture,then the picture will save 'rightly' else some of part will cut off.

4.Your extra routine will need to cope with Saving the rotated picture.

----------


## LaVolpe

Jonney, thanx for the bug report.
Regarding your previous post and rotation, there is something I need to update in multiple routines. The code is already present & suitable

1. In the SaveAs.... routines, there is a statement that needs to be changed in each of them (except SaveAsOriginalFormat).
From:  If m_Attr Then
To:  If m_Attr <> 0& Or m_Angle <> 0! Then
or depending on taste:  If Not (m_Attr = 0& And m_Angle = 0!) Then

2. In CloneImage, need to offset the rendering so it doesn't get chopped off; oversight.
From: cRenderer.RenderImageClassToDC ....
To: 

```
cRenderer.RenderImageClassToDC Me, tDC, (cX - Width) \ 2, (cY - Height) \ 2, _
      Width, Height, , , , , InterpolationModeHighQualityBicubic, SmoothingModeAntiAlias
```

*Edited*: Tweaks incorporated into latest Zip in post #1

----------


## Jonney

> Jonney, thanx for the bug report.
> Regarding your previous post and rotation, there is something I need to update in multiple routines. The code is already present & suitable
> 
> 1. In the SaveAs.... routines, there is a statement that needs to be changed in each of them (except SaveAsOriginalFormat).
> From:  If m_Attr Then
> To:  If m_Attr <> 0& Or m_Angle <> 0! Then
> or depending on taste:  If Not (m_Attr = 0& And m_Angle = 0!) Then
> 
> 2. In CloneImage, need to offset the rendering so it doesn't get chopped off; oversight.
> ...


Yes,it works now.

----------


## cnewer

Thanx.

----------


## LaVolpe

Jonney brought up a good example where the classes I supplied may not be ideal as is. 

Scenario: Loading some massive compressed image like a TIFF or JPG or PNG, for example, that is 5-10x screen size.

Issue: Takes quite awhile for the image to eventually be displayed. 

Reasons: The classes create stand-alone images to prevent accidental GDI+ crashes if the source image is removed while the GDI+ image is bound to that source. See this link that describes the issue and workarounds by MSDN.  
1. Because of this the image file is processed (bytes read from file - potential bottleneck for massive images). The image is not processed directly from file because GDI+ has issues with many types of image formats. Therefore the bytes are sent to another routine that will handle those issues.
2. In order to eventually create the source image, an IStream interface is created and the bytes copied to that interface so that GDIpLoadFromStream API can be used by GDI+ to load image from an array.  Another potential bottleneck for massive images.
3. The stand-alone image is a bitmap, same size and bit depth of the original, then the source is rendered into the stand-alone or a data-copy from source to stand-alone is performed. For typical sized image, this process is quick but not for massive images.
4. Now, if scaling or attributes are applied (i.e., flipping, grayscaling, rotation, etc), GDI+ must process that massive image pixel by pixel and this will take time, no matter what.
5. Also, if one were to use one or more of the high-quality rendering options, that time is increased yet again.

Possible Solutions. 
1. Since these massive image sources will undoubtedly be files vs byte arrays... Create a new procedure or rework existing ones that create a GDI+ image directly from file (GDIpLoadImageFromFile API) and do not move, rename or delete the file while the GDI+ image is active.  This should speed things up dramatically.  
2. Create a new procedure or rework existing ones that will create a destination sized or a maximum of screen-sized GDI+ 32bpp bitmap, then load the file using GDIpLoadImageFromFile API and render that image into the 32bpp image, releasing the image created from the file and using the 32bpp GDI+ image.  This will be a bit slower than option 1 initially, but since the image used by GDI+ is now no larger than the screen, any future rendering and applied attributes will be far faster.
3. Any tweaks to existing procedures may require tweaks to supporting procedures within the classes; so before tweaking ensure you fully understand how the classes really work.
Note: GDIpLoadFromFile should be used for formats GDI+ handles easily: TIFF, PNG for example, not all bitmaps, icons, and not cursors.

Just some ideas. The classes were designed for every day use and ideally handling relatively normal-sized images, say screen-size or smaller.  Because the classes are designed that way, they may not be optimal for special situations and that is where you may wish to modify/tweak the code for those circumstances.

----------


## kontolimansyah

Hi LaVolpe

First I want to thank you for sharing this knowledge.

I just want to render a full size PNG file to my Form DC using your 'Bonus Code'  :Smilie: 
But the image displayed in my form is the mini version the original image.
My image resolution is 787x765, the displayed one is 252x245... how come?
I just simply copy paste your code, nothing changed.

any tought?

----------


## LaVolpe

I haven't a clue. Obviously GDI+ sees the image as 252x245. Maybe the image may have multiple resolutions/pages?  Try loading it with the main classes in post #1, double click on the picturebox and get the image details; they may explain it and may not.

Edited: See post #22 below for the reason

----------


## kontolimansyah

I follow your instruction and the image info is:
Image Type: PNG
Size : 787 x 765
Color Type : True Color + Alpha

BTW.. i change the draw function:
GdipDrawImage hGraphics, hBitmap, X, Y
to:
Call GdipGetImageBounds(hBitmap, m_Size, UnitPixel)
Call GdipDrawImageRectRectI(hGraphics, hBitmap, 0, 0, m_Size.nWidth, m_Size.nWidth, 0, 0, m_Size.nWidth, m_Size.nHeight, UnitPixel, 0&, 0&, 0&)
and it display the image correctly.

Thank's

----------


## LaVolpe

Ah, so GDI+ gdipDrawImage was the culprit? 

Well, I'm glad I never use that function in real life.  Always have used gdipDrawImageRectRectI since it allows much more flexibility.

Edited: Just another thing to chalk up to GDI+'s sometimes strange behavior.  There is probably a reason why it did what it did, would require a bit of research for the answer.

Edited again. I modified post #8 to reference your #19 & #21 should someone else have a similar experience.

*Follow up*: I believe the issue is with GDI+ auto-DPI scaling. Per MSDN:



> Another feature of GDI+ is that images carry around the DPI they were designed for (as in Image:GetPhysicalDimension and Bitmap:SetResolution, for example). You can use this information to scale images properly, or you can let GDI+ do it. If you don’t specify a height and width when you call DrawImage, GDI+ calculates them based on the screen’s DPI and the image’s DPI.


GdipDrawImage does not have width/height parameters. Per reply #19 above the image reports being 787x765 but rendered at 252x245. Well, if the image's DPI property/value reported 300 DPI, then using DPI scaling formula: 
logicalSize = (physicalSize * screenDPI) / imageDPI

Width: (787 * 96) / 300 = 251.84 rounded to 252
Height: (765 * 96) / 300 = 244.8 rounded to 245
So, 787x765 = 252x245 @ source image DPI of 300

----------


## GPRM_Dave

Thanks LaVolpe...

This all looks very interesting. I'm working on a old old vb app which I need to get transparency working and we always resorted to using GIF files before and always wondered whether we could get true alpha channels working, this will be quite exciting to test out, especially as we can change brightness as well!

Does GDI+ allow for blending modes? Multiply/add/screen/overlay?

----------


## LaVolpe

GDI+ does allow this via matrices if I am understanding your question correctly.  If so, it can be a little complex but not too much.  In post #1, I provided a link that describes GDI+ API functions. At the link are also some examples. They are in PowerBasic syntax, but I found them fairly easy to translate to VB.  

May be worth looking at...  Some functions you may want to look at (Graphics section of link): GdipMultiplyWorldTransform, GdipScaleWorldTransform, GdipSetWorldTrfansform, GdipTranslateWorldTransform. Also in the Matrix section of the link are the Matrix modification routines.

As far as overlay goes?  If you are talking about transaprency, then the classes already have that built in.

*Edited*:  Additional questions regarding GDI+ capabilities and usage of the functions I mentioned above should be posted in the Graphics portion of the forum.

----------


## GPRM_Dave

Just one small question on your post 8 code, I'm trying to get it to load the png file into a Picturebox and don't seem to be able to find the correct syntax... (sorry it's been a while since I've dabbled in vb, moved on to more graphics based things)

----------


## LaVolpe

> Just one small question on your post 8 code, I'm trying to get it to load the png file into a Picturebox and don't seem to be able to find the correct syntax... (sorry it's been a while since I've dabbled in vb, moved on to more graphics based things)


You might want to make the picturebox's AutoRedraw=True. Then refresh it after the call.  Note the remarks in that posting too; it is bare-bones code and offers no flexibility. You can't even get the image dimensions with it.  However, you can modify the code to include more functionality. Review the main classes in post #1 and pull what you need from there, as appropriate.



```
Call RenderPNG("C:\TestImage.png", Picture1.hDC, 0, 0)
```

----------


## Jonney

I do believe you are adding more stuff into the class.But how about some basic tutorial for new babies like Path/Region/Pen/Brush/Font/Matrix...etc?

----------


## LaVolpe

I like your structure/outline.  Couple things

1. MetaFiles (nope, I don't deal with them and IMO they are rarely used (creation) vs PNGs, Bmps, etc)
2. The Effect branch is something I'm not familiar with and haven't played with in great detail.  Those effects are only available with GDI+ v2 (I believe on Vista and above). Whether it works on XP or not, I don't know.
3. With the exception of strings, regions & paths, the other stuff is provided in various degrees of completeness. I do have a semi-complete paths class which I know you are a bit familiar with.  When I find the time, I'll add it to this project. Right now, I am focused on another project at the moment.

Note: My intention, as stated in post #1, is to get one's feet wet; not to attempt a comprehensive "how to" tutorial.

----------


## Jonney

Regarding GetScaledImageSizes function, if Rotation is being set,e.g. 45 degree,How to calculate the BestFit size? GetScaledImageSizes doesn't consider Rotation. In you demo,can you try to Rotation Example?part of picture will cut visually.

How can we scale the picture to best fit the picture1.width and picture1.height with any angles rotation?

----------


## Jonney

> A more effective method may be one of these:
> 
> 1. If no transparency is in effect...
> 
> 2. Transparency is different. ...


It's a little complicated. I will probably give up until you can give a good solution :Big Grin: .

----------


## LaVolpe

Ok, here is a routine you can tweak/add to your routines. 

First the remarks.
1. Rotating and scaling an image relative to a destination rectangle can produce undesirable effects. For example rotating a square within the same square without having the rotated shape escape the original square's bounds, produces a rubber-band effect on the rotated shape -- changing sizes as angle changes.
2. In most cases, I would think rubber-banding would not be desirable. For example, a clock hand rotating.

Anyway, here is the algo that will rubberband.  Adjust as needed. 
a. New form, add 2 shape controls, 1 command button
b. Leave all controls at default names, properties, positions
c. Copy & paste the following into the form and run it


```
Option Explicit

Private Declare Sub Sleep Lib "kernel32.dll" (ByVal dwMilliseconds As Long)
Private Type POINTAPI
    X As Long
    Y As Long
End Type

Private Sub Form_Load()
    Me.ForeColor = vbBlue
    With Shape1
        .Move 135, 135, 2550, 1485
    End With
    With Shape2
        .Move 3105, 135, 1485, 1485
    End With
    With Command1
        .Move 2250, 1710, 1215, 495
        .Caption = "Do It"
    End With
    Me.Move Me.Left, Me.Top, 4875, 2805
End Sub

Private Sub Command1_Click()

    Dim Angle As Single, A As Single, d2r As Double
    
    Dim sinT As Double, cosT As Double
    Dim h1 As Long, h2 As Long, hh As Long, ww As Long
    
    d2r = (4& * Atn(1)) / 180   ' conversion factor for degree>radian
    For A = 0 To 360
        
        Angle = Abs(A)          ' keep range between -90 to 90 for bounds calcs
        Select Case Angle
            Case Is < 91
            Case Is < 181
                Angle = 180 - Angle
            Case Is < 271
                Angle = Angle - 180
            Case Else
                Angle = 360 - Angle
        End Select
        sinT = Sin(Angle * d2r)
        cosT = Cos(Angle * d2r)
        
        With Shape1
            h1 = .Height * .Height / (.Width * sinT + .Height * cosT)
            h2 = .Width * .Width / (.Width * cosT + .Height * sinT)
            If h1 < h2 Then hh = h1 Else hh = h2
            ww = hh * .Width / .Height
        End With
    
        Me.Cls
        Call ShowResults(A, ww, hh, Shape1)
        
        With Shape2
            h1 = .Height * .Height / (.Width * sinT + .Height * cosT)
            h2 = .Width * .Width / (.Width * cosT + .Height * sinT)
            If h1 < h2 Then hh = h1 Else hh = h2
            ww = hh * .Width / .Height
        End With
    
        Call ShowResults(A, ww, hh, Shape2)
        Me.Caption = A & " Degree Rotation"
        DoEvents
        Sleep 10
    
    Next

        Me.Caption = "Done"

End Sub

Private Sub ShowResults(Angle As Single, ScaledCx As Long, ScaledCy As Long, sampleShape As Shape)
    
    ' following is not part of the rotation/resizing routine
    
    Dim tPTs(1 To 4) As POINTAPI, X As Long
    Dim ctrSrc As POINTAPI, ctrDest As POINTAPI
    Dim d2r As Double
    Dim sinT As Double, cosT As Double
    
    ctrSrc.X = ScaledCx \ 2
    ctrSrc.Y = ScaledCy \ 2
    With sampleShape
        ctrDest.X = .Width \ 2 + .Left
        ctrDest.Y = .Height \ 2 + .Top
    End With
            
    d2r = (4& * Atn(1)) / 180
    sinT = Sin((Angle Mod 360) * d2r)
    cosT = Cos((Angle Mod 360) * d2r)
            
            tPTs(1).X = (-ctrSrc.X * cosT) - (-ctrSrc.Y * sinT) + ctrDest.X
            tPTs(1).Y = (-ctrSrc.X * sinT) + (-ctrSrc.Y * cosT) + ctrDest.Y

            tPTs(2).X = (ScaledCx - ctrSrc.X) * cosT - (-ctrSrc.Y * sinT) + ctrDest.X
            tPTs(2).Y = (ScaledCx - ctrSrc.X) * sinT + (-ctrSrc.Y * cosT) + ctrDest.Y

            tPTs(3).X = (ScaledCx - ctrSrc.X) * cosT - (ScaledCy - ctrSrc.Y) * sinT + ctrDest.X
            tPTs(3).Y = (ScaledCx - ctrSrc.X) * sinT + (ScaledCy - ctrSrc.Y) * cosT + ctrDest.Y

            tPTs(4).X = (-ctrSrc.X * cosT) - (ScaledCy - ctrSrc.Y) * sinT + ctrDest.X
            tPTs(4).Y = (-ctrSrc.X * sinT) + (ScaledCy - ctrSrc.Y) * cosT + ctrDest.Y
        
    For X = 1 To 3
        Me.Line (tPTs(X).X, tPTs(X).Y)-(tPTs(X + 1).X, tPTs(X + 1).Y)
    Next
    Me.Line (tPTs(X).X, tPTs(X).Y)-(tPTs(1).X, tPTs(1).Y)
    
End Sub
```

Edited:  ww & hh would be the scaled width/height in relation to the target rectangle and angle of rotation. Rendering must be done from center of target rectangle to prevent image drawing over target bounds.

Edited yet again.  Now if you wanted to know what would be the scaled size of an image in a target rectangle so that the image can be rotated at any angle without rubberbanding, you can use this formula (refering to the GetScaledImageSizes code):


```
Dim rotSize As Long
rotSize = Sqr(m_Size.nWidth *m_Size.nWidth + m_Size.nHeight * m_Size.nHeight)
xRatio = destWidth / rotSize
yRatio = destHeight / rotSize
If xRatio > yRatio Then xRatio = yRatio
.... rest of code
```

----------


## Jonney

> Edited yet again.  Now if you wanted to know what would be the scaled size of an image in a target rectangle so that the image can be rotated at any angle without rubberbanding, you can use this formula (refering to the GetScaledImageSizes code):
> 
> 
> ```
> Dim rotSize As Long
> rotSize = Sqr(m_Size.nWidth *m_Size.nWidth + m_Size.nHeight * m_Size.nHeight)
> xRatio = destWidth / rotSize
> yRatio = destHeight / rotSize
> If xRatio > yRatio Then xRatio = yRatio
> ...


You missed considering the rotation angle? e.g. Rotate 5 degree,the image boundary is much less than picturebox size. We have to calculate the boundary of 4 corner of image  with a rotation. See screenshot.

----------


## LaVolpe

> You missed considering the rotation angle? ...


Nope. I gave you two options

The first option I provided accounts for *dynamic* sizes and if you run the code in a test form, you will see that.

The second option is a shortcut, its size is *static*.  The minimum size is determined that will allow the image to be rotated at any angle.  This means that the size can be larger, than needed, for some angles but will ensure all angles can be rotated within that size if rendered from the center out.  In other words, the scaled size ensures the image can be rotated at any angle without rubberbanding the image.

*Edited*: Here is something you can play with:
Try the following, remarks follow the code
1. Replace: GetScaledImageSizes


```
Public Function GetScaledImageSizes(ByVal destWidth As Long, ByVal destHeight As Long, ScaledWidth As Long, ScaledHeight As Long, _
                                        Optional ByVal CanScaleUp As Boolean = True, Optional ByVal ClipRotation As Boolean = True) As Boolean

    ' Function returns scaled (maintaining scale ratio) for passed destination width/height
    ' The CanScaleUp when set to false will never return scaled sizes > than 1:1
    ' The ClipRotation parameter, if False, will ensure rotated image does not exceed destination width/height
    
    If m_Image(m_SourceIndex) = 0& Then Exit Function
    
    Dim xRatio As Single, yRatio As Single
    Dim sinT As Double, cosT As Double, d2r As Double
    Dim h1 As Long, h2 As Long, A As Single

    xRatio = destWidth / m_Size.nWidth
    yRatio = destHeight / m_Size.nHeight
    If xRatio > yRatio Then xRatio = yRatio
    
    If Abs(Int(m_Angle + 0.99!) Mod 360) = 0! Or ClipRotation = True Then
    
        If xRatio >= 1! And CanScaleUp = False Then
            ScaledWidth = m_Size.nWidth
            ScaledHeight = m_Size.nHeight
        Else
            ScaledWidth = m_Size.nWidth * xRatio
            ScaledHeight = m_Size.nHeight * xRatio
        End If
    
    Else

        ScaledWidth = m_Size.nWidth * xRatio
        ScaledHeight = m_Size.nHeight * xRatio

        A = Abs(m_Angle)
        Select Case A
            Case Is < 91
            Case Is < 181: A = 180 - A
            Case Is < 271: A = A - 180
            Case Else: A = 360 - A
        End Select
        d2r = (4& * Atn(1)) / 180   ' conversion factor for degree>radian
        sinT = Sin(A * d2r)
        cosT = Cos(A * d2r)

        h1 = destHeight * destHeight / (ScaledWidth * sinT + ScaledHeight * cosT)
        h2 = destWidth * destHeight / (ScaledWidth * cosT + ScaledHeight * sinT)
        If h1 < h2 Then h2 = h1
        h1 = h2 * destWidth / destHeight
        
        If xRatio >= 1& And CanScaleUp = False Then

            If Not (h1 < m_Size.nWidth Or h2 < m_Size.nHeight) Then
                ScaledWidth = m_Size.nWidth
                ScaledHeight = m_Size.nHeight
                GetScaledImageSizes = True
                Exit Function
            End If

        End If
    
        xRatio = h1 / m_Size.nWidth
        yRatio = h2 / m_Size.nHeight
        If xRatio > yRatio Then xRatio = yRatio
        
        ScaledWidth = m_Size.nWidth * xRatio
        ScaledHeight = m_Size.nHeight * xRatio
        
    End If
    
    GetScaledImageSizes = True

End Function
```

Note remarks at top of new function above.

2. Test it by hardcoding the final two parameters in the form's RenderTheImage function. Try different combinations.


```
cImage.GetScaledImageSizes sampleCx, sampleCy, Cx, Cy, False, False
```

Edited: Had formula a bit hosed if destination rect is not square; changed the "h2 = " line to fix that.

----------


## Jonney

I believe he algorithm is the first release in GDI+ world and VB world :Smilie: . (You always create marvels in/for VB world!!! :Thumb: )

On best fit mode (rotated picture will fit the canvas,will re-size the rotated picture),it works seamless.How about non-bestfit mode (picturebox force to fit rotated picture, will auto re-size the picture box)?

*scenario:*
   Auto-resize canvas (e.g.picturebox) when rotating a raw size picture or fixed ratio picture. What is the algorithm to calculate new size of canvas (in this case,picture is always on center of canvas or with a fixed offset X/Y) with a rotation?

 :LOL:  I talks incessantly.

----------


## LaVolpe

> ...How about non-bestfit mode (picturebox force to fit rotated picture, will auto re-size the picture box)?


Good question, try this.
1. Add this as new function in cGDIpImage class


```
Public Sub GetScaledCanvasSize(ByVal imgWidth As Long, ByVal imgHeight As Long, ByRef CanvasWidth As Long, ByRef CanvasHeight As Long)

    Dim sinT As Double, cosT As Double
    Dim A As Double, d2r As Double
    Dim ctrX As Double, ctrY As Double

    A = Abs(Int(m_Angle) Mod 360)
    Select Case A
        Case Is < 91#
        Case Is < 181#: A = 180# - A
        Case Is < 271#: A = A - 180#
        Case Else: A = 360# - A
    End Select
    A = A + m_Angle - Int(m_Angle)
    d2r = (4# * Atn(1)) / 180#   ' conversion factor for degree>radian
    sinT = Sin(A * d2r)
    cosT = Cos(A * d2r)
    
    ctrX = imgWidth / 2#
    ctrY = imgHeight / 2#

    A = (-ctrX * sinT) + (-ctrY * cosT)
    CanvasHeight = (imgWidth - ctrX) * sinT + (imgHeight - ctrY) * cosT - A
    
    A = ((-ctrX * cosT) - (imgHeight - ctrY) * sinT)
    CanvasWidth = (imgWidth - ctrX) * cosT - (-ctrY * sinT) - A

End Sub
```

2. In the form's RenderThePicture routine, tweak to show bounding canvas size. Add this before Picture1.Refresh:


```
    Dim canvasCx As Long, canvasCy As Long
    cImage.GetScaledCanvasSize Cx, Cy, canvasCx, canvasCy
    Picture1.Line (X + (Cx - canvasCx) \ 2, Y + (Cy - canvasCy) \ 2)-(X + (Cx + canvasCx) \ 2, Y + (Cy + canvasCy) \ 2), vbBlue, B
```

3. You will see the canvas rectangle adjust when you rotate the image.

Suggest selecting a rectangular, non-transparent image < 256x256 and pass CanScaleUp=False to the render function.  This way you will easily see the canvas rectangle.

*Edited*: The canvas rectangle would be your picturebox (question in your previous reply). However, remember that the CanvasWidth/CanvasHeight is returned in pixels and you should convert to needed scalemode to resize picturebox.  Also note that you need to include any extra pixels for borders if the picturebox has borders.

Also remember that images with transparency often have buffers of transparent pixels.  This gives the illusion that the canvas rectangle is much larger than required.  No algo is going to correct for that and you really only have 3 choices that I can think of:  1) accept it, 2) run a trimming routine to transfer only non-transparent pixels to another appropriately-sized image & use that image, 3) process the image to determine the tightest rectangle that excludes all fully-transparent pixels and use that size to pass to your algos (complicated because rendering offsets will most likely be necessary).

----------


## TomT

Hi, I would like to make a large number of multiple shapes with the same parameters, except location (left and top) and color.  Is there a way to easily make multiple rectangles, say?

TomT

----------


## LaVolpe

Tom, I am not sure I understand your question. If this is not related to my posted project, you should post your question in the appropriate language forum: VB6 & earlier, .Net, VBA, C++, etc, etc.

----------


## TomT

Sorry, I'm new here.  I thought I was in a VB6 forum, and that my question related peripherally to the thread.

----------


## Jonney

```
Private Sub Label1_Click()
  '
  cImage.LoadPicture_stdPicture Clipboard.GetData(vbCFBitmap), cGDIplus, 1
  Call RenderTheImage

End Sub
```

The picture look weird with <Black> transparent color. The Return of lColorFormat in pvProcessAlphaBitmap is PixelFormat32bppARGB instead of PixelFormat32bppRGB.

----------


## LaVolpe

I don't know how you created the clipboard bitmap. 
Here's the only way pvProcessAlphaBitmap will return ARGB vs pARGB or RGB.
-- One of the R,G,B values was > the alpha channel value & the alpha value is non-zero
In that routine, you can place a break on this line: lColorFormat = PixelFormat32bppARGB 
That should help indicate that what I said above is correct.

If it is correct and you are creating your own DIBs with CreateDIBSection API, don't assume the DIB is created with all bytes zeroed out.  I have seen similar issues before in other parsing engines I've written in the past.  As a matter of habit, it might be wise to call FillMemory and zero out the DIB.

Let's say DIB was 256x256 & 32 bit.


```
Private Declare Sub FillMemory Lib "kernel32.dll" Alias "RtlFillMemory" (ByRef Destination As Any, ByVal Length As Long, ByVal Fill As Byte)

Dim lSize As Long
lSize = 256& * 256& * 4&
FillMemory ByVal DibPtr, lSize, 0
```

*P.S.* In that routine, RGB vs. pARGB/ARGB is only returned if ALL alpha values are either 0 or 255.  It does appear the bitmap you passed is using the alpha channel, whether on purpose, accidentally, or dirty when initialized, I can't tell you.

----------


## Jonney

> I don't know how you created the clipboard bitmap.


By press <Prt Sr> key. You give a try then you see what I means. Your 32bpp code also has the same issue.

----------


## LaVolpe

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.

----------


## Jonney

> 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:



> ```
> 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)
> ...


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)

----------


## LaVolpe

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.

----------


## Jonney

> 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. :Big Grin: 



> 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

----------


## LaVolpe

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.

----------


## Jonney

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


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?

----------


## LaVolpe

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&#37; 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.

----------


## Jonney

> And if any alpha values are used, must assume all alpha channel values are used as alpha, nothing else.


Can we manually process?

----------


## LaVolpe

Sure, you can use GetDIBits to return a byte array in 24bpp format. That will truncate the alpha channels.

----------


## rojaldearintok

How to resize the picturebox and maintain the aspect ratio of the picture when resizing the form?

----------


## LaVolpe

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

----------


## rojaldearintok

Please, can you add sample code for GetScaledImageSizes & GetScaledCanvasSize. 

Thanks.

----------


## LaVolpe

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

----------


## rojaldearintok

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.

----------


## LaVolpe

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.


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


```
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
```

----------


## rojaldearintok

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

----------


## LaVolpe

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

----------


## rojaldearintok

thank you very much.

----------


## beat

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:


```
CopyMemory BHI.bmiColors(0), pal(8), .biClrImportant
```

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:



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

Kind regards
Beat

----------


## LaVolpe

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.

----------


## Ben321

> 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.
> 
> A great site for descriptions of the GDI+ API functions.  
> ...


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.

----------


## egits75

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: 


```
Set Img = wApp.Selection.InlineShapes.AddPicture (FileName: = IMAGEFILE, LinkToFile: = false, SaveWithDocument: = True)
```

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

----------


## rebo

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

----------


## minimega

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:



```
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:



```
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



```
.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*.

Thanks a lot!

----------


## LaVolpe

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.

----------


## minimega

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


Great!!! You are the man LaVolpe!!!  :Smilie: 

So, new function to create in-memory bitmap could be:



```
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
```

Then, calling 

```
.LoadPicture_Memory(300, 300, PixelFormat32bppARGB, mclsToken)
```

 will do the trick!

Image created by GdipCreateBitmapFromScan0  (handle in m_Image(0)) will be automatically disposed by 

```
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!

----------


## LaVolpe

> 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

----------


## minimega

> 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


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

Have you some suggestions??

Thanks a lot!

----------


## LaVolpe

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.

----------


## rebo

> 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


It seems you ignore me here.  :Mad:

----------


## LaVolpe

> It seems you ignore me here.


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  :Wink: 

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

----------


## rebo

Thank you very much!  :wave:

----------


## minimega

Thanks for your reply!




> 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!




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




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


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.



```
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?!?

----------


## Schmidt

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

The appropriate codebank-example is here:
http://www.vbforums.com/showthread.p...ely-per-WIA%29

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


Olaf

----------


## LaVolpe

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

----------


## SomeYguy

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!

----------


## Daniel Duta

> Thank you very much!


I can't believe it. This guy was waiting for a response almost 2 years... No doubt it is a record of this forum  :Smilie: 
My respect, Rebo.

----------


## MikiSoft

Can someone give me minimal code which will load and display an ICO file into a PictureBox? Thanks in advance!

----------


## LaVolpe

Minimal code: Picture1.Picture = LoadPicture(path/filename)

Maybe you can more specific. To handle icons that O/S supports but VB doesn't


```
hIcon = LoadImage(0&, path/filename, IMAGE_ICON, desiredWidth, desiredHeight, LR_LOADFROMFILE)
If hIcon Then
    DrawIconEx Picture1.hDC, 0, 0, hIcon, targetWidth, targetHeight, 0, 0, DI_NORMAL)
    DestroyIcon hIcon
End If
```

LoadImage: https://msdn.microsoft.com/en-us/lib...=vs.85%29.aspx
DrawIconEx: https://msdn.microsoft.com/en-us/lib...=vs.85%29.aspx

----------


## MikiSoft

EDIT: Fixed, thanks very much for this!

Here is the final code: 

```
Private Declare Function LoadImage Lib "user32" Alias "LoadImageA" (ByVal hInst As Long, ByVal lpsz As String, ByVal dwImageType As Long, ByVal dwDesiredWidth As Long, ByVal dwDesiredHeight As Long, ByVal dwFlags As Long) As Long
Private Declare Function DrawIconEx Lib "user32.dll" (ByVal hdc As Long, ByVal xLeft As Long, ByVal yTop As Long, ByVal hIcon As Long, ByVal cxWidth As Long, ByVal cyWidth As Long, ByVal istepIfAniCur As Long, ByVal hbrFlickerFreeDraw As Long, ByVal diFlags As Long) As Long
Private Declare Function DestroyIcon Lib "user32.dll" (ByVal hIcon As Long) As Long
Private Const IMAGE_ICON As Long = 1
Private Const LR_LOADFROMFILE As Long = &H10
Private Const DI_NORMAL As Long = &H3

Private Sub Command1_Click()
Dim hIcon As Long
Picture1.Picture = Nothing
hIcon = LoadImage(0&, App.Path & "\1.ico", IMAGE_ICON, 0&, 0&, LR_LOADFROMFILE)
If hIcon Then
    DrawIconEx Picture1.hDC, 0, 0, hIcon, 0&, 0&, 0&, 0&, DI_NORMAL
    DestroyIcon hIcon
End If
End Sub
```

----------


## MikiSoft

Sorry, this is a bit off-topic, but I'm having another issue now - the PictureBox content erases after I drag some window above it. How can I keep (redraw) the loaded icon?

----------


## LaVolpe

Picture1.AutoRedraw = True

----------


## MikiSoft

That was so simple...  :big yellow:  Thanks again!

----------


## LaVolpe

You're welcome. GDI+ is overkill for most icon needs and GDI+ doesn't even handle icons as well as it should. Good old fashioned GDI works well enough for simple needs.

----------


## Jonney

In " Private Sub pvModifyAttributes() ", If We set m_GrayScale > 0 and m_Lightness <>0, the clrMatrix(1,1) and clrMatrix(2, 2) will conflict ? If so, should we separate them into two Sub or Call GdipSetImageAttributesColorMatrix after new m_GrayScale?



```
Private Sub pvModifyAttributes()

    If m_GrayScale > attrGrayNone Then ' apply grayscale ratios
       '...
      ' grayscale the image
            clrMatrix(0, 0) = R: clrMatrix(1, 0) = R: clrMatrix(2, 0) = R
            clrMatrix(0, 1) = G: clrMatrix(1, 1) = G: clrMatrix(2, 1) = G
            clrMatrix(0, 2) = B: clrMatrix(1, 2) = B: clrMatrix(2, 2) = B
            clrMatrix(3, 3) = CSng((100! - m_Alpha) / 100!)  ' global blending; value between 0 & 1
            clrMatrix(4, 4) = 1! ' required; cannot be anything else
        End If
        If m_Lightness <> 0! Then ' add/subtract light intensity
            If clrMatrix(4, 4) = 0! Then
                clrMatrix(0, 0) = 1!: clrMatrix(1, 1) = 1!: clrMatrix(2, 2) = 1!
                clrMatrix(3, 3) = CSng((100! - m_Alpha) / 100!)  ' global blending; value between 0 & 1
                clrMatrix(4, 4) = 1! ' required; cannot be anything else
            End If
            clrMatrix(0, 4) = m_Lightness / 100! ' red added/subtracted brightness
            clrMatrix(1, 4) = clrMatrix(0, 4)    ' same for blue
            clrMatrix(2, 4) = clrMatrix(0, 4)    ' same for green
        End If
        '...

End Sub
```

----------


## LaVolpe

Jonney, nope. Notice that clrMatrix(4, 4) is set to non-null when grayscale matrix was created. The lightness matrix section is only filling in those matrix items if clrMatrix(4, 4) is null. That should be the case for all the matrix sections. Only fill in the required items if they haven't already been filled in.

My alpha image control rewrote that logic, but fundamentally, it uses the same checks & balances.

----------


## Jonney

> Jonney, nope. Notice that clrMatrix(4, 4) is set to non-null when grayscale matrix was created. The lightness matrix section is only filling in those matrix items if clrMatrix(4, 4) is null. That should be the case for all the matrix sections. Only fill in the required items if they haven't already been filled in.
> 
> My alpha image control rewrote that logic, but fundamentally, it uses the same checks & balances.


I miss the check.  :Big Grin: 

I am troubleshooting why the alpha doesn't work： hGraphics bind to hImage.


```
GdipGetImageGraphicsContext hImage, hGraphics 
imgAttributesHandle = pvModifyAttributes(Alpha,Lightness, TransColor,GrayScale)
GdipDrawImageRectRectI hGraphics, hImage, 0, 0, imgWidth, imgHeight, 0, 0, imgWidth, imgHeight, UnitPixel, imgAttributesHandle
If imgAttributesHandle Then GdipDisposeImageAttributes imgAttributesHandle
GdipDeleteGraphics hGraphics
```

Edited:
But this one is OK: hGraphics created by hdc.


```
Public Sub gdipStretchPicture(ByVal hdc As Long, ByVal x As Long, ByVal y As Long, ByVal Width As Long, ByVal Height As Long, hImage As Long, ByVal srcX As Long, ByVal srcY As Long, ByVal srcWidth As Long, ByVal srcHeight As Long, Optional ByVal srcUnit As GpUnit = UnitPixel)
   
   If hImage <> 0 Then
      Dim hGraphics As Long
      GdipCreateFromHDC hdc, hGraphics
      Dim Attributes As Long                              'Testing Code
      Attributes = pvModifyAttributes(70, 0, 0, 1)  'Testing Code
      GdipDrawImageRectRectI hGraphics, hImage, x, y, Width, Height, srcX, srcY, srcWidth, srcHeight, srcUnit, Attributes
      If Attributes <> 0 Then GdipDisposeImageAttributes Attributes
      GdipReleaseDC hGraphics, hdc
      GdipDeleteGraphics hGraphics
   End If
   
End Sub
```

----------


## LaVolpe

What is TransColor? I assume that is what you are talking about.  Remember that most color references in GDI+ are BGR vs RGB
How do you use it in your version of pvModifyAttributes? May need to see that code portion

----------


## Jonney

> What is TransColor? I assume that is what you are talking about.  Remember that most color references in GDI+ are BGR vs RGB
> How do you use it in your version of pvModifyAttributes? May need to see that code portion


Please refresh the page, I have edited my writing. 
I think this rung has problem. They overide each other?



> GdipGetImageGraphicsContext hImage, hGraphics 
> imgAttributesHandle = pvModifyAttributes(Alpha,Lightness, TransColor,GrayScale)
> GdipDrawImageRectRectI hGraphics, hImage, 0, 0, imgWidth, imgHeight, 0, 0, imgWidth, imgHeight, UnitPixel, imgAttributesHandle
> If imgAttributesHandle Then GdipDisposeImageAttributes imgAttributesHandle
> GdipDeleteGraphics hGraphics


my usage is not correct  I think. But why others like Lightness and GrayScale work? Only Alpha doesn't take effect.
Do we have to use GdipDrawImageRectRectI to apply the attribute for hImage, any other GDI+ API?



```
Private Function pvModifyAttributes(Optional Alpha As Long = 0, Optional ByVal Lightness As Long = 0, Optional ByVal TransColor As OLE_COLOR = 0, Optional ByVal eGrayScale As GrayScaleConstants = 0) As Long


' about attributes.
' The following are added to a GDI+ attributes handle:
'       eGrayScale, GlobalTranparency, GlobalLightness, ExtraTransparency
' Mirroring is done inside the GDI+ image itself
' Rotation is done via DC WorldTransformation on-the-fly when rendering
' So to determine if any attributes are in play, 3 variables must be checked:
'   Inplay = ((Me.ImageAttributesHandle=0& And Me.Rotation=0! And Me.Mirrored=attrMirrorNone) = False)
    
    Dim Attr As Long
    If Alpha <> 0 Or Lightness <> 0 Or TransColor <> 0 Or eGrayScale <> 0 Then
       Call GdipCreateImageAttributes(Attr)
    Else
       Exit Function
    End If
    
    Dim clrMatrix(0 To 4, 0 To 4) As Single
    Dim r As Single, g As Single, b As Single
    Const ColorAdjustTypeBitmap As Long = &H1&
    
    'ColorMatrix ClrMatrix ={
    '        1.0f, 0.0f, 0.0f, 0.0f, 0.0f,
    '        0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
    '        0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
    '        0.0f, 0.0f, 0.0f, 0.5f, 0.0f, //Alpha =0.5  (50% Transparency)
    '        0.0f, 0.0f, 0.0f, 0.0f, 1.0f };
    
        If eGrayScale > attrGrayNone Then ' apply grayscale ratios
            Select Case eGrayScale
            Case attrGrayNTSCPAL
                r = 0.299: g = 0.587: b = 0.114 ' standard weighted average
            Case attrGraySimpleAverage
                r = 0.333: g = 0.334: b = r     ' pure average
            Case attrGrayCCIR709
                r = 0.213: g = 0.715: b = 0.072 ' CCIR709
            Case attrGrayRedMask
                r = 0.8: g = 0.1: b = g     ' personal preferences: could be r=1:g=0:b=0 or other weights
            Case attrGrayGreenMask
                r = 0.1: g = 0.8: b = r     ' personal preferences: could be r=0:g=1:b=0 or other weights
            Case attrGrayBlueMask
                r = 0.1: g = r: b = 0.8     ' personal preferences: could be r=0:g=0:b=1 or other weights
            Case attrGrayBlueGreenMask
                r = 0.1: g = 0.45: b = g    ' personal preferences: could be r=0:g=.5:b=.5 or other weights
            Case attrGrayRedGreenMask
                r = 0.45: g = r: b = 0.1    ' personal preferences: could be r=.5:g=.5:b=0 or other weights
            End Select
            ' grayscale the image
            clrMatrix(0, 0) = r: clrMatrix(1, 0) = r: clrMatrix(2, 0) = r
            clrMatrix(0, 1) = g: clrMatrix(1, 1) = g: clrMatrix(2, 1) = g
            clrMatrix(0, 2) = b: clrMatrix(1, 2) = b: clrMatrix(2, 2) = b
            clrMatrix(3, 3) = CSng((100! - Alpha) / 100!)  ' global blending; value between 0 & 1
            clrMatrix(4, 4) = 1! ' required; cannot be anything else
        End If
        If Lightness <> 0! Then ' add/subtract light intensity
            If clrMatrix(4, 4) = 0! Then
                clrMatrix(0, 0) = 1!: clrMatrix(1, 1) = 1!: clrMatrix(2, 2) = 1!
                clrMatrix(3, 3) = CSng((100! - Alpha) / 100!)  ' global blending; value between 0 & 1
                clrMatrix(4, 4) = 1! ' required; cannot be anything else
            End If
            clrMatrix(0, 4) = Lightness / 100! ' red added/subtracted brightness
            clrMatrix(1, 4) = clrMatrix(0, 4)    ' same for blue
            clrMatrix(2, 4) = clrMatrix(0, 4)    ' same for green
        End If
        If Alpha <> 0! Then ' add global transparency
            If clrMatrix(4, 4) = 0! Then
                clrMatrix(0, 0) = 1!: clrMatrix(1, 1) = 1!: clrMatrix(2, 2) = 1!
                clrMatrix(3, 3) = CSng((100! - Alpha) / 100!) ' global blending; value between 0 & 1
                clrMatrix(4, 4) = 1! ' required; cannot be anything else
            End If
        End If
        If clrMatrix(4, 4) = 1! Then ' create attributes?
            If GdipCreateImageAttributes(Attr) = 0& Then
                If GdipSetImageAttributesColorMatrix(Attr, ColorAdjustTypeBitmap, 1&, clrMatrix(0, 0), clrMatrix(0, 0), 0&) Then
                    GdipDisposeImageAttributes Attr
                    Attr = 0&
                End If
            End If
        End If
        If TransColor Then
            If Attr = 0& Then Call GdipCreateImageAttributes(Attr)
            If Attr Then
                If GdipSetImageAttributesColorKeys(Attr, 1&, 1&, TransColor, TransColor) Then ' failure
                    If clrMatrix(4, 4) = 0! Then
                        GdipDisposeImageAttributes Attr
                        Attr = 0&
                    End If
                End If
            End If
        End If
        
        pvModifyAttributes = Attr
        
End Function
```

----------


## Jonney

OK. I expose the return of pvModifyAttributes to my Image Class Public Property: Image.gdpiAttributes so that the attributes can be applied when calling gdipStretchPicture. My Image Class will take care Dispose.



```
Public Sub gdipStretchPicture(ByVal hdc As Long, ByVal x As Long, ByVal y As Long, ByVal Width As Long, ByVal Height As Long, hImage As Long, ByVal srcX As Long, ByVal srcY As Long, ByVal srcWidth As Long, ByVal srcHeight As Long, Optional ByVal srcUnit As GpUnit = UnitPixel,Optional Byval Attributes As Long =0)
   
   If hImage <> 0 Then
      Dim hGraphics As Long
      GdipCreateFromHDC hdc, hGraphics
      GdipDrawImageRectRectI hGraphics, hImage, x, y, Width, Height, srcX, srcY, srcWidth, srcHeight, srcUnit, Attributes
      GdipReleaseDC hGraphics, hdc
      GdipDeleteGraphics hGraphics
   End If
```

----------


## LaVolpe

I see what you were trying to do by binding it to the image. The reason why the alpha doesn't work is because the pixel data is still on the image. You are rendering semi-transparency over the same image. You can't make an existing image less transparent by rendering something over it. Blending same images on each other, changing only transparency, should result in no change to the image.

It would be like taking a screen capture of the desktop. Then rendering another copy of the desktop over the 1st one, just at 70% transparency. You should see no difference after rendering. However, if the 70% version was rendered on some other image, then you'd see the semi-transparent blended into that other image.

----------


## Jonney

> I see what you were trying to do by binding it to the image. The reason why the alpha doesn't work is because the pixel data is still on the image. You are rendering semi-transparency over the same image. You can't make an existing image less transparent by rendering something over it. Blending same images on each other, changing only transparency, should result in no change to the image.
> 
> It would be like taking a screen capture of the desktop. Then rendering another copy of the desktop over the 1st one, just at 70% transparency. You should see no difference after rendering. However, if the 70% version was rendered on some other image, then you'd see the semi-transparent blended into that other image.


I can't get sleep in whole night for this GDI+ matter.
Thanks for this analyst. I learned the lesson. 
Thank you Sir.

----------


## MikiSoft

@LaVolpe: Can you help me again, I need a basic example on how to load and display GIF image, first frame?

----------


## LaVolpe

First frame is always displayed first. Simply load the image via GDI+ or VB's LoadPicture() which also supports GIF. Not sure I understand the problem.

----------


## Jonney

> Fist frame is always displayed first. Simply load the image via GDI+ or VB's LoadPicture() which also supports GIF. Not sure I understand the problem.


tired with all kinds of strange questions.

----------


## LaVolpe

> tired with all kinds of strange questions.


Gut feeling is that that is not the entire question or not stated completely

----------


## MikiSoft

I want simple GDI+ API code which will load GIF image and display its first frame in a PictureBox, because LoadPicture loads it in poor quality.

----------


## LaVolpe

GDIpLoadImageFromFile/GdipLoadImageFromStream will load a properly formatted GIF. VB's LoadPicture will do the same. Both will have the exact same quality at 100% scale. In any case, the 1st frame is always rendered initially.

After reading your edited comment:
You can extract the code from this project to do what you are asking. The steps would look like this:

1. Load image via GDIpLoadImageFromFile
2. Get picturebox DC into GDI+ graphics object via GDIpCreateFromHDC
3. Set bicubic scaling on the graphics object via GdipSetInterpolationMode
4. Get the image bounds via GDIpGetImageBounds
5. Render the image to the graphics object: GDIpDrawImageRectRect or GDIpDrawImageRectRectI
6. Destroy the GDI+ graphics & image objects

----------


## ricardovpm

Using your topic, I wonder how I can save only one area (x, y, heigh, width) of the image using the code below



> Dim bData() As Byte, fn As Integer, bOk As Boolean
> Dim tImage() As cGDIpImage
> 
> On Error GoTo ExitRoutine
> 
> bOk = cImage.SaveAsJPG(bData(), , True)
> If bOk = False Then
>    MsgBox "Failed to save to the desired image format", vbInformation + vbOKOnly
> Else
> ...

----------


## joaquim

LaVolpe: please correct that link on #1 post. the link isn't working for i see the GDIPlus API functions.
thanks for all

----------


## VbNetMatrix

Lavolpe, I know this post is rather old (and dead) but I needed that routine (bonus code Post #8) to load png simply.
Post #21 as you stated contain a fix, but the solution was incompleted and bugged too (m_Size.nWidth, m_Size.nWidth instead of m_Size.nWidth, m_Size.nHeight) so I decided to provide missing code to make your bonus code fully work.
plz don't flame me.  All the credit still go to you, thanks again.



```
Option Explicit

Private Const UNIT_PIXEL As Long = &H2&

Private Type GdiplusStartupInput
  GdiplusVersion           As Long
  DebugEventCallback       As Long
  SuppressBackgroundThread As Long
  SuppressExternalCodecs   As Long
End Type

Private Type RECTF
  nLeft As Single
  nTop As Single
  nWidth As Single
  nHeight As Single
End Type

Private Declare Function GdiplusStartup Lib "GdiPlus.dll" (Token As Long, inputbuf As GdiplusStartupInput, Optional ByVal outputbuf As Long = 0) As Long
Private Declare Function GdipLoadImageFromFile Lib "GdiPlus.dll" (ByVal mFilename As Long, ByRef mImage As Long) As Long
Private Declare Function GdipDeleteGraphics Lib "GdiPlus.dll" (ByVal mGraphics As Long) As Long
Private Declare Function GdipCreateFromHDC Lib "GdiPlus.dll" (ByVal hDC As Long, hGraphics As Long) As Long
Private Declare Function GdipDrawImage Lib "GdiPlus.dll" (ByVal mGraphics As Long, ByVal mImage As Long, ByVal mX As Single, ByVal mY As Single) As Long
Private Declare Function GdipDisposeImage Lib "GdiPlus.dll" (ByVal Image As Long) As Long
Private Declare Sub GdiplusShutdown Lib "GdiPlus.dll" (ByVal Token As Long)
Private Declare Function GdipGetImageBounds Lib "GdiPlus.dll" (ByVal nImage As Long, srcRect As RECTF, srcUnit As Long) As Long
Private Declare Function GdipDrawImageRectRectI Lib "GdiPlus.dll" (ByVal hGraphics As Long, ByVal hImage As Long, ByVal dstX As Long, ByVal dstY As Long, ByVal dstWidth As Long, ByVal dstHeight As Long, ByVal srcX As Long, ByVal srcY As Long, ByVal srcWidth As Long, ByVal srcHeight As Long, ByVal srcUnit As Long, Optional ByVal imageAttributes As Long = 0, Optional ByVal Callback As Long = 0, Optional ByVal callbackData As Long = 0) As Long

Public Function RenderPNG(FileName As String, hDC As Long, ByVal plngDestX As Long, ByVal plngDestY As Long) As Long
  Dim lngRetValue As Long
  Dim GDIsi As GdiplusStartupInput
  Dim gToken As Long
  Dim hGraphics As Long
  Dim hBitmap As Long
  Dim lngSuccess As Long
  Dim typPicSize As RECTF
  
  lngRetValue = 0

  On Error Resume Next
  GDIsi.GdiplusVersion = 1&
  lngSuccess = GdiplusStartup(gToken, GDIsi)
  'If Err Then
  '  Err.Clear
  '  Exit Function
  'ElseIf gToken = 0& Then
  '  Exit Function
  'End If
  On Error GoTo 0

  If (gToken <> 0) Then
    Call GdipCreateFromHDC(hDC, hGraphics)
    If hGraphics Then
      Call GdipLoadImageFromFile(StrPtr(FileName), hBitmap)
      If hBitmap Then
        'GdipDrawImage hGraphics, hBitmap, plngDestX, plngDestY
        Call GdipGetImageBounds(hBitmap, typPicSize, UNIT_PIXEL)
        Call GdipDrawImageRectRectI(hGraphics, hBitmap, plngDestX, plngDestY, typPicSize.nWidth, typPicSize.nHeight, 0, 0, typPicSize.nWidth, typPicSize.nHeight, UNIT_PIXEL, 0&, 0&, 0&)

        GdipDisposeImage hBitmap
        lngRetValue = 1
      End If
      GdipDeleteGraphics hGraphics
    End If
    GdiplusShutdown gToken
  End If
  
  RenderPNG = lngRetValue
End Function

calling is something like:
  'frmMain.Autorefresh = True
  RenderPNG strPath, frmMain.hDC, 0, 0
  frmMain.Refresh
```

----------


## neotechni

PolygonBlt isn't actually drawing a proper polygon, but a parallelogram. How would I draw an image as a polygon using an array of 4 POINTAPIs to set each corner's coordinate (like DrawPolygon, but drawing an image instead of a color). A parallelogram only lets me set 3 of the coordinates and GDI+ guesses the 4th, which is incorrect in my use case.

----------


## LaVolpe

What you are asking, I don't believe there is an "easy" solution. Maybe there are free libraries out there that will do what you are asking. Without such a library/class, basically each pixel in the bitmap needs to be "stretched" proportionally to fill a dynamic shape/polygon. GDI+ offers a method of warping points to a polygon, but not a method to warp an image to other than a parallelogram (rectangle or otherwise). If you google "Free Image Transformation", you should see a project on CodeProject site. It isn't written in VB, but offers logic and a sample project. I do think you need an account to download the project.

----------


## neotechni

> What you are asking, I don't believe there is an "easy" solution. Maybe there are free libraries out there that will do what you are asking. Without such a library/class, basically each pixel in the bitmap needs to be "stretched" proportionally to fill a dynamic shape/polygon. GDI+ offers a method of warping points to a polygon, but not a method to warp an image to other than a parallelogram (rectangle or otherwise). If you google "Free Image Transformation", you should see a project on CodeProject site. It isn't written in VB, but offers logic and a sample project. I do think you need an account to download the project.


Turns out I was wrong anyway. ParallelBlt worked just fine. Thank you though.

----------


## selst

Hi there, what an amazing piece of code!

Sorry to bother you for this, but I noticed that saving a picture as a gif is not possible.

And for an old VB6 project I specifically need to convert png's to gif's...

Is there a simple way to add this feature to the existing code?

Any help would be greatly appreciated, thanks!

----------


## selst

Hi there, what an amazing piece of code!

Sorry to bother you for this, but I noticed that saving a picture as a gif is not possible.

And for an old VB6 project I specifically need to convert png's to gif's...

Is there a simple way to add this feature to the existing code?

Any help would be greatly appreciated, thanks!

----------


## selst

I was able to change the code a little bit so now it saves the png's to gif's, but how can I preserve transparency?

----------


## LaVolpe

Not sure what code you are using to convert PNG to GIF. But if using GDI+ to create a GIF, the color format must be 8 byte, paletted image with transparency.

As for preserving transparency. That can be really difficult for PNG to GIF because of alphablending that most PNGs have. Typically, some sort of threshold is set to say if transparency is greater than x percent make the color transparent else make it opaque.  One index of the 256 possible color indexes in that 8 byte format would be reserved for transparency. Every transparent pixel will be assigned that index. So you only have 255 possible colors for color reduction (if PNG has more than 255 colors) when transparency exists.

----------


## joaquim

LaVolpe  where can i see the GDIPlus functions declarations for VB6?

----------

