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

发布于 2024-09-19 23:28:17 字数 2176 浏览 9 评论 0原文

我尝试使用 .Net 并行调整 jpeg 的大小。我所有的尝试都失败了,因为 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 ?在实际应用中,应该同时调整多个图像的大小(在四核电脑上),而不是始终相同。发布的代码仅用于测试目的...

提前致谢

编辑:根据评论更新代码

I've tried to parallelize the resizing of jpegs using .Net. All my tries failed, because the Graphics.DrawImage-func seems to lock while active. Try the following snipped:

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

All threads wait at graph.DrawImage(). Is there a way to speed up code performance using other functions? Is it impossible to use Graphics.Draw with multiple threads? In the real application multiple images should be resized at the same time (on a quad-core pc), not always the same. The posted code is only for testing purposes...

Thanks in advance

Edit: Updated the code according to comments

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

却一份温柔 2024-09-26 23:28: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

Use Processes.

GDI+ blocks per process a lot of ways. Yep, a pain, but there's no way around it. Fortunately with tasks like this one (and any one that processes files on the filesystem), it's too easy to just split the workload up between multiple processes. Fortunately it looks like GDI+ is using locks, not mutex, so it is concurrent!

We have some graphics programs where I work to do image processing. One programmer starts 6-7 copies at once of a conversion program, in production. So it is not messy, trust me. Hack? You're not getting paid to look pretty. Get the job done!

Cheap Example (note this will NOT work in the ide, build it and run the 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
來不及說愛妳 2024-09-26 23:28:17

我不确定为什么 Graphics.DrawImage 的执行似乎会为您序列化,但我实际上注意到您对工作项进行排队的一般模式存在竞争条件。竞争发生在 WaitOneSet 之间。第一个工作项有可能在其他任何工作项尚未排队之前就已Set。这将导致 WaitOne 在所有工作项完成之前立即返回。

解决方案是将主线程视为一个工作项。在排队开始之前递增 pRuns 一次,然后在排队完成后递减并向等待句柄发出信号,就像在正常工作项中一样。但是,更好的方法是使用 CountdownEvent 类(如果您可以使用该类),因为它可以简化代码。幸运的是我最近在另一个问题中发布了该模式

I am not sure why execution of Graphics.DrawImage seems to serialize for you, but I actually noticed a race condition with your general pattern of queuing the work items. The race is between the WaitOne and the Set. It is possible for the first work item to Set before any of the others have even been queued yet. That will cause WaitOne to return immediately before all work items have completed.

The solution is to treat the main thread as if it were a work item. Increment pRuns once before queueing begins and then decrement and signal the wait handle after queueing is complete just as you would in a normal work item. However, the better approach is to use the CountdownEvent class if that is available to you as it simplifies the code. As luck would have it I just recently posted the pattern in another question.

故事未完 2024-09-26 23:28:17

如果您不介意 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();
    }
 }

If you don't mind a WPF approach, here is something to try. The following is a simple rescale method that accepts image streams and produces a byte[] containing the resulting JPEG data. Since you do not want to actually draw the images with GDI+, I thought this was suitable for you despite being WPF-based. (The only requirement is to reference WindowsBase and PresentationCore in your project.)

Advantages include faster encoding (by 200-300% on my machine) and better parallel speedup, although I also see some unwanted serialization in the WPF rendering path. Let me know how this works for you. I'm sure it could be optimized further if necessary.

The code:

 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();
    }
 }
转瞬即逝 2024-09-26 23:28:17

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

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

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

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

Use a image-processing library other than GDI+.

We use ImageMagick at a pretty high-volume web site it resize uploaded images (the uploaded images are usually 10-40 MPixels but to be able to work with them in the website (in Flash modules), we resize them to have smallest size 1500 pixels). Processing is pretty fast and gives excellent results.

We currently start a new ImageMagick process using the command-line interface. This gives some overhead when starting new processes, but since the images are so large it is usually a pretty small time slice of the total resizing process. It is also possible to use ImageMagick in-process but have haven't tried that yet since it 1. we do not need the extra performance it gives and 2. it feels good to run third-party software in other processes.

When using ImageMagick, you also get a number of other possibilities like better filtering and lots of other functions.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文