2010-09-15 110 views
15

我试图使用.Net并行化jpeg的大小调整。我的所有尝试都失败了,因为Graphics.DrawImage-func似乎在激活时锁定。尝试以下内容:并行GDI +图像调整大小.net

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与多个线程?在实际应用中,多个图像应该同时调整大小(在四核电脑上),但并不总是相同的。张贴的代码仅用于测试目的...

在此先感谢

编辑:更新的代码根据意见

+0

如果为pSema和pRuns添加声明,则剪切并粘贴和测试proggy的答案将更容易。 – FastAl 2010-09-22 20:58:40

+0

我发现很多时间都花在IO上,而不是在DrawImage调用中。如果你传递一个流的Image构造函数,你将避免解码器中的一些锁定问题。 (而且速度稍快)。我在构建[imageresizing.net库](http://imageresizing.net)时运行了一些基准测试。 – 2011-05-18 18:14:03

+0

此外,WS2008 R2和Win7上的WIC是一个不错的选择,而http://imageresizing.net/支持将该代码路径作为一组插件。 WPF在服务器上仍然是一个坏主意,太多未修补的内存泄漏。 – 2012-01-07 07:45:06

回答

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

我会继续这个建议。迟早,在多线程进程中的GDI操作中,您会遇到死锁或完全随机故障。在将一个示例升级到Microsoft PSS之后,他们承认由于某些竞争条件而导致堆栈/堆栈损坏,但没有真正的解决方案。如果要并行化(或使用线程安全的第三方解决方案),请坚持使用进程外转换! – 2010-09-23 03:53:07

+1

难道你不应该在完成这些过程时处理这些过程吗? – notJim 2010-09-25 00:03:59

+1

回复:“GDI +每个进程阻塞很多方法。”_今天这仍然是真的吗? [MSDN博客文章](http://blogs.msdn.com/b/e7/archive/2009/04/25/engineering-windows-7-for-graphics-performance.aspx“'工程Windows 7图形性能'通过史蒂文Sinofsky“)会导致我相信,这已被固定(或至少改善)在Windows 7 – stakx 2015-05-15 14:26:43

4

我不知道为什么Graphics.DrawImage执行似乎序列化你,但我实际上注意到您的排队条件与您排队工作项目的一般模式。比赛介于WaitOneSet之间。第一个工作项目有可能在Set之前,其他任何人都已排队。这将导致WaitOne在所有工作项目完成之前立即返回。

解决方案是将主线程看作是工作项目。在排队开始前增加pRuns一次,然后递减并在排队完成后发出等待处理信号,就像您在正常工作项目中一样。然而,更好的方法是使用CountdownEvent类,如果这对您是可用的,因为它简化了代码。幸运的是,它会有I just recently posted the pattern in another question

+0

我无法使用CountdownEvent类。但是(如果主线程睡得足够长)可能有可能,函数中的if语句从来都不是真的,不是吗?你有一个想法如何解决这个问题? – PTa 2010-09-15 19:19:32

+1

@PTa:不,你在循环之前递增'pRuns',然后在循环之后递减它......在调用'WaitOne'之前。所有的“Increment”和“Decrement”调用应该平衡。有人最终会将其减至0.它可能是一个工作项目,或者它可能是主线程。你其实已经拥有了大部分的正确性。 – 2010-09-15 19:27:08

+0

对不起 - 我在推理中出错了! :) – PTa 2010-09-15 19:33:33

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(); 
    } 
} 
0

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

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

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

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