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