Graphics Mill incorporates an image processing approach named "pipeline", originally introduced in version 6. Pipelines allow reading and writing image files and processing them without the necessity to load a whole bitmap into memory. This essentially means that you can process large images without getting out-of-memory errors, even on an x86 platform.
This topic discusses the benefits pipelines offer when measured against a conventional image processing approach - keeping a full sized bitmap in RAM. It also demonstrates how pipelines can be used in a real life.
In a common case, to process an image using pipeline you should perform the following steps:
Construct the pipeline. Here you can use a variety of building blocks (descendants of the PipelineElement class) which can be divided into the following groups:
This code sample reads an image from JPEG file, crops a square from its center, adjusts brightness, and writes the result to another file.
using (var reader = ImageReader.Create(@"Images\in.jpg")) { var rect = new System.Drawing.Rectangle() { X = Math.Max((reader.Width - reader.Height) / 2, 0), Y = Math.Max((reader.Height - reader.Width) / 2, 0), Width = Math.Min(reader.Width, reader.Height), Height = Math.Min(reader.Width, reader.Height) }; using (var crop = new Crop(rect)) using (var brightness = new Brightness()) using (var writer = ImageWriter.Create(@"Images\Output\out.jpg")) { var pipeline = new Pipeline(); pipeline.Add(reader); pipeline.Add(crop); pipeline.Add(brightness); pipeline.Add(writer); pipeline.Run(); } }
To utilize pipelines in a more convenient way you may use the addition (+
) operator and a static Pipeline.Run(Pipeline) method which allows producing and running an arbitrary length pipeline in a single line of code. The following code demonstrates how to use this simplified syntax to implement the same functionality as the snippet above.
using (var reader = ImageReader.Create(@"Images\in.jpg")) { var rect = new System.Drawing.Rectangle() { X = Math.Max((reader.Width - reader.Height) / 2, 0), Y = Math.Max((reader.Height - reader.Width) / 2, 0), Width = Math.Min(reader.Width, reader.Height), Height = Math.Min(reader.Width, reader.Height) }; using (var crop = new Crop(rect)) using (var brightness = new Brightness()) using (var writer = ImageWriter.Create(@"Images\Output\out.jpg")) { Pipeline.Run(reader + crop + brightness + writer); } }
As mentioned above, pipelines support branched image processing. To branch a pipeline you need to use the PipelineElement.Receivers property which represents a collection of pipeline elements that will receive the result of the current. In a common case, this collection consists of a single element - the next one in the pipeline. So to pass the result of some element to several pipelines, you need to add these pipelines to the element's Receivers collection. In the code, each pipeline branch should be constructed and built separately and then added to the collections of receivers. After you are done with a pipeline don't forget to dispose of its elements and make sure that each branch is disposed as well. See the Disposing Pipeline Elements article for additional details.
The following code shows how to create two 128x128 and 2048x2048 thumbnails for a single image. Here we branch the first pipeline element - ImageReader, and pass its result to two pipelines. Each of them consists of its own Resize transform and ImageWriter.
using (var reader = ImageReader.Create(@"Images\in.jpg")) { var resizeBig = new Pipeline() { new Resize(2048, 0), ImageWriter.Create(@"Images\Output\out_2048.jpg") }; var resizeSmall = new Pipeline() { new Resize(128, 0), ImageWriter.Create(@"Images\Output\out_128.jpg") }; reader.Receivers.Add(resizeBig.Build()); reader.Receivers.Add(resizeSmall.Build()); Pipeline.Run(reader); resizeBig.DisposeAllElements(); resizeSmall.DisposeAllElements(); }