.net下灰度模式图像
在.net下,如果你加载了一副8位的灰度图像,然后想向其中绘制一些线条、或者填充一些矩形、椭圆等,都需要通过Grahpics.FromImage创建Grahphics对象,而此时会出现:无法从带有索引像素格式的图像创建graphics对象 这个错误,让我们的后续工作无法完成。本文叙述了一种另外的方法来实现它。
我们通过Reflector发编译.net framework的相关函数后发现,FromImage的实现过程如下:
public static Graphics FromImage(Image image)
{
if (image == null)
{
throw new ArgumentNullException("image");
}
if ((image.PixelFormat & PixelFormat.Indexed) != PixelFormat.Undefined)
{
throw new Exception(SR.GetString("GdiplusCannotCreateGraphicsFromIndexedPixelFormat"));
}
IntPtr zero = IntPtr.Zero;
int status = SafeNativeMethods.Gdip.GdipGetImageGraphicsContext(new HandleRef(image, image.nativeImage), out zero);
if (status != 0)
{
throw SafeNativeMethods.Gdip.StatusException(status);
}
return new Graphics(zero) { backingImage = image };
}
而在MSDN中,对GdipGetImageGraphicsContext函数的描述有如下部分:
This constructor also fails if the image uses one of the following pixel formats:
- PixelFormatUndefined
- PixelFormatDontCare
- PixelFormat1bppIndexed
- PixelFormat4bppIndexed
- PixelFormat8bppIndexed
- PixelFormat16bppGrayScale
- PixelFormat16bppARGB1555
因此,.net是判断当图像为索引模式时,直接返回错误,而不是通过判断GdipGetImageGraphicsContext的返回值来实现的。
针对这个事实,我们其实觉得也无可厚非,Graphics对象是用来干什么的,是用来向对应的Image中添加线条,路径、实体图形、图像数据等的,而普通的索引图像,其矩阵的内容并不是实际的颜色值,而只是个索引,真正的颜色值在调色板中,因此,一些绘制的过程用在索引图像上存在着众多的不适。
但是有个特列,那就是灰度图像,严格的说,灰度图像完全符合索引图像的格式,可以认为是索引图像的一种特例。但是我也可以认为他不属于索引图像一类:即他的图像数据总的值可以认为就是其颜色值,我们可以抛开其调色板中的数据。所以在photoshop中把索引模式和灰度模式作为两个模式来对待。
真是有这个特殊性,一些画线、填充路径等等的过程应该可以在灰度图像中予以实现,单GDI+为了规避过多的判断,未对该模式进行特殊处理。
但是,在一些特殊的场合,对灰度进行上述操作很有用途和意义。比如:在高级的图像设计中,有着选区的概念,而选区的实质上就是一副灰度图像,如果我们创建一个椭圆选区,设计上就是在灰度图像上填充了一个椭圆。如果能借助GDI+提供的优质的抗锯齿填充模式加上丰富自由的填充函数,那么就可以创建出多种多样的选区了。可.net的一个无法创建Graphics让我们此路不通。
有没有办法呢,其实也是有的,熟悉GDI+平板化API的人还知道有GdipCreateFromHDC函数,该函数可以从HDC中创建Graphics。因此我的想法就是利用GDI的方式创建位图对象吗,然后从GDI的HDC中创建对应的Graphics。经过实践,这种方法是可以行的。
为此,我用GDI结合GDI+的方式创建了一个GrayBitmap类,该类的主要代码如下:
unsafe class GrayBitmap
{ #region GDIAPI private const int DIB_RGB_COLORS = 0;
private const int BI_RGB = 0; [StructLayout(LayoutKind.Sequential, Pack = 1)]
private struct RGBQUAD
{
internal byte Blue;
internal byte Green;
internal byte Red;
internal byte Reserved;
} [StructLayout(LayoutKind.Sequential, Pack = 1)]
private struct BITMAPINFOHEADER
{
internal uint Size;
internal int Width;
internal int Height;
internal ushort Planes;
internal ushort BitCount;
internal uint Compression;
internal uint SizeImage;
internal int XPelsPerMeter;
internal int YPelsPerMeter;
internal uint ClrUsed;
internal uint ClrImportant;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
private struct BITMAPINFO
{
internal BITMAPINFOHEADER Header;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)]
internal RGBQUAD[] Palette;
} [StructLayout(LayoutKind.Sequential)]
internal struct LOGPALETTE
{
internal ushort PalVersion;
internal ushort PalNumEntries;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x400)]
internal byte[] PalPalEntry;
} [DllImport("User32.dll", SetLastError = true)]
private extern static IntPtr GetDC(IntPtr Hwnd); [DllImport("User32.dll", SetLastError = true)]
private extern static int ReleaseDC(IntPtr Hwnd, IntPtr Hdc); [DllImport("Gdi32.dll", SetLastError = true)]
private extern static IntPtr CreateCompatibleDC(IntPtr Hdc); [DllImport("Gdi32.dll", SetLastError = true)]
private static extern uint SetDIBColorTable(IntPtr Hdc, int un1, int un2, RGBQUAD[] pcRGBQUAD); [DllImport("Gdi32.dll", SetLastError = true)]
private static extern IntPtr CreateDIBSection(IntPtr Hdc, ref BITMAPINFO BmpInfo, uint iUsage, out byte* ppvBits, IntPtr hSection, uint dwOffset); [DllImport("Gdi32.dll", SetLastError = true)]
private extern static Boolean DeleteDC(IntPtr Hdc); [DllImport("Gdi32.dll", SetLastError = true)]
private extern static IntPtr SelectObject(IntPtr Hdc, IntPtr Object); [DllImport("Gdi32.dll", SetLastError = true)]
private static extern bool DeleteObject(IntPtr Object); #endregion #region PrivateVariable private int m_Width = 0;
private int m_Height = 0;
private int m_Stride = 0;
private IntPtr m_Hdc = IntPtr.Zero;
private Graphics m_Graphics = null;
private IntPtr m_Handle = IntPtr.Zero;
private byte* m_Pointer = null;
private Bitmap m_Bitmap = null;
private bool Disposed = false; #endregion #region Property public int Width { get { return m_Width; } }
public int Height { get { return m_Height; } }
public int Stride { get { return m_Stride; } }
public IntPtr Handle { get { return m_Handle; } }
public IntPtr Hdc { get { return m_Hdc; } }
public Graphics Graphics { get { return m_Graphics; } }
public byte* Pointer { get { return m_Pointer; } }
public Bitmap Bitmap { get { return m_Bitmap; } } #endregion #region Constructor public GrayBitmap(int Width, int Height)
{
AllocateBitmap(Width, Height);
} public GrayBitmap(string FileName)
{
Bitmap Bmp = (Bitmap)Bitmap.FromFile(FileName);
if (IsGrayBitmap(Bmp) == false)
{
Bmp.Dispose();
throw new Exception("Wrong PixelFormat");
}
else
{
AllocateBitmap(Bmp.Width, Bmp.Height);
BitmapData BmpData = new BitmapData();
BmpData.Scan0 = (IntPtr)m_Pointer;
BmpData.Stride = m_Stride; // 把Image对象的数据拷贝到DIBSECITON中去
Bmp.LockBits(new Rectangle(0, 0, Bmp.Width, Bmp.Height), ImageLockMode.ReadWrite | ImageLockMode.UserInputBuffer, Bmp.PixelFormat, BmpData);
Bmp.UnlockBits(BmpData);
Bmp.Dispose();
}
} public GrayBitmap(Bitmap Bmp)
{
if (IsGrayBitmap(Bmp) == false)
throw new Exception("Wrong PixelFormat");
else
{
AllocateBitmap(Bmp.Width, Bmp.Height);
BitmapData BmpData = new BitmapData();
BmpData.Scan0 = (IntPtr)m_Pointer;
BmpData.Stride = m_Stride; // 把Image对象的数据拷贝到DIBSECITON中去
Bmp.LockBits(new Rectangle(0, 0, Bmp.Width, Bmp.Height), ImageLockMode.ReadWrite | ImageLockMode.UserInputBuffer, Bmp.PixelFormat, BmpData);
Bmp.UnlockBits(BmpData);
}
} ~GrayBitmap()
{
Dispose();
} #endregion #region PublicMethod public static GrayBitmap FromFile(string FileName)
{
GrayBitmap Bmp = new GrayBitmap(FileName);
return Bmp;
} public void Dispose()
{
Dispose(true);
} protected void Dispose(bool Suppress = true)
{
if (Disposed == false)
{
Disposed = true;
if (m_Hdc != IntPtr.Zero) DeleteDC(m_Hdc); m_Hdc = IntPtr.Zero;
if (m_Graphics != null) m_Graphics.Dispose(); m_Graphics = null;
if (m_Bitmap != null) m_Bitmap.Dispose(); m_Bitmap = null;
if (m_Handle != IntPtr.Zero) DeleteObject(m_Handle); m_Handle = IntPtr.Zero;
m_Width = 0; m_Height = 0; m_Stride = 0; m_Pointer = null;
if (Suppress == true) GC.SuppressFinalize(this);
}
} #endregion #region PrivateMethod private void AllocateBitmap(int Width, int Height)
{
if (Width <= 0) throw new ArgumentOutOfRangeException("Width", Width, "Width must be >=0");
if (Height <= 0) throw new ArgumentOutOfRangeException("Height", Height, "Height must be >=0"); BITMAPINFO BmpInfo = new BITMAPINFO();
BmpInfo.Header.Size = (uint)sizeof(BITMAPINFOHEADER);
BmpInfo.Header.Width = Width;
BmpInfo.Header.Height = -Height; // 为了和GDI对象的坐标系统(起点坐标在左上角),建立一个倒序的DIB
BmpInfo.Header.BitCount = (ushort)8; ;
BmpInfo.Header.Planes = 1;
BmpInfo.Header.Compression = BI_RGB; // 创建DIBSection必须用不压缩的格式
BmpInfo.Header.XPelsPerMeter = 0; // CreateDIBSection does not use the BITMAPINFOHEADER parameters biXPelsPerMeter or biYPelsPerMeter and will not provide resolution information in the BITMAPINFO structure.
BmpInfo.Header.YPelsPerMeter = 0;
BmpInfo.Header.ClrUsed = 0;
BmpInfo.Header.SizeImage = 0;
BmpInfo.Header.ClrImportant = 0;
BmpInfo.Header.SizeImage = 0;
BmpInfo.Palette = new RGBQUAD[256];
for (int X = 0; X <256 ; X++) // for (byte X=0;X<=255;X++) 用这个代码试试,呵呵
{
BmpInfo.Palette[X].Red = (byte)X;
BmpInfo.Palette[X].Green = (byte)X;
BmpInfo.Palette[X].Blue = (byte)X;
BmpInfo.Palette[X].Reserved = 255;
}
IntPtr ScreecDC = GetDC(IntPtr.Zero);
m_Hdc = CreateCompatibleDC(ScreecDC);
ReleaseDC(IntPtr.Zero, ScreecDC);
m_Handle = CreateDIBSection(Hdc, ref BmpInfo, DIB_RGB_COLORS, out m_Pointer, IntPtr.Zero, 0);
if (m_Handle == IntPtr.Zero)
{
DeleteDC(m_Hdc);
m_Hdc = IntPtr.Zero;
throw new OutOfMemoryException("CreateDIBSection function failed,this may be caused by user input too large size of image.");
}
else
{
SelectObject(m_Hdc, m_Handle);
SetDIBColorTable(m_Hdc, 0, 256, BmpInfo.Palette);
m_Width = Width; m_Height = Height; m_Stride = (int)((m_Width + 3) & 0XFFFFFFFC);
m_Graphics = Graphics.FromHdc(m_Hdc);
m_Bitmap = new Bitmap(m_Width, m_Height, m_Stride, PixelFormat.Format8bppIndexed, (IntPtr)m_Pointer);
ColorPalette Pal = m_Bitmap.Palette;
for (int X = 0; X < 256; X++) Pal.Entries[X] = Color.FromArgb(255, X, X, X); // 设置灰度图像的调色板
m_Bitmap.Palette = Pal;
}
} private bool IsGrayBitmap(Bitmap Bmp)
{
bool IsGray;
if (Bmp.PixelFormat == PixelFormat.Format8bppIndexed)
{
IsGray = true;
if (Bmp.Palette.Entries.Length != 256)
IsGray = false;
else
{
for (int X = 0; X < Bmp.Palette.Entries.Length; X++)
{
if (Bmp.Palette.Entries[X].R != Bmp.Palette.Entries[X].G || Bmp.Palette.Entries[X].R != Bmp.Palette.Entries[X].B || Bmp.Palette.Entries[X].B != Bmp.Palette.Entries[X].G)
{
IsGray = false;
break;
}
}
}
}
else
{
IsGray = false;
}
return IsGray;
}
#endregion }
正如上面所述,我们用GDI的方式(CreateDIBSection)创建灰度图像,然后从HDC中创建Graphics,从而可以顺利的调用Graphics的任何绘制函数了。
比如填充椭圆:
SolidBrush SB = new SolidBrush(Color.FromArgb(255, 255, 255, 255));
Bmp.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
Bmp.Graphics.FillEllipse(SB, new Rectangle(100, 100, 200, 300));
SB.Dispose();
Canvas.Invalidate();
心细的朋友可以在测试中会发现,通过这种方式绘制的颜色可能和指定的颜色有所不同,比如上面我们要求绘制白色的椭圆,但是实际绘制的颜色是RGB(252,252,252)的,但是并不是所有的颜色都有误差,引起这个的原因估计还是GDI+的内部的一些机制上的问题吧。
工程完整代码:http://files.cnblogs.com/Imageshop/GrayModeBitmap.rar
希望朋友们喜欢我的文章。
*****************************基本上我不提供源代码,但是我会尽量用文字把对应的算法描述清楚或提供参考文档**************************
*******************************因为靠自己的努力和实践写出来的效果才真正是自己的东西,人一定要靠自己****************************
***************************作者: laviewpbt 时间: 2013.7.13 联系QQ: 33184777 转载请保留本行信息*************************
.net下灰度模式图像的更多相关文章
- .net下灰度模式图像在创建Graphics时出现:无法从带有索引像素格式的图像创建graphics对象 问题的解决方案。
在.net下,如果你加载了一副8位的灰度图像,然后想向其中绘制一些线条.或者填充一些矩形.椭圆等,都需要通过Grahpics.FromImage创建Grahphics对象,而此时会出现:无法从带有索引 ...
- Caffe框架下的图像回归测试
Caffe框架下的图像回归测试 参考资料: 1. http://stackoverflow.com/questions/33766689/caffe-hdf5-pre-processing 2. ht ...
- matlab读取指定路径下的图像
利用matlab读取指定路径下的图像 %% 读入指定路径imgFolder下的图像imgName imgFolder = 'F:\博\快盘\图像+数据\images\文章实验图'; %指定路径 img ...
- matlab从文件夹名中获得该文件夹下所图像文件名
function [s,nameC]=get_FileNameFromFolderPath(path) % 函数调用:[s,nameC]=get_FileNameFromFolderPath(path ...
- 使用OpenCV读、操作、写图像并与bash合作对某个目录下所有图像进行类似处理
我门要对某个目录下所有图像文件进行统一处理,如果图像的数量过多,那么手动地一张张处理就会显得有些麻烦.本文使用OpenCV和bash来完成我们指定的任务. 任务 将目录A下的所有统一格式的jpg图像变 ...
- 使用OpenCV读、操作、写图像并与bash合作对某个文件夹下全部图像进行相似处理
我门要对某个文件夹下全部图像文件进行统一处理,假设图像的数量过多.那么手动地一张张处理就会显得有些麻烦.本文使用OpenCV和bash来完毕我们指定的任务. 任务 将文件夹A下的全部统一格式的jpg图 ...
- centos下安装图像化界面
前面我们安装的centos系统多为没有图像化界面的命令行界面,为了安装oracle等工具,我们先为我们的centos安装图像化界面 使用命令为 yum groupinstall "Deskt ...
- java web实现读取指定盘符下的图像(二)
之前写了一篇文章是关于如何读取指定盘符下的图片,虽然功能可以实现,但是使用的是I/O流的方式,效率不高.现在发现还有一个更好的办法,使用也更加的方便. 我们知道,当我们的图片是放在tomcat下web ...
- java web实现img读取盘符下的图像
最近做了一个项目,用户上传图片后通过img控件显示出来.大家都知道img通过src属性就可以显示图片.如<img src="http://127.0.0.1/a/b/abc.jpg&q ...
随机推荐
- LeetCode——Spiral Matrix
Given a matrix of m x n elements (m rows, n columns), return all elements of the matrix in spiral or ...
- sql server drop talbe 自动删除关联的外键 ,权限体系(二)
alter table dbo.Sys_PowerTeamForUser add constraint FK_Sys_User_Sys_PowerTeamForUser foreign key (Sy ...
- 在Mac OS X 10.8中配置Apache+PHP+MySQL
在Mac OS X 10.8中配置Apache+PHP+MySQL的内容包括: 配置Apache 配置PHP 安装MySQL 配置PHPAdmin 设置数据库默认字符集 一. 配置Apache 1. ...
- C#中如何获取系统环境变量
原文:C#中如何获取系统环境变量 C#中获取系统环境变量需要用到Environment Class.其中提供了有关当前环境和平台的信息以及操作它们的方法.该类不能被继承. 以下代码得到%systemd ...
- Jquery页面中添加键盘按键事件,如ESC事件
$(document).keydown(function(event){ if(event.keyCode == 38 || event.keyCode == 104){ i--; if(i<= ...
- Github资源汇集
Github资源汇集 突然发现申请博客园已经两年有余,没有发表过一篇文章,十分惭愧.言归正传,先分享一下两年来收集的部分编程资源,大部分为Github上的项目.虽然网上这样的分享已不在少数,但不如我理 ...
- .net4.5的弱事件
.net4.5的弱事件 没有伟大的愿望,就没有伟大的天才--Aaronyang的博客(www.ayjs.net)-www.8mi.me 1. 事件-我的讲法 老师常告诉我,事件是特殊的委托,为委托提供 ...
- 快速构建Windows 8风格应用37-常见发布注意事项
原文:快速构建Windows 8风格应用37-常见发布注意事项 引言 通常我们发布Windows Store应用失败后,会返回一些错误需要我们去修改.我之前在给学生做培训的时候发现大部分同学应用被打回 ...
- SQL Server之记录筛选(top、ties、offset)汇总
一.TOP 筛选 如果有 ORDER BY 子句,TOP 筛选将根据排序的结果返回指定的行数.如果没有 ORDER BY 子句,TOP 筛选将按照行的物理顺序返回指定的行数. 1. 返回指定数目的行 ...
- 安装WindowsXP操作系统(安装版) - 初学者系列 - 学习者系列文章
本文主要介绍下Windows XP操作系统的安装. 1. 将光驱装入光驱.启动电脑,在开始界面按下DEL键,进入BIOS设置界面.将光驱设置为第一启动项.下面以虚拟机为例子. ...