using PaintDotNet.SystemLayer; using System; using System.Drawing; using System.Drawing.Imaging; namespace PaintDotNet { /// /// This is our StitchSurface type. We allocate our own blocks of memory for this, /// and provide ways to create a GDI+ Bitmap object that aliases our StitchSurface. /// That way we can do everything fast, in memory and have complete control, /// and still have the ability to use GDI+ for drawing and rendering where /// appropriate. /// //[Serializable] public sealed class StitchSurface : IDisposable { /// /// 持有的mat /// private OpenCvSharp.Mat mat; /// /// 持有mat的绘制区域 /// public Rectangle stitchBounds; /// /// 持有mat的绘制区域外背景颜色 /// public Color stitchBackColor; /// /// 图像内存块 /// public MemoryBlock scan0; /// /// 宽 /// private int width; /// /// 高 /// private int height; /// /// 扫描宽度 /// private int stride; /// /// 释放标记 /// private bool disposed = false; /// /// 存储打开时图片的格式 /// private PixelFormat pixelFormat = PixelFormat.Format24bppRgb; public PixelFormat PixelFormat { get { return this.pixelFormat; } set { this.pixelFormat = value; } } public bool IsDisposed { get { return this.disposed; } } /// /// 持有mat的有效区域 /// public OpenCvSharp.Mat StitchMat { get { if (this.mat == null || this.stitchBounds.Width == 0 || this.stitchBounds.Height == 0) return null; unsafe { return new OpenCvSharp.Mat(stitchBounds.Height, stitchBounds.Width, OpenCvSharp.MatType.CV_8UC3, new IntPtr((void*)((byte*)scan0.VoidStar + GetPointByteOffsetUnchecked(stitchBounds.X, stitchBounds.Y, this.PixelFormat == PixelFormat.Format32bppArgb ? 4 : 3))), stride); } } } /// /// Gets the width, in pixels, of this StitchSurface. /// /// /// This property will never throw an ObjectDisposedException. /// public int StitchWidth { get { return Math.Min(this.width, this.stitchBounds.Width + 2 * this.stitchBounds.X); } } /// /// Gets the height, in pixels, of this StitchSurface. /// /// /// This property will never throw an ObjectDisposedException. /// public int StitchHeight { get { return Math.Min(this.height, this.stitchBounds.Height + 2 * this.stitchBounds.Y); } } /// /// Gets the width, in pixels, of this StitchSurface. /// /// /// This property will never throw an ObjectDisposedException. /// public int Width { get { return this.width; } } /// /// Gets the height, in pixels, of this StitchSurface. /// /// /// This property will never throw an ObjectDisposedException. /// public int Height { get { return this.height; } } /// /// Gets the stride, in bytes, for this StitchSurface. /// /// /// Stride is defined as the number of bytes between the beginning of a row and /// the beginning of the next row. Thus, in loose C notation: stride = (byte *)&this[0, 1] - (byte *)&this[0, 0]. /// Stride will always be equal to or greater than Width * ColorBgrB.SizeOf. /// This property will never throw an ObjectDisposedException. /// public int Stride { set { this.stride = value; } } /// /// Gets the size, in pixels, of this StitchSurface. /// /// /// This is a convenience function that creates a new Size instance based /// on the values of the Width and Height properties. /// This property will never throw an ObjectDisposedException. /// public Size Size { get { return new Size(this.width, this.height); } } /// /// Gets the bounds of this StitchSurface, in pixels. /// /// /// This is a convenience function that returns Rectangle(0, 0, Width, Height). /// This property will never throw an ObjectDisposedException. /// public Rectangle Bounds { get { return new Rectangle(0, 0, width, height); } } /// /// Creates a new instance of the StitchSurface class. /// /// The size, in pixels, of the new StitchSurface. public StitchSurface(Size size, int SizeOf = 4, OpenCvSharp.Mat mat = null) { this.mat = mat; int width = size.Width; int height = size.Height; int stride = 0; long bytes; try { stride = checked(width * SizeOf); bytes = (long)height * (long)stride; } catch (OverflowException ex) { throw new OutOfMemoryException("Dimensions are too large - not enough memory, width=" + width.ToString() + ", height=" + height.ToString(), ex); } MemoryBlock scan0 = new MemoryBlock(width, height, SizeOf); this.width = width; this.height = height; this.stride = stride; this.scan0 = scan0; } /// /// Creates a new instance of the StitchSurface class that reuses a block of memory that was previously allocated. /// /// The width, in pixels, for the StitchSurface. /// The height, in pixels, for the StitchSurface. /// The stride, in bytes, for the StitchSurface. /// The MemoryBlock to use. The beginning of this buffer defines the upper left (0, 0) pixel of the StitchSurface. private StitchSurface(int width, int height, int stride, MemoryBlock scan0) { this.width = width; this.height = height; this.stride = stride; this.scan0 = scan0; } ~StitchSurface() { Dispose(false); } /// /// Creates a StitchSurface that aliases a portion of this StitchSurface. /// /// The portion of this StitchSurface that will be aliased. /// The upper left corner of the new StitchSurface will correspond to the /// upper left corner of this rectangle in the original StitchSurface. /// A StitchSurface that aliases the requested portion of this StitchSurface. public StitchSurface CreateWindow(Rectangle bounds) { return CreateWindow(bounds.X, bounds.Y, bounds.Width, bounds.Height); } public StitchSurface CreateWindow(int x, int y, int windowWidth, int windowHeight) { if (disposed) { throw new ObjectDisposedException("StitchSurface"); } if (windowHeight == 0) { throw new ArgumentOutOfRangeException("windowHeight", "must be greater than zero"); } Rectangle original = this.Bounds; Rectangle sub = new Rectangle(x, y, windowWidth, windowHeight); Rectangle clipped = Rectangle.Intersect(original, sub); if (clipped != sub) { throw new ArgumentOutOfRangeException("bounds", new Rectangle(x, y, windowWidth, windowHeight), "bounds parameters must be a subset of this StitchSurface's bounds"); } long offset = ((long)stride * (long)y) + ((long)(this.pixelFormat == PixelFormat.Format24bppRgb ? 3 : 4) * (long)x); long length = ((windowHeight - 1) * (long)stride) + (long)windowWidth * (long)(this.pixelFormat == PixelFormat.Format24bppRgb ? 3 : 4); MemoryBlock block = new MemoryBlock(this.scan0, offset, length); return new StitchSurface(windowWidth, windowHeight, this.stride, block); } /// /// Gets the offset, in bytes, of the requested row from the start of the StitchSurface. /// /// The row. /// The number of bytes between (0,0) and (0,y) /// /// This method does not do any bounds checking and is potentially unsafe to use, /// but faster than GetRowByteOffset(). /// public unsafe long GetRowByteOffsetUnchecked(int y) { #if DEBUG if (y < 0 || y >= this.height) { Tracing.Ping("y=" + y.ToString() + " is out of bounds of [0, " + this.height.ToString() + ")"); } #endif return (long)y * (long)stride; } /// /// Gets a pointer to the beginning of the requested row in the StitchSurface. /// /// The row /// A pointer that references (0,y) in this StitchSurface. /// /// This method does not do any bounds checking and is potentially unsafe to use, /// but faster than GetRowAddress(). /// public unsafe ColorBgrB* GetRowAddressUnchecked(int y) { #if DEBUG if (y < 0 || y >= this.height) { Tracing.Ping("y=" + y.ToString() + " is out of bounds of [0, " + this.height.ToString() + ")"); } #endif return (ColorBgrB*)(((byte*)scan0.VoidStar) + GetRowByteOffsetUnchecked(y)); } /// /// Gets the number of bytes from the beginning of a row to the requested column. /// /// The column. /// /// The number of bytes between (0,n) and (x,n) where n is in the range [0, Height). /// /// /// This method does not do any bounds checking and is potentially unsafe to use, /// but faster than GetColumnByteOffset(). /// public long GetColumnByteOffsetUnchecked(int x, int SizeOf) { #if DEBUG if (x < 0 || x >= this.width) { Tracing.Ping("x=" + x.ToString() + " is out of bounds of [0, " + this.width.ToString() + ")"); } #endif return (long)x * (long)SizeOf; } /// /// Gets the number of bytes from the beginning of the StitchSurface's buffer to /// the requested point. /// /// The x offset. /// The y offset. /// /// The number of bytes between (0,0) and (x,y). /// /// /// This method does not do any bounds checking and is potentially unsafe to use, /// but faster than GetPointByteOffset(). /// public long GetPointByteOffsetUnchecked(int x, int y, int SizeOf) { #if DEBUG if (x < 0 || x >= this.width) { Tracing.Ping("x=" + x.ToString() + " is out of bounds of [0, " + this.width.ToString() + ")"); } if (y < 0 || y >= this.height) { Tracing.Ping("y=" + y.ToString() + " is out of bounds of [0, " + this.height.ToString() + ")"); } #endif return GetRowByteOffsetUnchecked(y) + GetColumnByteOffsetUnchecked(x, SizeOf); } /// /// Gets the address in memory of the requested point. /// /// The x offset. /// The y offset. /// A pointer to the requested point in the StitchSurface. /// /// This method does not do any bounds checking and is potentially unsafe to use, /// but faster than GetPointAddress(). /// public unsafe ColorBgrB* GetPointAddressUnchecked(int x, int y) { #if DEBUG if (x < 0 || x >= this.width) { Tracing.Ping("x=" + x.ToString() + " is out of bounds of [0, " + this.width.ToString() + ")"); } if (y < 0 || y >= this.height) { Tracing.Ping("y=" + y.ToString() + " is out of bounds of [0, " + this.height.ToString() + ")"); } #endif return unchecked(x + (ColorBgrB*)(((byte*)scan0.VoidStar) + (y * stride))); } /// /// Determines if the requested row offset is within bounds. /// /// The row. /// true if y >= 0 and y < height, otherwise false public bool IsRowVisible(int y) { return y >= 0 && y < Height; } /// /// Determines if the requested column offset is within bounds. /// /// The column. /// true if x >= 0 and x < width, otherwise false. public bool IsColumnVisible(int x) { return x >= 0 && x < Width; } /// /// Helper function. Same as calling CreateAliasedBounds(Bounds). /// /// A GDI+ Bitmap that aliases the entire StitchSurface. public Bitmap CreateAliasedBitmap() { return CreateAliasedBitmap(this.Bounds); } /// /// Helper function. Same as calling CreateAliasedBounds(bounds, true). /// /// A GDI+ Bitmap that aliases the entire StitchSurface. public Bitmap CreateAliasedBitmap(Rectangle bounds) { return CreateAliasedBitmap(bounds, true); } /// /// Creates a GDI+ Bitmap object that aliases the same memory that this StitchSurface does. /// Then you can use GDI+ to draw on to this StitchSurface. /// Note: Since the Bitmap does not hold a reference to this StitchSurface object, nor to /// the MemoryBlock that it contains, you must hold a reference to the StitchSurface object /// for as long as you wish to use the aliased Bitmap. Otherwise the memory may be /// freed and the Bitmap will look corrupt or cause other errors. You may use the /// RenderArgs class to help manage this lifetime instead. /// /// The rectangle of interest within this StitchSurface that you wish to alias. /// If true, the returned bitmap will use PixelFormat.Format32bppArgb. /// If false, the returned bitmap will use PixelFormat.Format32bppRgb. /// A GDI+ Bitmap that aliases the requested portion of the StitchSurface. /// bounds was not entirely within the boundaries of the StitchSurface /// This StitchSurface instance is already disposed. public Bitmap CreateAliasedBitmap(Rectangle bounds, bool alpha = true) { if (disposed) { throw new ObjectDisposedException("StitchSurface"); } if (bounds.IsEmpty) { throw new ArgumentOutOfRangeException(); } Rectangle clipped = Rectangle.Intersect(this.Bounds, bounds); if (clipped != bounds) { throw new ArgumentOutOfRangeException(); } unsafe { OpenCvSharp.Mat temp = new OpenCvSharp.Mat(bounds.Height, bounds.Width,OpenCvSharp.MatType.CV_8UC3, new IntPtr((void*)((byte*)scan0.VoidStar + GetPointByteOffsetUnchecked(bounds.X, bounds.Y, this.PixelFormat == PixelFormat.Format32bppArgb ? 4 : 3))), stride); Bitmap bitmap1; if (temp.Rows < 3000) bitmap1 = OpenCvSharp.Extensions.BitmapConverter.ToBitmap(temp); else bitmap1 = new Bitmap(temp.Width, temp.Height, (int)temp.Step(), PixelFormat.Format24bppRgb, temp.Data); return bitmap1;// new Bitmap(temp.Width, temp.Height, (int)temp.Step(), PixelFormat.Format24bppRgb, temp.Data); //return new Bitmap(bounds.Width, bounds.Height, (stride / 4) * 4, /*alpha ? PixelFormat.Format32bppArgb :*/ this.PixelFormat/*PixelFormat.Format24bppRgb*/, // new IntPtr((void*)((byte*)scan0.VoidStar + GetPointByteOffsetUnchecked(bounds.X, bounds.Y, this.PixelFormat == PixelFormat.Format32bppArgb ? 4 : 3)))); } } /// /// Releases all resources held by this StitchSurface object. /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } private void Dispose(bool disposing) { if (!disposed) { disposed = true; if (disposing) { scan0.Dispose(); scan0 = null; if (mat != null) { mat.Dispose(); mat = null; } } } } } }