using PaintDotNet.SystemLayer; using System; using System.ComponentModel; using System.Drawing; using System.Drawing.Imaging; namespace PaintDotNet { /// /// This is our Surface type. We allocate our own blocks of memory for this, /// and provide ways to create a GDI+ Bitmap object that aliases our surface. /// 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 Surface : IDisposable, ICloneable { /// /// 持有的mat /// public OpenCvSharp.Mat mat; /// /// 图像内存块 /// public MemoryBlock scan0; /// /// 宽 /// private int width; /// /// 高 /// private int height; /// /// 扫描宽度 /// private int stride; /// /// 释放标记 /// private bool disposed = false; /// /// 存储打开时图片的格式 /// private PixelFormat pixelFormat = PixelFormat.Format32bppArgb; /// /// 缩略图 /// private Bitmap thumbnail; /// /// 备份的mat /// private OpenCvSharp.Mat backUpMat; public OpenCvSharp.Mat BackUpMat { get { if (this.backUpMat == null) this.backUpMat = this.mat.Clone(); return this.backUpMat.Clone(); } set { this.backUpMat = value.Clone(); } } public Bitmap Thumbnail { get { if (this.thumbnail == null) CreateThumbnail(); return this.thumbnail; } } /// /// 创建缩略图 /// public void CreateThumbnail() { //TODO 回头去掉 if (this.mat == null) this.mat = PaintDotNet.Camera.Tools.ToMat(CreateAliasedBitmap()); //Bitmap origin = CreateAliasedBitmap(); Bitmap bitmap = null; if (this.mat.Width > this.mat.Height) { bitmap = MakeThumbnail(this.mat, 90, 90, "W"); } else if (this.mat.Height > this.mat.Width) { bitmap = MakeThumbnail(this.mat, 90, 90, "H"); } else { bitmap = MakeThumbnail(this.mat, 90, 90, "W"); } this.thumbnail = bitmap; } public PixelFormat PixelFormat { get { return this.pixelFormat; } set { this.pixelFormat = value; } } public bool IsDisposed { get { return this.disposed; } } /// /// Gets a MemoryBlock which is the buffer holding the pixels associated /// with this Surface. /// public MemoryBlock Scan0 { get { if (this.disposed) { throw new ObjectDisposedException("Surface"); } return this.scan0; } } /// /// Gets the width, in pixels, of this Surface. /// /// /// This property will never throw an ObjectDisposedException. /// public int Width { get { return this.width; } } /// /// Gets the height, in pixels, of this Surface. /// /// /// This property will never throw an ObjectDisposedException. /// public int Height { get { return this.height; } } /// /// Gets the stride, in bytes, for this Surface. /// /// /// 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 * ColorBgra.SizeOf. /// This property will never throw an ObjectDisposedException. /// public int Stride { get { return this.stride; } set { this.stride = value; } } /// /// Gets the size, in pixels, of this Surface. /// /// /// 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 Surface, 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 Surface class. /// /// The size, in pixels, of the new Surface. public Surface(Size size) : this(size.Width, size.Height) { } public Surface(Size size, PixelFormat pixelFormat) : this(size.Width, size.Height, pixelFormat) { } /// /// Creates a new instance of the Surface class. /// /// The width, in pixels, of the new Surface. /// The height, in pixels, of the new Surface. public Surface(int width, int height) { int stride = 0; long bytes; try { stride = checked(width * ColorBgra.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); Create(width, height, stride, scan0); } public Surface(int width, int height, PixelFormat pixelFormat) { int ch; PixelFormat = pixelFormat; try { if (pixelFormat == PixelFormat.Format24bppRgb) ch = 3; else if (pixelFormat == PixelFormat.Format8bppIndexed) ch = 1; else ch = 4; } 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, ch); Create(width, height, width * ch, scan0); } /// /// Creates a new instance of the Surface class that reuses a block of memory that was previously allocated. /// /// The width, in pixels, for the Surface. /// The height, in pixels, for the Surface. /// The stride, in bytes, for the Surface. /// The MemoryBlock to use. The beginning of this buffer defines the upper left (0, 0) pixel of the Surface. private Surface(int width, int height, int stride, MemoryBlock scan0) { Create(width, height, stride, scan0); } [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "width")] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "height")] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "stride")] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1500:VariableNamesShouldNotMatchFieldNames", MessageId = "scan0")] private void Create(int width, int height, int stride, MemoryBlock scan0) { this.width = width; this.height = height; this.stride = stride; this.scan0 = scan0; } ~Surface() { Dispose(false); } /// /// Creates a Surface that aliases a portion of this Surface. /// /// The portion of this Surface that will be aliased. /// The upper left corner of the new Surface will correspond to the /// upper left corner of this rectangle in the original Surface. /// A Surface that aliases the requested portion of this Surface. public Surface CreateWindow(Rectangle bounds) { return CreateWindow(bounds.X, bounds.Y, bounds.Width, bounds.Height); } public Surface CreateWindow(int x, int y, int windowWidth, int windowHeight) { if (disposed) { throw new ObjectDisposedException("Surface"); } 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 Surface's bounds"); } long offset = ((long)stride * (long)y) + ((long)ColorBgra.SizeOf * (long)x); long length = ((windowHeight - 1) * (long)stride) + (long)windowWidth * (long)ColorBgra.SizeOf; MemoryBlock block = new MemoryBlock(this.scan0, offset, length); return new Surface(windowWidth, windowHeight, this.stride, block); } /// /// Gets the offset, in bytes, of the requested row from the start of the surface. /// /// The row. /// The number of bytes between (0,0) and (0,y). public long GetRowByteOffset(int y) { if (y < 0 || y >= height) { throw new ArgumentOutOfRangeException("y", "Out of bounds: y=" + y.ToString()); } return (long)y * (long)stride; } /// /// Gets the offset, in bytes, of the requested row from the start of the surface. /// /// 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) { return (long)y * (long)stride; } /// /// Gets a pointer to the beginning of the requested row in the surface. /// /// The row /// A pointer that references (0,y) in this surface. /// Since this returns a pointer, it is potentially unsafe to use. public unsafe ColorBgra* GetRowAddress(int y) { return (ColorBgra*)(((byte*)scan0.VoidStar) + GetRowByteOffset(y)); } /// /// Gets a pointer to the beginning of the requested row in the surface. /// /// The row /// A pointer that references (0,y) in this surface. /// /// This method does not do any bounds checking and is potentially unsafe to use, /// but faster than GetRowAddress(). /// public unsafe ColorBgra* GetRowAddressUnchecked(int y) { return (ColorBgra*)(((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) { return (long)x * (long)ColorBgra.SizeOf; } /// /// Gets the number of bytes from the beginning of the surface'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) { return GetRowByteOffsetUnchecked(y) + GetColumnByteOffsetUnchecked(x); } /// /// Gets the color at a specified point in the surface. /// /// The x offset. /// The y offset. /// The color at the requested location. public ColorBgra GetPoint(int x, int y) { return this[x, y]; } /// /// Gets the address in memory of the requested point. /// /// The x offset. /// The y offset. /// A pointer to the requested point in the surface. /// Since this method returns a pointer, it is potentially unsafe to use. public unsafe ColorBgra* GetPointAddress(int x, int y) { if (x < 0 || x >= Width) { throw new ArgumentOutOfRangeException("x", "Out of bounds: x=" + x.ToString()); } return GetRowAddress(y) + x; } /// /// Gets the address in memory of the requested point. /// /// The point to retrieve. /// A pointer to the requested point in the surface. /// Since this method returns a pointer, it is potentially unsafe to use. public unsafe ColorBgra* GetPointAddress(Point pt) { return GetPointAddress(pt.X, pt.Y); } /// /// Gets the address in memory of the requested point. /// /// The x offset. /// The y offset. /// A pointer to the requested point in the surface. /// /// This method does not do any bounds checking and is potentially unsafe to use, /// but faster than GetPointAddress(). /// public unsafe ColorBgra* GetPointAddressUnchecked(int x, int y) { return unchecked(x + (ColorBgra*)(((byte*)scan0.VoidStar) + (y * stride))); } /// /// Gets a MemoryBlock that references the row requested. /// /// The row. /// A MemoryBlock that gives access to the bytes in the specified row. /// This method is the safest to use for direct memory access to a row's pixel data. public MemoryBlock GetRow(int y) { return new MemoryBlock(scan0, GetRowByteOffset(y), (long)width * (long)ColorBgra.SizeOf); } /// /// Determines if the requested pixel coordinate is within bounds. /// /// The x coordinate. /// The y coordinate. /// true if (x,y) is in bounds, false if it's not. public bool IsVisible(int x, int y) { return x >= 0 && x < width && y >= 0 && y < height; } /// /// 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; } /// /// Gets or sets the pixel value at the requested offset. /// /// /// This property is implemented with correctness and error checking in mind. If performance /// is a concern, do not use it. /// public ColorBgra this[int x, int y] { get { if (disposed) { throw new ObjectDisposedException("Surface"); } if (x < 0 || y < 0 || x >= this.width || y >= this.height) { throw new ArgumentOutOfRangeException("(x,y)", new Point(x, y), "Coordinates out of range, max=" + new Size(width - 1, height - 1).ToString()); } unsafe { return *GetPointAddressUnchecked(x, y); } } set { if (disposed) { throw new ObjectDisposedException("Surface"); } if (x < 0 || y < 0 || x >= this.width || y >= this.height) { throw new ArgumentOutOfRangeException("(x,y)", new Point(x, y), "Coordinates out of range, max=" + new Size(width - 1, height - 1).ToString()); } unsafe { *GetPointAddressUnchecked(x, y) = value; } } } /// /// Helper function. Same as calling CreateAliasedBounds(Bounds). /// /// A GDI+ Bitmap that aliases the entire Surface. public Bitmap CreateAliasedBitmap(bool alpha) { return CreateAliasedBitmap(this.Bounds, alpha); } public OpenCvSharp.Mat CreatedAliasedMat() { if (mat == null)//<-CreateAliasedBitmap return Camera.Tools.ToMat(this.CreateAliasedBitmap());// OpenCvSharp.Extensions.BitmapConverter.ToMat(this.CreateAliasedBitmap()); return mat/*.Clone()*/;//待测试 } /// /// Helper function. Same as calling CreateAliasedBounds(Bounds). /// /// A GDI+ Bitmap that aliases the entire Surface. public Bitmap CreateAliasedBitmap() { return CreateAliasedBitmap(this.Bounds); } /// /// Helper function. Same as calling CreateAliasedBounds(bounds, true). /// /// A GDI+ Bitmap that aliases the entire Surface. public Bitmap CreateAliasedBitmap(Rectangle bounds) { return CreateAliasedBitmap(bounds, true); } /// /// Creates a GDI+ Bitmap object that aliases the same memory that this Surface does. /// Then you can use GDI+ to draw on to this surface. /// Note: Since the Bitmap does not hold a reference to this Surface object, nor to /// the MemoryBlock that it contains, you must hold a reference to the Surface 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 Surface 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 Surface. /// bounds was not entirely within the boundaries of the Surface /// This Surface instance is already disposed. public Bitmap CreateAliasedBitmap(Rectangle bounds, bool alpha = true) { if (disposed) { throw new ObjectDisposedException("Surface"); } if (bounds.IsEmpty) { throw new ArgumentOutOfRangeException(); } Rectangle clipped = Rectangle.Intersect(this.Bounds, bounds); if (clipped != bounds) { throw new ArgumentOutOfRangeException(); } unsafe { return new Bitmap(bounds.Width, bounds.Height, stride, alpha ? PixelFormat.Format32bppArgb : this.PixelFormat, new IntPtr((void*)((byte*)scan0.VoidStar + GetPointByteOffsetUnchecked(bounds.X, bounds.Y)))); } } /// /// Creates a new Surface and copies the pixels from a Bitmap to it. /// /// The Bitmap to duplicate. /// A new Surface that is the same size as the given Bitmap and that has the same pixel values. public static Surface CopyFromBitmap(Bitmap bitmap) { Surface surface = new Surface(bitmap.Width, bitmap.Height); BitmapData bd = bitmap.LockBits(surface.Bounds, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); unsafe { for (int y = 0; y < bd.Height; ++y) { Memory.Copy((void*)surface.GetRowAddress(y), (byte*)bd.Scan0.ToPointer() + (y * bd.Stride), (ulong)bd.Width * ColorBgra.SizeOf); } } bitmap.UnlockBits(bd); return surface; } /// /// Copies the contents of the given surface to the upper left corner of this surface. /// /// The surface to copy pixels from. /// /// The source surface does not need to have the same dimensions as this surface. Clipping /// will be handled automatically. No resizing will be done. /// public void CopySurface(Surface source) { if (disposed) { throw new ObjectDisposedException("Surface"); } if (this.stride == source.stride && (this.width * ColorBgra.SizeOf) == this.stride && this.width == source.width && this.height == source.height) { unsafe { Memory.Copy(this.scan0.VoidStar, source.scan0.VoidStar, ((ulong)(height - 1) * (ulong)stride) + ((ulong)width * (ulong)ColorBgra.SizeOf)); } } else { int copyWidth = Math.Min(width, source.width); int copyHeight = Math.Min(height, source.height); unsafe { for (int y = 0; y < copyHeight; ++y) { Memory.Copy(GetRowAddressUnchecked(y), source.GetRowAddressUnchecked(y), (ulong)copyWidth * (ulong)ColorBgra.SizeOf); } } } } object ICloneable.Clone() { return Clone(); } /// /// Creates a new surface with the same dimensions and pixel values as this one. /// /// A new surface that is a clone of the current one. public Surface Clone() { if (disposed) { throw new ObjectDisposedException("Surface"); } Surface ret = new Surface(this.Size); ret.CopySurface(this); return ret; } /// /// Clears the surface to all-white (BGRA = [255,255,255,255]). /// public void Clear() { Clear(ColorBgra.FromBgra(255, 255, 255, 255)); } /// /// Clears the surface to the given color value. /// /// The color value to fill the surface with. public void Clear(ColorBgra color) { //new UnaryPixelOps.Constant(color).Apply(this, this.Bounds); } /// /// Fits the source surface to this surface using super sampling. If the source surface is less wide /// or less tall than this surface (i.e. magnification), bicubic resampling is used instead. If either /// the source or destination has a dimension that is only 1 pixel, nearest neighbor is used. /// /// The surface to read pixels from. /// The rectangle to clip rendering to. /// This method was implemented with correctness, not performance, in mind. public void SuperSamplingFitSurface(Surface source, Rectangle dstRoi) { if (source.Width == Width && source.Height == Height) { CopySurface(source); } else if (source.Width <= Width || source.Height <= Height) { if (source.width < 2 || source.height < 2 || this.width < 2 || this.height < 2) { this.NearestNeighborFitSurface(source, dstRoi); } else { this.BicubicFitSurface(source, dstRoi); } } else unsafe { Rectangle dstRoi2 = Rectangle.Intersect(dstRoi, this.Bounds); for (int dstY = dstRoi2.Top; dstY < dstRoi2.Bottom; ++dstY) { double srcTop = (double)(dstY * source.height) / (double)height; double srcTopFloor = Math.Floor(srcTop); double srcTopWeight = 1 - (srcTop - srcTopFloor); int srcTopInt = (int)srcTopFloor; double srcBottom = (double)((dstY + 1) * source.height) / (double)height; double srcBottomFloor = Math.Floor(srcBottom - 0.00001); double srcBottomWeight = srcBottom - srcBottomFloor; int srcBottomInt = (int)srcBottomFloor; ColorBgra* dstPtr = this.GetPointAddressUnchecked(dstRoi2.Left, dstY); for (int dstX = dstRoi2.Left; dstX < dstRoi2.Right; ++dstX) { double srcLeft = (double)(dstX * source.width) / (double)width; double srcLeftFloor = Math.Floor(srcLeft); double srcLeftWeight = 1 - (srcLeft - srcLeftFloor); int srcLeftInt = (int)srcLeftFloor; double srcRight = (double)((dstX + 1) * source.width) / (double)width; double srcRightFloor = Math.Floor(srcRight - 0.00001); double srcRightWeight = srcRight - srcRightFloor; int srcRightInt = (int)srcRightFloor; double blueSum = 0; double greenSum = 0; double redSum = 0; double alphaSum = 0; // left fractional edge ColorBgra* srcLeftPtr = source.GetPointAddressUnchecked(srcLeftInt, srcTopInt + 1); for (int srcY = srcTopInt + 1; srcY < srcBottomInt; ++srcY) { double a = srcLeftPtr->A; blueSum += srcLeftPtr->B * srcLeftWeight * a; greenSum += srcLeftPtr->G * srcLeftWeight * a; redSum += srcLeftPtr->R * srcLeftWeight * a; alphaSum += srcLeftPtr->A * srcLeftWeight; srcLeftPtr = (ColorBgra*)((byte*)srcLeftPtr + source.stride); } // right fractional edge ColorBgra* srcRightPtr = source.GetPointAddressUnchecked(srcRightInt, srcTopInt + 1); for (int srcY = srcTopInt + 1; srcY < srcBottomInt; ++srcY) { double a = srcRightPtr->A; blueSum += srcRightPtr->B * srcRightWeight * a; greenSum += srcRightPtr->G * srcRightWeight * a; redSum += srcRightPtr->R * srcRightWeight * a; alphaSum += srcRightPtr->A * srcRightWeight; srcRightPtr = (ColorBgra*)((byte*)srcRightPtr + source.stride); } // top fractional edge ColorBgra* srcTopPtr = source.GetPointAddressUnchecked(srcLeftInt + 1, srcTopInt); for (int srcX = srcLeftInt + 1; srcX < srcRightInt; ++srcX) { double a = srcTopPtr->A; blueSum += srcTopPtr->B * srcTopWeight * a; greenSum += srcTopPtr->G * srcTopWeight * a; redSum += srcTopPtr->R * srcTopWeight * a; alphaSum += srcTopPtr->A * srcTopWeight; ++srcTopPtr; } // bottom fractional edge ColorBgra* srcBottomPtr = source.GetPointAddressUnchecked(srcLeftInt + 1, srcBottomInt); for (int srcX = srcLeftInt + 1; srcX < srcRightInt; ++srcX) { double a = srcBottomPtr->A; blueSum += srcBottomPtr->B * srcBottomWeight * a; greenSum += srcBottomPtr->G * srcBottomWeight * a; redSum += srcBottomPtr->R * srcBottomWeight * a; alphaSum += srcBottomPtr->A * srcBottomWeight; ++srcBottomPtr; } // center area for (int srcY = srcTopInt + 1; srcY < srcBottomInt; ++srcY) { ColorBgra* srcPtr = source.GetPointAddressUnchecked(srcLeftInt + 1, srcY); for (int srcX = srcLeftInt + 1; srcX < srcRightInt; ++srcX) { double a = srcPtr->A; blueSum += (double)srcPtr->B * a; greenSum += (double)srcPtr->G * a; redSum += (double)srcPtr->R * a; alphaSum += (double)srcPtr->A; ++srcPtr; } } // four corner pixels ColorBgra srcTL = source.GetPoint(srcLeftInt, srcTopInt); double srcTLA = srcTL.A; blueSum += srcTL.B * (srcTopWeight * srcLeftWeight) * srcTLA; greenSum += srcTL.G * (srcTopWeight * srcLeftWeight) * srcTLA; redSum += srcTL.R * (srcTopWeight * srcLeftWeight) * srcTLA; alphaSum += srcTL.A * (srcTopWeight * srcLeftWeight); ColorBgra srcTR = source.GetPoint(srcRightInt, srcTopInt); double srcTRA = srcTR.A; blueSum += srcTR.B * (srcTopWeight * srcRightWeight) * srcTRA; greenSum += srcTR.G * (srcTopWeight * srcRightWeight) * srcTRA; redSum += srcTR.R * (srcTopWeight * srcRightWeight) * srcTRA; alphaSum += srcTR.A * (srcTopWeight * srcRightWeight); ColorBgra srcBL = source.GetPoint(srcLeftInt, srcBottomInt); double srcBLA = srcBL.A; blueSum += srcBL.B * (srcBottomWeight * srcLeftWeight) * srcBLA; greenSum += srcBL.G * (srcBottomWeight * srcLeftWeight) * srcBLA; redSum += srcBL.R * (srcBottomWeight * srcLeftWeight) * srcBLA; alphaSum += srcBL.A * (srcBottomWeight * srcLeftWeight); ColorBgra srcBR = source.GetPoint(srcRightInt, srcBottomInt); double srcBRA = srcBR.A; blueSum += srcBR.B * (srcBottomWeight * srcRightWeight) * srcBRA; greenSum += srcBR.G * (srcBottomWeight * srcRightWeight) * srcBRA; redSum += srcBR.R * (srcBottomWeight * srcRightWeight) * srcBRA; alphaSum += srcBR.A * (srcBottomWeight * srcRightWeight); double area = (srcRight - srcLeft) * (srcBottom - srcTop); double alpha = alphaSum / area; double blue; double green; double red; if (alpha == 0) { blue = 0; green = 0; red = 0; } else { blue = blueSum / alphaSum; green = greenSum / alphaSum; red = redSum / alphaSum; } // add 0.5 so that rounding goes in the direction we want it to blue += 0.5; green += 0.5; red += 0.5; alpha += 0.5; dstPtr->Bgra = (uint)blue + ((uint)green << 8) + ((uint)red << 16) + ((uint)alpha << 24); ++dstPtr; } } } } /// /// Fits the source surface to this surface using nearest neighbor resampling. /// /// The surface to read pixels from. /// The rectangle to clip rendering to. public void NearestNeighborFitSurface(Surface source, Rectangle dstRoi) { Rectangle roi = Rectangle.Intersect(dstRoi, this.Bounds); unsafe { for (int dstY = roi.Top; dstY < roi.Bottom; ++dstY) { int srcY = (dstY * source.height) / height; ColorBgra* srcRow = source.GetRowAddressUnchecked(srcY); ColorBgra* dstPtr = this.GetPointAddressUnchecked(roi.Left, dstY); for (int dstX = roi.Left; dstX < roi.Right; ++dstX) { int srcX = (dstX * source.width) / width; *dstPtr = *(srcRow + srcX); ++dstPtr; } } } } private double CubeClamped(double x) { if (x >= 0) { return x * x * x; } else { return 0; } } /// /// Implements R() as defined at http://astronomy.swin.edu.au/%7Epbourke/colour/bicubic/ /// private double R(double x) { return (CubeClamped(x + 2) - (4 * CubeClamped(x + 1)) + (6 * CubeClamped(x)) - (4 * CubeClamped(x - 1))) / 6; } /// /// Fits the source surface to this surface using bicubic interpolation. /// /// The Surface to read pixels from. /// The rectangle to clip rendering to. /// /// This method was implemented with correctness, not performance, in mind. /// Based on: "Bicubic Interpolation for Image Scaling" by Paul Bourke, /// http://astronomy.swin.edu.au/%7Epbourke/colour/bicubic/ /// public void BicubicFitSurface(Surface source, Rectangle dstRoi) { float leftF = (1 * (float)(width - 1)) / (float)(source.width - 1); float topF = (1 * (height - 1)) / (float)(source.height - 1); float rightF = ((float)(source.width - 3) * (float)(width - 1)) / (float)(source.width - 1); float bottomF = ((float)(source.Height - 3) * (float)(height - 1)) / (float)(source.height - 1); int left = (int)Math.Ceiling((double)leftF); int top = (int)Math.Ceiling((double)topF); int right = (int)Math.Floor((double)rightF); int bottom = (int)Math.Floor((double)bottomF); Rectangle[] rois = new Rectangle[] { Rectangle.FromLTRB(left, top, right, bottom), new Rectangle(0, 0, width, top), new Rectangle(0, top, left, height - top), new Rectangle(right, top, width - right, height - top), new Rectangle(left, bottom, right - left, height - bottom) }; for (int i = 0; i < rois.Length; ++i) { rois[i].Intersect(dstRoi); if (rois[i].Width > 0 && rois[i].Height > 0) { if (i == 0) { BicubicFitSurfaceUnchecked(source, rois[i]); } else { BicubicFitSurfaceChecked(source, rois[i]); } } } } /// /// Implements bicubic filtering with bounds checking at every pixel. /// private void BicubicFitSurfaceChecked(Surface source, Rectangle dstRoi) { if (this.width < 2 || this.height < 2 || source.width < 2 || source.height < 2) { SuperSamplingFitSurface(source, dstRoi); } else { unsafe { Rectangle roi = Rectangle.Intersect(dstRoi, this.Bounds); Rectangle roiIn = Rectangle.Intersect(dstRoi, new Rectangle(1, 1, width - 1, height - 1)); IntPtr rColCacheIP = Memory.Allocate(4 * (ulong)roi.Width * (ulong)sizeof(double)); double* rColCache = (double*)rColCacheIP.ToPointer(); // Precompute and then cache the value of R() for each column for (int dstX = roi.Left; dstX < roi.Right; ++dstX) { double srcColumn = (double)(dstX * (source.width - 1)) / (double)(width - 1); double srcColumnFloor = Math.Floor(srcColumn); double srcColumnFrac = srcColumn - srcColumnFloor; int srcColumnInt = (int)srcColumn; for (int m = -1; m <= 2; ++m) { int index = (m + 1) + ((dstX - roi.Left) * 4); double x = m - srcColumnFrac; rColCache[index] = R(x); } } // Set this up so we can cache the R()'s for every row double* rRowCache = stackalloc double[4]; for (int dstY = roi.Top; dstY < roi.Bottom; ++dstY) { double srcRow = (double)(dstY * (source.height - 1)) / (double)(height - 1); double srcRowFloor = (double)Math.Floor(srcRow); double srcRowFrac = srcRow - srcRowFloor; int srcRowInt = (int)srcRow; ColorBgra* dstPtr = this.GetPointAddressUnchecked(roi.Left, dstY); // Compute the R() values for this row for (int n = -1; n <= 2; ++n) { double x = srcRowFrac - n; rRowCache[n + 1] = R(x); } // See Perf Note below //int nFirst = Math.Max(-srcRowInt, -1); //int nLast = Math.Min(source.height - srcRowInt - 1, 2); for (int dstX = roi.Left; dstX < roi.Right; dstX++) { double srcColumn = (double)(dstX * (source.width - 1)) / (double)(width - 1); double srcColumnFloor = Math.Floor(srcColumn); double srcColumnFrac = srcColumn - srcColumnFloor; int srcColumnInt = (int)srcColumn; double blueSum = 0; double greenSum = 0; double redSum = 0; double alphaSum = 0; double totalWeight = 0; // See Perf Note below //int mFirst = Math.Max(-srcColumnInt, -1); //int mLast = Math.Min(source.width - srcColumnInt - 1, 2); ColorBgra* srcPtr = source.GetPointAddressUnchecked(srcColumnInt - 1, srcRowInt - 1); for (int n = -1; n <= 2; ++n) { int srcY = srcRowInt + n; for (int m = -1; m <= 2; ++m) { // Perf Note: It actually benchmarks faster on my system to do // a bounds check for every (m,n) than it is to limit the loop // to nFirst-Last and mFirst-mLast. // I'm leaving the code above, albeit commented out, so that // benchmarking between these two can still be performed. if (source.IsVisible(srcColumnInt + m, srcY)) { double w0 = rColCache[(m + 1) + (4 * (dstX - roi.Left))]; double w1 = rRowCache[n + 1]; double w = w0 * w1; blueSum += srcPtr->B * w * srcPtr->A; greenSum += srcPtr->G * w * srcPtr->A; redSum += srcPtr->R * w * srcPtr->A; alphaSum += srcPtr->A * w; totalWeight += w; } ++srcPtr; } srcPtr = (ColorBgra*)((byte*)(srcPtr - 4) + source.stride); } double alpha = alphaSum / totalWeight; double blue; double green; double red; if (alpha == 0) { blue = 0; green = 0; red = 0; } else { blue = blueSum / alphaSum; green = greenSum / alphaSum; red = redSum / alphaSum; // add 0.5 to ensure truncation to uint results in rounding alpha += 0.5; blue += 0.5; green += 0.5; red += 0.5; } dstPtr->Bgra = (uint)blue + ((uint)green << 8) + ((uint)red << 16) + ((uint)alpha << 24); ++dstPtr; } // for (dstX... } // for (dstY... Memory.Free(rColCacheIP); } // unsafe } } /// /// Implements bicubic filtering with NO bounds checking at any pixel. /// public void BicubicFitSurfaceUnchecked(Surface source, Rectangle dstRoi) { if (this.width < 2 || this.height < 2 || source.width < 2 || source.height < 2) { SuperSamplingFitSurface(source, dstRoi); } else { unsafe { Rectangle roi = Rectangle.Intersect(dstRoi, this.Bounds); Rectangle roiIn = Rectangle.Intersect(dstRoi, new Rectangle(1, 1, width - 1, height - 1)); IntPtr rColCacheIP = Memory.Allocate(4 * (ulong)roi.Width * (ulong)sizeof(double)); double* rColCache = (double*)rColCacheIP.ToPointer(); // Precompute and then cache the value of R() for each column for (int dstX = roi.Left; dstX < roi.Right; ++dstX) { double srcColumn = (double)(dstX * (source.width - 1)) / (double)(width - 1); double srcColumnFloor = Math.Floor(srcColumn); double srcColumnFrac = srcColumn - srcColumnFloor; int srcColumnInt = (int)srcColumn; for (int m = -1; m <= 2; ++m) { int index = (m + 1) + ((dstX - roi.Left) * 4); double x = m - srcColumnFrac; rColCache[index] = R(x); } } // Set this up so we can cache the R()'s for every row double* rRowCache = stackalloc double[4]; for (int dstY = roi.Top; dstY < roi.Bottom; ++dstY) { double srcRow = (double)(dstY * (source.height - 1)) / (double)(height - 1); double srcRowFloor = Math.Floor(srcRow); double srcRowFrac = srcRow - srcRowFloor; int srcRowInt = (int)srcRow; ColorBgra* dstPtr = this.GetPointAddressUnchecked(roi.Left, dstY); // Compute the R() values for this row for (int n = -1; n <= 2; ++n) { double x = srcRowFrac - n; rRowCache[n + 1] = R(x); } rColCache = (double*)rColCacheIP.ToPointer(); ColorBgra* srcRowPtr = source.GetRowAddressUnchecked(srcRowInt - 1); for (int dstX = roi.Left; dstX < roi.Right; dstX++) { double srcColumn = (double)(dstX * (source.width - 1)) / (double)(width - 1); double srcColumnFloor = Math.Floor(srcColumn); double srcColumnFrac = srcColumn - srcColumnFloor; int srcColumnInt = (int)srcColumn; double blueSum = 0; double greenSum = 0; double redSum = 0; double alphaSum = 0; double totalWeight = 0; ColorBgra* srcPtr = srcRowPtr + srcColumnInt - 1; for (int n = 0; n <= 3; ++n) { double w0 = rColCache[0] * rRowCache[n]; double w1 = rColCache[1] * rRowCache[n]; double w2 = rColCache[2] * rRowCache[n]; double w3 = rColCache[3] * rRowCache[n]; double a0 = srcPtr[0].A; double a1 = srcPtr[1].A; double a2 = srcPtr[2].A; double a3 = srcPtr[3].A; alphaSum += (a0 * w0) + (a1 * w1) + (a2 * w2) + (a3 * w3); totalWeight += w0 + w1 + w2 + w3; blueSum += (a0 * srcPtr[0].B * w0) + (a1 * srcPtr[1].B * w1) + (a2 * srcPtr[2].B * w2) + (a3 * srcPtr[3].B * w3); greenSum += (a0 * srcPtr[0].G * w0) + (a1 * srcPtr[1].G * w1) + (a2 * srcPtr[2].G * w2) + (a3 * srcPtr[3].G * w3); redSum += (a0 * srcPtr[0].R * w0) + (a1 * srcPtr[1].R * w1) + (a2 * srcPtr[2].R * w2) + (a3 * srcPtr[3].R * w3); srcPtr = (ColorBgra*)((byte*)srcPtr + source.stride); } double alpha = alphaSum / totalWeight; double blue; double green; double red; if (alpha == 0) { blue = 0; green = 0; red = 0; } else { blue = blueSum / alphaSum; green = greenSum / alphaSum; red = redSum / alphaSum; // add 0.5 to ensure truncation to uint results in rounding alpha += 0.5; blue += 0.5; green += 0.5; red += 0.5; } dstPtr->Bgra = (uint)blue + ((uint)green << 8) + ((uint)red << 16) + ((uint)alpha << 24); ++dstPtr; rColCache += 4; } // for (dstX... } // for (dstY... Memory.Free(rColCacheIP); } // unsafe } } /// /// Releases all resources held by this Surface 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.IsDisposed) { mat.Dispose(); mat = null; } } } } /// /// 生成缩略图 /// /// 源图片 /// 缩略图宽度 /// 缩略图高度 /// 生成缩略图的方式 public static OpenCvSharp.Mat MakeThumbnailMat(OpenCvSharp.Mat mat, int width, int height, string mode) { int towidth = width; int toheight = height; switch (mode) { case "HW"://指定高宽缩放(可能变形) break; case "W"://指定宽,高按比例 toheight = mat.Height * width / mat.Width; break; case "H"://指定高,宽按比例 towidth = mat.Width * height / mat.Height; break; default: break; } OpenCvSharp.Mat temp = new OpenCvSharp.Mat(); OpenCvSharp.Cv2.Resize(mat, temp, new OpenCvSharp.Size(towidth, toheight)); return temp; } /// /// 生成缩略图 /// /// 源图片 /// 缩略图宽度 /// 缩略图高度 /// 生成缩略图的方式 public static Bitmap MakeThumbnail(/*Image originalImage*/OpenCvSharp.Mat mat, int width, int height, string mode) { int towidth = width; int toheight = height; int x = 0; int y = 0; int ow = mat.Width; int oh = mat.Height; switch (mode) { case "HW"://指定高宽缩放(可能变形) break; case "W"://指定宽,高按比例 toheight = mat.Height * width / mat.Width; break; case "H"://指定高,宽按比例 towidth = mat.Width * height / mat.Height; break; case "Cut"://指定高宽裁减(不变形) if ((double)mat.Width / (double)mat.Height > (double)towidth / (double)toheight) { oh = mat.Height; ow = mat.Height * towidth / toheight; y = 0; x = (mat.Width - ow) / 2; } else { ow = mat.Width; oh = mat.Width * height / towidth; x = 0; y = (mat.Height - oh) / 2; } break; default: break; } OpenCvSharp.Mat temp = new OpenCvSharp.Mat(); OpenCvSharp.Cv2.Resize(mat, temp, new OpenCvSharp.Size(towidth, toheight)); return OpenCvSharp.Extensions.BitmapConverter.ToBitmap(temp); /*//新建一个bmp图片 Bitmap bitmap = new System.Drawing.Bitmap(towidth, toheight); //新建一个画板 Graphics g = System.Drawing.Graphics.FromImage(bitmap); //设置高质量插值法 g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High; //设置高质量,低速度呈现平滑程度 g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; //清空画布并以透明背景色填充 g.Clear(Color.Transparent); //在指定位置并且按指定大小绘制原图片的指定部分 g.DrawImage(originalImage, new Rectangle(0, 0, towidth, toheight), new Rectangle(x, y, ow, oh), GraphicsUnit.Pixel); return bitmap;*/ } public static Bitmap MakeThumbnail(Image originalImage, int width, int height, string mode) { int towidth = width; int toheight = height; int x = 0; int y = 0; int ow = originalImage.Width; int oh = originalImage.Height; switch (mode) { case "HW"://指定高宽缩放(可能变形) break; case "W"://指定宽,高按比例 toheight = originalImage.Height * width / originalImage.Width; break; case "H"://指定高,宽按比例 towidth = originalImage.Width * height / originalImage.Height; break; case "Cut"://指定高宽裁减(不变形) if ((double)originalImage.Width / (double)originalImage.Height > (double)towidth / (double)toheight) { oh = originalImage.Height; ow = originalImage.Height * towidth / toheight; y = 0; x = (originalImage.Width - ow) / 2; } else { ow = originalImage.Width; oh = originalImage.Width * height / towidth; x = 0; y = (originalImage.Height - oh) / 2; } break; default: break; } //新建一个bmp图片 Bitmap bitmap = new System.Drawing.Bitmap(towidth, toheight); //新建一个画板 Graphics g = System.Drawing.Graphics.FromImage(bitmap); //设置高质量插值法 g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High; //设置高质量,低速度呈现平滑程度 g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; //清空画布并以透明背景色填充 g.Clear(Color.Transparent); //在指定位置并且按指定大小绘制原图片的指定部分 g.DrawImage(originalImage, new Rectangle(0, 0, towidth, toheight), new Rectangle(x, y, ow, oh), GraphicsUnit.Pixel); return bitmap; } } }