// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // namespace ImageProcessorCore { using System; using System.Runtime.InteropServices; /// /// Provides per-pixel access to an images pixels. /// public sealed unsafe class PixelAccessor : IDisposable { /// /// The position of the first pixel in the bitmap. /// private Color* pixelsBase; /// /// Provides a way to access the pixels from unmanaged memory. /// private GCHandle pixelsHandle; /// /// A value indicating whether this instance of the given entity has been disposed. /// /// if this instance has been disposed; otherwise, . /// /// If the entity is disposed, it must not be disposed a second /// time. The isDisposed field is set the first time the entity /// is disposed. If the isDisposed field is true, then the Dispose() /// method will not dispose again. This help not to prolong the entity's /// life in the Garbage Collector. /// private bool isDisposed; /// /// Initializes a new instance of the class. /// /// /// The image to provide pixel access for. /// public PixelAccessor(ImageBase image) { Guard.NotNull(image, nameof(image)); Guard.MustBeGreaterThan(image.Width, 0, "image width"); Guard.MustBeGreaterThan(image.Height, 0, "image height"); this.Width = image.Width; this.Height = image.Height; // Assign the pointer. this.pixelsHandle = GCHandle.Alloc(image.Pixels, GCHandleType.Pinned); this.pixelsBase = (Color*)this.pixelsHandle.AddrOfPinnedObject().ToPointer(); } /// /// Finalizes an instance of the class. /// ~PixelAccessor() { this.Dispose(); } /// /// Gets the width of the image. /// public int Width { get; } /// /// Gets the height of the image. /// public int Height { get; } /// /// Gets or sets the color of a pixel at the specified position. /// /// /// The x-coordinate of the pixel. Must be greater /// than zero and smaller than the width of the pixel. /// /// /// The y-coordinate of the pixel. Must be greater /// than zero and smaller than the width of the pixel. /// /// The at the specified position. public Color this[int x, int y] { get { #if DEBUG if ((x < 0) || (x >= this.Width)) { throw new ArgumentOutOfRangeException(nameof(x), "Value cannot be less than zero or greater than the bitmap width."); } if ((y < 0) || (y >= this.Height)) { throw new ArgumentOutOfRangeException(nameof(y), "Value cannot be less than zero or greater than the bitmap height."); } #endif return *(this.pixelsBase + ((y * this.Width) + x)); } set { #if DEBUG if ((x < 0) || (x >= this.Width)) { throw new ArgumentOutOfRangeException(nameof(x), "Value cannot be less than zero or greater than the bitmap width."); } if ((y < 0) || (y >= this.Height)) { throw new ArgumentOutOfRangeException(nameof(y), "Value cannot be less than zero or greater than the bitmap height."); } #endif *(this.pixelsBase + ((y * this.Width) + x)) = value; } } /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// public void Dispose() { if (this.isDisposed) { return; } if (this.pixelsHandle.IsAllocated) { this.pixelsHandle.Free(); } this.pixelsBase = null; // Note disposing is done. this.isDisposed = true; // This object will be cleaned up by the Dispose method. // Therefore, you should call GC.SuppressFinalize to // take this object off the finalization queue // and prevent finalization code for this object // from executing a second time. GC.SuppressFinalize(this); } } }