using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Imaging; using System.Runtime.InteropServices; namespace TeamAAS.Camera.Images { [Serializable] /// /// 通用图像类 — 不依赖任何视觉库,仅保存原始像素数据。 /// 可在各视觉库插件(VisionPro/Halcon/OpenCV等)中做类型转换。 /// public class GenImage { public int Width { get; } public int Height { get; } public int Stride { get; } public PixelType Format { get; } public byte[] Data { get; } /// /// 元数据字典 — 存储设备参数、标定信息、采集时间等。 /// 例如: "ExposureTime", "LaserPower", "ScanRate", "CalibrationDate" 等。 /// public Dictionary Metadata { get; } public int BitsPerPixel { get { switch (Format) { case PixelType.Grey8: return 8; case PixelType.Grey16: return 16; case PixelType.RGB24: case PixelType.BGR24: return 24; case PixelType.BGRA32: return 32; case PixelType.Depth16: return 16; case PixelType.Depth16Conf8: case PixelType.Depth16Intensity8: return 24; default: return 8; } } } public int BytesPerPixel => BitsPerPixel / 8; public int ActualStride => Stride > 0 ? Stride : Width * BytesPerPixel; public GenImage(int width, int height, PixelType format, byte[] data, int stride = 0) : this(width, height, format, data, stride, null) { } public GenImage(int width, int height, PixelType format, byte[] data, int stride, Dictionary metadata) { if (width <= 0) throw new ArgumentException("width must > 0", nameof(width)); if (height <= 0) throw new ArgumentException("height must > 0", nameof(height)); Width = width; Height = height; Format = format; Stride = stride; Data = data ?? throw new ArgumentNullException(nameof(data)); Metadata = metadata ?? new Dictionary(); } public GenImage Clone() { var copy = new byte[Data.Length]; Buffer.BlockCopy(Data, 0, copy, 0, Data.Length); return new GenImage(Width, Height, Format, copy, Stride, new Dictionary(Metadata)); } #region → Bitmap (标准像素格式) /// /// 转换为 System.Drawing.Bitmap(调用方负责 Dispose)。 /// 深度图格式将自动使用伪彩映射。 /// public Bitmap ToBitmap() { if (Format == PixelType.Depth16 || Format == PixelType.Depth16Conf8 || Format == PixelType.Depth16Intensity8) return ToBitmapPseudoColor(); int srcStride = ActualStride; int dstBpp = BitsPerPixel == 16 ? 8 : BitsPerPixel; PixelFormat pf; switch (Format) { case PixelType.Grey8: case PixelType.Grey16: pf = PixelFormat.Format8bppIndexed; break; case PixelType.RGB24: case PixelType.BGR24: pf = PixelFormat.Format24bppRgb; break; case PixelType.BGRA32: pf = PixelFormat.Format32bppArgb; break; default: pf = PixelFormat.Format8bppIndexed; break; } int dstStride = ((Width * dstBpp + 31) / 32) * 4; var bmp = new Bitmap(Width, Height, pf); if (pf == PixelFormat.Format8bppIndexed) { var pal = bmp.Palette; for (int i = 0; i < 256; i++) pal.Entries[i] = Color.FromArgb(i, i, i); bmp.Palette = pal; } var rect = new Rectangle(0, 0, Width, Height); var bmpData = bmp.LockBits(rect, ImageLockMode.WriteOnly, pf); try { IntPtr dstScan = bmpData.Scan0; int copyBytes = Math.Min(srcStride, dstStride); if (Format == PixelType.Grey16) { for (int y = 0; y < Height; y++) { int srcOff = y * srcStride; int dstOff = y * dstStride; for (int x = 0; x < Width; x++) { Marshal.WriteByte(dstScan, dstOff + x, Data[srcOff + x * 2 + 1]); } } } else if (Format == PixelType.RGB24) { for (int y = 0; y < Height; y++) { int srcOff = y * srcStride; int dstOff = y * dstStride; for (int x = 0; x < Width; x++) { int si = srcOff + x * 3; int di = dstOff + x * 3; Marshal.WriteByte(dstScan, di, Data[si + 2]); Marshal.WriteByte(dstScan, di + 1, Data[si + 1]); Marshal.WriteByte(dstScan, di + 2, Data[si]); } } } else { for (int y = 0; y < Height; y++) { Marshal.Copy(Data, y * srcStride, dstScan + y * dstStride, copyBytes); } } } finally { bmp.UnlockBits(bmpData); } return bmp; } #endregion #region → Bitmap (深度图伪彩) /// /// 深度图伪彩显示 — 将 Depth16/Depth16Conf8/Depth16Intensity8 映射为彩色 Bitmap。 /// 使用 Jet colormap (蓝→青→绿→黄→红) 进行深度可视化。 /// public Bitmap ToBitmapPseudoColor(ushort minDepth = 0, ushort maxDepth = 0) { if (Format != PixelType.Depth16 && Format != PixelType.Depth16Conf8 && Format != PixelType.Depth16Intensity8) return ToBitmap(); int srcStride = ActualStride; int bpp = BytesPerPixel; if (maxDepth <= minDepth) maxDepth = FindMaxDepth(); int dstStride = ((Width * 24 + 31) / 32) * 4; var bmp = new Bitmap(Width, Height, PixelFormat.Format24bppRgb); var rect = new Rectangle(0, 0, Width, Height); var bmpData = bmp.LockBits(rect, ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb); try { IntPtr dstScan = bmpData.Scan0; float range = (float)(maxDepth - minDepth); if (range < 1) range = 1; for (int y = 0; y < Height; y++) { int srcOff = y * srcStride; int dstOff = y * dstStride; for (int x = 0; x < Width; x++) { ushort depth = (ushort)(Data[srcOff + x * bpp] | (Data[srcOff + x * bpp + 1] << 8)); float t = (depth - minDepth) / range; if (t < 0) t = 0; if (t > 1) t = 1; Color jet = JetColormap(t); Marshal.WriteByte(dstScan, dstOff + x * 3, jet.B); Marshal.WriteByte(dstScan, dstOff + x * 3 + 1, jet.G); Marshal.WriteByte(dstScan, dstOff + x * 3 + 2, jet.R); } } } finally { bmp.UnlockBits(bmpData); } return bmp; } private ushort FindMaxDepth() { int bpp = BytesPerPixel; ushort max = 0; for (int y = 0; y < Height; y++) { int rowOff = y * ActualStride; for (int x = 0; x < Width; x++) { ushort d = (ushort)(Data[rowOff + x * bpp] | (Data[rowOff + x * bpp + 1] << 8)); if (d > max) max = d; } } return max; } private static Color JetColormap(float t) { float r, g, b; if (t < 0.125f) { r = 0; g = 0; b = 0.5f + 4f * t; } else if (t < 0.375f) { r = 0; g = 4f * (t - 0.125f); b = 1f; } else if (t < 0.625f) { r = 4f * (t - 0.375f); g = 1f; b = 1f - 4f * (t - 0.375f); } else if (t < 0.875f) { r = 1f; g = 1f - 4f * (t - 0.625f); b = 0; } else { r = 1f - 4f * (t - 0.875f); g = 0; b = 0; } return Color.FromArgb( (int)(Math.Max(0, Math.Min(1, r)) * 255), (int)(Math.Max(0, Math.Min(1, g)) * 255), (int)(Math.Max(0, Math.Min(1, b)) * 255)); } #endregion #region ← Bitmap /// /// 从 System.Drawing.Bitmap 创建 GenImage。 /// public static GenImage FromBitmap(Bitmap bmp) { if (bmp == null) throw new ArgumentNullException(nameof(bmp)); PixelType format; switch (bmp.PixelFormat) { case PixelFormat.Format8bppIndexed: format = PixelType.Grey8; break; case PixelFormat.Format24bppRgb: format = PixelType.BGR24; break; case PixelFormat.Format32bppArgb: case PixelFormat.Format32bppRgb: format = PixelType.BGRA32; break; default: using (var c = new Bitmap(bmp.Width, bmp.Height, PixelFormat.Format24bppRgb)) using (var g = Graphics.FromImage(c)) { g.DrawImage(bmp, 0, 0, bmp.Width, bmp.Height); return FromBitmap(c); } } int bpp = Image.GetPixelFormatSize(bmp.PixelFormat) / 8; int stride = bmp.Width * bpp; var data = new byte[stride * bmp.Height]; var rect = new Rectangle(0, 0, bmp.Width, bmp.Height); var bd = bmp.LockBits(rect, ImageLockMode.ReadOnly, bmp.PixelFormat); try { int srcStride = bd.Stride; if (srcStride == stride) Marshal.Copy(bd.Scan0, data, 0, data.Length); else for (int y = 0; y < bmp.Height; y++) Marshal.Copy(bd.Scan0 + y * srcStride, data, y * stride, stride); } finally { bmp.UnlockBits(bd); } return new GenImage(bmp.Width, bmp.Height, format, data, stride); } #endregion #region 工厂方法 public static GenImage CreateGrey8(int width, int height) => new GenImage(width, height, PixelType.Grey8, new byte[width * height]); public static GenImage CreateDepth16(int width, int height) => new GenImage(width, height, PixelType.Depth16, new byte[width * height * 2]); public static GenImage FromIntPtr(int width, int height, PixelType format, IntPtr buffer, int bufferStride = 0) { int bpp; switch (format) { case PixelType.Grey8: bpp = 1; break; case PixelType.Grey16: case PixelType.Depth16: bpp = 2; break; case PixelType.RGB24: case PixelType.BGR24: case PixelType.Depth16Conf8: case PixelType.Depth16Intensity8: bpp = 3; break; case PixelType.BGRA32: bpp = 4; break; default: bpp = 1; break; } int stride = bufferStride > 0 ? bufferStride : width * bpp; var data = new byte[stride * height]; Marshal.Copy(buffer, data, 0, data.Length); return new GenImage(width, height, format, data, stride); } #endregion #region 元数据便捷访问 public void SetMetadata(string key, object value) => Metadata[key] = value; public T GetMetadata(string key, T defaultValue = default) { if (Metadata.TryGetValue(key, out var val) && val is T typed) return typed; return defaultValue; } #endregion #region 图像处理 /// /// 对图像应用 Gamma 校正(直接修改像素数据)。 /// 仅支持 Grey8、Grey16、RGB24、BGR24、BGRA32 格式。 /// gamma=1.0 时不做任何操作;gamma>1.0 提亮暗部;gamma<1.0 压暗暗部。 /// public void ApplyGamma(float gamma) { if (Math.Abs(gamma - 1.0f) < 0.001f) return; switch (Format) { case PixelType.Grey8: ApplyGammaGrey8(gamma); break; case PixelType.Grey16: ApplyGammaGrey16(gamma); break; case PixelType.RGB24: case PixelType.BGR24: case PixelType.BGRA32: ApplyGammaColor(gamma); break; } } private void ApplyGammaGrey8(float gamma) { int stride = ActualStride; for (int y = 0; y < Height; y++) { int rowOff = y * stride; for (int x = 0; x < Width; x++) { float norm = Data[rowOff + x] / 255f; float corrected = (float)Math.Pow(norm, gamma); Data[rowOff + x] = (byte)Math.Max(0, Math.Min(255, corrected * 255f)); } } } private void ApplyGammaGrey16(float gamma) { int stride = ActualStride; for (int y = 0; y < Height; y++) { int rowOff = y * stride; for (int x = 0; x < Width; x++) { int pxOff = rowOff + x * 2; ushort val = (ushort)(Data[pxOff] | (Data[pxOff + 1] << 8)); float norm = val / 65535f; float corrected = (float)Math.Pow(norm, gamma); ushort outVal = (ushort)Math.Max(0, Math.Min(65535, corrected * 65535f)); Data[pxOff] = (byte)(outVal & 0xFF); Data[pxOff + 1] = (byte)(outVal >> 8); } } } private void ApplyGammaColor(float gamma) { int stride = ActualStride; int channels = BytesPerPixel; int colorChannels = Format == PixelType.BGRA32 ? 3 : channels; for (int y = 0; y < Height; y++) { int rowOff = y * stride; for (int x = 0; x < Width; x++) { int pxOff = rowOff + x * channels; for (int c = 0; c < colorChannels; c++) { float norm = Data[pxOff + c] / 255f; float corrected = (float)Math.Pow(norm, gamma); Data[pxOff + c] = (byte)Math.Max(0, Math.Min(255, corrected * 255f)); } } } } #endregion } /// /// 通用图像像素格式 /// public enum PixelType { /// 8-bit 灰度 Grey8, /// 16-bit 灰度 Grey16, /// 24-bit RGB 交错 RGB24, /// 24-bit BGR 交错 BGR24, /// 32-bit BGRA 交错 BGRA32, /// 16-bit 深度图 (单通道) Depth16, /// 16-bit 深度 + 8-bit 置信度 (3 字节/像素) Depth16Conf8, /// 16-bit 深度 + 8-bit 强度 (3 字节/像素) Depth16Intensity8 } }