WPF 图像大小调整和存储适用于大多数图像,但无法调整或保存其他图像

WPF Image resizing and storing works on most images but fails to resize or save on others

我正在尝试调整并保存 Page_load 事件中定义的 3 张图像。

在方法 ResizeAndSave 中,我尝试了 2 种方法:FastResizeSlowResize

当我取消注释 FastResize 代码行时:IMAGE 1 和 2 已正确保存和调整大小。然而,图像 3 以 625x441 像素的尺寸保存,因此不符合我希望它调整到的 200x200 框。

当我改为使用 SlowResize 代码行时:IMAGE 1 和 2 再次保存并正确调整大小。然而,IMAGE 3 根本没有保存。

我的代码中没有出现错误。我将从各种来源导入图像,因此我的代码适用于各种图像格式至关重要。显然 IMAGE 3 有一些特别之处,我不知道它是什么或如何处理它。

这是我的完整代码,您应该可以 copy/paste 自己测试一下:

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")) + "\" + 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

更新 1

@Hans Passant:您关于文件命名和 pixelWidth 使用的建议都很准确,并帮助我 运行 在 Page_load 事件中的 3 个图像上成功使用此代码。 我更新了我的原始代码。
但是,当我 运行 此代码作为我实际应用程序的一部分时,我从提要中导入了约 100 张图像。新代码在尝试处理 IMAGE 3 时因内存不足异常而失败。FastResizeSlowResize 方法都会发生这种情况。我的代码或有问题的图像中是否有某些东西会导致内存使用量增加,可能是某处泄漏或我使用的调整大小方法效率低下?

我的机器上有很多可用内存,所以如果那是问题我会感到非常惊讶,尽管我确实看到 System and compressed memory(到 1.1GB)任务在我的 Windows任务管理器。而且,如此多的内存使用量会让我相信我的代码有问题。

它可以是什么?

IMAGE 3 however, is saved in dimensions 625x441px

这是因为图像与其他图像略有不同,它的 DPI(每英寸点数)是 300 而不是 96。它的大小 像素 是 3071 x 2172 但您正在使用 Width 和 Height 属性,尺寸 in inches with unit of 1/96" 对于这张图片来说是 982.72 x 695.04。通过使用 PixelWidth 和 PixelHeight 属性来解决这个问题:

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

IMAGE 3 however, is not saved at all

这并没有完全加起来,但你在这个陈述中确实有一个严重的错误:

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

此名称不够独特,无法确保您不会覆盖现有文件。如果代码是 "fast",那么 Date.Now.Second 将具有相同的值,并且您的代码会覆盖以前写入的图像文件。请注意,此错误在您调试时不会重现,这会人为地使代码变慢,而第二个会有所不同。

你需要一个更好的方式来命名文件,Guid.NewGuid.ToString() 是一个很好的方式,例如,保证是唯一的。或者使用一个简单的计数器,您可以为每个图像递增。您确实需要专注于清理工作。