Foxite.COM Community Weblog

Foxite.COM Community Weblog - free weblog service for the Visual FoxPro Community.
Welcome to Foxite.COM Community Weblog Sign in | Join | Help
in
Home Blogs Forum Photos Forum Archives

VFP IMAGING



Manipulate images with no disk access with GdiPlusX

Since the latest version, GdiPlusX offers many different ways to manipulate images with no disk access.

This introduction is from Calvin Hsia, from his blog: "Programs need to read and write data. Sometimes the data storage is only used temporarily. If the storage medium is a file, temporary files need to be deleted. Some Windows APIs allow reading and writing data to an object that implements IStream. IStream has methods that read and write to the underlying storage, which can be a file, memory, or even a named pipe: whatever the object chooses. (...) Using memory for the stream instead of a disk file can be much faster, and releasing the stream can free the used memory."

Sometimes we need to do some manipulation in some images on the fly, and show them in a picture object. This image does not need to be saved. Since VFP9, with the PictureVal property, we can send just the binaries from our images to the PictureVal property. In other cases, users may need to save only the binaries in a table or cursor field, or even  do some bit per bit or byte per byte manipulation in the image.

Think of a Memory Stream just like a disk stored in the memory. You can do with a memory stream the same things you do with a disk file: read and write. Instead of a physical location, it is stored in the memory. MSDN says: "The MemoryStream class creates streams that have memory as a backing store instead of a disk or a network connection. The encapsulated data is directly accessible in memory. Memory streams can reduce the need for temporary buffers and files in an application."

GDI+ brings functions that allow us to manipulate images using streams just like we do with phisical files. We can read a bitmap from the binaries retrieved from a stream and we can send all the binaries of the image in any desired format to a stream.

Last year, Calvin Hsia came with a great post, Use an IStream object to avoid disk access http://blogs.msdn.com/calvin_hsia/archive/2006/02/17/534529.aspx, when he created a simple COM wrapper that can read and write a stream. That DLL works great, and brings some important functions to manipulate streams with GDI+. The only problem is that it uses a COM object, that needs to be registered, using the REGSVR32 command. That article is highly recommended.

After a lot of discussion, researches and tests we finally managed to finish the GdiPlusX MemoryStream class, using only pure VFP code, with some API calls. The final magic that really enabled us to finish it came from the FoxPert Christof Wollenhaupt, in a UT thread. Thanks Christof !

The "MemoryStream" class is stored in the "System.IO" namespace.

The samples below show some ways to manipulate streams with GdiPlusX, and obtain the PictureVal binary string for an image loaded using GETPICT() function. In all samples, the PictureVal obtained will be stored in an imaginary Image object from your imaginary form. In some of the cases, all the job will be done "behind the scenes", allowing the user to do tasks without even knowing what a Memory Stream is.

In these samples we'll be loading an image using the GETPICT() command, and obtain the PictureVal using Memory Streams

Also, for didactic purposes, I'm putting the full codes needed, including the library initialization on every sample.

 

IMPORTANT
Requires VFP9 and GdiPlusX to run. 
Please make sure that you have the latest version, because VFPPaint uses some functions that were added recently.
http://www.codeplex.com/VFPX/Wiki/View.aspx?title=GDIPlusX&referringTitle=Home

 

 

1 - BASIC USAGE

This aproach is totally compatible with the .NET classes

* Sample 1 - BASIC USAGE
*
*    Load the image to GDI+
*    Create an empty Stream
*    Save the image to the Stream
*    Retrieve the Image binaries from the stream and store in PictureVal Property

DO LOCFILE("System.Prg")

LOCAL loBmp as xfcBitmap
LOCAL loStream as xfcMemoryStream
loStream = _Screen.System.IO.MemoryStream.New()

* Saving to a stream and retrieving to PictureVal
WITH _Screen.System.Drawing
    loBmp = .Bitmap.FromFile(GETPICT())
    loBmp.Save(loStream, .Imaging.ImageFormat.Jpeg)
    Thisform.Image1.PictureVal = loStream.GetBuffer()
ENDWITH

 

