并行化GDI +图像大小调整.net

时间:2010-09-15 16:30:58

标签: .net multithreading resize gdi+ jpeg

我尝试使用.Net并行调整jpegs的大小。我的所有尝试都失败了,因为Graphics.DrawImage-func似乎在激活时锁定。请尝试以下剪辑:

Sub Main()
    Dim files As String() = IO.Directory.GetFiles("D:\TEMP")
    Dim imgs(25) As Image
    For i As Integer = 0 To 25
      imgs(i) = Image.FromFile(files(i))
    Next

    Console.WriteLine("Ready to proceed ")
    Console.ReadLine()

    pRuns = 1
    For i As Integer = 0 To 25
      Threading.Interlocked.Increment(pRuns)
      Threading.ThreadPool.QueueUserWorkItem(New Threading.WaitCallback(AddressOf LongTerm), imgs(i))
    Next
    Threading.Interlocked.Decrement(pRuns)

    pSema.WaitOne()
    Console.WriteLine("Fin")
    Console.ReadLine()
  End Sub

  Sub LongTerm(ByVal state As Object)
    Dim newImageHeight As Integer
    Dim oldImage As Image = CType(state, Image)
    Dim newImage As Image
    Dim graph As Graphics
    Dim rect As Rectangle
    Dim stream As New IO.MemoryStream

    Try
      newImageHeight = Convert.ToInt32(850 * oldImage.Height / oldImage.Width)
      newImage = New Bitmap(850, newImageHeight, oldImage.PixelFormat)
      graph = Graphics.FromImage(newImage)
      rect = New Rectangle(0, 0, 850, newImageHeight)

      With graph
        .CompositingQuality = Drawing2D.CompositingQuality.HighQuality
        .SmoothingMode = Drawing2D.SmoothingMode.HighQuality
        .InterpolationMode = Drawing2D.InterpolationMode.HighQualityBicubic
      End With

      'Save image to memory stream
      graph.DrawImage(oldImage, rect)
      newImage.Save(stream, Imaging.ImageFormat.Jpeg)
    Catch ex As Exception

    Finally
      If graph IsNot Nothing Then
        graph.Dispose()
      End If
      If newImage IsNot Nothing Then
        newImage.Dispose()
      End If
      oldImage.Dispose()
      stream.Dispose()

      Console.WriteLine("JobDone {0} {1}", pRuns, Threading.Thread.CurrentThread.ManagedThreadId)
      Threading.Interlocked.Decrement(pRuns)
      If pRuns = 0 Then
        pSema.Set()
      End If
    End Try

  End Sub

所有线程都在graph.DrawImage()处等待。有没有办法加快使用其他功能的代码性能?是不是可以使用多线程的Graphics.Draw?在实际应用中,应该同时调整多个图像的大小(在四核PC上),并不总是相同。发布的代码仅用于测试目的......

提前致谢

编辑:根据评论更新代码

4 个答案:

答案 0 :(得分:17)

使用流程。

GDI +阻止每个进程的很多方法。是的,痛苦,但没有办法绕过它。幸运的是,像这样的任务(以及处理文件系统上的文件的任何任务),在多个进程之间分配工作负载太容易了。幸运的是,看起来GDI +使用的是锁,而不是互斥锁,所以它是并发的!

我们有一些图形程序用于处理图像处理。一位程序员在转换程序中同时启动6-7份副本。所以它并不凌乱,相信我。哈克?你看起来很漂亮没有得到报酬。完成工作!

廉价示例(注意这不会在ide中工作,构建它并运行EXE):

