필요하신 분 받아가세요..
'C# > 자료실' 카테고리의 다른 글
| Multi Magic Square(마방진) 소스 (0) | 2008/06/16 |
|---|---|
| 주민등록번호 유효성 체크 (0) | 2008/06/16 |
| 에디트플러스 입니다.. (0) | 2008/06/16 |
| FMOD모듈을 이용한 음악 재생기 입니다. (1) | 2008/06/16 |
| 투명창이 지원되는 메모장 (0) | 2008/06/16 |
| 간단한 메신저 예제 프로그램 (0) | 2008/06/16 |
| Multi Magic Square(마방진) 소스 (0) | 2008/06/16 |
|---|---|
| 주민등록번호 유효성 체크 (0) | 2008/06/16 |
| 에디트플러스 입니다.. (0) | 2008/06/16 |
| FMOD모듈을 이용한 음악 재생기 입니다. (1) | 2008/06/16 |
| 투명창이 지원되는 메모장 (0) | 2008/06/16 |
| 간단한 메신저 예제 프로그램 (0) | 2008/06/16 |
| Multi Magic Square(마방진) 소스 (0) | 2008/06/16 |
|---|---|
| 주민등록번호 유효성 체크 (0) | 2008/06/16 |
| 에디트플러스 입니다.. (0) | 2008/06/16 |
| FMOD모듈을 이용한 음악 재생기 입니다. (1) | 2008/06/16 |
| 투명창이 지원되는 메모장 (0) | 2008/06/16 |
| 간단한 메신저 예제 프로그램 (0) | 2008/06/16 |
| Multi Magic Square(마방진) 소스 (0) | 2008/06/16 |
|---|---|
| 주민등록번호 유효성 체크 (0) | 2008/06/16 |
| 에디트플러스 입니다.. (0) | 2008/06/16 |
| FMOD모듈을 이용한 음악 재생기 입니다. (1) | 2008/06/16 |
| 투명창이 지원되는 메모장 (0) | 2008/06/16 |
| 간단한 메신저 예제 프로그램 (0) | 2008/06/16 |
| 주민등록번호 유효성 체크 (0) | 2008/06/16 |
|---|---|
| 에디트플러스 입니다.. (0) | 2008/06/16 |
| FMOD모듈을 이용한 음악 재생기 입니다. (1) | 2008/06/16 |
| 투명창이 지원되는 메모장 (0) | 2008/06/16 |
| 간단한 메신저 예제 프로그램 (0) | 2008/06/16 |
| 스크롤바 스타일 변경 소스 (0) | 2008/06/16 |
| 에디트플러스 입니다.. (0) | 2008/06/16 |
|---|---|
| FMOD모듈을 이용한 음악 재생기 입니다. (1) | 2008/06/16 |
| 투명창이 지원되는 메모장 (0) | 2008/06/16 |
| 간단한 메신저 예제 프로그램 (0) | 2008/06/16 |
| 스크롤바 스타일 변경 소스 (0) | 2008/06/16 |
| SMTP 메일전송 프로그램 소스 (0) | 2008/06/16 |
| FMOD모듈을 이용한 음악 재생기 입니다. (1) | 2008/06/16 |
|---|---|
| 투명창이 지원되는 메모장 (0) | 2008/06/16 |
| 간단한 메신저 예제 프로그램 (0) | 2008/06/16 |
| 스크롤바 스타일 변경 소스 (0) | 2008/06/16 |
| SMTP 메일전송 프로그램 소스 (0) | 2008/06/16 |
| 오피스 문어 뷰어 프로그램 소스입니다. (0) | 2008/06/16 |
| 투명창이 지원되는 메모장 (0) | 2008/06/16 |
|---|---|
| 간단한 메신저 예제 프로그램 (0) | 2008/06/16 |
| 스크롤바 스타일 변경 소스 (0) | 2008/06/16 |
| SMTP 메일전송 프로그램 소스 (0) | 2008/06/16 |
| 오피스 문어 뷰어 프로그램 소스입니다. (0) | 2008/06/16 |
| 그림판 소스 (0) | 2008/06/16 |
| 간단한 메신저 예제 프로그램 (0) | 2008/06/16 |
|---|---|
| 스크롤바 스타일 변경 소스 (0) | 2008/06/16 |
| SMTP 메일전송 프로그램 소스 (0) | 2008/06/16 |
| 오피스 문어 뷰어 프로그램 소스입니다. (0) | 2008/06/16 |
| 그림판 소스 (0) | 2008/06/16 |
| 마우스 이벤트 (0) | 2008/06/16 |
| 스크롤바 스타일 변경 소스 (0) | 2008/06/16 |
|---|---|
| SMTP 메일전송 프로그램 소스 (0) | 2008/06/16 |
| 오피스 문어 뷰어 프로그램 소스입니다. (0) | 2008/06/16 |
| 그림판 소스 (0) | 2008/06/16 |
| 마우스 이벤트 (0) | 2008/06/16 |
| 메모장 소스입뉘당. 공부열쒸미하세요. (0) | 2008/06/16 |
| SMTP 메일전송 프로그램 소스 (0) | 2008/06/16 |
|---|---|
| 오피스 문어 뷰어 프로그램 소스입니다. (0) | 2008/06/16 |
| 그림판 소스 (0) | 2008/06/16 |
| 마우스 이벤트 (0) | 2008/06/16 |
| 메모장 소스입뉘당. 공부열쒸미하세요. (0) | 2008/06/16 |
| [강좌자료] 설치 및 배포 프로젝트 강좌 예제 화일입니다 (0) | 2008/06/16 |
| 오피스 문어 뷰어 프로그램 소스입니다. (0) | 2008/06/16 |
|---|---|
| 그림판 소스 (0) | 2008/06/16 |
| 마우스 이벤트 (0) | 2008/06/16 |
| 메모장 소스입뉘당. 공부열쒸미하세요. (0) | 2008/06/16 |
| [강좌자료] 설치 및 배포 프로젝트 강좌 예제 화일입니다 (0) | 2008/06/16 |
| 윈폼과 컨트롤을 여러가지모양으로.. (0) | 2008/06/16 |
| 그림판 소스 (0) | 2008/06/16 |
|---|---|
| 마우스 이벤트 (0) | 2008/06/16 |
| 메모장 소스입뉘당. 공부열쒸미하세요. (0) | 2008/06/16 |
| [강좌자료] 설치 및 배포 프로젝트 강좌 예제 화일입니다 (0) | 2008/06/16 |
| 윈폼과 컨트롤을 여러가지모양으로.. (0) | 2008/06/16 |
| 메신져 소스 공개합니당..ㅡ0ㅡㅋㅋ (0) | 2008/06/16 |
| 마우스 이벤트 (0) | 2008/06/16 |
|---|---|
| 메모장 소스입뉘당. 공부열쒸미하세요. (0) | 2008/06/16 |
| [강좌자료] 설치 및 배포 프로젝트 강좌 예제 화일입니다 (0) | 2008/06/16 |
| 윈폼과 컨트롤을 여러가지모양으로.. (0) | 2008/06/16 |
| 메신져 소스 공개합니당..ㅡ0ㅡㅋㅋ (0) | 2008/06/16 |
| 탐색기 소스입니다. (0) | 2008/06/16 |
| 메모장 소스입뉘당. 공부열쒸미하세요. (0) | 2008/06/16 |
|---|---|
| [강좌자료] 설치 및 배포 프로젝트 강좌 예제 화일입니다 (0) | 2008/06/16 |
| 윈폼과 컨트롤을 여러가지모양으로.. (0) | 2008/06/16 |
| 메신져 소스 공개합니당..ㅡ0ㅡㅋㅋ (0) | 2008/06/16 |
| 탐색기 소스입니다. (0) | 2008/06/16 |
| 윈2000 환경의 사용자에게 메세지 반복 보냄 (0) | 2008/06/16 |
| [강좌자료] 설치 및 배포 프로젝트 강좌 예제 화일입니다 (0) | 2008/06/16 |
|---|---|
| 윈폼과 컨트롤을 여러가지모양으로.. (0) | 2008/06/16 |
| 메신져 소스 공개합니당..ㅡ0ㅡㅋㅋ (0) | 2008/06/16 |
| 탐색기 소스입니다. (0) | 2008/06/16 |
| 윈2000 환경의 사용자에게 메세지 반복 보냄 (0) | 2008/06/16 |
| 아이피나 호스트의 정보를 추출하는 소스 (0) | 2008/06/16 |
| 윈폼과 컨트롤을 여러가지모양으로.. (0) | 2008/06/16 |
|---|---|
| 메신져 소스 공개합니당..ㅡ0ㅡㅋㅋ (0) | 2008/06/16 |
| 탐색기 소스입니다. (0) | 2008/06/16 |
| 윈2000 환경의 사용자에게 메세지 반복 보냄 (0) | 2008/06/16 |
| 아이피나 호스트의 정보를 추출하는 소스 (0) | 2008/06/16 |
| 윈폼 강좌중에 프린터 컨트롤의 소스입니다. (0) | 2008/06/16 |
| 메신져 소스 공개합니당..ㅡ0ㅡㅋㅋ (0) | 2008/06/16 |
|---|---|
| 탐색기 소스입니다. (0) | 2008/06/16 |
| 윈2000 환경의 사용자에게 메세지 반복 보냄 (0) | 2008/06/16 |
| 아이피나 호스트의 정보를 추출하는 소스 (0) | 2008/06/16 |
| 윈폼 강좌중에 프린터 컨트롤의 소스입니다. (0) | 2008/06/16 |
| 투명 윈도우 소스 입니당.. (0) | 2008/06/16 |
| 탐색기 소스입니다. (0) | 2008/06/16 |
|---|---|
| 윈2000 환경의 사용자에게 메세지 반복 보냄 (0) | 2008/06/16 |
| 아이피나 호스트의 정보를 추출하는 소스 (0) | 2008/06/16 |
| 윈폼 강좌중에 프린터 컨트롤의 소스입니다. (0) | 2008/06/16 |
| 투명 윈도우 소스 입니당.. (0) | 2008/06/16 |
| PDF를 BMP,PNG,TIFF,JPEG (2) | 2008/06/16 |
| 윈2000 환경의 사용자에게 메세지 반복 보냄 (0) | 2008/06/16 |
|---|---|
| 아이피나 호스트의 정보를 추출하는 소스 (0) | 2008/06/16 |
| 윈폼 강좌중에 프린터 컨트롤의 소스입니다. (0) | 2008/06/16 |
| 투명 윈도우 소스 입니당.. (0) | 2008/06/16 |
| PDF를 BMP,PNG,TIFF,JPEG (2) | 2008/06/16 |
| 2D Polygon Collision Detection (0) | 2008/06/16 |
| 아이피나 호스트의 정보를 추출하는 소스 (0) | 2008/06/16 |
|---|---|
| 윈폼 강좌중에 프린터 컨트롤의 소스입니다. (0) | 2008/06/16 |
| 투명 윈도우 소스 입니당.. (0) | 2008/06/16 |
| PDF를 BMP,PNG,TIFF,JPEG (2) | 2008/06/16 |
| 2D Polygon Collision Detection (0) | 2008/06/16 |
| The Aqualizer Strikes Back! (0) | 2008/06/16 |
| 윈폼 강좌중에 프린터 컨트롤의 소스입니다. (0) | 2008/06/16 |
|---|---|
| 투명 윈도우 소스 입니당.. (0) | 2008/06/16 |
| PDF를 BMP,PNG,TIFF,JPEG (2) | 2008/06/16 |
| 2D Polygon Collision Detection (0) | 2008/06/16 |
| The Aqualizer Strikes Back! (0) | 2008/06/16 |
| ImageConverter - Converts images to a specific image format, changing sizes on the flow (0) | 2008/06/16 |