The samples below use some functions that were created or adapted only in GdiPlusX. Unfortunately, there's still not a documentation available for them, so I'll try to get you started:

 

2 - SAVING DIRECTLY TO IMAGE OBJECT

This is really cool. You can manipulate an image, draw on it, aply some special effects, rotate, flip, etc, and then send it directly to an image object in your form.

To do this, use the SAVE() method from the Bitmap / Image class, sending 3 parameters:

SAVE() additional parameters

   toImage - VFP Image object

   toEncoder - xfcImageFormat (eg. .Imaging.ImageFormat.Bmp)

   toEncoderParams - xfcEncoderParameter (for the case of JPGs with specific quality)

Note that you can even choose in what image format the pictureval will be obtained !

* Sample 2 - Saving directly to Image object
*
*    Load the image to GDI+
*    Save the image to the VFP Image object

DO LOCFILE("System.Prg")

LOCAL loBmp as xfcBitmap

* Saving directly to Image object
WITH _Screen.System.Drawing
    loBmp = .Bitmap.FromFile(GETPICT()) 
    loBmp.Save(Thisform.Image1, .Imaging.ImageFormat.Jpeg)
ENDWITH

 

3 - GETPICTUREVAL() FUNCTION

Another cool way to get the PictureVal, is just calling the new GETPICTUREVAL() function

GETPICTUREVAL() Parameters:

   toEncoder - xfcImageFormat (eg. .Imaging.ImageFormat.Bmp)

   toEncoderParams - xfcEncoderParameter (for the case of JPGs with specific quality)

* Sample 3 - Using the GetPictureVal function
*
*    Load the image to GDI+
*    Call the Bitmap.GetPictureVal() function

DO LOCFILE("System.Prg")

LOCAL loBmp as xfcBitmap

* Obtaining the PictureVal directly
WITH _Screen.System.Drawing
    loBmp = .Bitmap.FromFile(GETPICT()) 
    Thisform.Image1.PictureVal = loBmp.GetPictureVal(.Imaging.ImageFormat.Bmp)
ENDWITH

 

4 - GETPICTUREVALFROMHBITMAP() FUNCTION

The result and usage of GETPICTUREVALFROMHBITMAP is very similar GETPICTUREVAL.

It retrieves the PictureVal binaries from the bitmap using ONLY the BMP ENCODER. Behind the scenes, these functions work totally different. This functions does not use the MemoryStream class that I referred in this post. In summary,it obtains the PictureVal for BMP doing the following steps internally: 1 - gets the hBitmap handle for the picture (the old GDI hande for pictures); 2 - Creates manually the image file header, based on the properties obtained from the image; 3 - Retrieved the image binaries from the hBitmap handle; 4 - Joins it all, and rebuilds the image.

GETPICTUREVALFROMHBITMAP Parameters: NONE !

* Sample 4 - Using the GetPictureValfromHBitmap
*
*    Load the image to GDI+
*    Call the Bitmap.GetPictureValfromhBitmap() function

DO LOCFILE("System.Prg")

LOCAL loBmp as xfcBitmap

* Obtaining the PictureVal directly
WITH _Screen.System.Drawing
    loBmp = .Bitmap.FromFile(GETPICT()) 
    Thisform.Image1.PictureVal = loBmp.GetPictureValFromhBitmap()
ENDWITH

 

5 - IMAGECANVAS object

Since the Alpha versions of GdiPlus-X we are already using the PictureVal property, direct drawing on an Image object. I already wrote a post about it, please refer to Direct Draw with the Image Canvas from GdiPlus-X for more information, and samples.

There's more to say about the image canvas, but this I'll leave for the future, when some new features will be available.

 

DOWNLOAD SAMPLE

Click here to download the source code from the samples above shown. Please note that when you click on an button for the first time, it will first look for the GdiPlusX "SYSTEM.PRG" file. Please go to that directory and click "OK". Next, select any image to show in the image object

 

In one of my next posts, I'll show how easy it is to do the opposite: sending the image binaries to a stream, and loading it to GDI+

Enjoy !

 

RELATED LINKS:

CALVIN HSIA - Use an IStream object to avoid disk access

