using System;
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
using System.Drawing.Imaging;
using NumSharp.Backends;
using NumSharp.Backends.Unmanaged;
// ReSharper disable once CheckNamespace
namespace NumSharp
{
[SuppressMessage("ReSharper", "SuggestVarOrType_SimpleTypes")]
[SuppressMessage("ReSharper", "UnusedMember.Global")]
public static class np_
{
///
/// Creates from given .
///
/// The image to load data from.
///
/// If true, returns NDArray be 1-d of pixels: `R1G1B1R2G2B2 ... RnGnBn` where n is the amount of pixels in the image.
/// If false, returns a 4-d NDArray shaped: (1, bmpData.Height, bmpData.Width, bbp)
/// or (1, bmpData.Height, bmpData.Width, 4) if alpha is present and is false.
///
///
/// If true, performs
/// and then copies the data to a new then finally releasing the locked bits.
/// If false, It'll call
/// , wraps the with an NDArray and call only when the NDArray will be collected by the .
///
/// If the given has an alpha pixel (transparency pixel), discard that data or return a slice without the alpha (depends on ).
/// An NDArray that holds the pixel data of the given bitmap
[SuppressMessage("ReSharper", "SuggestVarOrType_Elsewhere")]
public static unsafe NDArray ToNDArray(this System.Drawing.Bitmap image, bool flat = false, bool copy = true, bool discardAlpha = false)
{
if (image == null) throw new ArgumentNullException(nameof(image));
BitmapData bmpData = image.LockBits(new Rectangle(0, 0, image.Width, image.Height), ImageLockMode.ReadOnly, image.PixelFormat);
if (copy)
try
{
unsafe
{
//Create a 1d vector without filling it's values to zero (similar to np.empty)
var nd = new NDArray(NPTypeCode.Byte, Shape.Vector(bmpData.Stride * image.Height), fillZeros: false);
// Get the respective addresses
byte* src = (byte*)bmpData.Scan0;
byte* dst = (byte*)nd.Unsafe.Address; //we can use unsafe because we just allocated that array and we know for sure it is contagious.
// Copy the RGB values into the array.
Buffer.MemoryCopy(src, dst, bmpData.Stride * image.Height, nd.size); //faster than Marshal.Copy
var ret = nd.reshape(1, image.Height, image.Width, bmpData.Stride / bmpData.Width);
if (discardAlpha && ret.shape[3] == 4)
ret = ret[Slice.All, Slice.All, Slice.All, new Slice(stop: 3)];
return flat && ret.ndim != 1 ? ret.flat : ret;
}
}
finally
{
try
{
image.UnlockBits(bmpData);
}
catch (ArgumentException)
{
//swallow
}
}
else
{
var ret = new NDArray(new ArraySlice(new UnmanagedMemoryBlock((byte*)bmpData.Scan0, bmpData.Stride * bmpData.Height, () =>
{
try
{
image.UnlockBits(bmpData);
}
catch (ArgumentException)
{
//swallow
}
})));
if (flat)
{
if (discardAlpha)
{
if (bmpData.Stride / bmpData.Width == 4) //1byte-per-color
{
return ReshapeFlatData(ret, bmpData) // reshape
[Slice.All, Slice.All, Slice.All, new Slice(stop: 3)] //slice
.flat; //flatten
}
if (bmpData.Stride / bmpData.Width == 8) //2bytes-per-color
{
return ReshapeFlatData(ret, bmpData) // reshape
[Slice.All, Slice.All, Slice.All, new Slice(stop: 6)] //slice
.flat; //flatten
}
throw new NotSupportedException($"Given bbp ({bmpData.Stride / bmpData.Width}) is not supported.");
}
return ret.flat;
}
else
{
ret = ReshapeFlatData(ret, bmpData); //reshape
if (discardAlpha)
{
if (ret.shape[3] == 4) //1byte-per-color
ret = ret[Slice.All, Slice.All, Slice.All, new Slice(stop: 3)]; //slice
else if (ret.shape[3] == 8) //2bytes-per-color
ret = ret[Slice.All, Slice.All, Slice.All, new Slice(stop: 6)]; //slice
else
throw new NotSupportedException($"Given bbp ({bmpData.Stride / bmpData.Width}) is not supported.");
}
return ret;
}
}
}
///
/// Reshapes the flat data to match the size of the bitmap.
///
/// flat 1-dimensional array containing the bitmap data
/// Source bitmap
/// An 4-dimensional NDArray that holds the bitmap data contained in `ret`
private static NDArray ReshapeFlatData(NDArray ret, BitmapData bmpData)
{
var colorBytes = bmpData.Stride / bmpData.Width;
var strideWidth = bmpData.Stride / colorBytes; // For odd widths, this width is not equal to bmpData.Width
ret = ret.reshape(1, bmpData.Height, strideWidth, bmpData.Stride / bmpData.Width);
if (strideWidth != bmpData.Width)
{
ret = ret[Slice.All, Slice.All, new Slice(stop: bmpData.Width), new Slice(stop: colorBytes)];
}
return ret;
}
///
/// Creates from given .
///
/// The image to load data from.
///
/// If true, returns NDArray be 1-d of pixels: `R1G1B1R2G2B2 ... RnGnBn` where n is the amount of pixels in the image.
/// If false, returns a 4-d NDArray shaped: (1, bmpData.Height, bmpData.Width, bbp)
///
///
/// If true, performs
/// and then copies the data to a new then finally releasing the locked bits.
/// If false, It'll call
/// , wraps the with an NDArray and call only when the NDArray will be collected by the .
///
/// If the given has an alpha pixel (transparency pixel), discard that data or return a slice without the alpha (depends on ).
/// An NDArray that holds the pixel data of the given bitmap
public static NDArray ToNDArray(this Image image, bool flat = false, bool copy = true, bool discardAlpha = false)
{
if (image == null)
throw new ArgumentNullException(nameof(image));
return ToNDArray(new System.Drawing.Bitmap(image), flat, copy, discardAlpha);
}
///
/// Wraps given as n without performing copy.
///
/// Targetted bitmap data with reading capabilities.
///
/// If true, returns NDArray be 1-d of pixels: R1G1B1R2G2B2 ... RnGnBn where n is the amount of pixels in the image.
/// If false, returns a 4-d NDArray shaped: (1, bmpData.Height, bmpData.Width, bbp)
///
/// If the given has an alpha pixel (transparency pixel), return a slice without the alpha.
/// An NDArray that wraps the given bitmap, doesn't copy
/// If the BitmapData is unlocked via - the NDArray will point to an invalid address which will cause heap corruption. Use with caution!
public static unsafe NDArray AsNDArray(this BitmapData bmpData, bool flat = false, bool discardAlpha = false)
{
if (bmpData == null)
throw new ArgumentNullException(nameof(bmpData));
var ret = new NDArray(new ArraySlice(new UnmanagedMemoryBlock((byte*)bmpData.Scan0, bmpData.Stride * bmpData.Height)));
if (flat)
{
if (ret.shape[3] == 4 && discardAlpha)
{
return ret.reshape(1, bmpData.Height, bmpData.Width, bmpData.Stride / bmpData.Width) //reshape
[Slice.All, Slice.All, Slice.All, new Slice(stop: 3)] //slice
.flat; //flatten
}
return ret;
}
else
{
ret = ret.reshape(1, bmpData.Height, bmpData.Width, bmpData.Stride / bmpData.Width); //reshape
if (ret.shape[3] == 4 && discardAlpha)
ret = ret[Slice.All, Slice.All, Slice.All, new Slice(stop: 3)]; //slice
return ret;
}
}
///
/// Converts to a .
///
/// The to copy pixels from, is ignored completely. If nd.Unsafe.Shape.IsContiguous == false then a copy is made.
/// The height of the
/// The width of the
/// The format of the expected bitmap, Must be matching to given NDArray otherwise unexpected results might occur.
/// A
/// When nd.size != width*height, which means the ndarray be turned into the given bitmap size.
public static unsafe Bitmap ToBitmap(this NDArray nd, int width, int height, PixelFormat format = PixelFormat.DontCare)
{
if (nd == null)
throw new ArgumentNullException(nameof(nd));
//if flat then initialize based on given format
if (nd.ndim == 1 && format != PixelFormat.DontCare)
nd = nd.reshape(1, height, width, format.ToBytesPerPixel()); //theres a check internally for size mismatc
if (nd.ndim != 4)
throw new ArgumentException("ndarray was expected to be of 4-dimensions, (1, bmpData.Height, bmpData.Width, bytesPerPixel)");
if (nd.shape[0] != 1)
throw new ArgumentException($"ndarray has more than one picture in it ({nd.shape[0]}) based on the first dimension.");
var bbp = nd.shape[3]; //bytes per pixel.
if (bbp != extractFormatNumber())
throw new ArgumentException($"Given PixelFormat: {format} does not match the number of bytes per pixel in the 4th dimension of given ndarray.");
if (bbp * width * height != nd.size)
throw new ArgumentException($"The expected size does not match the size of given ndarray. (expected: {bbp * width * height}, actual: {nd.size})");
var ret = new Bitmap(width, height, format);
var bitdata = ret.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, format);
if (bitdata.Stride != width * format.ToBytesPerPixel())
nd = np.concatenate((nd, np.zeros((1, height, 1, format.ToBytesPerPixel())).astype(NPTypeCode.Byte)), 2);
try
{
var dst = new ArraySlice(new UnmanagedMemoryBlock((byte*)bitdata.Scan0, bitdata.Stride * bitdata.Height));
if (nd.Shape.IsContiguous)
nd.CopyTo(dst);
else
MultiIterator.Assign(new UnmanagedStorage(dst, Shape.Vector(bitdata.Stride * bitdata.Height)), nd.Unsafe.Storage);
}
finally
{
try
{
ret.UnlockBits(bitdata);
}
catch (ArgumentException)
{
//swallow
}
}
return ret;
int extractFormatNumber()
{
if (format == PixelFormat.DontCare)
{
switch (bbp)
{
case 3:
format = PixelFormat.Format24bppRgb;
break;
case 4:
format = PixelFormat.Format32bppArgb;
break;
case 6:
format = PixelFormat.Format48bppRgb;
break;
case 8:
format = PixelFormat.Format64bppArgb;
break;
}
return bbp;
}
return format.ToBytesPerPixel();
}
}
///
/// Converts to a .
///
/// The to copy pixels from, is ignored completely. If nd.Unsafe.Shape.IsContiguous == false then a copy is made.
/// The format of the expected bitmap, Must be matching to given NDArray otherwise unexpected results might occur.
/// A
/// When nd.size != width*height, which means the ndarray be turned into the given bitmap size.
public static Bitmap ToBitmap(this NDArray nd, PixelFormat format = PixelFormat.DontCare)
{
if (nd == null)
throw new ArgumentNullException(nameof(nd));
if (nd.ndim != 4)
throw new ArgumentException("ndarray was expected to be of 4-dimensions, (1, bmpData.Height, bmpData.Width, bytesPerPixel)");
if (nd.shape[0] != 1)
throw new ArgumentException($"ndarray has more than one picture in it ({nd.shape[0]}) based on the first dimension.");
var height = nd.shape[1];
var width = nd.shape[2];
return ToBitmap(nd, width, height, format);
}
///
/// Returns how many bytes are per pixel based on given .
///
/// The format is not supported by us.
/// Invalid PixelFormat enum value.
public static int ToBytesPerPixel(this PixelFormat format)
{
int ret;
switch (format)
{
case PixelFormat.Format16bppArgb1555:
ret = 16;
break;
case PixelFormat.Format16bppGrayScale:
ret = 16;
break;
case PixelFormat.Format16bppRgb555:
ret = 16;
break;
case PixelFormat.Format16bppRgb565:
ret = 16;
break;
case PixelFormat.Format24bppRgb:
ret = 24;
break;
case PixelFormat.Format32bppArgb:
ret = 32;
break;
case PixelFormat.Format32bppPArgb:
ret = 32;
break;
case PixelFormat.Format32bppRgb:
ret = 32;
break;
case PixelFormat.Format48bppRgb:
ret = 48;
break;
case PixelFormat.Format64bppArgb:
ret = 64;
break;
case PixelFormat.Format64bppPArgb:
ret = 64;
break;
case PixelFormat.Format1bppIndexed:
case PixelFormat.Format8bppIndexed:
case PixelFormat.Format4bppIndexed:
case PixelFormat.Alpha:
case PixelFormat.Canonical:
case PixelFormat.Extended:
case PixelFormat.Gdi:
case PixelFormat.Indexed:
case PixelFormat.Max:
case PixelFormat.PAlpha:
throw new ArgumentException($"Given PixelFormat: {format} is not supported.");
case PixelFormat.DontCare:
throw new ArgumentException("Given PixelFormat can't be DontCare.");
default:
throw new ArgumentOutOfRangeException(nameof(format), format, null);
}
return ret / 8;
}
}
}