Распараллеливание GDI+ изменение размера изображения.net
Я пытался распараллелить изменение размера JPEG с помощью.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
Все потоки ждут на графике. Рисунок (). Есть ли способ ускорить выполнение кода, используя другие функции? Разве нельзя использовать Graphics.Draw с несколькими потоками? В реальном приложении размер нескольких изображений должен быть изменен одновременно (на четырехъядерном ПК), но не всегда одинаково. Размещенный код предназначен только для тестирования...
заранее спасибо
Изменить: Обновлен код в соответствии с комментариями
4 ответа
Используйте процессы.
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
Если вы не возражаете против подхода 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();
}
}
Я не уверен, почему исполнение Graphics.DrawImage
кажется, сериализует для вас, но я на самом деле заметил состояние гонки с вашей общей схемой работы с рабочими элементами. Гонка между WaitOne
и Set
, Для первого рабочего элемента возможно Set
до того, как кто-либо еще был поставлен в очередь. Это приведет к WaitOne
немедленно вернуться до того, как все рабочие элементы будут выполнены.
Решение состоит в том, чтобы обрабатывать основной поток как рабочий элемент. инкремент pRuns
один раз, прежде чем начнется очередь, а затем уменьшите и подайте сигнал ожидания после завершения очереди, как в обычном рабочем элементе. Тем не менее, лучший подход заключается в использовании CountdownEvent
Класс, если это доступно для вас, поскольку это упрощает код. По счастливой случайности я недавно опубликовал шаблон в другом вопросе.
Используйте библиотеку обработки изображений, отличную от GDI+.
Мы используем ImageMagick на довольно объемном веб-сайте, он изменяет размер загружаемых изображений (обычно загружаемые изображения составляют 10-40 мегапикселей, но чтобы иметь возможность работать с ними на веб-сайте (в модулях Flash), мы изменяем их размер, чтобы иметь наименьший размер 1500 пиксели). Обработка довольно быстрая и дает отличные результаты.
В настоящее время мы запускаем новый процесс ImageMagick с использованием интерфейса командной строки. Это дает некоторые издержки при запуске новых процессов, но, поскольку изображения настолько велики, это обычно довольно маленький временной интервал всего процесса изменения размера. Также возможно использовать ImageMagick в процессе, но еще не пробовал, так как он 1. нам не нужна дополнительная производительность, которую он дает, и 2. приятно запускать стороннее программное обеспечение в других процессах.
Используя ImageMagick, вы также получаете ряд других возможностей, таких как лучшая фильтрация и множество других функций.