Saving Pictures to JPG Files Using the Intel JPEG Library

Sample JPG Image

Whilst Visual Basic provides support for loading graphic files in various formats into a StdPicture object, it sadly forgets about all of these when it comes to saving the file again. You normally only have one choice of file format for writing: a BMP at the system colour depth.

Whilst there are various third party controls available to save files in other formats, these are often have unacceptable 'per seat' licensing policies and can be incredibly expensive. This article shows how to use a free JPEG DLL library to save VB pictures.

JPEG File Format

The JPEG file format is aimed at providing very high compression levels for photographic quality images. Before deciding whether you want to use JPEG files in any application you write, there are points to consider about how JPEG operates:

Advantages

  • High compression ratio
  • Wide support - thanks to the free availability of source code to implement JPG read and write, many applications support it. Windows 98 and 2000 even include native support for reading JPG files.
  • No license requirements, unlike GIF.

Disadvantages

  • The JPEG format is lossy: colour accuracy is affected when you save the file. Images saved to JPEG many times loose quality in a similar way that multiple generations of analogue recordings do.
  • It is difficult to encode - not a task you would like to perform in VB code!
  • Works on true colour (24 bit images); not suitable as a format for storing files aimed at paletised systems.

The Independent JPEG Group have done a great deal to make the JPEG format much more accessible to developers by providing free, platform independent C source code to implement JPEG load and save functions. Their library is used in Internet Explorer *, Netscape * Navigator and countless other projects (some of which have even have the audacity to charge for free code!), including a JPEG Library DLL, ijl11.dll, available from the Intel* Developer Site.

IJL11.DLL

This DLL makes conversion to and from JPG simpler by working on a DIB byte format. By working on a DIB byte format it is easier for Windows programmers than the Independent JPEG Group's C code, since the input and output format in memory does not need to be converted. In VB terms, there is still some conversion from the native StdPicture objects to a DIB. This is achieved using the cDIBSection Class.

From VB

Download the sample project. This contains all the files required. Firstly, unzip Ijl11.zip and place ijl11.dll in your project's path (or in the Windows\System directory). Read the licensing agreement included in the zip. If you are going to use this file for a distributed or commercial project you must download a registered copy (free) from Intel at their JPEG Library page. Then there are two VB files needed:

  • mIJL.bas
  • cDIBSection.cls

mIJL.bas contains the declares for ijl11.dll and four wrapper functions:

  1. LoadJPG
  2. LoadJPGFromPtr
  3. SaveJPG
  4. SaveJPGToPtr

All these function accept an instance of the cDIBSection Class. The standard versions of the functions accept a file name as an argument, while the Ptr versions accept a long value pointing to a memory buffer and a variable indicating the buffer size.

Although VB can already load a JPG, LoadJPG is provided as it gives a shortcut for loading a JPG file directly into a cDIBSection class without requiring a StdPicture object. LoadJPGFromPtr allows you to load a JPG directly from a byte array containing the JPG, a function which can't be otherwise be achieved easily in VB.

To save a StdPicture object directly, you need to do this:

Dim c As New cDibSection

   ' Convert Picture object to DIBSection:
   c.CreateFromPicture picThis.Picture
   ' Save it:
   SaveJPG c, sFileName

Saving and Loading From Memory Buffers

To Load a JPG from a memory buffer, first you create a byte array containing all the bytes in the JPG, or use the Shell's IMalloc implementation to allocate memory directly and have that contain the JPG bytes. Then you pass the pointer to the data and the size of the buffer into the LoadJPGFromPtr method. If you use a byte array, the pointer is obtained using VB's VarPtr function:

   ' Assuming the byte array is b():
   lPtr = VarPtr(b(0))

To save a JPG to a memory buffer directly, you need to create a byte array or allocate sufficient memory to hold the resultant JPG. Normally this means creating a larger buffer than the resulting JPG. Since JPG always compresses an image, the size is always going to be smaller than the size of an equivalent DIB buffer to hold the bytes. In the sample code I chose 1/4 of the bytes in the DIBSection (m_cDib.Height * m_cDib.BytesPerScanLine / 4). Then you pass a pointer to this buffer and its size to the SaveJPGFromPtr. The method modifies the size variable passed in to reflect the actual size of the JPG created which you can then use to trim down the buffer to size.

Performance

Interestingly, using cDIBSection with the Intel JPG library can be substantially faster than than using VB's standard methods. The following code was used for benchmarking. In the VB case, a new StdPicture object is created and loaded with a JPG file. For the IJL library, a new cDIBSection object is created and the same JPG file is loaded:

Private Sub cmdIJL_Click()
   Dim fT As Double
   Dim cD As New cDIBSection
   fT = Timer
   LoadJPG cD, App.Path & "/home/VB/Code/vbMedia/Saving_Pictures_to_JPG/Using_Intel_JPG_Library/earth.jpg"
   Debug.Print "Time:", Timer - fT
End Sub

Private Sub cmdVB_Click()
   Dim fT As Double
   Dim picThis As New StdPicture
   fT = Timer
   Set picThis = LoadPicture(App.Path & "/home/VB/Code/vbMedia/Saving_Pictures_to_JPG/Using_Intel_JPG_Library/earth.jpg")
   Debug.Print "Time:", Timer - fT
End Sub

The tested file, earth.jpg, was a 1022x747 pixel JPG, with a file size of 66kb. Performance on the test machine (Pentium II 266 with 256Mb memory) showed the IJL library version to be more than twice as quick as the VB version. Typically, VB turned in a load speed of around 1 second, whilst the IJL version achieved around 0.4s.