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