.NET 位图,支持完整的 16 位






4.80/5 (4投票s)
读取和写入每个通道具有完整 16 位(甚至更多)的位图
引言
如果您曾经尝试使用 System.Drawing.Bitmap
类读取 16 位图像,您可能大失所望。出于某种难以理解的原因,它仅以 13 位存储 16 位数据(从 65535 个值到 8192 个,差异很大),而且即使这样也无法正常工作。
但是,好消息是:这里有一个解决方法,我将在这里介绍!
背景
要使这段代码正常工作,您需要引用四个 .NET DLL
-
系统
-
PresentationCore
-
WindowsBase
-
System.Xaml
代码
我创建了一个相当简单的类来存储和处理数据,并提供基本方法。您可以根据需要添加您可能需要的方法。最后,我将整个类放在一起,以便您可以更轻松地复制/粘贴它。
首先,这个类的变量
public int Width; //Width of image in pixel
public int Height; //Height of image in pixel
public int ChannelCount; //Number of channels
public int BytesPerChannel; //Number of bytes per channel
public bool HasAlpha; //Image has alpha channel or not
public int BitDepth; //Bits per channel
public bool IsBGR; //Byte order BGR or RGB
public bool IsPinned; //ImageData pinned or not
public IntPtr Scan0; //Position of first byte (Set when ImageData is pinned)
private PixelFormat InputPixelFormat; //Pixel format
private GCHandle pinHandle; //Handler to pin/unpin ImageData
private byte[] ImageData; //The image data itself
现在是构造函数,基本上,我们可以通过各种方式创建一个 BitampSource
类
/// <summary>
/// Loads an image from a path. (Jpg, Png, Tiff, Bmp, Gif and Wdp are supported)
/// </summary>
/// <param name="path">Path to the image</param>
public BitmapEx(string path)
{
using (Stream str = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
{
BitmapDecoder dec = BitmapDecoder.Create(str,
BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
if (dec.Frames.Count > 0) SetFromBitmapSource(dec.Frames[0]);
else throw new FileLoadException("Couldn't load file " + path);
}
}
/// <summary>
/// Loads an image from an encoded stream. (Jpg, Png, Tiff, Bmp, Gif and Wdp are supported)
/// </summary>
public BitmapEx(Stream encodedStream)
{
encodedStream.Position = 0;
BitmapDecoder dec = BitmapDecoder.Create(encodedStream,
BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
if (dec.Frames.Count > 0) SetFromBitmapSource(dec.Frames[0]);
else throw new FileLoadException("Couldn't load file");
}
/// <summary>
/// Loads an image from an encoded byte array. (Jpg, Png, Tiff, Bmp, Gif and Wdp are supported)
/// </summary>
public BitmapEx(byte[] encodedData)
{
using (MemoryStream str = new MemoryStream(encodedData))
{
BitmapDecoder dec = BitmapDecoder.Create(str,
BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
if (dec.Frames.Count > 0) SetFromBitmapSource(dec.Frames[0]);
else throw new FileLoadException("Couldn't load file");
}
}
现在我们有了 BitampSource
,我们可以从中读取信息并存储图像数据
private void SetFromBitmapSource(BitmapSource bmpSrc)
{
this.Width = bmpSrc.PixelWidth;
this.Height = bmpSrc.PixelHeight;
InputPixelFormat = bmpSrc.Format;
if (bmpSrc.Format == PixelFormats.Bgr24)
{
this.ChannelCount = 3;
this.BytesPerChannel = 1;
this.HasAlpha = false;
this.BitDepth = 8;
this.IsBGR = true;
}
else if (bmpSrc.Format == PixelFormats.Bgra32)
{
this.ChannelCount = 4;
this.BytesPerChannel = 1;
this.HasAlpha = true;
this.BitDepth = 8;
this.IsBGR = true;
}
else if (bmpSrc.Format == PixelFormats.Rgb24)
{
this.ChannelCount = 3;
this.BytesPerChannel = 1;
this.HasAlpha = false;
this.BitDepth = 8;
this.IsBGR = false;
}
else if (bmpSrc.Format == PixelFormats.Rgb48)
{
this.ChannelCount = 3;
this.BytesPerChannel = 2;
this.HasAlpha = false;
this.BitDepth = 16;
this.IsBGR = false;
}
else
{
//There are some more special cases you might want to handle
//Also, it's possible that bmpSrc.Format == PixelFormats.Default
//Then you can only check for BitsPerPixel field
//and guess the channel order (I assume it's BGR)
throw new NotImplementedException();
}
int stride = this.Width * this.BytesPerChannel * this.ChannelCount;
ImageData = new byte[this.Height * stride]; //Init our byte array with the right size
bmpSrc.CopyPixels(ImageData, stride, 0); //Copy image data to our byte array
}
太好了,我们得到了我们的图像!您可能现在想对它进行一些操作。为此,请固定数据以进行处理,并在完成后取消固定它。我在最后添加了一个关于如何操作数据的示例。
public void LockBits()
{
if (!IsPinned)
{
pinHandle = GCHandle.Alloc(ImageData, GCHandleType.Pinned); //Pin the image data
Scan0 = pinHandle.AddrOfPinnedObject(); //Get the pointer to the first byte
IsPinned = true;
}
}
public void UnlockBits()
{
if (IsPinned)
{
pinHandle.Free(); //Unpin the image data
IsPinned = false;
}
}
很好。完成图像处理后,您可能想再次保存它。所以让我们这样做
/// <summary>
/// Saves the image to a path. (Jpg, Png, Tiff, Bmp, Gif and Wdp are supported)
/// </summary>
/// <param name="path">Path where the image should be saved to</param>
public void Save(string path)
{
string ext = Path.GetExtension(path).ToLower();
using (FileStream str = new FileStream(path, FileMode.Create)) { this.Save(str, ext); }
}
/// <summary>
/// Saves the image into a stream.
/// </summary>
/// <param name="ext">Extension of the desired file format.
/// Allowed values: ".jpg", ".jpeg", ".png", ".tiff",
/// ".tif", ".bmp", ".gif", ".wdp"</param>
/// <param name="str">The stream where the image will be saved to.</param>
public void Save(Stream str, string ext)
{
BitmapEncoder enc; //Find the right encoder
switch (ext)
{
case ".jpg":
case ".jpeg": enc = new JpegBitmapEncoder();
((JpegBitmapEncoder)enc).QualityLevel = 100; break;
case ".tif":
case ".tiff": enc = new TiffBitmapEncoder();
((TiffBitmapEncoder)enc).Compression = TiffCompressOption.Lzw; break;
case ".png": enc = new PngBitmapEncoder(); break;
case ".bmp": enc = new BmpBitmapEncoder(); break;
case ".wdp": enc = new WmpBitmapEncoder(); break;
default:
throw new ArgumentException("File format not supported *" + ext);
}
//Create a BitmapSource from all the data
BitmapSource src = BitmapSource.Create((int)this.Width, (int)this.Height, 96, 96,
InputPixelFormat, null, ImageData, (int)(this.Width * this.BytesPerChannel * this.ChannelCount));
enc.Frames.Add(BitmapFrame.Create(src)); //Add the data to the first frame
enc.Save(str); //Save the data to the stream
}
使用示例
以下是如何使用该类的示例。将 CalcNewRed
、CalcNewGreen
、CalcNewBlue
方法替换为有用的方法,您可以在其中计算新值。
为了确保新值在范围内,这似乎是最快的方法(其中 MAXVALUE
是它可以拥有的最大值,例如 byte
的 255
或 ushort
的 65535
)
Math.Max(0, Math.Min(value, MAXVALUE)
这是它如何与 8 位图像一起工作。
BitmapEx bmp = new BitmapEx("Example.jpg");
try
{
bmp.LockBits();
unsafe
{
int index, x, y;
int stride = bmp.Width * bmp.ChannelCount;
byte* pix = (byte*)bmp.Scan0;
for (y = 0; y < bmp.Height; y++)
{
for (x = 0; x < bmp.Width; x++)
{
index = y * stride + (x * bmp.ChannelCount);
float newR = CalcNewRed();
float newG = CalcNewGreen();
float newB = CalcNewBlue();
if (bmp.IsBGR)
{
pix[index] = (byte)Math.Max(0, Math.Min(newB, 255)); //Blue
pix[index + 1] = (byte)Math.Max(0, Math.Min(newG, 255)); //Green
pix[index + 2] = (byte)Math.Max(0, Math.Min(newR, 255)); //Red
}
else
{
pix[index] = (byte)Math.Max(0, Math.Min(newR, 255)); //Red
pix[index + 1] = (byte)Math.Max(0, Math.Min(newG, 255)); //Green
pix[index + 2] = (byte)Math.Max(0, Math.Min(newB, 255)); //Blue
}
if (bmp.HasAlpha) pix[index + 3] = 0; //Alpha
}
}
}
}
finally { bmp.UnlockBits(); }
bmp.Save("ExampleOut.jpg");
这是它如何与 16 位图像一起工作。
BitmapEx bmp = new BitmapEx("Example.tif");
try
{
bmp.LockBits();
unsafe
{
int index, x, y;
int stride = bmp.Width * bmp.ChannelCount;
ushort* pix = (ushort*)bmp.Scan0;
for (y = 0; y < bmp.Height; y++)
{
for (x = 0; x < bmp.Width; x++)
{
index = y * stride + (x * bmp.ChannelCount);
float newR = CalcNewRed();
float newG = CalcNewGreen();
float newB = CalcNewBlue();
if (bmp.IsBGR)
{
pix[index] = (ushort)Math.Max(0, Math.Min(newB, 65535)); //Blue
pix[index + 1] = (ushort)Math.Max(0, Math.Min(newG, 65535)); //Green
pix[index + 2] = (ushort)Math.Max(0, Math.Min(newR, 65535)); //Red
}
else
{
pix[index] = (ushort)Math.Max(0, Math.Min(newR, 65535)); //Red
pix[index + 1] = (ushort)Math.Max(0, Math.Min(newG, 65535)); //Green
pix[index + 2] = (ushort)Math.Max(0, Math.Min(newB, 65535)); //Blue
}
if (bmp.HasAlpha) pix[index + 3] = 0; //Alpha
}
}
}
}
finally { bmp.UnlockBits(); }
bmp.Save("ExampleOut.tif");
对于更多的位数,它将以相同的方式工作,只需使用正确的值类型(例如,对于 32 位使用 uint
),并且不要忘记更改最大值。
完整源代码
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Windows.Media;
using System.Windows.Media.Imaging;
public class BitmapEx
{
public int Width;
public int Height;
public int ChannelCount;
public int BytesPerChannel;
public bool HasAlpha;
public int BitDepth;
public bool IsBGR;
public bool IsPinned;
public IntPtr Scan0;
private PixelFormat InputPixelFormat;
private GCHandle pinHandle;
private byte[] ImageData;
/// <summary>
/// Loads an image from a path. (Jpg, Png, Tiff, Bmp, Gif and Wdp are supported)
/// </summary>
/// <param name="path">Path to the image</param>
public BitmapEx(string path)
{
using (Stream str = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
{
BitmapDecoder dec = BitmapDecoder.Create(str,
BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
if (dec.Frames.Count > 0) SetFromBitmapSource(dec.Frames[0]);
else throw new FileLoadException("Couldn't load file " + path);
}
}
/// <summary>
/// Loads an image from an encoded stream. (Jpg, Png, Tiff, Bmp, Gif and Wdp are supported)
/// </summary>
public BitmapEx(Stream encodedStream)
{
encodedStream.Position = 0;
BitmapDecoder dec = BitmapDecoder.Create(encodedStream,
BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
if (dec.Frames.Count > 0) SetFromBitmapSource(dec.Frames[0]);
else throw new FileLoadException("Couldn't load file");
}
/// <summary>
/// Loads an image from an encoded byte array. (Jpg, Png, Tiff, Bmp, Gif and Wdp are supported)
/// </summary>
public BitmapEx(byte[] encodedData)
{
using (MemoryStream str = new MemoryStream(encodedData))
{
BitmapDecoder dec = BitmapDecoder.Create(str,
BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
if (dec.Frames.Count > 0) SetFromBitmapSource(dec.Frames[0]);
else throw new FileLoadException("Couldn't load file");
}
}
private void SetFromBitmapSource(BitmapSource bmpSrc)
{
this.Width = bmpSrc.PixelWidth;
this.Height = bmpSrc.PixelHeight;
InputPixelFormat = bmpSrc.Format;
if (bmpSrc.Format == PixelFormats.Bgr24)
{
this.ChannelCount = 3;
this.BytesPerChannel = 1;
this.HasAlpha = false;
this.BitDepth = 8;
this.IsBGR = true;
}
else if (bmpSrc.Format == PixelFormats.Bgra32)
{
this.ChannelCount = 4;
this.BytesPerChannel = 1;
this.HasAlpha = true;
this.BitDepth = 8;
this.IsBGR = true;
}
else if (bmpSrc.Format == PixelFormats.Rgb24)
{
this.ChannelCount = 3;
this.BytesPerChannel = 1;
this.HasAlpha = false;
this.BitDepth = 8;
this.IsBGR = false;
}
else if (bmpSrc.Format == PixelFormats.Rgb48)
{
this.ChannelCount = 3;
this.BytesPerChannel = 2;
this.HasAlpha = false;
this.BitDepth = 16;
this.IsBGR = false;
}
else
{
//There are some more special cases you might want to handle
//Also, it's possible that bmpSrc.Format == PixelFormats.Default
//Then you can only check for BitsPerPixel field and guess the channel order (I assume it's BGR)
throw new NotImplementedException();
}
int stride = this.Width * this.BytesPerChannel * this.ChannelCount;
ImageData = new byte[this.Height * stride];
bmpSrc.CopyPixels(ImageData, stride, 0);
}
public void LockBits()
{
if (!IsPinned)
{
pinHandle = GCHandle.Alloc(ImageData, GCHandleType.Pinned);
Scan0 = pinHandle.AddrOfPinnedObject();
IsPinned = true;
}
}
public void UnlockBits()
{
if (IsPinned)
{
pinHandle.Free();
IsPinned = false;
}
}
/// <summary>
/// Saves the image to a path. (Jpg, Png, Tiff, Bmp, Gif and Wdp are supported)
/// </summary>
/// <param name="path">Path where the image should be saved to</param>
public void Save(string path)
{
string ext = Path.GetExtension(path).ToLower();
using (FileStream str = new FileStream(path, FileMode.Create)) { this.Save(str, ext); }
}
/// <summary>
/// Saves the image into a stream.
/// </summary>
/// <param name="ext">Extension of the desired file format.
/// Allowed values: ".jpg", ".jpeg", ".png",
/// ".tiff", ".tif", ".bmp", ".gif", ".wdp"</param>
/// <param name="str">The stream where the image will be saved to.</param>
public void Save(Stream str, string ext)
{
BitmapEncoder enc;
switch (ext)
{
case ".jpg":
case ".jpeg": enc = new JpegBitmapEncoder();
((JpegBitmapEncoder)enc).QualityLevel = 100; break;
case ".tif":
case ".tiff": enc = new TiffBitmapEncoder();
((TiffBitmapEncoder)enc).Compression = TiffCompressOption.Lzw; break;
case ".png": enc = new PngBitmapEncoder(); break;
case ".bmp": enc = new BmpBitmapEncoder(); break;
case ".wdp": enc = new WmpBitmapEncoder(); break;
default:
throw new ArgumentException("File format not supported *" + ext);
}
BitmapSource src = BitmapSource.Create((int)this.Width, (int)this.Height, 96, 96,
InputPixelFormat, null, ImageData, (int)(this.Width * this.BytesPerChannel * this.ChannelCount));
enc.Frames.Add(BitmapFrame.Create(src));
enc.Save(str);
}
}
关注点
您可以根据需要使用此代码。有关更多信息,请参阅 MIT 许可证。
通过付出更多的努力,您还可以使用这个类来处理灰度图像(即单通道图像)或一些更特殊的像素格式。
还有什么问题吗?请给我留言,我不会咬人的(也许;))!
历史
- 2014 年 3 月 - 初始版本