3

I'm going to post what my design objective was, and how I obtained it. I am interested to see if there are any ways in which I missed the mark or could have optimized better.

[Objectives]

~ Add 'fast' photo scrolling to an existing WinForms application.

~ "Similar to Picasa" - flip from one image to another, forward and back, keeping the lag as minimal as possible and the responsiveness of the App as great as possible.

~ Other non-related to this question objectives (renaming; tagging; exporting; etc.)

~ Resize image after loading from disk to reduce memory usage and speed 'flipping' from image to image; resize to the dimensions of the Control that displays the image.

[Constraints]

~ No converting the App to WPF

~ Maximum .NET Framework version: 4.0

[Test Platform]

Dell M6800 with SSD

note: In all cases below, the majority of time consumed was in resizing, not in loading from disk to memory. Also, the load and resize operations were performed in a thread that queued the images. I didn't even bother with using thumbnails; the quality was too poor for this application, though it was fast. As the user 'flips' from one image to the next, a thread loads the next image in the directory to the queue, and drops one at the back of the queue (vice-versa if flipping back through the queue); the operations involved in enqueuing or dropping from the queue the images were negligible.

[Test Results: Image.FromFile]

54ms average to load and resize, using the following code. HMI response was poor.

m_Image = Image.FromFile(Me.FullName)
m_Image = ResizeImage(m_Image, ImageSize, True)

Public Function ResizeImage(ByVal image As Image, ByVal size As Size, Optional ByVal preserveAspectRatio As Boolean = True) As Image
    Dim newWidth As Integer
    Dim newHeight As Integer
    If preserveAspectRatio Then
        Dim originalWidth As Integer = image.Width
        Dim originalHeight As Integer = image.Height
        Dim percentWidth As Single = CSng(size.Width) / CSng(originalWidth)
        Dim percentHeight As Single = CSng(size.Height) / CSng(originalHeight)
        Dim percent As Single = If(percentHeight < percentWidth, percentHeight, percentWidth)
        newWidth = CInt(originalWidth * percent)
        newHeight = CInt(originalHeight * percent)
    Else
        newWidth = size.Width
        newHeight = size.Height
    End If
    Dim newImage As Image = New Bitmap(newWidth, newHeight)
    Using graphicsHandle As Graphics = Graphics.FromImage(newImage)
        graphicsHandle.InterpolationMode = InterpolationMode.HighQualityBicubic
        graphicsHandle.DrawImage(image, 0, 0, newWidth, newHeight)
    End Using
    Return newImage
End Function

[Test Results: ImageFast]

41ms average to load and resize, using the following code. HMI response was somewhat improved, though still not good. Loading from disk better than the 'traditional' method.

Source for ImageFast: Fastest Image Resizing in .NET

m_Image = ImageFast.FromFile(Me.FullName)
m_Image = ResizeImage1(m_Image, ImageSize.Width, ImageSize.Height)

[Test Results: ImageProcessor]

122ms average to load and resize, using the following code. HMI response was poor.

Source for ImageProcessor: http://www.hanselman.com/blog/NuGetPackageOfTheWeekImageProcessorLightweightImageManipulationInC.aspx

Private Sub UseImageProcessor(ByVal fileName As String)
        Dim photoBytes As Byte() = File.ReadAllBytes(fileName)
        Dim quality As Integer = 70
        Dim format As ImageFormat = ImageFormat.Jpeg
        Dim size As New Size(150, 0)
        Using inStream As New MemoryStream(photoBytes)
            Using outStream As New MemoryStream()
                    imageFactory.Load(inStream).Resize(size)  '// resizing takes about 127mS on Dell 
                    m_Image = imageFactory.Image
                    m_Image = Image.FromStream(inStream)
                End Using
            End Using
        End Using
End Sub

None of these results were very good. This is a screaming fast PC, and I was underwhelmed, particularly when I compared the image-to-image performance to that of afore-mentioned Picasa.

So, I tried something different. I added a reference for PresentationCore version 4.0.0.0 to my WinForms application, allowing me to use:

Imports System.Windows.Media.Imaging

Now, I could do this for both loading and resizing:

m_Image = GetBitMap(GetImageBitMap(Me.FullName))

Public Function GetImageBitMap(ByVal fullName As String) As BitmapImage
    Dim imageData = File.ReadAllBytes(fullName)
    Dim resizedImage As New BitmapImage()
    resizedImage.BeginInit()  ' Needed only so we can call EndInit()
    With resizedImage
        .StreamSource = New MemoryStream(imageData)
        .CreateOptions = BitmapCreateOptions.IgnoreColorProfile
        .DecodePixelHeight = ImageSize.Height
        .DecodePixelWidth = ImageSize.Width
    End With
    resizedImage.EndInit()    ' This does the actual loading and resizing
    Return resizedImage
End Function

For both operations (loading and resizing), this was a four-letter word &= ing fast: 3ms average, total. To load AND resize. Holy Grail.

The obvious problem here is that the return for the above Function is a BitmapImage object, not usable in a WinForms application (that I know of). So, I had to convert it to a BitMap, using the following code:

Private Function GetBitMap(bitmapImage As BitmapImage) As Bitmap
    Try
        '// https://stackoverflow.com/questions/6484357/converting-bitmapimage-to-bitmap-and-vice-versa
        Using outStream As New MemoryStream()
            Dim enc As BitmapEncoder = New BmpBitmapEncoder()
            enc.Frames.Add(BitmapFrame.Create(bitmapImage))
            enc.Save(outStream)
            Dim bitmap As New System.Drawing.Bitmap(outStream)
            Return New Bitmap(bitmap)
        End Using
    Catch ex As Exception
        Throw
    End Try
End Function

The result of using the WPF loading & resizing method, plus converting back to BitMap for our WinForms app was ~22ms for load, resize AND convert (to BitMap from BitmapImage) on the mentioned platform. The HMI response was good, if not great. It was better than experienced using only the WinForms methods mentioned.

I'm open to any further suggestions, though (hence the question here). The current result is acceptable. I wish that I did not have to cost the overhead of conversion from BitmapImage to BitMap, but it is, after all, a WinForms App.

Community
  • 1
  • 1
El-Ahrairah
  • 153
  • 1
  • 7

1 Answers1

4

It looks like what you really want to do is use a BitmapImage from within your WinForms app. To do this, you just need to use ElementHost to host a WPF Image control.

Image imageControl;
imageControl.Source = GetImageBitMap(filename);
Gabe
  • 84,912
  • 12
  • 139
  • 238
  • It took me a while to get back to this issue, but this answer did exactly what I needed. The result is fast and efficient. – El-Ahrairah Dec 08 '15 at 02:27