摘要: 好久没有写了,真是灰常地惭愧。Heroes都已经放到第24集了,而我只写到第11集,实在是很惭愧。我在前面几章里面提到过ColorMatrix,可以将图像的色彩进行仿射变换。但是如果要对图像的色彩进行非线性变换的话,那就必须用到更强悍的API了。在Windows早期,有一套标准的色彩管理的API,叫做ICM 2.0 (Image Color Management 2.0)。在Windows Vis...
阅读全文
posted @
2009-04-22 23:44 Hotcan 阅读(1202) |
评论 (3) |
编辑
10. Graphics的几个属性。
今天我来讲讲Graphics在DrawImage里的几个的属性。
Graphics是GDI+里面的大拿,可以用来画线,画矩形,甚至可以用来画各种各样的材质。通过不同的Pen,Brush来实现。具体的使用方法是所有想用GDI+的同学的基础,我就不详细讲了,具体可以参考MSDN:http://msdn.microsoft.com/en-us/library/haxsc50a(VS.80).aspx。我主要来讲2个大家不太注意的属性。
a.Graphics.CompositingMode
这是一个枚举属性,可以取的值有2种,一种是SourceOver, 另外一种是SourceCopy。这定义了Graphics如何将当前颜色和背景合成。如果是SourceCopy,那么颜色不和当前背景合成。如果是SourceOver,那么背景颜色会和当前的颜色混合,算法如下:
显示颜色 = 源颜色 × alpha / 255 + 背景颜色 × (255 - alpha) / 255
新颜色的透明分量是255,也就是不透明。我们来看看下面的代码:
private int CompositeColor(int color, int alpha, int backgroudColor)
{
//显示颜色 = 源颜色 × alpha / 255 + 背景颜色 × (255 - alpha) / 255
return color * alpha / 255 + backgroudColor * (255 - alpha) / 255;
}
private void Draw(object sender, EventArgs e)
{
this.BackColor = Color.FromArgb(255, 255, 255);
Graphics g = this.CreateGraphics();
g.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceOver;
g.FillRectangle(new SolidBrush(Color.FromArgb(127, 255, 0, 0)), new Rectangle(0, 0, 200, 200));
g.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy;
g.FillRectangle(new SolidBrush(Color.FromArgb(127, 255, 0, 0)), new Rectangle(200, 0, 200, 200));
g.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy;
g.FillRectangle(new SolidBrush(Color.FromArgb(
255,
CompositeColor(255, 127, 255),
CompositeColor(0, 127, 255),
CompositeColor(0, 127, 255))
), new Rectangle(0, 200, 200, 200));
g.Dispose();
}
第1个色块和第2个色块分别是混合和不混合的,如果我们用不混合的方式希望得到混合的效果,那么应该用第三个色块的写法。从下面的图像中我们可以很清楚地看到结果。

b.Graphics.CompositingQuality
合成质量,一共有5种
| |
成员名称 |
说明 |
|
AssumeLinear |
假定线性值。 |
|
Default |
默认质量。 |
|
GammaCorrected |
使用灰度校正。 |
|
HighQuality |
高质量、低速度复合。 |
|
HighSpeed |
高速度、低质量。 |
|
Invalid |
无效质量。 |
这部分东西稍有点学问,MSDN里面没怎么讲清楚,有些实践派的同学用了其他的几个相关的属性来解释GDI+中的图像质量,例如
http://www.cnblogs.com/adow/archive/2007/10/05/914573.html,不过不得精髓。我来解释一下图像合成的一些理论基础。这里还需要和另外一个属性InterpolationMode加以区分。这个属性的具体使用我会在下一节讲到,而合成质量与插值不是一回事。
根据我们上一节的算法,图像的合成是浮点运算,计算量非常大。此外,由于图像存储最后是需要被量化的,所以在量化的过程中会不可避免地出现锯齿的情况,为了平滑锯齿,又需要大量的计算。还有一个问题,如果我们有很多层不同的透明图像,需要进行合成,那么每一层都需要进行合成运算。其实这种合成运算式可以被优化的。CompositingQuality这个属性就是GDI+用来解决这些问题的。MSDN里面只是简单地说质量越高速度越慢,具体的算法不得而知。
HighQuality使用平滑技术去除在合成中出现的锯齿,并合成当前的Gamma灰度信息,这种计算是最慢的,并且出来的颜色与非GammaCorrected是不一样的。
GammaCorrected 合成当前Gamma灰度信息,但是不进行计算优化。
HighSpeed优化计算速度,出来的质量稍微有点差,如果不是对质量要求很高时看不出来的。
AssumeLinear的质量比Default稍好,速度稍慢,这种算法是假定合成中插值的像素变化是线性的。
Default就是最基本的计算方法。
Invalid未知,我也不知道,要是有知道的朋友可以告诉我。
其中HighQuality/GammaCorrected效果一样,其余四种一样。可以参考下图。

posted @
2008-11-26 18:40 Hotcan 阅读(2611) |
评论 (1) |
编辑
昨天晚上看了Heroes第9集,Eclipse又要来了,激动中。
今天来讲讲上个星期遗留下来的东西:ColorMatrix。
9. Color Matrix
图像的本质是什么?对不同的人来说这是不同的东西。在计算机的世界中,啥东西都是数据,图像也是一种数据。从自然界的光变成计算机的数据,需要通过采样和量化的处理。图像在计算机中,其实是一个二维数组,从数学上来说,这其实是一个矩阵。图像中的每一个点都是个四维向量,也就是(R,G,B,A), 在RGBA色彩空间中,我们可以使用一个矩阵对每一个点(R,G,B,A)作矩阵乘法运算,这样就可以对图像色彩进行变换。这种做法其实是从三维空间坐标系中的仿射变换类推过来的。具体关于仿射变换,可以参考http://en.wikipedia.org/wiki/Affine_transformation对于仿射变换的介绍。
色彩矩阵就是这个用来对色彩作仿射变换的矩阵。这是一个5*5的矩阵,如图

其实和在空间中的仿射变换完全一样,可以实现缩放,旋转,平移等功能。我看到网上有个人写了一篇深入浅出的文章"GDI+ ColorMatrix的完全揭秘与代码实现" http://blog.csdn.net/maozefa/archive/2008/09/08/2896752.aspx 写得不错,只是没有理解到ColorMatrix应用的精髓。简单套用了一些什么颜色剪切,颜色旋转,颜色平移的概念,这些东西其实在三维空间中很好理解,但是在色彩空间中,就完全不是那么回事情了,什么叫做颜色旋转60度呢? 这东西忽悠人很有用,只是看完了还是不知道怎么用,有兴趣的同学可以去看看。我下面举几个例子,说明ColorMatrix的具体应用。
a.灰度化
灰度化是指去除图像的彩色信息,讲所有的色调归为0,所有的饱和度也归为零。这个世界上有很多种不同的灰度化的算法,随便写个算法,弄篇paper搞个硕士毕业应该不成问题,比如说所有的颜色替换成R' = G' = B' = (R+B+G)/3。有一种很通用的灰度化算法如下,这其实是NTSC的色彩权重。
R'=B'=G' = 0.299*R + 0.587*G + 0.114*B
那如果我们要使用ColorMatrix, 可以用以下的矩阵:
// ColorMatrix elements
float[][] ptsArray =
{
new float[] {0.299f, 0.299f, 0.299f, 0, 0},
new float[] {0.518f, 0.518f, 0.518f, 0, 0},
new float[] {0.114f, 0.114f, 0.114f, 0, 0},
new float[] {0, 0, 0, 1, 0},
new float[] {0, 0, 0, 0, 1}
};

再引用一下博客园里的这篇文章http://www.cnblogs.com/sunbingzibo/archive/2008/09/11/1289260.html,如果用他的算法,那么矩阵如下
// ColorMatrix elements
float[][] ptsArray =
{
new float[] {cr, cr, cr, 0, 0},
new float[] {cg, cg, cg, 0, 0},
new float[] {cb, cb, cb, 0, 0},
new float[] {0, 0, 0, 1, 0},
new float[] {0, 0, 0, 0, 1}
};
b.调整色彩
使用ColorMatrix调整色彩很简单,用m11对红色乘一个系数,用m51对红色加一个值,这样就可以简单地调整红色。其他B,G,A通道以此类推。例如下面这个矩阵可以把增加红色25.5个像素(如果使用24bppArgb):
// ColorMatrix elements
float[][] ptsArray =
{
new float[] {1, 0, 0, 0, 0},
new float[] {0, 1, 0, 0, 0},
new float[] {0, 0, 1, 0, 0},
new float[] {0, 0, 0, 1, 0},
new float[] {0.1f, 0, 0, 0, 1}
};

