色子作画 Dice Mosaic
大概是一年前,公司的一个部门举办活动,他们想把请来的嘉宾的头像照片用色子拼出来,这个创意应该是来自http://my.tv.sohu.com/us/5721245/4911597.shtml,其实这件事可以用PS弄出来,但是比较费时间,所以花了一天时间给他们写了一个小程序。程序参考了这篇文章http://blog.jobbole.com/8563/。
程序原理很简单,首先把图片转为一张灰度图,然后根据灰度图来判断用哪种类型的色子。
1.首先把图片转为灰度图
/// <summary>
/// 把原图像转化为灰度图,因为人眼对rgb三种颜色的敏感度不同,rgb的权值也不同
/// </summary>
/// <param name="rawBitmap"></param>
/// <returns></returns>
public Bitmap ConverttoGrayscale(Bitmap rawBitmap)
{
Bitmap grayBitmap = new Bitmap(rawBitmap.Width, rawBitmap.Height);
for (int i = 0; i < rawBitmap.Width; i++)
{
for (int j = 0; j < rawBitmap.Height; j++)
{
Color c = rawBitmap.GetPixel(i, j);
int grayValue = Convert.ToInt32(0.3 * c.R + 0.5 * c.G + 0.2 * c.B);
grayBitmap.SetPixel(i, j, Color.FromArgb(grayValue, grayValue, grayValue));
}
}
return grayBitmap;
}
2.根据灰度图来算出每个单元格的数值。
比如图片是800*800像素的,我有1600个色子,那么图片应该分成40*40的单元格,每个单元格用一个色子表示。换句话说,每个色子代表了20*20像素的面积。
public Int32[,] ConverttoValue(Bitmap grayBitmap, int width, int height)
{
Int32[,] grayArray = new Int32[height, width]; //用于存储灰度的数据
int gridSize = grayBitmap.Width / width;
int average, total;
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
total = 0;
for (int m = 0; m < gridSize; m++)
{
for (int n = 0; n < gridSize; n++)
{
total += grayBitmap.GetPixel(j * gridSize + n, i * gridSize + m).R;
//这里用的是R,因为已经是灰度图了,所以每个像素的RGB值是相同的
}
}
average = total / gridSize / gridSize; // 每个单元格灰度的平局值
grayArray[i, j] = average;
}
}
return grayArray;
}
3.根据灰度图中每个单元格的平局值,计算出对应哪种色子。
//valueArray即第二步中计算出的数组
//min,max即色子的最小和最大点数,一般我们用的色子最小是1最大是6
public Int32[,] ConverttoNumber(Int32[,] valueArray, int min, int max)
{
Int32[,] numberArray = new Int32[valueArray.GetLength(0), valueArray.GetLength(1)];
try
{
for (int i = 0; i < valueArray.GetLength(0); i++)
{
for (int j = 0; j < valueArray.GetLength(1); j++)
{
int t = valueArray[i, j];
//numberArray[i, j] = t * (max - min ) / 255 + min;
numberArray[i, j] = (t == 255) ? max : t * (max - min + 1) / 255 + min;
}
}
}
catch (Exception ex)
{ }
return numberArray;
}
4.知道所有单元格用哪种色子,就可以画图了。当然,这里还需要各种色子的图片。
public Bitmap DrawwithDice(Int32[,] numberArray)
{
int gridSize = 30;
//每个色子图片的大小。比如上文中800*800的图片,用40*40个色子,那么生成的图片大小为:40个*30像素即1200*1200像素。
int row = numberArray.GetLength(0);
int line = numberArray.GetLength(1);
Bitmap bmpPaint = new Bitmap(line * gridSize + 90, row * gridSize + 90);
Graphics gBoard = Graphics.FromImage(bmpPaint);
gBoard.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
for (int i = 0; i < row; i++)
{
for (int j = 0; j < line; j++)
{
string filePath = Application.StartupPath + @"\Resources\s" + numberArray[i, j].ToString() + ".png";
//每种色子对应的图片名称。
Bitmap bmpDice = new Bitmap(filePath);
gBoard.DrawImage(bmpDice, gridSize * j + 30, gridSize * i + 30);
bmpDice.Dispose();
}
System.Threading.Thread.Sleep(100);
}
for (int m = 0; m < line; m++) //添加横纵坐标,方便查找
{
gBoard.DrawString((m + 1).ToString(), new Font("微软雅黑", 10), Brushes.Black, new Point(m * 30 + 30, 0));
gBoard.DrawString((m + 1).ToString(), new Font("微软雅黑", 10), Brushes.Black, new Point(m * 30 + 30, row * 30 + 30));
}
for (int n = 0; n < row; n++)
{
gBoard.DrawString((n + 1).ToString(), new Font("微软雅黑", 10), Brushes.Black, new Point(0, n * 30 + 30));
gBoard.DrawString((n + 1).ToString(), new Font("微软雅黑", 10), Brushes.Black, new Point(line * 30 + 30, n * 30 + 30));
}
return bmpPaint;
}
5.如果只是生成一张色子图片,上文中的代码已经够了,但是如果你真的想用色子把图片拼出来,那么还需要一个更直观的数据。
public Bitmap DrawwithNumber(Int32[,] numberArray)
{
int gridSize = 30;
Font f = new Font("微软雅黑", 9);
Pen p = new Pen(Brushes.RoyalBlue, 2);
int row = numberArray.GetLength(0);
int line = numberArray.GetLength(1);
Bitmap bmpPaint = new Bitmap(line * gridSize + gridSize * 3, row * gridSize + gridSize * 7);
Graphics gBoard = Graphics.FromImage(bmpPaint);
for (int i = 1; i <= line; i++) //先把横纵坐标画出来
{
gBoard.DrawString(i.ToString(), f, Brushes.Black, new PointF(i * 30 + 5, 5));
gBoard.DrawString(i.ToString(), f, Brushes.Black, new PointF(i * 30 + 5, row * gridSize + gridSize * 1));
}
for (int j = 1; j <= row; j++) //先把横纵坐标画出来
{
gBoard.DrawString(j.ToString(), f, Brushes.Black, new PointF(0, j * 30 + 5));
gBoard.DrawString(j.ToString(), f, Brushes.Black, new PointF(line * gridSize + gridSize, j * 30 + 5));
}
for (int i = 0; i < row; i++) //画方格和数字
{
for (int j = 0; j < line; j++)
{
gBoard.DrawRectangle(p, gridSize * j + gridSize, gridSize * i + gridSize, 30, 30);
gBoard.DrawString(numberArray[i, j].ToString(), new Font("微软雅黑", 18), Brushes.Black, new PointF(gridSize * j + gridSize, gridSize * i + gridSize));
}
}
return bmpPaint;
}