using System;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices;
using Emgu.CV;
using Emgu.CV.Cuda;
using Emgu.CV.CvEnum;
using Emgu.CV.Structure;
using Emgu.CV.Util;
using Emgu.Util;
namespace UVtools.GUI.Extensions
{
///
/// Provide extension method to convert IInputArray to and from Bitmap
///
public static class BitmapExtension
{
#region Color Palette
///
/// The ColorPalette of Grayscale for Bitmap Format8bppIndexed
///
public static readonly ColorPalette GrayscalePalette = GenerateGrayscalePalette();
private static ColorPalette GenerateGrayscalePalette()
{
using (Bitmap image = new Bitmap(1, 1, PixelFormat.Format8bppIndexed))
{
ColorPalette palette = image.Palette;
for (int i = 0; i < 256; i++)
{
palette.Entries[i] = Color.FromArgb(i, i, i);
}
return palette;
}
}
///
/// Convert the color palette to four lookup tables
///
/// The color palette to transform
/// Lookup table for the B channel
/// Lookup table for the G channel
/// Lookup table for the R channel
/// Lookup table for the A channel
public static void ColorPaletteToLookupTable(ColorPalette palette, out Matrix bTable,
out Matrix gTable, out Matrix rTable, out Matrix aTable)
{
bTable = new Matrix(256, 1);
gTable = new Matrix(256, 1);
rTable = new Matrix(256, 1);
aTable = new Matrix(256, 1);
byte[,] bData = bTable.Data;
byte[,] gData = gTable.Data;
byte[,] rData = rTable.Data;
byte[,] aData = aTable.Data;
Color[] colors = palette.Entries;
for (int i = 0; i < colors.Length; i++)
{
Color c = colors[i];
bData[i, 0] = c.B;
gData[i, 0] = c.G;
rData[i, 0] = c.R;
aData[i, 0] = c.A;
}
}
#endregion
///
/// Convert raw data to bitmap
///
/// The pointer to the raw data
/// The step
/// The size of the image
/// The source image color type
/// The number of channels
/// The source image depth type
/// Try to create Bitmap that shares the data with the image
/// The Bitmap
public static Bitmap RawDataToBitmap(IntPtr scan0, int step, Size size, Type srcColorType, int numberOfChannels,
Type srcDepthType, bool tryDataSharing = false)
{
if (tryDataSharing)
{
if (srcColorType == typeof(Gray) && srcDepthType == typeof(Byte))
{
//Grayscale of Bytes
Bitmap bmpGray = new Bitmap(
size.Width,
size.Height,
step,
PixelFormat.Format8bppIndexed,
scan0
) {Palette = GrayscalePalette};
return bmpGray;
}
// Mono in Linux doesn't support scan0 constructor with Format24bppRgb, use ToBitmap instead
// See https://bugzilla.novell.com/show_bug.cgi?id=363431
// TODO: check mono buzilla Bug 363431 to see when it will be fixed
if (
Platform.OperationSystem == Platform.OS.Windows &&
Platform.ClrType == Platform.Clr.DotNet &&
srcColorType == typeof(Bgr) && srcDepthType == typeof(Byte)
&& (step & 3) == 0)
{
//Bgr byte
return new Bitmap(
size.Width,
size.Height,
step,
PixelFormat.Format24bppRgb,
scan0);
}
if (srcColorType == typeof(Bgra) && srcDepthType == typeof(Byte))
{
//Bgra byte
return new Bitmap(
size.Width,
size.Height,
step,
PixelFormat.Format32bppArgb,
scan0);
}
//PixelFormat.Format16bppGrayScale is not supported in .NET
//else if (typeof(TColor) == typeof(Gray) && typeof(TDepth) == typeof(UInt16))
//{
// return new Bitmap(
// size.width,
// size.height,
// step,
// PixelFormat.Format16bppGrayScale;
// scan0);
//}
}
PixelFormat format; //= System.Drawing.Imaging.PixelFormat.Undefined;
if (srcColorType == typeof(Gray)) // if this is a gray scale image
{
format = PixelFormat.Format8bppIndexed;
}
else if (srcColorType == typeof(Bgra)) //if this is Bgra image
{
format = PixelFormat.Format32bppArgb;
}
else if (srcColorType == typeof(Bgr)) //if this is a Bgr Byte image
{
format = PixelFormat.Format24bppRgb;
}
else
{
using (Mat m = new Mat(size.Height, size.Width, CvInvoke.GetDepthType(srcDepthType), numberOfChannels,
scan0, step))
using (Mat m2 = new Mat())
{
CvInvoke.CvtColor(m, m2, srcColorType, typeof(Bgr));
return RawDataToBitmap(m2.DataPointer, m2.Step, m2.Size, typeof(Bgr), 3, srcDepthType);
}
}
Bitmap bmp = new Bitmap(size.Width, size.Height, format);
BitmapData data = bmp.LockBits(
new Rectangle(Point.Empty, size),
ImageLockMode.WriteOnly,
format);
using (Mat bmpMat = new Mat(size.Height, size.Width, DepthType.Cv8U, numberOfChannels, data.Scan0,
data.Stride))
using (Mat dataMat = new Mat(size.Height, size.Width, CvInvoke.GetDepthType(srcDepthType), numberOfChannels,
scan0, step))
{
if (srcDepthType == typeof(Byte))
dataMat.CopyTo(bmpMat);
else
{
double scale = 1.0, shift = 0.0;
RangeF range = dataMat.GetValueRange();
if (range.Max > 255.0 || range.Min < 0)
{
scale = range.Max.Equals(range.Min) ? 0.0 : 255.0 / (range.Max - range.Min);
shift = scale.Equals(0) ? range.Min : -range.Min * scale;
}
CvInvoke.ConvertScaleAbs(dataMat, bmpMat, scale, shift);
}
}
bmp.UnlockBits(data);
if (format == PixelFormat.Format8bppIndexed)
bmp.Palette = GrayscalePalette;
return bmp;
}
///
/// Convert the mat into Bitmap, the pixel values are copied over to the Bitmap
///
public static Bitmap ToBitmap(this Mat mat)
{
if (mat.Dims > 3)
return null;
int channels = mat.NumberOfChannels;
Size s = mat.Size;
Type colorType;
switch (channels)
{
case 1:
colorType = typeof(Gray);
if (s.Equals(Size.Empty))
return null;
if ((s.Width | 3) != 0) //handle the special case where width is not a multiple of 4
{
Bitmap bmp = new Bitmap(s.Width, s.Height, PixelFormat.Format8bppIndexed)
{
Palette = GrayscalePalette
};
BitmapData bitmapData = bmp.LockBits(new Rectangle(Point.Empty, s), ImageLockMode.WriteOnly,
PixelFormat.Format8bppIndexed);
using (Mat m = new Mat(s.Height, s.Width, DepthType.Cv8U, 1, bitmapData.Scan0,
bitmapData.Stride))
{
mat.CopyTo(m);
}
bmp.UnlockBits(bitmapData);
return bmp;
}
break;
case 3:
colorType = typeof(Bgr);
break;
case 4:
colorType = typeof(Bgra);
break;
default:
throw new Exception("Unknown color type");
}
return RawDataToBitmap(mat.DataPointer, mat.Step, s, colorType, mat.NumberOfChannels,
CvInvoke.GetDepthType(mat.Depth), true);
}
///
/// Convert the umat into Bitmap, the pixel values are copied over to the Bitmap
///
public static Bitmap ToBitmap(this UMat umat)
{
using (Mat tmp = umat.GetMat(AccessType.Read))
{
return tmp.ToBitmap();
}
}
///
/// Convert the gpuMat into Bitmap, the pixel values are copied over to the Bitmap
///
public static Bitmap ToBitmap(this GpuMat gpuMat)
{
using (Mat tmp = new Mat())
{
gpuMat.Download(tmp);
return tmp.ToBitmap();
}
}
///
/// Create an Image < TColor, TDepth > from Bitmap
///
public static Image ToImage(this Bitmap bitmap) where
TColor : struct, IColor
where TDepth : new()
{
Size size = bitmap.Size;
Image image = new Image(size);
switch (bitmap.PixelFormat)
{
case PixelFormat.Format32bppRgb:
if (typeof(TColor) == typeof(Bgr) && typeof(TDepth) == typeof(Byte))
{
BitmapData data = bitmap.LockBits(
new Rectangle(Point.Empty, size),
ImageLockMode.ReadOnly,
bitmap.PixelFormat);
using (Image mat =
new Image(size.Width, size.Height, data.Stride, data.Scan0))
{
CvInvoke.MixChannels(mat, image, new[] {0, 0, 1, 1, 2, 2});
}
bitmap.UnlockBits(data);
}
else
{
using (Image tmp = bitmap.ToImage())
image.ConvertFrom(tmp);
}
break;
case PixelFormat.Format32bppArgb:
if (typeof(TColor) == typeof(Bgra) && typeof(TDepth) == typeof(Byte))
image.CopyFromBitmap(bitmap);
else
{
BitmapData data = bitmap.LockBits(
new Rectangle(Point.Empty, size),
ImageLockMode.ReadOnly,
bitmap.PixelFormat);
using (Image tmp =
new Image(size.Width, size.Height, data.Stride, data.Scan0))
image.ConvertFrom(tmp);
bitmap.UnlockBits(data);
}
break;
case PixelFormat.Format8bppIndexed:
if (typeof(TColor) == typeof(Bgra) && typeof(TDepth) == typeof(Byte))
{
Matrix bTable, gTable, rTable, aTable;
ColorPaletteToLookupTable(bitmap.Palette, out bTable, out gTable, out rTable, out aTable);
BitmapData data = bitmap.LockBits(
new Rectangle(Point.Empty, size),
ImageLockMode.ReadOnly,
bitmap.PixelFormat);
using (Image indexValue =
new Image(size.Width, size.Height, data.Stride, data.Scan0))
{
using (Mat b = new Mat())
using (Mat g = new Mat())
using (Mat r = new Mat())
using (Mat a = new Mat())
{
CvInvoke.LUT(indexValue, bTable, b);
CvInvoke.LUT(indexValue, gTable, g);
CvInvoke.LUT(indexValue, rTable, r);
CvInvoke.LUT(indexValue, aTable, a);
using (VectorOfMat mv = new VectorOfMat(b, g, r, a))
{
CvInvoke.Merge(mv, image);
}
}
}
bitmap.UnlockBits(data);
bTable.Dispose();
gTable.Dispose();
rTable.Dispose();
aTable.Dispose();
}
else
{
using (Image tmp = bitmap.ToImage())
image.ConvertFrom(tmp);
}
break;
case PixelFormat.Format24bppRgb:
if (typeof(TColor) == typeof(Bgr) && typeof(TDepth) == typeof(Byte))
image.CopyFromBitmap(bitmap);
else
{
BitmapData data = bitmap.LockBits(
new Rectangle(Point.Empty, size),
ImageLockMode.ReadOnly,
bitmap.PixelFormat);
using (Image tmp =
new Image(size.Width, size.Height, data.Stride, data.Scan0))
image.ConvertFrom(tmp);
bitmap.UnlockBits(data);
}
break;
case PixelFormat.Format1bppIndexed:
if (typeof(TColor) == typeof(Gray) && typeof(TDepth) == typeof(Byte))
{
int rows = size.Height;
int cols = size.Width;
BitmapData data = bitmap.LockBits(
new Rectangle(Point.Empty, size),
ImageLockMode.ReadOnly,
bitmap.PixelFormat);
int fullByteCount = cols >> 3;
int partialBitCount = cols & 7;
int mask = 1 << 7;
Int64 srcAddress = data.Scan0.ToInt64();
Byte[,,] imagedata = image.Data as Byte[,,];
Byte[] row = new byte[fullByteCount + (partialBitCount == 0 ? 0 : 1)];
int v = 0;
for (int i = 0; i < rows; i++, srcAddress += data.Stride)
{
Marshal.Copy((IntPtr) srcAddress, row, 0, row.Length);
for (int j = 0; j < cols; j++, v <<= 1)
{
if ((j & 7) == 0)
{
//fetch the next byte
v = row[j >> 3];
}
imagedata[i, j, 0] = (v & mask) == 0 ? (Byte) 0 : (Byte) 255;
}
}
}
else
{
using (Image tmp = bitmap.ToImage())
image.ConvertFrom(tmp);
}
break;
default:
#region Handle other image type
// Bitmap bgraImage = new Bitmap(value.Width, value.Height, PixelFormat.Format32bppArgb);
// using (Graphics g = Graphics.FromImage(bgraImage))
// {
// g.DrawImageUnscaled(value, 0, 0, value.Width, value.Height);
// }
// Bitmap = bgraImage;
using (Image tmp1 = new Image(size))
{
Byte[,,] data = tmp1.Data;
for (int i = 0; i < size.Width; i++)
for (int j = 0; j < size.Height; j++)
{
Color color = bitmap.GetPixel(i, j);
data[j, i, 0] = color.B;
data[j, i, 1] = color.G;
data[j, i, 2] = color.R;
data[j, i, 3] = color.A;
}
image.ConvertFrom(tmp1);
}
#endregion
break;
}
return image;
}
///
/// Utility function for converting Bitmap to Image
///
/// the bitmap to copy data from
/// The image to copy data to
private static void CopyFromBitmap(this Image image, Bitmap bmp) where
TColor : struct, IColor
where TDepth : new()
{
BitmapData data = bmp.LockBits(
new Rectangle(Point.Empty, bmp.Size),
ImageLockMode.ReadOnly,
bmp.PixelFormat);
using (Matrix mat =
new Matrix(bmp.Height, bmp.Width, image.NumberOfChannels, data.Scan0, data.Stride))
CvInvoke.cvCopy(mat.Ptr, image.Ptr, IntPtr.Zero);
bmp.UnlockBits(data);
}
///
/// Provide a more efficient way to convert Image<Gray, Byte>, Image<Bgr, Byte> and Image<Bgra, Byte> into Bitmap
/// such that the image data is shared with Bitmap.
/// If you change the pixel value on the Bitmap, you change the pixel values on the Image object as well!
/// For other types of image this property has the same effect as ToBitmap()
/// Take extra caution not to use the Bitmap after the Image object is disposed
///
/// The color of the image
/// The depth of the image
/// The image to create Bitmap from
/// A bitmap representation of the image. In the cases of Image<Gray, Byte>, Image<Bgr, Byte> and Image<Bgra, Byte>, the image data is shared between the Bitmap and the Image object.
public static Bitmap AsBitmap(this Image image) where
TColor : struct, IColor
where TDepth : new()
{
IntPtr scan0;
int step;
Size size;
CvInvoke.cvGetRawData(image.Ptr, out scan0, out step, out size);
return RawDataToBitmap(scan0, step, size, typeof(TColor), image.NumberOfChannels, typeof(TDepth), true);
}
///
/// Convert this image into Bitmap, the pixel values are copied over to the Bitmap
///
/// For better performance on Image<Gray, Byte> and Image<Bgr, Byte>, consider using the Bitmap property
/// This image in Bitmap format, the pixel data are copied over to the Bitmap
public static Bitmap ToBitmap(this Image image) where
TColor : struct, IColor
where TDepth : new()
{
Type typeOfColor = typeof(TColor);
Type typeofDepth = typeof(TDepth);
PixelFormat format = PixelFormat.Undefined;
if (typeOfColor == typeof(Gray)) // if this is a gray scale image
{
format = PixelFormat.Format8bppIndexed;
}
else if (typeOfColor == typeof(Bgra)) //if this is Bgra image
{
format = PixelFormat.Format32bppArgb;
}
else if (typeOfColor == typeof(Bgr)) //if this is a Bgr Byte image
{
format = PixelFormat.Format24bppRgb;
}
else
{
using (Image temp = image.Convert())
return ToBitmap(temp);
}
if (typeof(TDepth) == typeof(Byte))
{
Size size = image.Size;
Bitmap bmp = new Bitmap(size.Width, size.Height, format);
BitmapData data = bmp.LockBits(
new Rectangle(Point.Empty, size),
ImageLockMode.WriteOnly,
format);
//using (Matrix m = new Matrix(size.Height, size.Width, data.Scan0, data.Stride))
using (Mat mat = new Mat(size.Height, size.Width, DepthType.Cv8U, image.NumberOfChannels,
data.Scan0, data.Stride))
{
image.Mat.CopyTo(mat);
}
bmp.UnlockBits(data);
if (format == PixelFormat.Format8bppIndexed)
bmp.Palette = GrayscalePalette;
return bmp;
}
using (Image temp = image.Convert())
return temp.ToBitmap();
}
/// Create a Bitmap image of certain size
/// The image to be converted to Bitmap
/// The width of the bitmap
/// The height of the bitmap
/// This image in Bitmap format of the specific size
public static Bitmap ToBitmap(this Image image, int width, int height) where
TColor : struct, IColor
where TDepth : new()
{
using (Image scaledImage = image.Resize(width, height, Inter.Linear))
return scaledImage.ToBitmap();
}
///
/// Convert the CudaImage to its equivalent Bitmap representation
///
public static Bitmap ToBitmap(this CudaImage cudaImage) where
TColor : struct, IColor
where TDepth : new()
{
if (typeof(TColor) == typeof(Bgr) && typeof(TDepth) == typeof(Byte))
{
Size s = cudaImage.Size;
Bitmap result = new Bitmap(s.Width, s.Height, PixelFormat.Format24bppRgb);
BitmapData data = result.LockBits(new Rectangle(Point.Empty, result.Size),
ImageLockMode.WriteOnly, result.PixelFormat);
using (Image tmp = new Image(s.Width, s.Height, data.Stride, data.Scan0)
)
{
cudaImage.Download(tmp);
}
result.UnlockBits(data);
return result;
}
using (Image tmp = cudaImage.ToImage())
{
return tmp.ToBitmap();
}
}
}
///
/// Class that can be used to read file into Mat
///
public class BitmapFileReaderMat : IFileReaderMat
{
///
/// Read the file into a Mat
///
/// The name of the image file
/// The Mat to read into
/// Image load type.
/// True if the file can be read into the Mat
public bool ReadFile(String fileName, Mat mat, ImreadModes loadType)
{
try
{
using (Bitmap bmp = new Bitmap(fileName))
using (Image image = bmp.ToImage())
image.Mat.CopyTo(mat);
return true;
}
catch (Exception e)
{
Debug.WriteLine(e);
//throw;
return false;
}
}
}
///
/// Class that can be used to write the Mat to a file
///
public class BitmapFileWriterMat : IFileWriterMat
{
///
/// Write the Mat into the file
///
/// The Mat to write
/// The name of the file to be written into
/// True if the file has been written into Mat
public bool WriteFile(Mat mat, String fileName)
{
try
{
//Try to save the image using .NET's Bitmap class
String extension = Path.GetExtension(fileName);
if (!String.IsNullOrEmpty(extension))
using (Bitmap bmp = mat.ToBitmap())
{
switch (extension.ToLower())
{
case ".jpg":
case ".jpeg":
bmp.Save(fileName, ImageFormat.Jpeg);
break;
case ".bmp":
bmp.Save(fileName, ImageFormat.Bmp);
break;
case ".png":
bmp.Save(fileName, ImageFormat.Png);
break;
case ".tiff":
case ".tif":
bmp.Save(fileName, ImageFormat.Tiff);
break;
case ".gif":
bmp.Save(fileName, ImageFormat.Gif);
break;
default:
throw new NotImplementedException(String.Format("Saving to {0} format is not supported", extension));
}
}
return true;
}
catch (Exception e)
{
Debug.WriteLine(e);
//throw;
return false;
}
}
}
}