c. 调整亮度, 可以用以下矩阵,将每个通道增加25.5的亮度
// ColorMatrix elements
float[][] ptsArray =
{
new float[] {1, 0, 0, 0, 0},
new float[] {0, 1, 0, 0, 0},
new float[] {0, 0, 1, 0, 0},
new float[] {0, 0, 0, 1, 0},
new float[] {0.1f, 0.1f, 0.1f, 0, 1}
};

d.调整对比度,可以使用以下矩阵,将每个通道升高10%的对比度
// ColorMatrix elements
float[][] ptsArray =
{
new float[] {1.1f, 0, 0, 0, 0},
new float[] {0, 1.1f, 0, 0, 0},
new float[] {0, 0, 1.1f, 0, 0},
new float[] {0, 0, 0, 1, 0},
new float[] {0, 0, 0, 0, 1}
};

很不幸,这是错的。这个算法里面有个关键的问题是Overflow,如果我们直接使用这个矩阵,你会看到图像上会有溢出,导致你的图像惨不忍睹。我在网上查到有个很发指的做法可以解决这个问题,虽然发指,但是能解决!就是把最下面的项修正一点点,这样图像就不溢出了。看下面这个矩阵。
// ColorMatrix elements
float[][] ptsArray =
{
new float[] {1.5f, 0, 0, 0, 0},
new float[] {0, 1.5f, 0, 0, 0},
new float[] {0, 0, 1.5f, 0, 0},
new float[] {0, 0, 0, 1, 0},
new float[] {0.001f, 0.001f, 0.001f, 0, 1}
};

e.调整饱和度
这个矩阵比较复杂,饱和度需要通过不同的色彩权值来修正。我这里只提供一个能用的矩阵,具体可以参考这篇paper:http://www.graficaobscura.com/matrix/index.html
float rwgt = 0.3086f;
float gwgt = 0.6094f;
float bwgt = 0.0820f;
float s = 1.2f;
float[][] ptsArray =
{
new float[] {(1f-s)*rwgt+s, (1f-s)*rwgt, (1f-s)*rwgt, 0, 0},
new float[] {(1f-s)*gwgt, (1f-s)*gwgt +s, (1f-s)*gwgt, 0, 0},
new float[] {(1f-s)*bwgt, (1f-s)*bwgt, (1f-s)*bwgt + s, 0, 0},
new float[] {0, 0, 0, 1, 0},
new float[] {0, 0, 0, 0, 1}
};

讲了那么多个矩阵,最后让我们来看看在GDI+里面ColorMatrix这个类到底怎么用:
FileStream fs = new FileStream(image, FileMode.Open, FileAccess.Read);
Image img = Image.FromStream(fs, false, false);
Bitmap bmp = new Bitmap(img);
img.Dispose();
fs.Close();
Graphics g = this.CreateGraphics();
float rwgt = 0.3086f;
float gwgt = 0.6094f;
float bwgt = 0.0820f;
float s = 1.2f;
float[][] ptsArray =
{
new float[] {(1f-s)*rwgt+s, (1f-s)*rwgt, (1f-s)*rwgt, 0, 0},
new float[] {(1f-s)*gwgt, (1f-s)*gwgt +s, (1f-s)*gwgt, 0, 0},
new float[] {(1f-s)*bwgt, (1f-s)*bwgt, (1f-s)*bwgt + s, 0, 0},
new float[] {0, 0, 0, 1, 0},
new float[] {0, 0, 0, 0, 1}
};
// Create a ColorMatrix
ColorMatrix matrix = new ColorMatrix(ptsArray);
ImageAttributes attr = new ImageAttributes();
// Set color matrix
attr.SetColorMatrix(matrix,
ColorMatrixFlag.Default,
ColorAdjustType.Default);
// Draw image with no affects
g.DrawImage(bmp, 0, 0, 200, 150);
// Draw image with ImageAttributes
g.DrawImage(bmp,
new Rectangle(205, 0, 200, 150),
0, 0, bmp.Width, bmp.Height,
GraphicsUnit.Pixel, attr);
// Dispose
bmp.Dispose();
g.Dispose();
多少博士大牛在研究这些不同的矩阵以期获得更强悍的效果。此外还有好多人申请了各种各样的专利来保护这个色彩变换,所以如果大家想混一篇简单的paper好毕业,这是个很好的方向。随便改两个数字,一个新的矩阵就出来了,然后版面费一交,就可以发表了。当然,这也是个蛮有意思的题目,可以做很多比较和研究,这些就不是我这种IT民工该讲的东西了。
posted @
2008-11-20 15:15 Hotcan 阅读(1894) |
评论 (5) |
编辑
哈哈,这个星期Heroes第八集终于出来了,我继续顺着上一节外传讲下去,修改颜色怎么做。
8.1 使用原始的方法修改图像的RGB以及色调,饱和度和亮度
最简单的办法,很容易,就是用之前的LockBits,然后直接修改R,G,B的数值,具体的就不多说了。在GDI+里面,Color有3个方法,分别是GetHue(), GetSaturation(), GetBrightness()。它是图像的色调,饱和度和亮度。其中Hue取值为[0,360),表示当前颜色在哪一个角度,Saturation和Brightness都是从[0,1],表示色彩的饱和度和亮度。这里GDI+的注释又犯了一个错误,比如说GetSaturation():Gets the hue-saturation-brightness (HSB) saturation value for this Color structure.,说自己是HSB,但是其实它的数值是HSL的结果。写注释的人应该拉出去暴打一顿,不知道让我在这个上面浪费了多少时间。
HSB也叫HSV,也是一种非常常用的色彩空间,它根据分离的亮度信息和色彩信息,广泛地应用在了各种计算机图像软件中。另外的一种色彩空间叫HSL,跟HSV类似,只是在亮度和饱和度的处理上稍有不同,有兴趣的同学可以去查这篇文章http://en.wikipedia.org/wiki/HSL_color_space,里面有很多好看的图和详细的比较,我这里就不多说了。在这里只给大家看一下HSV色彩空间的模型:

言归正传,如果我想修改一幅图像的饱和度,怎么做呢?GDI+又做了个半吊子的事情,Color里面有个静态方法叫FromArgb,但是没有方法叫FromHSL(float hue, float Saturation, float lightness)。所以搞得大家都不开心。这里我把brightness改成lightness,以避免出现注释中的错误。
1 private Color FromHSL(float hue, float saturation, float lightness)
2 {
3 double q = lightness < 0.5d ? lightness * (1 + saturation) : lightness + saturation - (lightness * saturation);
4 double p = 2 * lightness - q;
5 double hk = hue / 360d;
6
7 double[] t = new double[3]; //save RGB in array
8 t[0] = hk + 1 / 3d;
9 t[1] = hk;
10 t[2] = hk - 1 / 3d;
11
12 for (int i = 0; i < 3; i++)
13 {
14 if (t[i] < 0) t[i] += 1d;
15 if (t[i] > 1) t[i] -= 1d;
16 }
17
18 double[] color = new double[3];
19 for (int i = 0; i < 3; i++)
20 {
21 if (t[i] < 1 / 6d)
22 {
23 color[i] = p + ((q - p) * 6 * t[i]);
24 }
25 else if (t[i] < 1 / 2d)
26 {
27 color[i] = q;
28 }
29 else if (t[i] < 2 / 3d)
30 {
31 color[i] = p + ((q - p) * 6 * (2 / 3d - t[i]));
32 }
33 else
34 {
35 color[i] = p;
36 }
37 }
38
39 return Color.FromArgb(0,
40 (int)Math.Round(color[0] * 255d),
41 (int)Math.Round(color[1] * 255d),
42 (int)Math.Round(color[2] * 255d));
43
44 }
这个函数是通过以下公式计算的。

