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;
}
}
}
}
}
}