Распараллеливание GDI + изменение размера изображения .net - PullRequest
15 голосов
/ 15 сентября 2010

Я пытался распараллелить изменение размера jpegs, используя .Net.Все мои попытки не увенчались успехом, потому что функция 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 с несколькими потоками?В реальном приложении размер нескольких изображений должен быть изменен одновременно (на четырехъядерном ПК), но не всегда одинаково.Размещенный код предназначен только для тестирования ...

Заранее спасибо

Редактировать: Обновлен код согласно комментариям

Ответы [ 4 ]

17 голосов
/ 22 сентября 2010

Использование процессов.

GDI + блоков на процесс множеством способов.Да, боль, но нет никакого способа обойти это.К счастью, с такими задачами, как эта (и с любой, которая обрабатывает файлы в файловой системе), слишком просто просто распределить нагрузку между несколькими процессами.К счастью, похоже, что GDI + использует блокировки, а не мьютекс, поэтому это происходит одновременно!

У нас есть несколько графических программ, в которых я работаю над обработкой изображений.Один программист запускает 6-7 копий сразу программы конвертации в производство.Так что это не грязно, поверь мне.Hack?Тебе не платят, чтобы выглядеть красиво.Выполни работу!

Дешевый пример (обратите внимание, что это не сработает в 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
4 голосов
/ 20 сентября 2010

Если вы не возражаете против подхода WPF, вот что попробовать. Ниже приведен простой метод масштабирования, который принимает потоки изображений и создает байт [], содержащий результирующие данные JPEG. Поскольку вы не хотите рисовать изображения с помощью 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();
    }
 }
4 голосов
/ 15 сентября 2010

Я не уверен, почему выполнение Graphics.DrawImage, кажется, сериализуется для вас, но я действительно заметил состояние гонки с вашей общей схемой постановки в очередь рабочих элементов. Гонка между WaitOne и Set. Первый рабочий элемент может набрать Set еще до того, как кто-либо еще окажется в очереди. Это приведет к немедленному возврату WaitOne до завершения всех рабочих элементов.

Решение состоит в том, чтобы обрабатывать основной поток как рабочий элемент. Увеличьте pRuns один раз, прежде чем начнется очередь, затем уменьшите и подайте сигнал ожидания после завершения очереди, как в обычном рабочем элементе. Однако лучше использовать класс CountdownEvent, если он вам доступен, поскольку он упрощает код. По счастливой случайности Я недавно опубликовал шаблон в другом вопросе .

0 голосов
/ 24 сентября 2010

Используйте библиотеку обработки изображений, отличную от GDI +.

Мы используем ImageMagick на довольно объемном веб-сайте, он изменяет размер загружаемых изображений (загружаемые изображения обычно составляют 10-40 МПикселей, но чтобы иметь возможность работать с ними на веб-сайте (в модулях Flash), мы изменяем их размер, чтобы наименьший размер 1500 пикселей). Обработка довольно быстрая и дает отличные результаты.

В настоящее время мы запускаем новый процесс ImageMagick с использованием интерфейса командной строки. Это дает некоторые издержки при запуске новых процессов, но, поскольку изображения настолько велики, обычно это довольно небольшой интервал времени всего процесса изменения размера. Также возможно использовать ImageMagick в процессе, но еще не пробовал этого, поскольку он 1. нам не нужна дополнительная производительность, которую он дает, и 2. приятно запускать стороннее программное обеспечение в других процессах.

При использовании ImageMagick вы также получаете ряд других возможностей, таких как улучшенная фильтрация и множество других функций.

...