Imports System.Drawing
Module Module1
    Dim CPUs As Integer = Environment.ProcessorCount

    Dim pRuns As New System.Collections.Generic.List(Of Process)

    Sub Main()
        Dim ts As Date = Now
        Try
            If Environment.GetCommandLineArgs.Length > 1 Then
                LongTerm(Environment.GetCommandLineArgs(1))
                Exit Sub
            End If

            Dim i As Integer = 0
            Dim files As String() = IO.Directory.GetFiles("D:\TEMP", "*.jpg")
            Dim MAX As Integer = Math.Min(26, files.Count)
            While pRuns.Count > 0 Or i < MAX

                System.Threading.Thread.Sleep(100)

                If pRuns.Count < CInt(CPUs * 1.5) And i < MAX Then ''// x2 = assume I/O has low CPU load
                    Console.WriteLine("Starting process pRuns.count = " & pRuns.Count & " for " & files(i) & " path " & _
                                        Environment.GetCommandLineArgs(0))
                    Dim p As Process = Process.Start(Environment.GetCommandLineArgs(0), """" & files(i) & """")
                    pRuns.Add(p)
                    i += 1
                End If

                Dim i2 As Integer
                i2 = 0
                While i2 < pRuns.Count
                    If pRuns(i2).HasExited Then
                        pRuns.RemoveAt(i2)
                    End If
                    i2 += 1
                End While


            End While
        Catch ex As Exception
            Console.WriteLine("Blew up." & ex.ToString)
        End Try
        Console.WriteLine("Done, press enter. " & Now.Subtract(ts).TotalMilliseconds)
        Console.ReadLine()
    End Sub


    Sub LongTerm(ByVal file As String)
        Try
            Dim newImageHeight As Integer
            Dim oldImage As Image
            Console.WriteLine("Reading " & CStr(file))
            oldImage = Image.FromFile(CStr(file))
            Dim rect As Rectangle

            newImageHeight = Convert.ToInt32(850 * oldImage.Height / oldImage.Width)
            Using newImage As New Bitmap(850, newImageHeight, oldImage.PixelFormat)
                Using graph As Graphics = Graphics.FromImage(newImage)
                    rect = New Rectangle(0, 0, 850, newImageHeight)

                    With graph
                        .CompositingQuality = Drawing2D.CompositingQuality.HighQuality
                        .SmoothingMode = Drawing2D.SmoothingMode.HighQuality
                        .InterpolationMode = Drawing2D.InterpolationMode.HighQualityBicubic
                    End With

                    Console.WriteLine("Converting " & CStr(file))
                    graph.DrawImage(oldImage, rect)

                    Console.WriteLine("Saving " & CStr(file))
                    newImage.Save("d:\temp\Resized\" & _
                                  IO.Path.GetFileNameWithoutExtension(CStr(file)) & ".JPG", _
                                   System.Drawing.Imaging.ImageFormat.Jpeg)
                End Using
            End Using
        Catch ex As Exception
            Console.WriteLine("Blew up on  " & CStr(file) & vbCrLf & ex.ToString)
            Console.WriteLine("Press enter")
            Console.ReadLine()
        End Try
    End Sub

End Module

答案 1 :(得分:4)

我不确定为什么Graphics.DrawImage的执行似乎会为你序列化,但实际上我注意到了一个竞争条件,你的工作项排队的一般模式。比赛在WaitOneSet之间。第一个工作项可能在Set之前就已经排队了。这将导致WaitOne在所有工作项完成之前立即返回。

解决方案是将主线程视为工作项。在排队开始之前递增pRuns一次,然后在排队完成后递减并发出等待句柄,就像在正常工作项中一样。但是,更好的方法是使用CountdownEvent类,如果它可用,因为它简化了代码。幸运的是I just recently posted the pattern in another question

答案 2 :(得分:4)

如果您不介意WPF方法,可以尝试以下方法。以下是一种简单的重新缩放方法,它接受图像流并生成包含结果JPEG数据的byte []。既然你不想用GDI +实际绘制图像,我认为这适合你,尽管是基于WPF的。 (唯一的要求是在项目中引用WindowsBase和PresentationCore。)

优点包括更快的编码(在我的机器上为200-300%)和更好的并行加速,尽管我在WPF渲染路径中也看到了一些不需要的序列化。让我知道这对你有什么用。我确信如有必要可以进一步优化。

代码:

 byte[] ResizeImage(Stream source)
 {
    BitmapFrame frame = BitmapFrame.Create(source, BitmapCreateOptions.IgnoreColorProfile, BitmapCacheOption.None);
    var newWidth = frame.PixelWidth >> 1;
    var newHeight = frame.PixelHeight >> 1;
    var rect = new Rect(new System.Windows.Size(newWidth, newHeight));
    var drawingVisual = new DrawingVisual();
    using (var drawingContext = drawingVisual.RenderOpen())
        drawingContext.DrawImage(frame, rect);
    var resizedImage = new RenderTargetBitmap(newWidth, newHeight, 96.0, 96.0, PixelFormats.Default);
    resizedImage.Render(drawingVisual);
    frame = BitmapFrame.Create(resizedImage);

    using (var ms = new MemoryStream())
    {
        var encoder = new JpegBitmapEncoder();
        encoder.Frames.Add(frame);
        encoder.Save(ms);
        return ms.ToArray();
    }
 }

答案 3 :(得分:0)

使用GDI +以外的图像处理库。

我们在一个相当高容量的网站上使用ImageMagick它调整上传图像的大小(上传的图像通常是10-40 MPixels但是为了能够在网站上使用它们(在Flash模块中),我们调整它们的大小最小尺寸1500像素)。处理速度非常快,效果极佳。

我们目前使用命令行界面启动新的ImageMagick进程。这在启动新进程时会产生一些开销,但由于图像太大,因此通常是整个调整大小过程的非常小的时间片。也可以在进程中使用ImageMagick,但是从1开始就没有尝试过。我们不需要它提供的额外性能和2.在其他进程中运行第三方软件感觉很好。

使用ImageMagick时,您还可以获得许多其他可能性,例如更好的过滤和许多其他功能。