1

I'm trying to resize and save the 3 images as defined in the Page_load event.

Within method ResizeAndSave I have 2 methods I'm trying: FastResize and SlowResize.

When I uncomment the FastResize codeline: IMAGE 1 and 2 are saved and resized correctly. IMAGE 3 however, is saved in dimensions 625x441px and so does not respect the 200x200 box I want it to resize to.

When I instead use the SlowResize codeline: IMAGE 1 and 2 are again saved and resized correctly. IMAGE 3 however, is not saved at all.

No errors are thrown in my code. I will import images from a variety of sources so it's critical my code works on a wide range of image formats. And apparently there's something special about IMAGE 3 and I don't know what it is or how to handle it.

Here's my full code, you should be able to just copy/paste it and test it for yourself:

Imports System.Drawing
Imports System.Drawing.Imaging
Imports System.IO
Imports System.Xml
Imports System.Data.SqlClient
Imports System.Net
Imports System.Windows.Media.Imaging
Imports System.Windows.Media

Partial Class importfeeds
    Inherits System.Web.UI.Page

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

    'IMAGE 1
    ResizeAndSave(200, 200, "https://upload.wikimedia.org/wikipedia/commons/8/82/Dell_Logo.png")    
    'IMAGE 2        
    ResizeAndSave(200, 200, "https://upload.wikimedia.org/wikipedia/commons/d/d8/Square-1_solved.jpg")  
    'IMAGE 3
    ResizeAndSave(200, 200, "http://cdn2.emobassets.eu/media/catalog/product/1/1/1116220.jpg")          

End Sub


Private Sub ResizeAndSave(ByVal maxWidth As Integer, ByVal maxHeight As Integer, ByVal imageURL As String)
    Dim imgRequest As WebRequest = WebRequest.Create(imageURL)
    Dim imgResponse As WebResponse = imgRequest.GetResponse()

    Dim streamPhoto As Stream = imgResponse.GetResponseStream()
    Dim memStream As New MemoryStream
    streamPhoto.CopyTo(memStream)
    memStream.Position = 0
    Dim bfPhoto As BitmapFrame = ReadBitmapFrame(memStream)

    Dim newWidth, newHeight As Integer
    Dim scaleFactor As Double

    If bfPhoto.Width > maxWidth Or bfPhoto.Height > maxHeight Then
        If bfPhoto.Width > maxWidth Then
            scaleFactor = maxWidth / bfPhoto.Width
            newWidth = Math.Round(bfPhoto.Width * scaleFactor, 0)
            newHeight = Math.Round(bfPhoto.Height * scaleFactor, 0)
        End If
        If newHeight > maxHeight Then
            scaleFactor = maxHeight / newHeight
            newWidth = Math.Round(newWidth * scaleFactor, 0)
            newHeight = Math.Round(newHeight * scaleFactor, 0)
        End If
    End If


    Dim bfResize As BitmapFrame = FastResize(bfPhoto, newWidth, newHeight)
    'Dim bfResize As BitmapFrame = SlowResize(bfPhoto, newWidth, newHeight, BitmapScalingMode.Linear)

    Dim baResize As Byte() = ToByteArray(bfResize)

    Dim strThumbnail As String = Guid.NewGuid.ToString() + ".png"
    Dim saveToPath As String = Server.MapPath(ConfigurationManager.AppSettings("products_photospath")) + "\49\" + strThumbnail

    File.WriteAllBytes(saveToPath, baResize)

End Sub

Private Shared Function FastResize(bfPhoto As BitmapFrame, nWidth As Integer, nHeight As Integer) As BitmapFrame
    Dim tbBitmap As New TransformedBitmap(bfPhoto, New ScaleTransform(nWidth / bfPhoto.PixelWidth, nHeight / bfPhoto.PixelHeight, 0, 0))
    Return BitmapFrame.Create(tbBitmap)
End Function

