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