using Sick.GenIStream;
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
public sealed class DisplayBitmapOptions
{
public ushort? Min { get; set; } = null;
public ushort? Max { get; set; } = null;
public bool ExcludeZero { get; set; } = true;
public double Gamma { get; set; } = 1.0;
public bool Invert { get; set; } = false;
}
public static class RulerDisplayBitmap
{
/// <summary>
/// Generates an 8bpp grayscale Bitmap from IFrame for PictureBox display.
/// 16bpp is normalized to 8bpp using min/max, 8bpp is copied directly.
/// * This method is responsible for calling frame.Release().
/// </summary>
public static Bitmap CreateDisplayBitmap8bpp_Safe(IFrame frame, DisplayBitmapOptions opt = null)
{
if (frame == null) throw new ArgumentNullException(nameof(frame));
if (opt == null) opt = new DisplayBitmapOptions();
var range = frame.GetRange();
int w = (int)range.GetWidth();
int h = (int)range.GetDeliveredHeight();
int bpp = (int)range.GetBitsPerPixel(); // Expected to be 8 or 16
int stride = (int)range.GetStride();
IntPtr src = range.GetData();
Bitmap bmp = null;
try
{
if (bpp == 8)
{
bmp = CreateFrom8bpp(src, w, h, stride);
}
else if (bpp == 16)
{
bmp = CreateFrom16bpp(src, w, h, stride, opt);
}
else
{
throw new NotSupportedException("Unsupported BitsPerPixel: " + bpp + ". Only 8 or 16 are supported.");
}
}
finally
{
// ★ Must be released once here (not called internally)
frame.Release();
}
return bmp;
}
// ===== 8bpp: Copy as is =====
private static Bitmap CreateFrom8bpp(IntPtr src, int w, int h, int strideSrc)
{
var bmp = new Bitmap(w, h, System.Drawing.Imaging.PixelFormat.Format8bppIndexed);
// Grayscale palette
var pal = bmp.Palette;
for (int i = 0; i < 256; i++) pal.Entries[i] = System.Drawing.Color.FromArgb(i, i, i);
bmp.Palette = pal;
var rect = new Rectangle(0, 0, w, h);
var data = bmp.LockBits(rect, ImageLockMode.WriteOnly, bmp.PixelFormat);
try
{
int strideDst = data.Stride;
var line = new byte[w];
for (int y = 0; y < h; y++)
{
Marshal.Copy(src + y * strideSrc, line, 0, w);
Marshal.Copy(line, 0, data.Scan0 + y * strideDst, w);
}
}
finally
{
bmp.UnlockBits(data);
}
return bmp;
}
// ===== 16bpp: Min/Max Normalization → 8bpp =====
private static Bitmap CreateFrom16bpp(IntPtr src, int w, int h, int strideSrc, DisplayBitmapOptions opt)
{
ushort min, max;
if (opt.Min.HasValue && opt.Max.HasValue)
{
min = opt.Min.Value;
max = opt.Max.Value;
}
else
{
ComputeMinMax16(src, w, h, strideSrc, opt.ExcludeZero, out min, out max);
}
if (max <= min) max = (ushort)(min + 1);
var bmp = new Bitmap(w, h, System.Drawing.Imaging.PixelFormat.Format8bppIndexed);
var pal = bmp.Palette;
for (int i = 0; i < 256; i++) pal.Entries[i] = System.Drawing.Color.FromArgb(i, i, i);
bmp.Palette = pal;
var rect = new Rectangle(0, 0, w, h);
var data = bmp.LockBits(rect, ImageLockMode.WriteOnly, bmp.PixelFormat);
try
{
int strideDst = data.Stride;
// Gamma LUT (0..255 → 0..255)
double gamma = opt.Gamma < 0.01 ? 0.01 : opt.Gamma;
byte[] gammaLut = BuildGammaLut(gamma, opt.Invert);
var srcLine = new byte[w * 2];
var dstLine = new byte[w];
double scale = 255.0 / (max - min);
for (int y = 0; y < h; y++)
{
Marshal.Copy(src + y * strideSrc, srcLine, 0, srcLine.Length);
for (int x = 0; x < w; x++)
{
// little-endian 2 bytes → ushort
ushort v = (ushort)(srcLine[(x << 1)] | (srcLine[(x << 1) + 1] << 8));
int g = (int)((v - min) * scale + 0.5);
if (g < 0) g = 0; else if (g > 255) g = 255;
dstLine[x] = gammaLut[g];
}
Marshal.Copy(dstLine, 0, data.Scan0 + y * strideDst, w);
}
}
finally
{
bmp.UnlockBits(data);
}
return bmp;
}
private static void ComputeMinMax16(IntPtr src, int w, int h, int stride, bool excludeZero, out ushort min, out ushort max)
{
min = ushort.MaxValue;
max = 0;
var line = new byte[w * 2];
bool sawAny = false;
for (int y = 0; y < h; y++)
{
Marshal.Copy(src + y * stride, line, 0, line.Length);
for (int x = 0; x < w; x++)
{
ushort v = (ushort)(line[(x << 1)] | (line[(x << 1) + 1] << 8));
if (excludeZero && v == 0) continue;
if (v < min) min = v;
if (v > max) max = v;
sawAny = true;
}
}
if (!sawAny)
{
min = 0; max = 1;
}
}
private static byte[] BuildGammaLut(double gamma, bool invert)
{
var lut = new byte[256];
double inv = 1.0 / gamma;
for (int i = 0; i < 256; i++)
{
double n = i / 255.0;
double g = Math.Pow(n, inv);
int v = (int)(g * 255.0 + 0.5);
if (v < 0) v = 0; else if (v > 255) v = 255;
if (invert) v = 255 - v;
lut[i] = (byte)v;
}
return lut;
}
}