'http://weblogs.asp.net/bleroy/resizing-images-from-the-server-using-wpf-wic-instead-of-gdi
Public Shared Function SlowResize(photo As BitmapFrame, width As Integer, height As Integer, scalingMode As BitmapScalingMode) As BitmapFrame

    Dim group = New DrawingGroup()
    RenderOptions.SetBitmapScalingMode(group, scalingMode)
    group.Children.Add(New ImageDrawing(photo, New Windows.Rect(0, 0, width, height)))
    Dim targetVisual = New DrawingVisual()
    Dim targetContext = targetVisual.RenderOpen()
    targetContext.DrawDrawing(group)
    Dim target = New RenderTargetBitmap(width, height, 96, 96, PixelFormats.[Default])
    targetContext.Close()
    target.Render(targetVisual)
    Dim targetFrame = BitmapFrame.Create(target)
    Return targetFrame
End Function

Private Shared Function ToByteArray(bfResize As BitmapFrame) As Byte()
    Using msStream As New MemoryStream()
        Dim pbdDecoder As New PngBitmapEncoder()
        pbdDecoder.Frames.Add(bfResize)
        pbdDecoder.Save(msStream)
        Return msStream.ToArray()
    End Using
End Function

Private Shared Function ReadBitmapFrame(streamPhoto As Stream) As BitmapFrame
    Dim bdDecoder As BitmapDecoder = BitmapDecoder.Create(streamPhoto, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.None)
    Return bdDecoder.Frames(0)
End Function

End Class

UPDATE 1

@Hans Passant: Both your suggestions on filenaming and pixelWidth usage are spot on and helped me run this code successfully on the 3 images in the Page_load event. I updated my original code.
However, when I run this code as part of my actual application, where I import ~100 images from a feed. The new code fails with an out of memory exception when it tries to process IMAGE 3. This happens for both FastResize and SlowResize methods. Is there something in my code or in the image in question that would cause this increase in memory usage, maybe a leak somewhere or an inefficient resizing method I use?

I have a lot of memory available on my machine, so would be very surprised if that were the problem, although I do see a big increase in the System and compressed memory (to 1.1GB) task in my Windows task manager. And still, this much memory usage would have me believe that there's something wrong in my code.

What can it be?

Adam
  • 6,041
  • 36
  • 120
  • 208

1 Answers1

3

IMAGE 3 however, is saved in dimensions 625x441px

That is because the image is slightly different from the other ones, its DPI (dots per inch) is 300 instead of 96. Its size in pixels is 3071 x 2172 but you are using the Width and Height properties, the size in inches with a unit of 1/96" which is 982.72 x 695.04 for this image. Fix this by using the PixelWidth and PixelHeight properties instead:

Dim tbBitmap As New TransformedBitmap(bfPhoto, 
   New ScaleTransform(nWidth / bfPhoto.PixelWidth, nHeight / bfPhoto.PixelHeight, 0, 0))

IMAGE 3 however, is not saved at all

That doesn't add up completely, but you do have a critical bug in this statement:

Dim strThumbnail As String = "success" + Date.Now.Second.ToString + ".png"

This name is not sufficiently unique to ensure that you don't overwrite an existing file. And if the code is "fast" then Date.Now.Second will have the same value and your code overwrites a previous written image file. Note how this bug won't repro when you debug, that makes the code artificially slower and the second will be different.

You'll need a better way to name the file, Guid.NewGuid.ToString() is a very good way for example, guaranteed to be unique. Or use a simple counter that you increment for each image. You do need to focus on cleanup.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • Thank you! One step further now, but still running into some issues, please see my update 1. Thank you so much! – Adam Feb 08 '16 at 17:05
  • Sigh, that doesn't have anything to do with the original question, you really need to click the Ask Question button again. After reading [this Q+A](http://stackoverflow.com/q/1591996/17034). Randomly: try throwing a GC.Collect() at it every, say, 20 images. – Hans Passant Feb 08 '16 at 18:21