Reputation: 12659
I am working on a site where I need to be able to split and image around 4000x6000 into 4 parts (amongst many other tasks) and I need this to be as quick as possible for multiple users.
My current code for doing this is
var bitmaps = new RenderTargetBitmap[elements.Length];
using (var stream = blobService.Stream(key))
{
BitmapImage bi = new BitmapImage();
bi.BeginInit();
bi.StreamSource = stream;
bi.EndInit();
for (var i = 0; i < elements.Length; i++)
{
var element = elements[i];
TransformGroup transformGroup = new TransformGroup();
TranslateTransform translateTransform = new TranslateTransform();
translateTransform.X = -element.Left;
translateTransform.Y = -element.Top;
transformGroup.Children.Add(translateTransform);
DrawingVisual vis = new DrawingVisual();
DrawingContext cont = vis.RenderOpen();
cont.PushTransform(transformGroup);
cont.DrawImage(bi, new Rect(new Size(bi.PixelWidth, bi.PixelHeight)));
cont.Close();
RenderTargetBitmap rtb = new RenderTargetBitmap(element.Width, element.Height, 96d, 96d, PixelFormats.Default);
rtb.Render(vis);
bitmaps[i] = rtb;
}
}
for (var i = 0; i < bitmaps.Length; i++)
{
using (MemoryStream ms = new MemoryStream())
{
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bitmaps[i]));
encoder.Save(ms);
var regionKey = WebPath.Variant(key, elements[i].Id);
saveBlobService.Save("image/png", regionKey, ms);
}
}
I am running multiple threads which take jobs off a queue. I am finding that if this part of code is hit by 4 threads at once I get an OutOfMemory exception. I can stop this happening by wrapping all the code above in a lock(obj)
but this isn't ideal. I have tried wrapping just the first using block (where the file is read from disk and split) but I still get the out of memory exceptions (this part of the code executes quite quickly).
UPDATE:
My new code as per Moozhe's help
public static void GenerateRegions(this IBlobService blobService, string key, Element[] elements)
{
using (var stream = blobService.Stream(key))
{
foreach (var element in elements)
{
stream.Position = 0;
BitmapImage bi = new BitmapImage();
bi.BeginInit();
bi.SourceRect = new Int32Rect(element.Left, element.Top, element.Width, element.Height);
bi.StreamSource = stream;
bi.EndInit();
DrawingVisual vis = new DrawingVisual();
DrawingContext cont = vis.RenderOpen();
cont.DrawImage(bi, new Rect(new Size(element.Width, element.Height)));
cont.Close();
RenderTargetBitmap rtb = new RenderTargetBitmap(element.Width, element.Height, 96d, 96d, PixelFormats.Default);
rtb.Render(vis);
using (MemoryStream ms = new MemoryStream())
{
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(rtb));
encoder.Save(ms);
var regionKey = WebPath.Variant(key, element.Id);
blobService.Save("image/png", regionKey, ms);
}
}
}
}
Upvotes: 3
Views: 702
Reputation: 11252
If you're trying to call DrawImage of a 4000x6000 image in parallel, you're going to have a bad time. You're cropping it too late, by the time you're rendering it to the RenderTargetBitmap it's already been rendered full size in memory.
Instead of cropping the image source with a transform, try to use the BitmapImage.SourceRect property like so:
BitmapImage.SourceRect = new Rect(element.Left, element.Top, element.Width, element.Height);
You may want to try putting that before you call BeginInit(), and get rid of the transform completely.
EDIT: Actually in your case you'd have to change the SourceRect in each iteration of the for loop. And remember that you have to change the Size parameter in DrawImage to be the element size.
Upvotes: 3