Bad luck for me. I got an InvalidOperationException. I thought I messed something up and didn't want to dive further. But recently I had a need to process a large amount of jpegs and got back to TPL.
Bad luck for me. As I figured out, image processing in WPF is actually Windows Imaging Component wrapped around, not a pure .Net code. Most of the work is hidden behind COM interfaces. I made a separate project for testing WPF's imaging and threading. I found different questions on stackoverflow (WPF/BackgroundWorker and BitmapSource problem and How to copy DispatcherObject (BitmapSource) into different thread?), something worthless on social.msdn, but no solution.
I launched Reflector to look under the hood, no idea.
I don't know how exactly I did deduce the solution, but it's pretty trivial: wrap your BitmapSource with WriteableBitmap.
Here is the code:
#define USE_DIRTY_TRICK_WITH_WRITEABLE_BITMAP //#define USE_DIRECT_CALL #define USE_THREADING //#define USE_TPL using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.IO; using System.Windows.Media.Imaging; using System.Diagnostics; using System.Threading; using System.Threading.Tasks; class Program { static Process p = Process.GetCurrentProcess(); const string path = @"Q:\"; // a folder with pictures static byte[] CompressBitmapSource(BitmapSource bitmapSource) { #if USE_DIRTY_TRICK_WITH_WRITEABLE_BITMAP { var t1 = p.TotalProcessorTime; // this changes everything bitmapSource = new WriteableBitmap(bitmapSource); // in this case, new WriteableBitmap(bitmapSource) is even called in a thread that is // different from the one bitmapSource was created in. var t2 = p.TotalProcessorTime; // How much did we 'pay' for the conversion? Console.WriteLine("Converting to WriteableBitmap: {0:F4} ms", (t2 - t1).TotalMilliseconds); } #endif byte[] buffer; { var t1 = p.TotalProcessorTime; var enc = new JpegBitmapEncoder(); enc.QualityLevel = 50; // some value var bf = BitmapFrame.Create(bitmapSource); // will throw InvalidOperationException (without WriteableBitmap trick) enc.Frames.Add(bf); var ms = new MemoryStream(); enc.Save(ms); buffer = ms.ToArray(); var t2 = p.TotalProcessorTime; Console.WriteLine("Encoding {0}x{1}: {2:F4} ms", bitmapSource.PixelWidth, bitmapSource.PixelHeight, (t2 - t1).TotalMilliseconds); } return buffer; } static BitmapFrame BitmapFrameFromFile(string filename) { var t1 = p.TotalProcessorTime; BitmapFrame res = null; using (var s = File.OpenRead(filename)) { // BitmapCacheOption.OnLoad must be there since s -- s stream -- will be disposed res = BitmapFrame.Create(s, BitmapCreateOptions.None, BitmapCacheOption.OnLoad); } //res.Freeze(); // not obligatory var t2 = p.TotalProcessorTime; Console.WriteLine("Loading: {0:F4} ms", (t2 - t1).TotalMilliseconds); return res; } void Run() { var jpgs = Directory.GetFiles(path, "*.jpg"); foreach (var file in jpgs.Take(10)) { var bf = BitmapFrameFromFile(file); byte[] bytes = new byte[0]; #if USE_DIRECT_CALL bytes = CompressBitmapFrame(bf); #endif #if USE_THREADING Thread th = new Thread((ThreadStart)(() => { bytes = CompressBitmapSource(bf); })); //th.SetApartmentState(ApartmentState.STA); // not obligatory th.Start(); th.Join(); #endif #if USE_TPL var task = new Task(() => bytes = CompressBitmapSource(bf)); task.Start(); task.Wait(); #endif Console.WriteLine("{0} length = {1} bytes when compressed.", file, bytes.Length); } } //[STAThread] // not obligatory static void Main(string[] args) { new Program().Run(); if (Debugger.IsAttached) { Console.WriteLine("Press [Enter] to exit..."); Console.ReadLine(); } } }
Here is the sample output:
Loading: 624,0040 ms Converting to WriteableBitmap: 93,6006 ms Encoding 4608x3456: 218,4014 ms Q:\DSC04935.JPG length = 849378 bytes when compressed. Loading: 639,6041 ms Converting to WriteableBitmap: 46,8003 ms Encoding 4608x3456: 249,6016 ms Q:\DSC04936.JPG length = 1129534 bytes when compressed. Loading: 624,0040 ms Converting to WriteableBitmap: 62,4004 ms Encoding 4608x3456: 218,4014 ms Q:\DSC04937.JPG length = 737203 bytes when compressed. Loading: 639,6041 ms Converting to WriteableBitmap: 46,8003 ms Encoding 4608x3456: 249,6016 ms Q:\DSC04938.JPG length = 896944 bytes when compressed. Loading: 608,4039 ms Converting to WriteableBitmap: 46,8003 ms Encoding 4608x3456: 234,0015 ms Q:\DSC04939.JPG length = 1010188 bytes when compressed. Loading: 608,4039 ms Converting to WriteableBitmap: 31,2002 ms Encoding 4608x3456: 234,0015 ms Q:\DSC04940.JPG length = 709967 bytes when compressed. Loading: 530,4034 ms Converting to WriteableBitmap: 46,8003 ms Encoding 4608x3456: 187,2012 ms Q:\DSC04941.JPG length = 414621 bytes when compressed. Loading: 561,6036 ms Converting to WriteableBitmap: 31,2002 ms Encoding 4608x3456: 249,6016 ms Q:\DSC04942.JPG length = 610337 bytes when compressed. Loading: 530,4034 ms Converting to WriteableBitmap: 46,8003 ms Encoding 4608x3456: 218,4014 ms Q:\DSC04943.JPG length = 648009 bytes when compressed. Loading: 546,0035 ms Converting to WriteableBitmap: 46,8003 ms Encoding 4608x3456: 202,8013 ms Q:\DSC04944.JPG length = 727393 bytes when compressed. Press any key to continue . . .
Happy coding!
P.S. I didn't look of what happens to metadata.
No comments:
Post a Comment