Direct Draw with the Image Canvas from GdiPlus-X

MSDN - Memory Stream Class

MSDN - Memory Stream Constructor

Published Wednesday, November 07, 2007 4:08 AM by cesarchalom
Filed Under:

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

 

Anime and other things » Blog Archive » Manipulate images with no disk access with GdiPlusX said:

November 7, 2007 5:41 AM
 

hggoto » Blog Archive » Manipulate images with no disk access with GdiPlusX said:

November 7, 2007 6:05 AM
 

Bernard Bout said:

Hi Cesar

I must be obtuse, but I just cannot see the point here. GETPIC() does use disk access.

Add this to a button on the form:

loBmp = FILETOSTR(GETPICT())
Thisform.Image1.PictureVal = loBmp

Done without GDI+

The image has to be retreived from disk at some stage. SO there is disk Access.

Maybe after you retreive it from disk you can maniuplate it in memory but where is the stream being used here? As I showed I can get the same effect without the GDI+ classes and just native VFP.

Hey Bernard,

Of course for this simple usage, GDI+ is not needed.

I just used GETPICT() to make the sample shorter.

But imagine a picture that you want to rotate at a specific angle, convert to greyscale, draw some semi-transparent text on it... Or even create a whole image from scratch, to be used, for example, as a customized tab of a PageFrame.

Capiche ?

November 10, 2007 4:52 AM
 

Bernard Bout said:

Further to abovem try this:

Add another image object, but make its Stretch property = 2 (stretch). Now you have 2 image objects, the STRETCH of one is ISOMETRIC and the other is STRETCH.

In any button copy the code and paste below so that image2 is filled as well. eg.
Thisform.Image1.PictureVal = loStream.GetBuffer()
Thisform.Image2.PictureVal = loStream.GetBuffer()

Now create a bitmap 1 pixel by 1 pixel and colour it say RED. Save as BMP.
Now run your code. What has happened here? Where did the gradient come from?

The Pictureval has inherent bugs in it.

 

Hi Bernard,

Thanks for your comment

These problems with pictureval have been reported many times already, bu you, me and Emerson. I've even blogged about them. Emerson reported on MSDN, you have reported in Craig Boyd's blog as comment... MS is totally aware of this, but they said that this will not be fixed.

This problem is one of the main reasons we can't make a heavy use of PictureVal.
Unfortunately, there's no way to fix this, GdiPlusX can't do anything regarding this.

The PictureVal is totally different from the Picture rendering. From what Emerson told me, in VB .NET the same thing happens, so I presume that the VFP team just pushed this from VB, but they did not test this.

Below is the image you've posted in a Foxite thread, with another extracted piece if your message that I'm bringing here for other people to see, and better understand what you showed above:

> The Pictureval has inherent bugs in it. In my image below, the PICTURE property displays the image as documented.
 

 
> Also add the 5th button and add this code:
> <PRE>loBmp = FILETOSTR(GETPICT())
> Thisform.Image1.PictureVal = loBmp
> Thisform.Image2.PictureVal = loBmp</PRE>

> Same effect but without GDI+X.
> So whether you use GDI+x or not, there is a difference in the images displayed in an image object depending on whether you use the <B>PICTURE</B> property or <B>PictureVal</B> property.


This is PictureVal problem, not GDI+X.

> On larger images it is not obvious but is very obvious on smaller images where the effect is exaggerated.

I prefer not using PictureVal for gradients for the case I need to use the Stretch property. For small pictures, the result is lousy too. I prefer using PictureVal for bigger images. Using the stretch property to show images in a bigger size, may bring also catastrophic results.

Thanks

Cesar

November 10, 2007 5:03 AM
 

PValenca said:

Your 4th example passes a parameter to GetPictureValFromhBitmap(). However, that is incorrect:

Thisform.Image1.PictureVal = loBmp.GetPictureValFromhBitmap(.Imaging.ImageFormat.Bmp)

should be

Thisform.Image1.PictureVal = loBmp.GetPictureValFromhBitmap()

You're right.

Fixed - Thanks a lot !

May 16, 2008 3:11 PM

What do you think?

(required) 
(optional)
(required)