通过这个函数,Color你就可以很简单地修改颜色的饱和度,色调和亮度了。不过这种方法还是需要LockBits/UnlockBits。还有一种更有趣的方法叫做色彩矩阵的仿射变换,是从图像坐标系的仿射变换来的。只是色彩用ColorMatrix,而坐标的变换直接有一个matrix类。下一节我会介绍ColorMatrix的使用方法,而把Matrix的使用留到以后,要看Heroes能放多少集了。
posted @
2008-11-14 16:24 Hotcan 阅读(1676) |
评论 (6) |
编辑
8. 颜色修正
我最近一直在颜色空间中纠结,前面讲的透明不透明只是一种特殊的颜色。今天我准备要讲的是真正的修正颜色。我们使用的数码相机有不同的型号,感光的CCD性能也是不一样的,再加上天气或者周围光线的原因,我们排出来的照片可能会偏色。此外,为了做一些特殊处理,可能需要将照片中某一个特殊的区域颜色进行修正。比如脸色可以变得更好一点,或者头发更黑一点等等。这个Topic很大,我会花好几个星期来讲这些内容,因为涉及到许多图像处理的基本知识。今天咱们不写code,只讲理论(谁让这个星期美国大选,Heroes第八集居然没有出,我只好写外传)。
外传1. RGB色彩空间
在之前的分析中,相信所有的人都知道R,G,B是什么东西了。我们在描述色彩的时候,最常用的就是用RGB色彩空间。通过描述颜色的三个不同的分量,我们可以记录某个像素的颜色值。我们在此不涉及设备色彩空间的概念,但是有一点需要大家记住的,(0,0,0)并不代表全黑,(255,255,255)也并不代表全白,在不同的设备上,显示出的内容是不完全一样的。这个问题的解决需要依靠我之前讲的ICM(http://www.color.org/)。如果要加上透明,那就是四维的空间,(A,R,G,B)。各个分量可以被量化为不同的级别,所以才造成了8位色,16位色等等,这种量化级别可以区分各种不同的颜色,直到人眼无法察觉的程度。所以对一个初学者来说,不透明的颜色就是个Cube,在一个三维空间中的一个点。如下图

这个图很好看吧,只是很可惜,这只是对颜色描述的一种最简单的方式,它所能描述颜色内容是极其有限的,也不精确。要彻底理解这个问题,我要帮大家分析什么光的原理,颜色的波长,材料的对不同波长光的吸收和反射或者漫反射。这里我就不多说了,有兴趣的同学去找物理系光学专业的老师好好问问,人家一辈子都研究着这个问题。更有兴趣的同学可以去问问物理系的大教授讨论一下光的波粒二象性,再研究一下人眼对光的感受,再研究不同材料表面对光的反应,再研究……,打住了~!再研究下去您这辈子就结束了,所以只要简单地理解颜色就是RGB就好了。IT民工能理解到这个程度已经很不错了。
这里再给大家看个有趣的图,表示的是一个色彩空间叫sRGB能描述的颜色,灰色区域是人眼可以感知的颜色,里面的那个三角就是sRGB色彩空间能描述的颜色。还有一点要注意的是RGB色彩空间只是描述颜色的一种手段。具体还可以参考wikipedia:http://en.wikipedia.org/wiki/RGB

RGB色彩空间并不是唯一的能用来描述色彩的方法,它是最简单的一种。如果你需要修改图像区域的R,G,B分量,以及亮度,对比度分量。那么使用这种色彩空间是最简单的。但是如果你想修正色度(hue),饱和度(Saturation),那么这种色彩空间就不是那么简单了,要经过一定的换算。色彩空间有很多种,比如RGBA,包括透明的RGB四维空间;CMYK:主要用于印刷业;YIQ/YUV:主要用于NTSC/PAL彩色电视制式系统;YPbPr/YCrBr:主要用于视频压缩等领域。HSB/HSV/HSL,这是一个非常常用的色彩空间,描述色度,饱和度和亮度,这个色彩空间我们会在下一节详细讲,它是RGB色彩空间的一种变形。
posted @
2008-11-07 15:33 Hotcan 阅读(1656) |
评论 (3) |
编辑
7. 多帧图像
为了赶上英雄第三季的播放日程,我决定一个星期出一集。
在第七集Heroes里面,Peter的功能都被他老爸吸收掉了。所以我的这个系列的第七集来讲讲GDI+没完全实现的一部分功能。
多帧图像是指在一幅图像中有多个帧,支持多帧图像的格式不多,只有TIFF和GIF。其他格式都不能作为多帧图像存储。其中TIFF可以支持很多页,GIF动画也支持多帧。使用GDI+可以生成多帧TIFF,却没办法实现GIF动画的生成,有可能是因为专利的缘故。首先让我们来看看怎么样在生成多帧的TIFF图像。
1 public void CreateMultiframeTIFF(string resultImage, string image1, params string [] images)
2 {
3 //Read multiple frames, the frames' size can be different
4 Image frame1 = Image.FromFile(image1);
5 int length = images.Length;
6 Image[] frames = new Image[length];
7 for (int i = 0; i < length; i++)
8 {
9 frames[i] = Image.FromFile(images[i]);
10 }
11
12 //Set the first frame as the base bitmap
13 Bitmap bmpResult = (Bitmap)frame1;
14
15 //Create encoder parameters with different values
16 EncoderParameters parameters = new EncoderParameters(1);
17 parameters.Param[0] = new EncoderParameter(Encoder.SaveFlag, (long)EncoderValue.MultiFrame);
18
19 //Find Tiff codec
20 List<ImageCodecInfo> supportedCodecs = new List<ImageCodecInfo> ();
21 supportedCodecs.AddRange(ImageCodecInfo.GetImageEncoders());
22 ImageCodecInfo tiffCodecInfo = supportedCodecs.Find(
23 delegate(ImageCodecInfo info)
24 {
25 return info.MimeType.Equals(
26 System.Net.Mime.MediaTypeNames.Image.Tiff,
27 StringComparison.OrdinalIgnoreCase);
28 }
29 );
30
31 //Save the first frame to a stream.
32 bmpResult.Save(resultImage, tiffCodecInfo, parameters);
33
34 //Save the second frame into the tiff
35 parameters.Param[0] = new EncoderParameter(Encoder.SaveFlag, (long)EncoderValue.FrameDimensionPage);
36 foreach (Image frame in frames)
37 {
38 bmpResult.SaveAdd(frame, parameters);
39 }
40
41 //flush the stream
42 parameters.Param[0] = new EncoderParameter(Encoder.SaveFlag, (long)EncoderValue.Flush);
43 bmpResult.SaveAdd(parameters);
44
45 //dispose all resources
46 frame1.Dispose();
47 foreach (Image frame in frames)
48 {
49 frame.Dispose();
50 }
51 bmpResult.Dispose();
52 }
这里我们使用了EncoderParameters来设置图像编码的参数,首先在第一帧保存的时候设置多帧属性(16,17行),然后拿到Tiff的编码器(20 - 29行)。先使用EncoderParamters 和 ImageCodecInfo对第一帧进行保存,然后使用SaveAdd方法添加新的帧。最后Flush了一下流,其实是可选的,因为在Bitmap dispose的时候会自动flush。
读取多帧TIFF比较简单,直接调用GetFrameCount和SelectActiveFrame就可以了,我们可以通过下面代码行1获得所有帧数,通过行2选择当前的帧。
1 int count = bmp.GetFrameCount(FrameDimension.Page);
2 bmp.SelectActiveFrame(FrameDimension.Page, 1);
GDI+可以读取GIF动画,所不同的是在上面代码1,2行中,要使用FrameDimension.Time参数传入。虽然GDI+没有现成的函数实现生成GIF动画,不过可以通过别的办法来实现。这年头没有做不到的,只有想不到的。不过那个方法过于复杂和底层了,等啥时候Heroes里面Peter拿回能力了,我再告诉大家。
今天就先到这里了。
posted @
2008-10-31 14:51 Hotcan 阅读(1304) |
评论 (5) |
编辑
6.2 GIF
GIF的全称是图像交换格式Graphics Interchange Format,是CompuServe公司在1987年创建并使用的。这种格式使用8位索引值来表达一个像素,也就是说1个像素1个byte,最多可以表示256种颜色。它使用LZW无损压缩算法来对图像进行压缩,之后这家公司又和几家其他的公司发明了PNG文件格式,并被更广泛地应用在Web以及其他领域。GIF支持动画,可以保存数个帧并不断地播放。关于动画的部分我们将会放到非常后面来讲,现在只谈谈GIF的透明。
在GIF文件的头部有一个调色板Palette,里面保存了颜色的信息。一般而言,如果对GIF进行LockBits的操作,只能把它lock成Format*bppIndexed,这样才不会导致前面调色板信息的丢失,在处理上也更方便一些。在调色板里面定义了透明的颜色,也就是说当实际数据为这个颜色时,那个位置的颜色为透明。让我们来看看Palette是怎么使用的。 顺便再说一句,GIF没有半透明,只支持完全透明或者不透明。此外,在一个调色板中,只有一种颜色可以设置为透明,这是GIF标准所决定的。
1 public static unsafe void ConvertTransparancyGif(int colorIndex, string baseFile, string outputFile)
2 {
3 using (FileStream fs = new FileStream(baseFile, FileMode.Open, FileAccess.Read))
4 {
5 Bitmap img = (Bitmap)Image.FromStream(fs, false, false);
6 int width = img.Width;
7 int height = img.Height;
8
9 Bitmap resultbmp = new Bitmap(width, height, PixelFormat.Format8bppIndexed);
10 ColorPalette palette = resultbmp.Palette;
11 int n = 0;
12 foreach (Color tc in img.Palette.Entries)
13 {
14 palette.Entries[n] = Color.FromArgb(255, tc);
15 n++;
16 }
17
18 palette.Entries[colorIndex] = Color.FromArgb(0, palette.Entries[colorIndex]);
19 resultbmp.Palette = palette;
20
21 //now to copy the actual bitmap data
22 BitmapData src = img.LockBits(
23 new Rectangle(0, 0, width, height),
24 ImageLockMode.ReadOnly,
25 img.PixelFormat);
26
27 BitmapData dst = resultbmp.LockBits(
28 new Rectangle(0, 0, width, height),
29 ImageLockMode.WriteOnly,
30 resultbmp.PixelFormat);
31
32 byte* pSrc = (byte*)src.Scan0.ToPointer();
33 byte* pDst = (byte*)dst.Scan0.ToPointer();
34 int offset = src.Stride - width;
35
36 //steps through each pixel
37 for (int y = 0; y < height; y++)
38 {
39 for (int x = 0; x < width; x++)
40 {
41 pDst[0] = pSrc[0];
42 pDst++;
43 pSrc++;
44 }
45 pDst += offset;
46 pSrc += offset;
47 }
48
49 //unlock the bitmaps
50 img.UnlockBits(src);
51 resultbmp.UnlockBits(dst);
52
53 resultbmp.Save(outputFile, ImageFormat.Gif);
54
55 img.Dispose();
56 resultbmp.Dispose();
57 }
59 }
请注意,在这里,我读图的时候和我之前推荐的方法不同。 我没有创建一个新的Bitmap,这是因为在创建新的Bitmap的时候,调色板信息会完全丢失,所以Indexed的格式不可以随意进行复制,否则将造成信息的丢失。这也就是为什么当时我说这是一个土办法的原因。真正的好办法是复制那个流,而不是直接去复制Bitmap。不过那是看需求的。在创建一个带透明颜色的GIF的时候,只要创建一个调色板,就一切OK了。这比Alpha通道修正要简单。还可以参考KB 319061 http://support.microsoft.com/kb/319061/en-us
最后提一句,Bitmap类还提供了一个MakeTransparent方法用于设置透明颜色,不过只对PNG有效。
posted @
2008-10-29 18:21 Hotcan 阅读(1381) |
评论 (4) |
编辑
6.透明,半透明和不透明
这是个大题目。在WinForm/WPF里面我们经常会看到一些关于透明的属性,比如Backcolor里面可以选择Transparant, Form里面有一个叫Opacity的属性。都是和透明以及透明度相关的。在其实是在GDI+应用层上的一些东西,在这里我就不讲了。主要从更基本的地方讲起,其中还包括两块完全不同的内容。
6.1 Alpha
我们在上一讲中提到了PixelFormat,当时我们在LockBits的时候把PixelFormat设定成为Format24bppRgb。但是如果你仔细研究,会发现其实里面有各种各样的图片格式,其中有一种叫做Format32bppArgb。这个意思是说除了RGB,在图像中还存在一个通道,叫做A。这个A就是用来描述当前像素是透明,半透明,还是全透明的分量。这个通道是2个叫Catmull和Smith在上世纪70年代初发明的。通过这个分量,我们可以进行alpha混合的一些计算。从而使表面的图像和背景图像混合,从而造成透明半透明的效果。在这种格式下A作为一个byte,取值可以从0到255,那么0表示图像完全透明,则完全不可见,255则表示图像完全不透明。每个像素都可以实现这种透明或者半透明的效果。更详细解释可以参考http://en.wikipedia.org/wiki/Alpha_compositing,或者去买本数字图像处理的书回来看。让我们来看看下面这段代码,这个函数可以把图像变成半透明的。
1 public unsafe Bitmap GenerateBitmap(byte alpha)
2 {
3 FileStream fs = new FileStream(image, FileMode.Open, FileAccess.Read);
4 Image img = Image.FromStream(fs, false, false);
5 Bitmap bmp = new Bitmap(img);
6 img.Dispose();
7 fs.Close();
8
9 int width = bmp.Width;
10 int height = bmp.Height;
11
12 BitmapData bmData = bmp.LockBits(
13 new Rectangle(0, 0, width, height),
14 ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
15
16 byte* p = (byte*)bmData.Scan0;
17 int offset = bmData.Stride - width * 4;
18 for (int j = 0; j < height; j++)
19 {
20 for (int i = 0; i < width; i++)
21 {
22 p[3] = alpha;
23 p += 4;
24 }
25 p += offset;
26 }
27
28 bmp.UnlockBits(bmData);
29
30 return bmp;
31 }
32
大家可以注意一下第17,22和23行,由于图像格式不对了,所以我们计算offset和递加的操作都该了,此外由于用小数端存储方式,Alpha通道在p[3]的位置。
顺便提一句,还有一种格式叫做Format32bppPArgb,这叫做premultiplied alpha,就是说在RGB分量里面,alpha分量的数据已经被预先乘进去了。比如说,一个半透明的红色点,在ARGB下,矢量是(255,0,0,128),而在PARGB下就变成了(128,0,0,128)。这是为了不要每次都做乘法。
还有要注意的是,如果你想把这个Bitmap保存成为一个文件,那么必须用png格式,才能够保存alpha通道的信息。如果你存为JPG/BMP/GIF,那么alpha通道的信息将会被丢失。如果存为BMP,那么文件格式将变成Format32bppRgb,其中1个字节不再使用;如果保存为JPEG,那么是Format24bppRgb;存为GIF,格式将变成Format8bppIndexed。根据标准,BMP/JPG本来就不支持透明通道,所以没有可能保留透明信息。GIF倒是支持透明,但是GIF中颜色的信息都是索引,所以Alpha的解释对GIF完全没有效果,接下去我们来分析怎么样使用GIF的透明。
posted @
2008-10-29 16:16 Hotcan 阅读(1260) |
评论 (5) |
编辑
5.读图是快了,处理怎么还是慢?
GDI+的Bitmap类提供了两个罪恶的函数GetPixel, SetPixel,用来获取某个像素点的颜色值。这个2个函数如果只调用一次两次也就罢了,万一我想把整张图片加红一点,用下面的代码,我估计你等到黄花菜都凉了,还没有算完呢。 看看下面的代码是怎么写的。
1 FileStream fs = new FileStream(image, FileMode.Open, FileAccess.Read);
2 Image img = Image.FromStream(fs, false, false);
3 Bitmap bmp = new Bitmap(img);
4 img.Dispose();
5 fs.Close();
6
7 for (int j = 0; j < bmp.Height; j++)
8 {
9 for (int i = 0; i < bmp.Width; i++)
10 {
11 Color color = bmp.GetPixel(i, j);
12 color = Color.FromArgb(color.R + 20, color.G, color.B);
13 bmp.SetPixel(i, j, color);
14 }
15 }
代码逻辑很清楚,第1到第5行,写得很好,用了我们在前几节里面的方法,读图速度飞快且不锁文件。当然如果不用覆盖原始文件,不用复制都可以,速度就更快了。接下来我们对图像做一个循环,一行一行更新图像的数据。殊不知GetPixel和SetPixel是GDI里面耗费最大的函数之一,此外bmp.Height和bmp.Width也是慢得够呛,如果处理一张500M像素的照片,您可以去喝杯茶,睡一觉再回来了。
Bitmap有个方法叫LockBits,就是把图像的内存区域根据格式锁定,拿到那块内存的首地址。这样就可以直接改写这段内存了。这个方法的设计是挺好,可惜都是C++作为源泉来的,.NET Framework里面根本就不推荐用指针,VB里面根本也就没有指针。最后设计了一个鸡肋的IntPtr,将就掉了这个问题。C#其实还好,可以用unsafe code,也算是间接用了指针,VB.NET就痛苦了,需要用Marshal.Copy把内容Copy到一个byte数组里面,然后处理完了再Copy回去。所以结论就是,要用GDI+做图像处理,最好别用VB.NET,否则内存翻倍。
让我们来看看快速的写法,注意在编译的时候加上unsafe开关,允许C#使用指针:
1 FileStream fs = new FileStream(image, FileMode.Open, FileAccess.Read);
2 Image img = Image.FromStream(fs, false, false);
3 Bitmap bmp = new Bitmap(img);
4 img.Dispose();
5 fs.Close();
6
7 int width = bmp.Width;
8 int height = bmp.Height;
9 BitmapData bmData = bmp.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
10 byte* p = (byte*)bmData.Scan0;
11 int offset = bmData.Stride - width * 3; //only correct when PixelFormat is Format24bppRgb
12
13 for (int j = 0; j < height; j++)
14 {
15 for (int i = 0; i < width; i++)
16 {
17 p[2] += 20; //should check boundary
18 p += 3;
19 }
20 p += offset;
21 }
22
23 bmp.UnlockBits(bmData);
24 bmp.Dispose();
1-5行一样的,不多说了。第7,8行保存一个临时变量,不要每次都调用,bmp.Width, bmp.Height。
第9行是把图像内容锁定到系统内存。这个函数有2个重载,第二种比较复杂,是把用户内存中的内容锁定。这个以后再说。第一种,也就是我们现在使用的这种,是把图像的内容根据一定的格式放到内存里面,这里我们使用的是Format24bppRgb,也就是24位色。在这种格式下3个字节表示一种颜色,也就是我们通常所知道的R,G,B, 所以每个字节表示颜色的一个分量。
第10行用了一个指针,指向这段内存的首地址。 这样我们可以直接来修改图像的内存信息。因为每个颜色分量都是一个字节,所以用byte的指针,如果是Format48bppRgb,每个分量用2个字节,那就该用Short指针了。
第11行需要多说两句,Stride是指图像每一行需要占用的字节数。根据BMP格式的标准,Stride一定要是4的倍数。据个例子,一幅1024*768的24bppRgb的图像,每行有效的像素信息应该是1024*3 = 3072。因为已经是4的倍数,所以Stride就是3072。那么如果这幅图像是35*30,那么一行的有效像素信息是105,但是105不是4的倍数,所以填充空字节,Stride应该是108。这一行计算出来的offset就是3。这里再留个问题,如果是16色图,也就是4位色,一幅50*50的图像,Stride又应该是多少呢?
第13-21行就是循环的处理。其中第17行需要注意以下。这句话其实是错的,因为p是byte,没有符号,最大值是255, 加20可能会溢出。如果你不希望它溢出,那么可以改成行1,如果希望溢出就是最大值,那么可以用行2。
1 p[2] = checked((byte)(p[2] + 20));
2 p[2] = (byte)Math.Min(byte.MaxValue, p[2] + 20);
大多处情况下,图像处理用的都是行2的做法,但是这样的话性能损失还是比较厉害的,需要计算一个最大值,还有一个类型的转换,我还在研究有什么更快的办法。还有一点,BMP图像里面用的是小数端的存储方式(Little Endian),所以实际存储的图像顺序是G,B,R,这里要把图像的红色分量增加一些,改变的是第三个字节而不是第一个。
最后两行就不说了,处理完毕需要Unlock,bitmap必须被dispose掉。
用以上代码进行图像处理的速度已经飞快了,最慢的部分就是调用那个LockBits的函数,这个速度基本上跟GDI是差不多的。但是如果你还是觉得慢,那还有以下2种办法可以提高性能,不过它已经远远超过GDI+的范畴了。
1.别用GDI+的方法读图像,自己把图像文件读出来。如果是BMP倒是好办,要是用JPG,TIF,PNG,GIF....如果你足够牛,可以随手写个FFT或者DCT,那我倒是建议可以自己写写看,把JPG解压缩出来,自己修改修改再压缩回去。要是PNG/GIF/GIF 98,就是那个会动的GIF,我就无语了。GIF的标准是公开的,不过时人家的专利,你写了是要付钱的。所以,还是算了吧。自己读BMP可以非常快地把图像处理掉,连LockBits都不需要。
2.如果图像超级大,你又用了后面的行2进行处理,你对性能的要求又无比苛刻,那还是有条路可以走。直接用MMX/SSE/3D Now指令集,4个字节同时上。你去谢谢Intel/Amd给了那么好的指令集把,可惜C#你就别想了,没有办法直接用的。自己用C++调用MMX指令集写个Dll,然后用interop调用。Interop的过程当中也是有损失的,Managed的内存空间用C#里面的Marshal是需要被转换的。所以如果真的要这样做,我推荐使用C++.NET,两块东西都能写。这里我就不给出用MMX/SSE的例子了。最后再提一句,MMX对于图像处理也足够了,SSE主要是给浮点运算的,如果你有图像渲染的那种,用SSE会快很多,那是图形学的内容了,偶不懂,不多说,怕被人用棒子打。
posted @
2008-10-23 11:51 Hotcan 阅读(1577) |
评论 (12) |
编辑
4. 为啥读个图那么慢?
一般来说,读图可以用以下几种方法:
1 public static Image FromFile(string filename);
2 public static Image FromFile(string filename, bool useEmbeddedColorManagement);
3 public static Bitmap FromHbitmap(IntPtr hbitmap);
4 public static Bitmap FromHbitmap(IntPtr hbitmap, IntPtr hpalette);
5 public static Image FromStream(Stream stream);
6 public static Image FromStream(Stream stream, bool useEmbeddedColorManagement);
7 public static Image FromStream(Stream stream, bool useEmbeddedColorManagement, bool validateImageData);
其中3,4两种方法主要用在从Windows句柄中拿到原来DIB的Bitmap,经常是用在需要读取资源图像啊,或者GDI图像的时候。最经常用的,无非是1,2和5,6,7,其中1和5类似,2和6类似,方法5,6会使用不同的参数调用7。我们可以做一个简单的性能测试。拿一张8000*7000大的TIF图像,这样的图像一般大小都在100M以上,用不同的参数调用方法7, 看到以下结果。
1 {
2 Stopwatch watch = new Stopwatch();
3 watch.Start();
4 FileStream fs = new FileStream(image, FileMode.Open, FileAccess.Read);
5 Image img = Image.FromStream(fs, true, true);
6 Console.WriteLine("Use ICM: {0}. Validate: {1}, ElapsedTicks:{2}.", true, true, watch.ElapsedTicks);
7 watch.Stop();
8 fs.Close();
9 }
10
11 {
12 Stopwatch watch = new Stopwatch();
13 watch.Start();
14 FileStream fs = new FileStream(image, FileMode.Open, FileAccess.Read);
15 Image img = Image.FromStream(fs, false, true);
16 Console.WriteLine("Use ICM: {0}. Validate: {1}, ElapsedTicks:{2}.", false, true, watch.ElapsedTicks);
17 watch.Stop();
18 fs.Close();
19 }
20
21 {
22 Stopwatch watch = new Stopwatch();
23 watch.Start();
24 FileStream fs = new FileStream(image, FileMode.Open, FileAccess.Read);
25 Image img = Image.FromStream(fs, true, false);
26 Console.WriteLine("Use ICM: {0}. Validate: {1}, ElapsedTicks:{2}.", true, false, watch.ElapsedTicks);
27 watch.Stop();
28 fs.Close();
29 }
30
31 {
32 Stopwatch watch = new Stopwatch();
33 watch.Start();
34 FileStream fs = new FileStream(image, FileMode.Open, FileAccess.Read);
35 Image img = Image.FromStream(fs, false, false);
36 Console.WriteLine("Use ICM: {0}. Validate: {1}, ElapsedTicks:{2}.", false, false, watch.ElapsedTicks);
37 watch.Stop();
38 fs.Close();
39 }
我们来看看执行结果:
Use ICM: True. Validate: True, ElapsedTicks:51853544.
Use ICM: False. Validate: True, ElapsedTicks:52507953.
Use ICM: True. Validate: False, ElapsedTicks:6880.
Use ICM: False. Validate: False, ElapsedTicks:5187.
所以你看到,罪魁祸首是Validate。当然Validate其实是有用的,图像的格式各种各样,就单BMP就有好多种不同的放法,更不要说JPEG, PNG, GIF了。JPEG有普通的用离散余弦变换生成的最早的,还有用小波生成的JPEG 2000。 PNG没啥研究,不知道里面什么名堂。GIF有静态的和能动的GIF 98.所以后面的数据流出问题是很正常的事情,验证在很多情况下是需要的,但是造成的性能损失实在是很大。 如果你确定这些照片一般不会错,而且读图又需要很好的性能,比如获得一个目录里面所有图像的缩略图,那还是用最后一种方法吧。
这里再提一下ICM. ICM是色彩管理中的一个概念,对于相同图像不同设备而言,呈现的色彩可能是不同的。比如一个液晶显示器和一个CRT显示器,先是一张相同的图片,色彩可能相差十万八千里。这主要是因为不同设备能够显示的色彩空间是不同的。色彩空间又是个值得研究的话题,这里就不多说了。对于色彩的研究可以写一本好厚的书。这个世界上最权威的一个网站http://www.color.org/ 可以告诉你很多有用的信息和数学公式,包括Gamma,色彩密度的概念,等等诸如此类。有些图像格式是可以把ICM的信息写在文件中的,比如JPEG,所以这个参数是说是否使用文件内嵌的ICM信息还是用设备默认的信息。
其实第7个函数是在.NET 2.0以后才出现的。1.1出来以后读图慢的这个问题被别人骂得要死,所以新版本中加了一个函数解决了这个问题。当时有个叫Justin Rogers的人写了个类叫ImageFast (http://weblogs.asp.net/justin_rogers/articles/131704.aspx),自己用Interop去调用GDI+的方法,跳过了icm和validation。在.NET 2.0之后,这个问题才算是被解决了。这又是.NET Framework对GDI+封装不完善的一个例子。
posted @
2008-10-21 18:05 Hotcan 阅读(1590) |
评论 (5) |
编辑
3. System.OutOfMemoryException
首先我们还是来看一段代码
Bitmap bmpTemp = new Bitmap(image);
Bitmap bmp = new Bitmap(bmpTemp);
bmpTemp.Dispose();
Bitmap bmp2 = bmp.Clone(new Rectangle(1, 1, bmp.Width, bmp.Height), PixelFormat.Format24bppRgb);
前一段我们还是使用了在第2节中介绍的读图像文件的方法,之后我们使用Bitmap.Clone()方法复制一份拷贝。其实这是除了new Bitamp()之外的另一种复制图像的方法。这个方法强大的地方是它可以复制图像的一块区域。不幸的是上面那句话会给你一个System.OutOfMemoryException。这个Exception索性连ErrorCode都不给你了,innerException还是空。Krzysztof Cwalina和Brad Abrams写过一本叫"Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries",不幸的是我们的System.Drawing下面的多个类都不符合这本书写的内容。说到这本书,中文版还是我一个同事翻译的,做一把广告。(http://www.amazon.cn/mn/detailApp?qid=1224568025&ref=SR&sr=13-2&uid=168-7715813-6370648&prodid=zjbk366428)
那么这个问题到底是为什么呢?我们再用Marshal.GetLastWin32Error() 来看看错误所在。可惜这次不灵了,我们拿到了一个2的错误代码,在winerror.h里,它是
//
// MessageId: ERROR_FILE_NOT_FOUND
//
// MessageText:
//
// The system cannot find the file specified.
//
#define ERROR_FILE_NOT_FOUND 2L
完全没有什么关系。细心的读者可能已经发现了,我在Rectangle里面传递的参数是1,1,而不是0,0。这也就是说这个矩形已经超过了原始图像的大小。所以会报这个异常。但是是不是应该报OutOfMemory呢?让我们再看看MSDN.上面倒是说的很清楚,如果rect区域超出,报OutOfMemory,如果rect宽或者高是0,那么报ArgumentException. 为什么不统一成一个ArgumentException呢?很费解。 其实Rect的有效性在函数的第一行就可以直接判断出来了,根本不需要到实际处理的时候才抛内存不足的异常。这个封装写得的确不怎么样。
(To be continued)
posted @
2008-10-21 14:11 Hotcan 阅读(1170) |
评论 (2) |
编辑
今天突然收到一封信,说我那个极度复杂的Marshal的问题被解决了(http://www.cnblogs.com/hotcan/archive/2005/01/12/91007.html)。顿时感觉好久没有在这个blog上写东西了。想当年刚毕业没事情干的时候,还是写得很不亦乐乎的。所以决定炒炒冷饭,写一篇技术文章,以说明我还没有忘记这里。
1.GDI+的前世今生
GDI+全称图形设备接口,Graphics Device Interface (GDI) ,他的爸爸叫做GDI, 用C写的。Windows XP出来以后用C++重新写了一下,变成了GDI+。从.NET Framework 1.0开始,GDI+就被正式封装在了.NET Framework里面,并被广泛地应用到了所有和图形图像相关的程序中。不幸的是,这个GDI+引入了微软有史以来最大的2个patch,造成了Microsoft IT, Support, Developer, Tester的无数麻烦。[1][2]
GDI+没有用显卡加速,所以Windows Vista推荐用Windows Display Driver Model (WDDM)了,支持渲染,3D加速。不过普通的应用程序,用GDI/GDI+其实是完全足够了,所以GDI+是在微软平台上开发图形图像程序的最好选择了。至少现在没有听说微软准备重新写GDI。
GDI+ 可以用来做图形处理,也可以做图像处理。这里只分析几个使用.NET Framework容易出错的地方。
2. GDI+一般性错误(A generic error occurred in GDI+)
这是使用GDI+的时候最滑稽的一个Exception,里面啥信息都没有。对于刚刚开始使用.NET Framework开发者来说,很难发现这个问题到底是为什么。
我们先来看看下面一段代码
string fileName = "sample.jpg";
Bitmap bmp = new Bitmap(fileName);
bmp.Save(fileName, ImageFormat.Jpeg);
这段代码的目的是要打开一个Bitmap,然后保存。可惜这段代码一定会给你一个GDI+一般性错误:
System.Runtime.InteropServices.ExternalException
其中的Error Code是0x80004005, innerException是空。如果你查Windows的Error Code表,会发现这个错误原因是“Unspecified Error”,还是什么都不知道。这其实是.NET Framework封装不好的问题,我们可以调用
Marshal.GetLastWin32Error()
拿到Win32的Error, 32。这个错误代码就有点信息量了,在winerror.h里面,我们可以找到下面的定义:
//
// MessageId: ERROR_SHARING_VIOLATION
//
// MessageText:
//
// The process cannot access the file because it is being used by another process.
//
#define ERROR_SHARING_VIOLATION 32L
原来是文件不能写。其实MSDN里面有一句话,The file remains locked until the Bitmap is disposed。所以文件读取以后是锁着的,没有办法写。那如果我想做点改动然后再保存原来的文件怎么办呢?
这里有个土办法可以搞定这个问题
Bitmap bmpTemp = new Bitmap(image);
Bitmap bmp = new Bitmap(bmpTemp);
bmpTemp.Dispose();
bmp.Save(image, ImageFormat.Jpeg);
只要把当前的图像复制一份,然后把旧的Dispose掉,那个文件就不被锁住了,这样就可以放心覆盖原始文件了。
想想如果你要用GDI+写一个Painter,很容易你就会遇到这个问题。
(To be continued)
[1]. Microsoft Security Bulletin MS04-028 Buffer Overrun in JPEG Processing (GDI+) Could Allow Code Execution (833987) http://www.microsoft.com/technet/security/bulletin/MS04-028.mspx
[2].Microsoft Security Bulletin MS08-052 – Critical Vulnerabilities in GDI+ Could Allow Remote Code Execution (954593)http://www.microsoft.com/technet/security/bulletin/MS08-052.mspx
posted @
2008-10-21 11:58 Hotcan 阅读(1801) |
评论 (6) |
编辑
好久没有在这里写Blog了,发现最早的一篇还是去年的4月。其他陆陆续续的东西也都在MSN Space上。不争气的MSN Space也实在让人觉得不爽,动不动就Not available, sigh....今天一定要过来擦擦灰,让Blog 焕发生机。
日子过的依然是忙碌,但是终于归于平淡,鉴于2006年自我感觉没有做出什么成绩,所以也有一些压抑和郁闷。我应该继续想想自己究竟想做什么事情,不想做什么事情。让我自己和身边的人过得更好。
不是愚人节的话语。
posted @
2007-04-01 01:25 Hotcan 阅读(359) |
评论 (0) |
编辑
澄清一下,买房只是为了自己住得开心,结婚的事情,大概八字还没有一撇。
posted @
2006-04-01 18:42 Hotcan 阅读(466) |
评论 (4) |
编辑
都说房子要跌了,不过我还是莫名其妙出手买了一套房子,付出了去年所有的积蓄还背了一身的债。那个小区居然卖房的时候还有人排队,房子的环境不错,质量也不错。今年继续存钱,为了明年的装修,要不了多久,我的存款数又会变成零。人总是在赚钱,花钱,这个过程不知道什么时候才会结束。
posted @
2006-04-01 18:37 Hotcan 阅读(311) |
评论 (3) |
编辑
Hi, Everyone,
Thanks for enthusiasm in my blog. Today, I would like to broadcast an advertisement. Microsoft SH ATC is eager for technical talents. We are currently hiring Software Development Engineer, Software Development Engineer in Test and Program Manager to work on the next generation Windows Server and Visual Studio in Shanghai Server & Tools team (the job descriptions are attached bellow). We also have leadership opportunities. It's a new team so you'd have the opportunity to come in and really have an impact; besides, we can even exchange our ideas in person instead of only on the blog J. So, if anyone takes interest in working here, please do not hesitate to contact our HR--Flora, 86-21-61455750, or i-floraz@microsoft.com.

This is a Shanghai based position.
JD for Software Development Engineer
Windows Server division is embarking on an incubation project (Project Silk Road) to develop engineering capability in China under the leadership of Advanced Technology Center (ATC). Project Silk Road is an integral part of the Windows Server division. While Project Silk Road has the near term focus of building a world-class engineering team who can effectively contribute in Windows Server division's engineering effort, it's a long-term strategic investment that will lead to exploration of new revenue opportunity in China market.
Project Silk Road is looking for high potential entry level Software Development Engineer to work on development of Windows Server Compute Cluster Edition. High Performance Computing is one of the fastest growing server workloads worldwide, driven by enterprises embracing server clustering and desktop cycle stealing to solve technical and business problems that only a few years ago required dedicated supercomputers. HPC features some of the most demanding and exciting application scenarios that drive innovation in distributed system development, large scale management, parallel computing, networking and storage. Winning in this important space against entrenched Linux/OSS competition requires creativity, innovation, speed of execution, and deep engagements with hardware, software and academic partners.
Applicants should have a minimum of 3 years of accomplishment in designing, developing, managing and shipping products and a full understanding of the development cycle. Strong communication and development skills a MUST. A MS degree in computer science, an engineering-related field, or equivalent experience required. Detailed knowledge of programming in .net framework, C#, or equivalent required. Working knowledge of management tools, enterprise application deployment, Windows performance counters, security attack mitigation, XML, SOAP, are pluses.
JD for Software Development Engineer in Test
This is a Shanghai based position.
Are you excited about the opportunity to work on the next version of Visual Studio.Net, a product used by millions of software developers worldwide? Are you interested in working in a challenging environment where you will need to learn quickly and deliver results consistently? The Developer Division is establishing a new R&D center in Shanghai, and we’re looking for experienced SDET candidates who are eager to help build a strong team in China that will directly impact the quality of our next product release.
You will be an integral part of Visual Studio’s engineering team. You will work closely with feature team members in testing, program management and development in both China and the US. You will be responsible for test design and development for new features in Visual Studio as well as contributing to the test automation framework used globally by the Developer Division. Most importantly, you will perform customer-focused testing to ensure a high-quality release.
Candidates must have strong problem solving skills and a desire to learn new technologies. The ideal candidate will have at least 2 years of software development/test experience and have excellent coding and debugging skills. Experience in the following areas is a plus: UI testing, test automation, automation frameworks, and .NET and C#. Excellent written and verbal English skills are essential. A BS/MS in Computer Science/Computer Engineering or related field is required.
JD for Program Manager
This is a Shanghai based position.
The Windows Server Division China Team is seeking an experienced and enterprising Program Manager to coordinate and manage its China outsourcing and Technical Adoption Program (TAP) efforts. This is a great opportunity to join a start-up team and contribute to product outsourcing engagements in MS. This position interfaces with multiple internal and external organizations across multiple geographical regions and drives accountability for cross-organization activities. The candidate will be exposed to best practices in outsourcing product work which will become common practice at MS and the industry. The processes and practices implemented in this project are being used to develop a model for best outsourcing practices at MS.
The ideal candidate is a self-starter and highly adaptable to a start-up environment; is organized and possesses excellent communication skills. This position has the following duties:
- Develop and maintain a best-in-class overseas outsourcing team
- Internal business development activities to acquire and on-board new outsourcing projects
- Monitor and enforce compliance with MS policies and guidelines (COI, PBM, Legal, Finance, product team and others)
- Identify new technical partnership candidate companies to participate in Technical Adoption Programs
- Drive new TAP programs and act as liaison between product team and TAP partner to facilitate progress
- Define product team and vendor expectations; manage vendor relationships
- Drive processes and productivity enhancements
- Ensure optimal resources utilization and cost efficacy
- Devise vendor and project performance metrics; track performance history
- Perform audits and manage resolution of audit findings with vendor companies
- Produce periodic newsletters to project owners and business leadership
- Periodic travel to Redmond HQ or within China might be required
Candidates must:
- Be passionate and committed to developing a good relationship with vendors, which results in the delivery of very high-quality products
- Have good working knowledge of product maintenance processes and software development product lifecycle
- Have strong program management skills
- Ability to deal with complexity in a fast-paced environment
- Have excellent problem solving skills
- Have excellent communications skills
- Have ability to work effectively across multiple groups.
- Be passionate about building better Windows Server products
- have a Bachelor’s degree is required in a technical discipline and three or more years experience in the software industry
posted @
2006-03-15 11:49 Hotcan 阅读(797) |
评论 (2) |
编辑
前年的生日,上午在家里和爸爸妈妈吃饭,下午去女朋友家里吃饭,一个小蛋糕,几张照片。然后生活发生变化。
去年的生日,北京,晚上在网上陪女朋友。被问到一个问题,回答不了。然后生活再次变化。
今年的生日,持续了2天,昨天是中国的生日,今天在美国还是生日。和同事去中餐馆吃饭,生活还会变化么?
posted @
2005-08-10 13:29 Hotcan 阅读(951) |
评论 (7) |
编辑
上周五搞定了签证,在美国领事馆呆了一个下午,签证官就问了我一个问题,你是写什么软件的?就用中文对我说,通过了。现在这个team要做的事情也基本上做完了,下个星期休息三天以后,我就要飞到地球的另一端去了。
posted @
2005-07-17 20:59 Hotcan 阅读(654) |
评论 (0) |
编辑
今天是我第二次走美罗大厦的楼梯。第一次应该是在3年前,当时在23楼当intern的时候。那次请师姐吃可爱多,然后她说要走楼梯消耗掉吃可爱多的能量,于是从1楼走到了23楼。今天看到中午的时候楼下人太多,于是便决定锻炼一把,从1楼走到25楼。感觉真是比上次要累多了,看来很久不锻炼,真是体力不支,sigh,不得不感叹老了。
posted @
2005-06-28 13:31 Hotcan 阅读(996) |
评论 (4) |
编辑
看来我的blog又有东西可以写了,如果不是太忙的话。我将在
2005年7月25日这个日子开始在美国工作至9月30日,然后大概可以在美国享受一下国庆的假期,在美国到处玩玩。鉴于我已经拿到了驾照,所以活动范围也变得非常大了,各位可以期待我的新游记了。
posted @
2005-06-15 12:50 Hotcan 阅读(1406) |
评论 (6) |
编辑
由于工作过于繁忙,而且我已经没有什么兴趣无病呻吟,感叹来感叹去,所以我决定从5月1日起改变这个Blog的基调。以后在这个blog中会出现以下三种类型的文章。
1,技术型, 对一些计算机技术问题的分析和研究,更偏重于.NET,有兴趣的话我会写一些关于Indigo, .NET 2.0上的内容。
2,探讨型, 对一些事件或现象的评论,但不会涉及政治等敏感内容。
3,游记及其他,一些照片等等,如果没有什么特殊情况,将不再涉及个人的一切想法。
posted @
2005-04-30 16:50 Hotcan 阅读(1396) |
评论 (9) |
编辑
头疼,一开始以为是长智齿,痛苦得一塌糊涂,忍不住去拔掉了,可是还痛。再次去医院,做CT,验血,发现是蝶窦炎,脓流不出来,痛苦至极。然后再去五官科医院看,医生似笑非笑地说这个是很痛的,也没什么办法把它弄出来,开了点扩张鼻孔的东西,继续吃消炎药,止痛片。我的命咋那么苦?
posted @
2005-04-15 08:40 Hotcan 阅读(1407) |
评论 (10) |
编辑
感觉很久没有写Blog了,每天在SharpReader上看着朋友们的blog一篇一篇地更新,而我的blog却总是保持停滞。不是没有什么好写,而是真的没有时间。
转眼工作已经2个星期了,虽然说没有做什么具体的事情,但是总有不同的事情吃掉时间,什么感觉都没有,就这样过去了。
posted @
2005-04-08 18:56 Hotcan 阅读(884) |
评论 (6) |
编辑
L'invitation au Voyage
“旅行的邀约”摄影展
Photographers:
Jian shuo Wang; Edward Wang; Claire Hu
Venue:
上海市莫干山路50号苏州河艺术区4号楼B座9室
建议交通方式:地铁轻轨三号线中潭路站+Taxi
Exhibition Opening:
2005年4月1日至2005年4月30日
每天10点至18点
Special Thanks:
posted @
2005-03-31 13:12 Hotcan 阅读(590) |
评论 (1) |
编辑
3月25日是必须离校的日子,这个星期五是我最后一天呆在徐汇校区,这个校区我总共呆了3年半。
早上一早8点起来,赶到学校去参加实验室的项目总结会,然后又参加了项目申请会。这样一个上午就没有了。中午去取我的硕士毕业照,英文的硕士学位证书之类的东西,然后把实验室最后的东西搬走,并把所有的文档资料当作废纸卖了。下午回去发现电脑实在不行了,决定重装的过程中发现combo已经没有办法读CD了,这样我就不能直接用Recovery Disk恢复了,所以再次赶到学校去找xp key重新安装,在实验室蹭到晚上10点。
最后一天我还是那么忙碌,为上海交大滴最后一滴汗。为实验室努力最后一天。
posted @
2005-03-26 22:28 Hotcan 阅读(723) |
评论 (2) |
编辑
说了那么长时间开车,现在也有点眉目了。前一段时间一直在练习侧方移位,倒车现在也差不多了,所以可以开开小路了。昨天第一次去开了小路,几个项目做得还有些问题。今天就直接冲上了大路,从驾校开到了嘉定镇上,中途差点撞到了一个老太太。幸好反应及时,看来开车还是很玄的,昨天睡觉太少了。下下周考试,然后继续路考,争取5月份拿到驾照。
posted @
2005-03-24 21:57 Hotcan 阅读(629) |
评论 (4) |
编辑
晚上心血来潮冲到了闵行,乘上了久违的徐闵线,在办完上海交通大学离校手续的这天,再次去生活过3年的地方看看,因为那里已经发生了巨大的变化,而我以后也不会再属于这个学校。
走在过去的路上,会勾起一幕幕的回忆,哪怕是经过一幢楼,一幢房子,都会让我想起以前的点点滴滴。毕竟生活过三年的地方,或多或少会留下些什么。前几天看一篇文章,上面说“和你一起相处过的人,或许最后因为这种或那种原因离开了你,但是有时候你会在不经意间发现,那些人对你造成了潜移默化的影响,或许这一辈子都没有办法忘记了。那些影响,便成了时间的印记,永远和你在一起了。”我想,一个生活过的地方,无论离开了多久,也都应该会有一些痕迹。
其实我只能算认识一半的闵行校区,另一半是我从来没有去过的,而今天晚上我去了那一半。很多楼是我没有见过的,还有很多楼正在造,整个闵行校区的大工地上显得很热闹,到处都是吊车和泥土,中间已经竣工并投入使用的几幢楼倒显得有些另类,相信过不了多久,所有的楼都会造好的。而那些楼,应该不会在我的脑海中有什么印象了,尽管它们都属于同一个地方,上海交通大学闵行校区。
辛弃疾说:物事人非万事休。如今物也早已不是那个物,人就更不是那个人了。能留在心中的,怕只是原来的那个物,原来的那个人了。
posted @
2005-03-17 23:07 Hotcan 阅读(650) |
评论 (8) |
编辑
东芝掌上电脑
Toshiba Pocket PC e350
主要参数:
Intel PXA255 Processor at 300MHz
64MB RAM SDRAM
3.5'' diagnal reflective TFT color display
SD slot for expandability
Infrared port
Built-in microphone & speaker
Stereo headphone jack
Rechargeable Lithium-lon battery
内置软件:
Microsoft Pocket PC 2003 operating system
Pocket Outlook
Pocket Word
Pocket Excel
Windows Media Player for Pocket PC
MSN Messenger
Notes
Voice Recorder
ActiveSync 3.7
Calculator
Solitaire 7 Jawbreaker
其他相关软件:本人搜集有相关PPC软件,可刻录附送
售:1100元
联系: 已经卖出
posted @
2005-03-17 13:21 Hotcan 阅读(2146) |
评论 (1) |
编辑
今天在家,没有做什么特别的事情,只是看了两部电影。
第一部来自于亚历山大-佩恩导演的《杯酒人生》,原名Sideways,讲述了步入中年的2个人在一次旅行中各自遇到了自己姓欣赏的女人,从而刻画他们之间心理的变化,并因此获得第62届金球奖7项提名。麦尔斯在和妻子离婚以后2年都非常郁闷,把自己沉浸在写小说之中,没有办法打开心扉去接受新的女人。而Jack却在快要结婚之前,又不断和其他的女人发生关系。他们所发生的事情,深刻地描绘了当代人在感情生活中状态。
另外一部来自徐静蕾,《一个陌生女人的来信》,改编自茨威格的同名小说。徐静蕾也因此而获得第52届圣塞巴斯蒂安电影节最佳导演奖。故事讲述了一个女人深切地爱着一个男人,并为他生了一个孩子。而这个男人却不曾记得她。2次把她当作是偶然相逢的女人而已。故事的结尾让人不禁唏嘘造化弄人。
"在评论模式下可以听到《一个陌生女人的来信》的主题音乐。"
posted @
2005-03-17 00:18 Hotcan 阅读(500) |
评论 (4) |
编辑
发信人: yaoxing (幸福深处 ), 信区: Love
标 题: zz女人跟着你,是要你疼的
发信站: 日月光华 (2005年03月11日19:19:33 星期五), 站内信件
女孩喜欢上了男人,对他很好,是很好的那种。她给他洗衣服,收拾房间,早晨买早点给他,小鸟依人的靠在男人身边。男人觉得有人这样无微不至的照顾是件很惬意的事情,于是他们顺理成章地在一起。男人习惯有女孩在身边的日子,可后来,女孩就离开了,是当男人在睡梦中的时候。
男人讲完之后一脸茫然的问我:“你说,我哪里做错了!我给她钱买化妆品,有人欺负她,我把那人揍了个半死,我这么爱她,她为什么就走了呢?”
我安静的听完,没办法给这个疑惑的男人一个满意的答案。我们从咖啡店走出来,过马路时男人瞅一个空挡便快步跑到对面向车流这边的我招手催我过去。我有些无奈的笑了。
我问男人是不是不愿意牵女孩的手。他说在家抱抱可以,在外面多不好意思啊。我说他过马路时一定比女孩快,他点头说你怎么知道?我说女孩在刷碗扫地的时候,他一定是悠闲的看着电视。男人摸着头说自己似乎明白了。我说,如果明白了就去挽回吧。
希望男人是真的明白了。
其实很多女人外表很坚强,内心却还是柔弱,需要男人呵护的。她不在乎你给了她多钱,却会永远记得你调皮的从路边花坛偷回的那朵放到她手中的月季花。她在厨房忙碌的时候,你从身后送来的一个吻会让她觉得幸福甜蜜。你们过马路时候,在左边的你紧紧握住她的手,不论是什么年纪,都会让她觉得安全。
世界上女人很多,美丽的、温柔的、聪明的、可爱的……可无论什么类型的女人,期待幸福的心情都是一样的。所以她们等待着一个男人的出现,等着这个男人对她们好。
其实女人期待的对自己好,是件很简单的事情。
她只希望自己的男人不要因为忙碌而忘记她的生日。想听他在耳边轻声说句“快乐吧,我的宝贝。”这时玫瑰也可以省略。她只希望做家务累的时候,他轻轻抚摩自己的额头说声“宝贝,喝了牛奶再睡吧。”即使对于家务男人一窍不通。她只希望害怕或者孤单的时候,男人在身边搂着她的肩膀坚定的对她说“别怕,有我。 ”
是的,有的时候,爱意是在不经意间流露的。可能男人你自己没感觉,可是女人却字一句的记在了心底。她们会用更多的爱恋回报你。
尝试着在出门之前吻一下你的女人。常常温存的告诉她,你有多么的爱她。休息的时候抢过她手里要洗的衣物。天气好的时候带她到公园散步。睡觉前给她讲讲公司里,回家路上看到的有趣的事情。偶尔耐心倾听女人讲的事情,即使你对白菜5角或是4角一大了,就说你又苗条了,如果裙子小了,就说如果大一点会更漂亮。逛街的时候可以拉着女人的手或者揽着她的肩膀,因为这样,她会觉得幸福。女人都希望在平凡中被呵护,被爱着。你温存的点点滴滴一定能让她闻到幸福的芳香。其实女人要的幸福很简单。你要耐心的对你的女人好,不需要如火山火热,也不需要如海浪汹涌,细水长流就足够让她幸福一辈子。
一个黄昏,我接到那个男人的电话。他很兴奋的告诉我,说女孩又回到了他身边。我问他是怎么做的,他说费了很大力气才约到女孩散步,还专挑路口走。过马路时候站在女孩左边,紧紧握住她的手。我笑了,说你现在明白了吧。男人嘿嘿的说:“明白了,明白了,她跟我,是需要我疼的。”
是啊,当上帝用亚当的肋骨造了一个夏娃时,就预示着男人该认真照顾身边那个是自己身上肋骨变的女子,好好爱她吧,否则你自己的胸口也是会疼痛的。
跟着你,是要你疼的!!
posted @
2005-03-12 22:20 Hotcan 阅读(2155) |
评论 (5) |
编辑