This article describes how to detect the collision between two moving (2D) polygons. It's not the first tutorial on the topic, however, the tutorials on the net tend to be a bit too complex for a relatively simple problem. The source codes I could find also had too many abbreviations that I don't get, or were crippled with C optimizations. So here, I'll try to keep it as simple as possible. In any case, it should be possible to include the functions presented here to your C projects quite straightforwardly. The technique can be used to detect collisions between sprites as an alternative to pixel-perfect collisions which are usually too slow.
To detect if two polygons are intersecting, we use the Separating Axis Theorem. The idea is to find a line that separates both polygons - if such a line exists, the polygons are not intersecting (Fig. 1). The implementation of this theorem is relatively simple, and could be summed up in this pseudo code:
This can be easily extended to deal with moving polygons by adding one additional step. After having checked that the current projections do not overlap, project the relative velocity of the polygons on the axis. Extend the projection of the first polygon by adding to it the velocity projection (Fig. 2). This will give you the interval spanned by the polygon over the duration of the motion. From there, you can use the technique used for static polygons: if the projections of polygons A and B don't overlap, the polygons won't collide. (NB: However, remember that if the intervals do overlap, it doesn't necessarily mean that the polygons will collide. We need to do the test for all the edges before knowing that.)
Once we have found that the polygons are going to collide, we calculate the translation needed to push the polygons apart. The axis on which the projection overlapping is minimum will be the one on which the collision will take place. We will push the first polygon along this axis. Then, the amount of translation will simply be the amount of overlapping on this axis (Fig. 3).
That is it! Now, each time a collision occurs, the first polygon will nicely slide along the surface of the other polygon.

Figure 1: Projections of the polygons onto an axis.

Figure 2: Projections for moving polygons.

Figure 3: Find the minimum interval overlapping, then calculate the translation required to push the polygons apart.
The PolygonCollision() function does all of the above, and returns a PolygonCollisionResult structure containing all the necessary information to handle the collision:
// Structure that stores the results of the PolygonCollision function
public struct PolygonCollisionResult {
// Are the polygons going to intersect forward in time?
public bool WillIntersect;
// Are the polygons currently intersecting?
public bool Intersect;
// The translation to apply to the first polygon to push the polygons apart.
public Vector MinimumTranslationVector;
}
Two helper functions are used by the PolygonCollision function. The first one is used to project a polygon onto an axis:
// Calculate the projection of a polygon on an axis
// and returns it as a [min, max] interval
public void ProjectPolygon(Vector axis, Polygon polygon,
ref float min, ref float max) {
// To project a point on an axis use the dot product
float dotProduct = axis.DotProduct(polygon.Points[0]);
min = dotProduct;
max = dotProduct;
for (int i = 0; i < polygon.Points.Count; i++) {
dotProduct = polygon.Points[i].DotProduct(axis);
if (d < min) {
min = dotProduct;
} else {
if (dotProduct> max) {
max = dotProduct;
}
}
}
}
The second one returns the signed distance between two given projections:
// Calculate the distance between [minA, maxA] and [minB, maxB]
// The distance will be negative if the intervals overlap
public float IntervalDistance(float minA, float maxA, float minB, float maxB) {
if (minA < minB) {
return minB - maxA;
} else {
return minA - maxB;
}
}
Finally, here is the main function:
// Check if polygon A is going to collide with polygon B.
// The last parameter is the *relative* velocity
// of the polygons (i.e. velocityA - velocityB)
public PolygonCollisionResult PolygonCollision(Polygon polygonA,
Polygon polygonB, Vector velocity) {
PolygonCollisionResult result = new PolygonCollisionResult();
result.Intersect = true;
result.WillIntersect = true;
int edgeCountA = polygonA.Edges.Count;
int edgeCountB = polygonB.Edges.Count;
float minIntervalDistance = float.PositiveInfinity;
Vector translationAxis = new Vector();
Vector edge;
// Loop through all the edges of both polygons
for (int edgeIndex = 0; edgeIndex < edgeCountA + edgeCountB; edgeIndex++) {
if (edgeIndex < edgeCountA) {
edge = polygonA.Edges[edgeIndex];
} else {
edge = polygonB.Edges[edgeIndex - edgeCountA];
}
// ===== 1. Find if the polygons are currently intersecting =====
// Find the axis perpendicular to the current edge
Vector axis = new Vector(-edge.Y, edge.X);
axis.Normalize();
// Find the projection of the polygon on the current axis
float minA = 0; float minB = 0; float maxA = 0; float maxB = 0;
ProjectPolygon(axis, polygonA, ref minA, ref maxA);
ProjectPolygon(axis, polygonB, ref minB, ref maxB);
// Check if the polygon projections are currentlty intersecting
if (IntervalDistance(minA, maxA, minB, maxB) > 0)\
result.Intersect = false;
// ===== 2. Now find if the polygons *will* intersect =====
// Project the velocity on the current axis
float velocityProjection = axis.DotProduct(velocity);
// Get the projection of polygon A during the movement
if (velocityProjection < 0) {
minA += velocityProjection;
} else {
maxA += velocityProjection;
}
// Do the same test as above for the new projection
float intervalDistance = IntervalDistance(minA, maxA, minB, maxB);
if (intervalDistance > 0) result.WillIntersect = false;
// If the polygons are not intersecting and won't intersect, exit the loop
if (!result.Intersect && !result.WillIntersect) break;
// Check if the current interval distance is the minimum one. If so store
// the interval distance and the current distance.
// This will be used to calculate the minimum translation vector
intervalDistance = Math.Abs(intervalDistance);
if (intervalDistance < minIntervalDistance) {
minIntervalDistance = intervalDistance;
translationAxis = axis;
Vector d = polygonA.Center - polygonB.Center;
if (d.DotProduct(translationAxis) < 0)
translationAxis = -translationAxis;
}
}
// The minimum translation vector
// can be used to push the polygons appart.
if (result.WillIntersect)
result.MinimumTranslationVector =
translationAxis * minIntervalDistance;
return result;
}
The function can be used this way:
Vector polygonATranslation = new Vector();
PolygonCollisionResult r = PolygonCollision(polygonA, polygonB, velocity);
if (r.WillIntersect) {
// Move the polygon by its velocity, then move
// the polygons appart using the Minimum Translation Vector
polygonATranslation = velocity + r.MinimumTranslationVector;
} else {
// Just move the polygon by its velocity
polygonATranslation = velocity;
}
polygonA.Offset(polygonATranslation);
| 투명 윈도우 소스 입니당.. (0) | 2008/06/16 |
|---|---|
| PDF를 BMP,PNG,TIFF,JPEG (2) | 2008/06/16 |
| 2D Polygon Collision Detection (0) | 2008/06/16 |
| The Aqualizer Strikes Back! (0) | 2008/06/16 |
| ImageConverter - Converts images to a specific image format, changing sizes on the flow (0) | 2008/06/16 |
| Aqua Gauge (0) | 2008/06/16 |
Ah. Aqua buttons. They're everywhere. Except on your website. Don't be frustrated anymore. You've just found the helping hand you need: The Aqualizer! The Aqualizer, or Aqualize, is a fantastic, excellent and downright amazing command line utility to create “aqua”-buttons like this one:
If you want a button like this, download The Aqualizer and type at the command prompt:
C:\>aqualize "The\nAqualizer" button.bmp
Or maybe you want a red button?
C:\>aqualize -c:FF0000 "The\nAqualizer" button.bmp
A black one?
C:\>aqualize -c:000000 -t:FFFFFF "The\nAqualizer" button.bmp
A small one?
C:\>aqualize -s:small -a:0 "Hi!" button.bmp
I found a GIMP tutorial on the net for a aqua button I liked, and thought it would be cool if people, especially programmers, with no knowledge of GIMP or Photoshop could put a nice aqua button on their website. I wrote all the code I needed to follow the steps in the tutorial. The Aqualizer is based on Shadow, my program to add drop shadows to images (see my site).
Let's take a look at the code in Main(). We're going to create an image with several layers, so we create a new LayeredImage object and call it image:
// create image
LayeredImage image = new LayeredImage(width, height);
The first layer we add is the background layer. We fill the layer with the background color.
// background
Layer bkgnd = image.Layers.Add();
bkgnd.Clear(bkcolor);
If the user wants a drop shadow, we create a new layer, select a circle, fill that circle with black, apply a blur filter, move the layer a bit to the right and to the bottom, and set the opacity of the layer so that the shadow is not too dark. The blur filter in Shadow uses a Gauss curve to calculate the contribution of the color of a pixel to the “current” pixel. The blur filter in The Aqualizer uses a Sigmoid curve. A Sigmoid curve is defined by three parameters: alpha, beta and gamma. Alpha is the start point, beta is the inflexion point, and gamma is the end point. The value at alpha is zero and the value at gamma is 1. The advantage of a Sigmoid curve is that you know that the value at alpha is 0; when using a Gauss curve, you cannot be 100% sure that the value will be 0. If you want to know how a blur filter works, see the article on my site.
if (dropshadow) {
// shadow
Layer shadow = image.Layers.Add();
image.ActiveLayer = shadow;
image.BackColor = Color.Black;
image.Selection.SelectEllipse(
margin,
margin,
buttonwidth,
buttonwidth,
SelectionMode.Replace);
image.Fill();
Blur.ApplyTo(shadow.Bitmap, blurx, blury);
shadow.OffsetX = shadowoffsetx;
shadow.OffsetY = shadowoffsety;
shadow.Opacity = shadowopacity;
}
Now, we're ready to paint the button. We begin by adding a layer for the border. We select a circle the size of the button and fill it with a radial gradient. Implementing the radial gradient code was easy: I just needed to calculate the distance from the current pixel to the center point, convert it to a value between 0 and 1, and mix the foreground and background color.
// border
Layer borderlayer = image.Layers.Add();
image.Selection.SelectEllipse(
margin,
margin,
buttonwidth,
buttonwidth,
SelectionMode.Replace);
image.ActiveLayer = borderlayer;
image.ForeColor = bordercolor;
image.BackColor = Color.Black;
image.Fill(
borderfillx0,
borderfilly0,
borderfillx1,
borderfilly1,
FillType.Radial);
We fill the inner area of the button with the button color. When the method Fill() is called with no parameters, it will fill the current selection with the background color.
// button color
image.Selection.SelectEllipse(
buttoncolorx,
buttoncolory,
buttoncolorwidth,
buttoncolorwidth,
SelectionMode.Replace);
image.BackColor = buttoncolor;
image.Fill();
We don't want our button to look flat. To achieve a nice effect, we will darken the top and left of the inner area a bit. To darken the left, we select a sickle shape by selecting the inner area and subtracting a circle from the current selection. The selection is feathered and filled with black. The method Feather() applies a blur filter to the selection mask. The same effect could be achieved by growing the selection and then applying a blur filter. To darken the top, we do the same: we select the inner area and subtract a circle about the same size, feather the selection, and fill it with black. We also add a mask to the shade layer because the feathering of the selection influences pixels outside the button, and we don't want that.
// shade
Layer shadelayer = image.Layers.Add();
image.ActiveLayer = shadelayer;
image.Selection.SelectEllipse(
buttoncolorx - 2,
buttoncolory - 2,
buttoncolorwidth + 4,
buttoncolorwidth + 4,
SelectionMode.Replace);
image.Selection.SelectEllipse(
shadeleftx,
shadelefty,
shadeleftwidth,
shadeleftheight,
SelectionMode.Subtract);
image.Selection.Feather(featherleft);
image.BackColor = Color.Black;
image.Fill();
shadelayer.Opacity = shadeopacity;
image.Selection.SelectEllipse(
buttoncolorx - 2,
buttoncolory - 2,
buttoncolorwidth + 4,
buttoncolorwidth + 4,
SelectionMode.Replace);
image.Selection.SelectEllipse(
shadetopx,
shadetopy,
shadetopwidth,
shadetopheight,
SelectionMode.Subtract);
image.Selection.Feather(feathertop);
image.Fill();
// shade mask -- set smoothing mode !!!
FastBitmap mask = new FastBitmap(width, height,
PixelFormat.Format24bppRgb);
Graphics g = Graphics.FromImage(mask._bitmap);
g.SmoothingMode = SmoothingMode.AntiAlias;
SolidBrush blackbrush = new SolidBrush(Color.Black);
SolidBrush whitebrush = new SolidBrush(Color.White);
g.FillRectangle(
blackbrush,
0,
0,
width,
height);
g.FillEllipse(
whitebrush,
buttoncolorx,
buttoncolory,
buttoncolorwidth,
buttoncolorwidth);
whitebrush.Dispose();
blackbrush.Dispose();
g.Dispose();
shadelayer.Mask = mask;
Before we can put the text on the button, we have to create a Font object and preprocess the text a bit. If we encounter a '\n' in the text, we replace it by a Environment.NewLine string. If we encounter a double backslash ("\\"), we replace it with a single backslash.
// text
Layer textlayer = image.Layers.Add();
FontStyle fs = FontStyle.Regular;
for (Int32 i = 0; i < fontstyle.Length; i++) {
switch (fontstyle[i]) {
case 'b':
fs |= FontStyle.Bold;
break;
case 'i':
fs |= FontStyle.Italic;
break;
case 'r':
fs |= FontStyle.Regular;
break;
case 's':
fs |= FontStyle.Strikeout;
break;
case 'u':
fs |= FontStyle.Underline;
break;
}
}
Font f = new Font(fontface, fontsize, fs);
StringFormat format = new StringFormat();
format.Alignment = StringAlignment.Center;
format.LineAlignment = StringAlignment.Center;
format.Trimming = StringTrimming.None;
SolidBrush brush = new SolidBrush(textcolor);
StringBuilder sb = new StringBuilder(text.Length);
for (Int32 i = 0; i < text.Length; i++) {
if (text[i] == '\\') {
if (i + 1 < text.Length) {
if (text[i + 1] == 'n') {
sb.Append(Environment.NewLine);
i++;
} else if (text[i + 1] == '\\') {
sb.Append('\\');
i++;
} else {
sb.Append('\\');
}
} else {
sb.Append('\\');
}
} else {
sb.Append(text[i]);
}
}
textlayer.DrawText(0, 0, width, height,
sb.ToString(), f, brush, format);
brush.Dispose();
f.Dispose();
textlayer.Opacity = 1.0 - texttrans;
And now, ladies and gentlemen, the finishing touch: the highlighting. We select an ellipse and fill the selection with a linear gradient which changes gradually from white to transparent. After that, we flatten the image and we're done. Tadaa! A very nice aqua button!
// highlight
Layer highlightlayer = image.Layers.Add();
image.ActiveLayer = highlightlayer;
image.ForeColor = Color.White;
image.BackColor = Color.Transparent;
image.Selection.SelectEllipse(
highlightx,
highlighty,
highlightwidth,
highlightheight,
SelectionMode.Replace);
image.Fill(
0,
highlightfilly0,
0,
highlightfilly1,
FillType.Linear);
// result
FastBitmap result = image.Flatten();| PDF를 BMP,PNG,TIFF,JPEG (2) | 2008/06/16 |
|---|---|
| 2D Polygon Collision Detection (0) | 2008/06/16 |
| The Aqualizer Strikes Back! (0) | 2008/06/16 |
| ImageConverter - Converts images to a specific image format, changing sizes on the flow (0) | 2008/06/16 |
| Aqua Gauge (0) | 2008/06/16 |
| Generate Thumbnail Images from PDF Documents (0) | 2008/06/16 |

Every week I take dozens of pictures during the soccer games of Kevin, my 7 year old son. Most of these pictures are placed on a web site created for his team. And every week I find myself performing the ever recurring task of resizing the images (mostly 3072 x 2048) to something more friendly (e.g.: 768 x 512). Why? Just for those less fortunate that have no ADSL or so.
Of course, every repeating task is prone to be automated, and a first application was born to resize the pictures according to my wishes. To cope with different source and target paths, as well as different ways of resizing, the application needed a user interface. Working on the application and its (simple, yet effective) user interface, generalizing the use of the application is evident. The next step then is to share it with all of you and hope you find the application or some code snippet useful.
Just download the zipped executable, unzip it, and open it. After you have opened ImageConverter, you can open the image files that you want to convert by pressing the Open... button. The target path for the converted images is automatically set to the source path, if not set already. By pressing the Browse... button, you can select an alternative path for the converted images.
Before you start the conversion process, you can define the image format after conversion, you can define how the images should be named, and you can define the required image size.
For naming the target images, you can use the original name, and the corresponding extension for the target image format is added. If a file with the resulting name already exists, it will be overwritten. Another option is to use a prefix defined by you. Each target image file will get a unique name based on this prefix and a sequence number.
The target size can be a specified size, which means that the original image will be stretched. If you are just interested in obtaining images for which the largest of width or height is less than or equal to the specified maximum, the image will be scaled if either width or height is larger than the specified maximum. The third option is to scale each image by the specified percentage.
The main code for the application is in FormImageConverter.cs whilst the code for the Thumbnail control I use is in Thumbnail.cs.
The images to be converted are selected by invoking the ShowDialog method of a standard OpenFileDialog instance. The selected image files are processed by the DisplayThumbnails method, which disposes off any Thumbnail control still present. Then for each image file, a Thumbnail control is created and added to panelThumbnails.Controls. Note that if the file to be loaded is not a (valid) image file, an OutOfMemoryException is thrown and the Thumbnail control will not be created.
Because I want some progress information displayed during the conversion process, the conversion process takes place in another thread. The thread is created and started from the event handler for the Convert button.
private void buttonConvert_Click(object sender, System.EventArgs e)
{
this.Cursor = Cursors.WaitCursor;
_convertThread = new Thread(new ThreadStart(ConvertImageFiles));
_convertThread.Start();
}
The ConvertImageFiles method takes care of determining the extension and image format for the target image, as well as the name for the image file. Using this information and knowing the size for the target image, the original image as represented by the Thumbnail control can be converted.
private void ConvertImageFiles()
{
this.Cursor = Cursors.WaitCursor;
_converting = true;
this.EnableControls();
try
{
...
// determine the target size
System.Drawing.Size targetSize = this.DetermineTargetSize(thumbnail.Image);
System.Drawing.Image targetImage = null;
try
{
// the new image is created and written based on
// the original, the target size and the desired image format
targetImage = new Bitmap(thumbnail.Image, targetSize);
targetImage.Save(targetFileName, targetImageFormat);
// invoke the delegate for updating progress information
this.Invoke(_imageInfoDelegate,
new object[] { thumbnail, targetFileName, targetSize });
targetImage.Dispose();
}
catch (System.Threading.ThreadAbortException e)
{
throw e;
}
catch {}
...
}
catch {}
finally
{
this.Cursor = Cursors.Default;
_converting = false;
this.EnableControls();
}
}
To prevent the user from changing the settings during the conversion process, the controls on the main form are disabled at the start, and the Close button becomes a Cancel button. Pressing the Cancel button causes the thread to be aborted by calling _convertThread.Abort method, which causes a System.Threading.ThreadAbortException to be thrown. The exception is caught and re-thrown until the outer try-catch is reached, and the controls can be enabled again.
To update the progress information during the conversion process, a delegate is executed using the Invoke method on the main form. The progress information uses the thumbnail, the target file name, and the target size. These objects are put in an object array, which holds the arguments for the DisplayImageInfo method to be called.
// the delegate
private delegate void ImageInfoDelegate(Thumbnail thumbnail,
string targetFile, System.Drawing.Size toSize);
private ImageInfoDelegate _imageInfoDelegate = null;
_imageInfoDelegate = new ImageInfoDelegate(DisplayImageInfo);
// invoke the delegate for updating progress information
this.Invoke(_imageInfoDelegate, new object[] { thumbnail,
targetFileName, targetSize });
As noted, the various controls on the main form are disabled during the conversion process and the Close button changed into a Cancel button. Also, if no images have been selected or no target path defined, the Convert button is disabled. This is implemented in the EnableControls method, which has to be called every time the conditions queried in the method have changed.
private void EnableControls()
{
this.buttonConvert.Enabled =
!_converting &&
this.panelThumbnails.Controls.Count > 0 &&
this.labelTargetPathValue.Text.Trim() != "";
this.buttonBrowseTargetPath.Enabled = !_converting;
this.buttonOpen.Enabled = !_converting;
this.groupBoxOnConvert.Enabled = !_converting;
this.groupBoxTargetSize.Enabled = !_converting;
this.comboBoxTargetFormat.Enabled = !_converting;
this.buttonClose.Text = _converting ? "Cancel" : "Close";
}
The Thumbnail control is a UserControl on which a Panel is placed that contains a PictureBox and two Labels. The PictureBox holds the thumbnail image, the file name Label, and the original image size Label. The border around the control is obtained by setting the control's DockPadding to 2 for all sides. The thin line between the PictureBox and the file name Label is the BackColor of the underlying Panel 'shining' through. This is accomplished by setting both Labels to dock on the bottom and by giving the PictureBox the size of the remaining space with the height set to just one pixel less.

The main task of the Thumbnail control is to show a thumbnail for each loaded image file and to present easy access to the corresponding image and its file path. The file path is supplied to the constructor, and the file is loaded using the FromFile method.
public Thumbnail(string filePath)
{
InitializeComponent();
_filePath = filePath;
try
{
_image = System.Drawing.Image.FromFile(_filePath);
}
catch (System.OutOfMemoryException e)
{
// file does not represent a valid image
throw e;
}
...
Loading a file that does not represent a (recognizable) image causes an OutOfMemoryException, so the exception is caught and thrown again to inform the code constructing the thumbnail.
If it is loadable, the size for the thumbnail image is calculated and the visual feedback is set.
...
int max = Math.Min(this.pictureBox.Width, this.pictureBox.Height);
int width = _image.Width;
int height = _image.Height;
// determine the size for the thumbnail image
if (_image.Width > max || _image.Height > max)
{
if (_image.Width > _image.Height)
{
width = max;
height = (int) (_image.Height * max / _image.Width);
}
else
{
width = (int) (_image.Width * max / _image.Height);
height = max;
}
}
// set feedback information
this.pictureBox.Image = new Bitmap(_image, width, height);
this.labelFileName.Text = Path.GetFileName(_filePath);
this.labelImageSize.Text = string.Format("{0} x {1}",
_image.Size.Width, _image.Size.Height);
}
| 2D Polygon Collision Detection (0) | 2008/06/16 |
|---|---|
| The Aqualizer Strikes Back! (0) | 2008/06/16 |
| ImageConverter - Converts images to a specific image format, changing sizes on the flow (0) | 2008/06/16 |
| Aqua Gauge (0) | 2008/06/16 |
| Generate Thumbnail Images from PDF Documents (0) | 2008/06/16 |
| Creating Glow Button (0) | 2008/06/16 |
I have chosen to develop this .NET user control to explore the easy yet powerful .NET GDI+. This simple gauge control developed using .NET 2.0 can cater to the entire range of monitoring purposes. Let's see how to develop such a glossy control using GDI+.
Normally, If we create user controls that have been fully drawn by the user, we should override the OnPaint and OnPaintBackground methods. Additionally, the control styles are to be set as appropriate. The following common styles can be set using this.SetStyle(ControlStyles.XXXXX, true/false);.
SupportsTransparentBackColor |
This will enable your control to support a transparent backcolor if set to true. |
ControlStyles.ResizeRedraw |
Allows repainting when the control is resized. |
ControlStyles.AllPaintingInWmPaint |
If true, the control ignores the window message WM_ERASEBKGND to reduce flicker. This style should only be applied if the UserPaint bit is set to true. |
ControlStyles.UserPaint |
If true, the control paints itself rather than the operating system doing so. |
ControlStyles.OptimizedDoubleBuffer |
If true, the control is first drawn to a buffer rather than directly to the screen, which can reduce flicker. If you set this property to true, you should also set the AllPaintingInWmPaint to true. |
The OnPaint and OnPaintBackground methods will be called whenever the control needs to be repainted. For example, when the control is resized or the form is minimized and maximized, the OnPaint method will be called.
OnPaintBackground paints the background (and thereby the shape) of the Window and is guaranteed to be fast. In contrast, OnPaint paints the details and might be slower because individual paint requests are combined into one Paint event that covers all areas that have to be redrawn. You might want to invoke the OnPaintBackground if, for instance, you want to draw a gradient-colored background for your control.
While OnPaintBackground has an event-like nomenclature and takes the same argument as the OnPaint method, OnPaintBackground is not a true event method. There is no PaintBackground event and OnPaintBackground does not invoke event delegates. When overriding the OnPaintBackground method, a derived class is not required to invoke the OnPaintBackground method of its base class.
First, let's see how to draw the dial. The dial requires a Scale, Threshold Indicator, some text and the current value to be displayed.
Drawing the scale requires calculating the positions for the rules that are to be drawn at the circumference. Let's say we need to draw a scale starting from 0 to 10 from angle 90 degrees to 270 degrees on the dial. In this case, the difference in the degrees (270-90 = 180) must be divided into 10 parts. To find the position for each part to be drawn, we need the following formula:
x = centerX + radius * cos(180/partNo)
y = centerY + radius * sin(180/partNo)
Note: when using Math.Cos or Math.Sin we should give angles in radians.
After finding the position, we can draw any type of scale mark on the circumference. I have chosen to draw a line as a scale mark. Since the dial area is not going to be changed often, it can be drawn in OnPaintBackground overridden method.
The pointer may need to be repainted often. So, it is better to draw it in the OnPaint method. Finding the pointer position is the same as the logic for drawing the scale. The pointer can be drawn using graphicsObj.FillPolygon() method and it can be transformed to any angle that will represent the current value. Otherwise, the pointer can be redrawn for every change made for the current value.
Drawing the glossiness is very simple. All you have to do is, after painting all the dial and pointer, fill two ellipses with gradient coloring. The LinearGradientBrush class provides the ability to draw gradient fills. Masking the gradient layer over the dial gives the glossiness as shown in the below figure.
This AquaGauge control can be used as any other user control provided by Windows. The following are the control-specific properties that can be used to configure this gauge to suit your requirements.
| Property Name | Type | Description |
DialColor |
Color |
Gets or Sets the background color for the gauge. |
DialText |
String |
Gets or Sets the Text displayed on the gauge dial. |
EnableTransparentBackground |
bool |
Enables or Disables Transparent Background color. Note: Enabling this will reduce the performance and may make the control flicker. |
Glossiness |
float |
Gets or Sets the strength of the Glossiness. |
MaxValue |
float |
Gets or Sets the maximum value shown on the gauge scale. |
MinValue |
float |
Gets or Sets the minimum value shown on the gauge scale. |
NoOfDivisions |
int |
Gets or Sets the number of divisions on the gauge scale. |
NoOfSubDivisions |
int |
Gets or Sets the number of subdivisions displayed on the scale for each division. |
RecommendedValue |
float |
Gets or Sets the recommended value on the scale. This will be used as a pivot point for drawing the threshold area. |
ThresholdPercent |
float |
Gets or Sets the Threshold area percentage on the scale. |
Value |
float |
Gets or Sets the value to which the pointer will point. |
Whenever we draw images with lots of manipulations, it is recommended to draw it on an image object and then paint. For example, drawing the gauge dial requires lots of CPU-consuming operations. So, we can draw the dial onto an image and then draw using graphicsObj.DrawImage(). Whenever changes are made on the dial properties, we can recreate the image object. It would improve the performance.
| 2D Polygon Collision Detection (0) | 2008/06/16 |
|---|---|
| The Aqualizer Strikes Back! (0) | 2008/06/16 |
| ImageConverter - Converts images to a specific image format, changing sizes on the flow (0) | 2008/06/16 |
| Aqua Gauge (0) | 2008/06/16 |
| Generate Thumbnail Images from PDF Documents (0) | 2008/06/16 |
| Creating Glow Button (0) | 2008/06/16 |
![]()
This article presents VB.NET code to create thumbnail images from a directory of Adobe Acrobat PDF documents.
Often when looking for documents it is much easier to find what you want visually, for example seeing the cover of a document.
The application was written for a website that I was developing that needed to display links to PDF documents. Instead of just showing a little PDF icon next to each document we wanted to display the front page of the actual document.
As shown below, this gives the listings better aesthetics and also enables the users to find documents quicker if they recognise it.
![]()
VS![]()
Note: please ignore the strange text, lorem ipsum is simply dummy text for this example
Hopefully people will agree that having the actual front cover displayed next to the hyperlink works better than the generic PDF icon.
The web site was a Content Management System (CMS) so new PDF documents were uploaded to the site by the users. We then had this application scheduled as a batch service to run every 5 minutes and check for new files.
In the backend system the documents have metadata stored in a SQL Server 2000 database. We would then write a flag to say the thumbnail had been created and when we generated the HTML content for the page request in ASP/ASP.NET we would return the appropriate IMG tag and source as appropriate.
Using the Acrobat SDK also meant we could programmically read the PDF metadata and retrieve the number of pages in the document, which could then be displayed as well. Although the end users could have entered that information it meant less work for them and a better overall impression of the web site. Another advantage was that many users relied on the number of pages to determine how large the document was rather than the more technical Kb/Mb value.
To generate the thumbnail image for each document I used the Adobe Acrobat 5.0 SDK and the Microsoft .NET 1.1 Framework.
Note: do not confuse the thumbnails that are part of a PDF document with the .png files this application generates.
The Acrobat SDK combined with the full version of Adobe Acrobat (sadly the free reader does not expose the COM interfaces) exposes a COM library of objects that can be used to manipulate and access PDF information.
So using these COM objects via COM Interop, we can load the PDF document, get the first page and render that page to the clipboard. Then using the .NET Framework we can copy this to a bitmap, scale and combine that image and then save the result as a .gif or .png file.
At first I just saved the scaled down image, but then decided to “fancy” up the thumbnail with a drop-shadow and folded corner. To achieve this effect I created a transparent .gif, called pdftemplate_portrait.gif, using Macromedia Fireworks MX where the main body of the page template was transparent.
By making the bottom-left pixel transparent too we can easily set the transparent colour for a bitmap in .NET.
I keep the top-right of the image white where the corner folds over, that means I can just combine the images by drawing the transparent template directly over the PDF image to achieve the final look.
![]()
The full version of Adobe Acrobat (the free reader does not expose the COM interfaces) which exposes a COM library of objects to manipulate and access PDF information.
The Adobe Acrobat 5.0 SDK which is a free download from the Adobe Solutions Network website (note: the site requires registration). The latest SDK for Acrobat 6.0 requires paid membership, so we will use the previous SDK version.
![]()
To quickly see if you have the full version of Adobe Acrobat installed, use regedit.exe and look under HKEY_CLASSES_ROOT for entry entry called AcroExch.PDDoc.
![]()
You'll also need the .NET 1.1 Framework and some PDF files to test the solution.
The code was written in VB.NET using the .NET 1.1 Framework and Visual Studio.NET 2003 on Windows XP, but there is no reason it wouldn't work on Windows NT/2000 or .NET 1.0.
The code is quite simple with a try/catch over the main body. It is purposely in one large block so it's easy to see what it happening and to step through and examine with the debugger.
Initially we create an instance of AcroExch.PDDoc using late-binding. The referenced Adobe Acrobat 5.0 Type Library (Acrobat.tlb from C:\Program Files\Adobe\Acrobat 5.0 SDK\InterAppCommunicationSupport\Headers) does not expose a COM class you can create using early-binding. By referencing the type library we can get the Intellisense and strong-typing of the other Acrobat objects.
Pass the filename of the PDF documents to be opened to the PDDoc object, which can then be accessed to get metadata on the document; GetNumPages() and GetInfo() for custom document properties.
' Create the document (Can only create the AcroExch.PDDoc object using
' late-binding)
pdfDoc = CreateObject("AcroExch.PDDoc")
' Open the document
ret = pdfDoc.Open(inputFile)
If ret = False Then
Throw New FileNotFoundException
End If
' Get the number of pages
pageCount = pdfDoc.GetNumPages()
Set a reference to the first page of the document as pdfPage, which is of type Acrobat.CAcroPDPage. From this we can get a rectangle object of the actual page dimensions. One strange point to notice here is that the Adobe Acrobat SDK documentation seems incorrect, as the PDFRect that is returned from the GetSize() method has IDispatch properties x, y but the PDFRect we need to supply to CopyToClipboard must have left, right, top, bottom.
Finally we render the PDF page to the clipboard at full size. We could have Acrobat scale the image down for us by a percentage, but we can get better visual results using the .NET scaling algorithms of the Bitmap class.
It would have been more efficient to render directly to an off-screen bitmap, and also not have overwritten what ever was previously on the clipboard, but I found the clipboard method the most stable way to get a rendered bitmap of the page using Acrobat.
Although it looks like the pdfPage object has a DrawEx method that can take an H<CODE>DC I couldn't get the method to work in a consistently successful way. Calling DrawEx in the paint event of a Windows Forms application did work but it still wouldn't write to an off-screen bitmap directly. Therefore the clipboard method is used and if the process runs on a batch server it won't cause too much worry.
Note: the Draw method is deprecated, as it only works on Win16 systems where hWnd was unique to Windows and not to each process as on NT.
![]()
' Get the first page
pdfPage = pdfDoc.AcquirePage(0)
' Get the size of the page
' This is really strange bug/documentation problem
' The PDFRect you get back from GetSize has properties
' x and y, but the PDFRect you have to supply CopyToClipboard
' has left, right, top, bottom
pdfRectTemp = pdfPage.GetSize
' Create PDFRect to hold dimensions of the page
pdfRect = CreateObject("AcroExch.Rect")
pdfRect.Left = 0
pdfRect.right = pdfRectTemp.x
pdfRect.Top = 0
pdfRect.bottom = pdfRectTemp.y
' Render to clipboard, scaled by 100 percent (ie. original size)
' Even though we want a smaller image, better for us to scale in .NET
' than Acrobat as it would greek out small text
' see http://www.adobe.com/support/techdocs/1dd72.htm
Call pdfPage.CopyToClipboard(pdfRect, 0, 0, 100)
Dim clipboardData As IDataObject = Clipboard.GetDataObject()
Grab the rendered page bitmap from the clipboard and based on the pdfRectTemp object determine if it's a portait or landscape document. Set the correct file to load as the template, and if it is landscape, switch the width and height.
Dim pdfBitmap As Bitmap = clipboardData.GetData(DataFormats.Bitmap)
' Size of generated thumbnail in pixels
Dim thumbnailWidth As Integer = 38
Dim thumbnailHeight As Integer = 52
Dim templateFile As String
' Switch between portrait and landscape
If (pdfRectTemp.x < pdfRectTemp.y) Then
templateFile = templatePortraitFile
Else
templateFile = templateLandscapeFile
' Swap width and height (little trick not using third temp variable)
thumbnailWidth = thumbnailWidth Xor thumbnailHeight
thumbnailHeight = thumbnailWidth Xor thumbnailHeight
thumbnailWidth = thumbnailWidth Xor thumbnailHeight
End If
Load the template file as as Bitmap and as an Image. We use both because the Bitmap class supports MakeTransparent and the image can easily be passed to the Graphics.DrawImage() method. It is slightly inefficent but speed isn't the primarly objective for this application.
Render the pdfImage using the GetThumbnailImage() method of the .NET Framework Bitmap class, this provides a very smooth scaled version of the image.
Next create a blank bitmap with room for the template border. Set the templateBitmap to use the bottom-left pixel of the image as the transparency colour using calling MakeTransparent(). See an article on Chris Sells website for more on transparencies in .NET.
Using the new blank bitmap, draw the rendered pdf page image to it and then the template with transparency directly over the top. Because it is transparent the main area of the page template will still appear through.
Finally, save the composited image back as a .png or .gif file, although .png does look better.
' Load the template graphic
Dim templateBitmap As Bitmap = New Bitmap(templateFile)
Dim templateImage As Image = Image.FromFile(templateFile)
' Render to small image using the bitmap class
Dim pdfImage As Image = pdfBitmap.GetThumbnailImage(thumbnailWidth, _
thumbnailHeight, _
Nothing, Nothing)
' Create new blank bitmap (+ 7 for template border)
Dim thumbnailBitmap As Bitmap = New Bitmap(thumbnailWidth + 7, _
thumbnailHeight + 7, _
Imaging.PixelFormat.Format32bppArgb)
' To overlayout the template with the image, we need to set the transparency
' http://www.sellsbrothers.com/writing/default.aspx?
' content=dotnetimagerecoloring.htm
templateBitmap.MakeTransparent()
Dim thumbnailGraphics As Graphics = Graphics.FromImage(thumbnailBitmap)
' Draw rendered pdf image to new blank bitmap
thumbnailGraphics.DrawImage(pdfImage, 2, 2, thumbnailWidth, thumbnailHeight)
' Draw template outline over the bitmap (pdf with show through the
' transparent area)
thumbnailGraphics.DrawImage(templateImage, 0, 0)
' Save as .png file
thumbnailBitmap.Save(outputFile, Imaging.ImageFormat.Png)
Write some feedback to the console as we work through each of the files.
Then actively release the reference code to the COM objects as Acrobat it isn't the best suited application to opening and closing multiple PDF documents without falling over. Luckily the code doesn't cause Acrobat to display any UI that might cause the process to hang waiting for user interaction.
Console.WriteLine("Generated thumbnail... {0}", outputFile)
thumbnailGraphics.Dispose()
pdfDoc.Close()
Marshal.ReleaseComObject(pdfPage)
Marshal.ReleaseComObject(pdfRect)
Marshal.ReleaseComObject(pdfDoc)
| 2D Polygon Collision Detection (0) | 2008/06/16 |
|---|---|
| The Aqualizer Strikes Back! (0) | 2008/06/16 |
| ImageConverter - Converts images to a specific image format, changing sizes on the flow (0) | 2008/06/16 |
| Aqua Gauge (0) | 2008/06/16 |
| Generate Thumbnail Images from PDF Documents (0) | 2008/06/16 |
| Creating Glow Button (0) | 2008/06/16 |
This is my very first article to any open source site. Since I've gotten a lot useful article from this site, I'll be glad if I can to contribute to this great site.
This article will explain how to make a button that has a glow feature.
When I installed Windows Vista, I was amazed by it's appearance. It had a very nice user interface and so many eye candy controls. That make me think about making something just like that. I've created some fancy controls that look like Vista components since that day. Also, I've always thought about making an article or tutorial to share my knowledge. Finally, while creating this glow button control, I've decided to make an article for it too.
So why Glow Button? I've been interested in this control since I played with Vista Windows, especially with its Minimize, Maximize and Close Buttons. They have some fancy glow affects. And thanks to .NET Framework 2.0, it's easier to create them.
Basically, to create the glow effect, you have to make a "Layer" that will create the glow with GDI+. Unfortunately, .NET Framework 2.0 doesn't allow a control that can make other controls mid-transparent. Or rather, when you place a control above another control, you can't see the control below, even you set full transparency at the upper control. The trick for this button is to request from another button that surrounds it to make a glow layer for that control.
Since we want to ask other glow controls to make glow effects, we need a layer status for the control:
Public Enum LayerStatus
None = 0
Self = 1
TopLeft = 2
Top = 3
TopRight = 4
Left = 5
Right = 6
BottomLeft = 7
Bottom = 8
BottomRight = 9
End Enum
Now we have a status for the control to make layer as requested. If we must ask another surrounding button to make glow effect, how can we do that if the button is at the edge, side, top, bottom etc? For this problem, we need a special container for our control, so the control at edge or side can ask this container to make the glow effect. Let's name it "Grid".

Another feature that we will add is the ability to change themes dynamically. For this, we will use a colortable class called GlassColorTable and some defined colors for our control. Let's name it ColorSet. Also remember that a button has four default states, which is: Normal, Highlighted (Mouse Over), Pressed and Disabled. For the moment, I'm not including Disabled state, due to to the fact that I don't have enough time to experiment with it. I'll include it when I update my article.
Public Interface IGlassColor
Enum States
Normal = 0
Highlighted = 1
Pressed = 2
Disabled = 3
End Enum
Sub NormalState()
Sub HighlightState()
Sub PressedState()
Sub DisabledState()
End Interface
Public Class ColorSet
'Normal State
Public BackgroundHigh As Color
'.
'.
'.
Public TextColor As Color
'Highlight State
Public BackgroundHighFocus As Color
'.
'.
'.
Public TextColorFocus As Color
'Glow Set
Public GlowCenter As Color
End Class
Public Class GlassColorTable
Implements IGlassColor
Protected _BackgroundHigh As Color
Protected _BackgroundLow As Color
Protected _ShineHigh As Color
Protected _ShineLow As Color
Protected _BorderLeft As Color
Protected _BorderRight As Color
Protected _BorderTop As Color
Protected _BorderBottom As Color
Protected _TextColor As Color
Protected _GlowCenter As Color
Private _State As IGlassColor.States
Private _cSet As ColorSet
Public Overridable ReadOnly Property BackgroundLow() As Color
Get
Return _BackgroundHigh
End Get
End Property
'.
'.
'.
End Class
To change themes dynamically, we must create a property.
<Browsable(False)> _
Public Property Renderer() As ColorSet
Get
Return _ColorTable.Renderer
End Get
Set(ByVal value As ColorSet)
_ColorTable.Renderer = value
_ColorTable.State = IGlassColor.States.Normal
Me.Refresh()
End Set
End Property
Since we now have the ability to change themes for the button, we must let the button know which color we want to be layered from another control.

<Browsable(False)> _
Public Property LayerColor() As Color
Get
Return _ColorTable.GlowCenter
End Get
Set(ByVal value As Color)
_LayerColor = value
End Set
End Property
So, the most important part is done. It's time to use them for our application. Since we must ask help from another control to make the glow effect, we must set up in each control events which are "mouse enter" and "mouse leave" events.
Private Sub GlowButton1_MouseEnter(ByVal sender As Object,
ByVal e As System.EventArgs) Handles GlowButton1.MouseEnter
GlowButton2.Layer = GlowingButton.GlowButton.LayerStatus.Left
GlowButton4.Layer = GlowingButton.GlowButton.LayerStatus.Top
GlowButton5.Layer = GlowingButton.GlowButton.LayerStatus.TopLeft
GlowButton2.LayerColor = GlowButton1.LayerColor
GlowButton4.LayerColor = GlowButton1.LayerColor
GlowButton5.LayerColor = GlowButton1.LayerColor
Grid1.GlowColor = GlowButton1.LayerColor
Grid1.GlowStartPoint = New Point(GlowButton1.Location.X - 7,
GlowButton1.Location.Y - 7)
Grid1.GlowStyle = GlowingButton.Grid.GlowStyles.TopLeft
End Sub
Private Sub GlowButton1_MouseLeave(ByVal sender As Object,
ByVal e As System.EventArgs) Handles GlowButton1.MouseLeave
GlowButton2.Layer = GlowingButton.GlowButton.LayerStatus.None
GlowButton4.Layer = GlowingButton.GlowButton.LayerStatus.None
GlowButton5.Layer = GlowingButton.GlowButton.LayerStatus.None
Grid1.GlowStyle = GlowingButton.Grid.GlowStyles.None
End Sub
To change themes dynamically, use the Renderer property.
For Each g As GlowingButton.GlowButton In Grid1.Controls
g.Renderer = New GlowingButton.DiamondRed
Next
Since we need to ask other controls to apply effects, we have to setup every control we have in the application. It will take time to setup everything, especially when we work with many buttons. To solve this problem, I suggest you make a class that inherits Grid control, place all glow buttons in the grid, and perform the required setup automatically with AddHandler methods. When I have more spare time, I'll explain in separate articles about this.
Additional notes about the demo: did some of you notice my other reason for creating this control? Yes, it's a Sudoku puzzle! My original motive with this control was for creating a Sudoku Puzzle Solver that can apply customized regions (that's why it needed themes or colorsets). The solver itself is still a long way to go because I've include it with many useful features.
That's all.
| 2D Polygon Collision Detection (0) | 2008/06/16 |
|---|---|
| The Aqualizer Strikes Back! (0) | 2008/06/16 |
| ImageConverter - Converts images to a specific image format, changing sizes on the flow (0) | 2008/06/16 |
| Aqua Gauge (0) | 2008/06/16 |
| Generate Thumbnail Images from PDF Documents (0) | 2008/06/16 |
| Creating Glow Button (0) | 2008/06/16 |