效果预览:
设计思路:通过设置每个单元格的背景色,利用每个单元格做像素点作画
使用工具:C# with NPOI
一、界面
简单的界面,包括图片选择按钮、图片预览控件、Excel生成按钮,用户可以自行设置需要生成的行数和列数。
二、实现方法
读取图片至Bitmap → 生成缩略图 → 创建Workbook工作簿并填充单元格
代码如下:
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.IO;
using System.Threading.Tasks;
using System.Windows.Forms;
using NPOI.SS.UserModel;
using NPOI.XSSF.UserModel;
namespace HWPicture2Excel
{
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
}
Bitmap bmp = null;
/// <summary>
/// 更新数目
/// </summary>
object lock1 = new object();
/// <summary>
/// 更新线程数
/// </summary>
object lock2 = new object();
/// <summary>
/// 更新字典
/// </summary>
object lock3 = new object();
private void buttonOpenPicture_Click(object sender, EventArgs e)
{
if (isCreating)
{
MessageBox.Show("正在生成中,请稍等");
return;
}
OpenFileDialog ofd = new OpenFileDialog();
ofd.Filter = "|*.jpg||*.png||*.bmp";
if (ofd.ShowDialog() != DialogResult.OK)
{
return;
}
try
{
bmp = new Bitmap(ofd.FileName);
}
catch
{
MessageBox.Show("图片已损坏!");
return;
}
pictureBox.Image = bmp;
labelSize.Text = bmp.Width + "×" + bmp.Height;
}
private void btnCreate_Click(object sender, EventArgs e)
{
if (bmp == null)
{
MessageBox.Show("先读取图片!");
return;
}
if (isCreating)
{
MessageBox.Show("正在生成中,请稍等");
return;
}
SaveFileDialog sfd = new SaveFileDialog();
sfd.Filter = "|*.xlsx";
if (sfd.ShowDialog() != DialogResult.OK)
{
return;
}
//缓存选择的路径
path = sfd.FileName;
CreatExcel();
}
string path;
bool isCreating = false;
private void CreatExcel()
{
isCreating = true;
int width = 100;
int.TryParse(textColunm.Text, out width);
int height = 100;
int.TryParse(textRow.Text, out height);
if (width > 16384)
{
MessageBox.Show("超过最大列数");
return;
}
if (height > 1048576)
{
MessageBox.Show("超过最大行数");
return;
}
//生成缩略图
Bitmap newBmp = new Bitmap(width, height);
using (Graphics g = Graphics.FromImage(newBmp))
{
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.DrawImage(bmp, 0, 0, width, height);
}
int total = width * height;
labelTotal.Text = "总数:" + total.ToString();
XSSFWorkbook workbook = new XSSFWorkbook();
ISheet sheet1 = workbook.CreateSheet("Sheet1");
int count = 0;
int threadCount = 0;
//对XSSFCellStyle做缓存
Dictionary<Color, XSSFCellStyle> colorDic = new Dictionary<Color, XSSFCellStyle>();
for (int i = 0; i < height; i++)
{
if (sheet1.GetRow(i) == null) sheet1.CreateRow(i);
for (int j = 0; j < width; j++)
{
if (i == 0)
{
sheet1.SetColumnWidth(j, 450);
}
if (j == 0)
{
sheet1.GetRow(i).Height = 200;
}
ICell cell = sheet1.GetRow(i).CreateCell(j);
Color color = newBmp.GetPixel(j, i);
color = Color.FromArgb(color.R, color.G, color.B); //舍弃A通道
if (!(color.R == 255 && color.G == 255 && color.B == 255)) //实测若是纯白会变黑色,舍之
{
Task t = new Task(() =>
{
//显示线程数
int d;
lock (lock2)
{
threadCount++;
}
d = threadCount;
delegate4UI d1 = () =>
{
labelThreadCount.Text = "线程:" + d.ToString();
};
BeginInvoke(d1);
bool isGet;
XSSFCellStyle style;
lock (lock3)
{
isGet = colorDic.TryGetValue(color, out style);
if (!isGet)
{
style = workbook.CreateCellStyle() as XSSFCellStyle;
colorDic.Add(color, style);
}
}
if (!isGet)
{
XSSFColor xColor = workbook.CreateColor(color);
style.SetFillForegroundColor(xColor); //这行代码是最耗时的一步
style.FillPattern = FillPattern.SolidForeground;
}
cell.CellStyle = style;
lock (lock1)
{
count++;
}
int c = count;
lock (lock2)
{
threadCount--;
}
d = threadCount;
delegate4UI d2 = () =>
{
if (c == total)
{
WriteToExcel(workbook);
labelProgress.Text = "完成!";
}
else
{
labelFinish.Text = "完成:" + c;
labelProgress.Text = "进度:" + (100.0f * c / total).ToString("0.00") + "%";
}
labelThreadCount.Text = "线程:" + d.ToString();
};
BeginInvoke(d2);
});
t.Start();
}
else
{
lock (lock1)
{
count++;
}
int c = count;
labelFinish.Text = "完成:" + c;
labelProgress.Text = "进度:" + (100.0f * c / total).ToString("0.00") + "%";
if (c == total)
{
WriteToExcel(workbook);
labelProgress.Text = "完成!";
}
}
}
}
}
/// <summary>
/// 最后的写入
/// </summary>
/// <param name="workbook"></param>
private void WriteToExcel(XSSFWorkbook workbook)
{
using (FileStream fs = new FileStream(path, FileMode.Create))
{
//将内存里面的文档对象,写入到一个文档流中
workbook.Write(fs);
}
isCreating = false;
}
/// <summary>
/// 单纯为了更新UI定义的delegate
/// </summary>
public delegate void delegate4UI();
#region 限制文本输入
private void textBox1_KeyPress(object sender, KeyPressEventArgs e)
{
if (textRow.Text == string.Empty)
{
if (e.KeyChar <= '0' || e.KeyChar > '9') e.Handled = true;
}
else if ((e.KeyChar < '0' || e.KeyChar > '9') && e.KeyChar != 8)
{
e.Handled = true;
}
}
private void textBox2_KeyPress(object sender, KeyPressEventArgs e)
{
if (textColunm.Text == string.Empty)
{
if (e.KeyChar <= '0' || e.KeyChar > '9') e.Handled = true;
}
else if ((e.KeyChar < '0' || e.KeyChar > '9') && e.KeyChar != 8)
{
e.Handled = true;
}
}
#endregion
}
}
两个扩展方法,用于通过RGB生成XSSFColor
using NPOI.XSSF.UserModel;
using System.Drawing;
namespace HWPicture2Excel
{
public static class Extension
{
public static XSSFColor CreateColor(this XSSFWorkbook workbook, Color color)
{
return workbook.CreatColor(color.R, color.G, color.B);
}
public static XSSFColor CreatColor(this XSSFWorkbook workbook, byte r, byte g, byte b)
{
XSSFColor aColor = new XSSFColor();
byte[] by = { r, g, b };
aColor.SetRgb(by);
return aColor;
}
}
}
三、注意的地方
1.可能是NPOI本身的问题,越到后面速率越慢,240*320的测试图都够跑几个小时(orz),再大基本就GG了。
2.NPOI里【XSSFCellStyle】中的【public void SetFillForegroundColor(XSSFColor color)】方法耗时相当可怕,故代码中对XSSFCellStyle做了缓存处理,如果是单一颜色的图片基本是秒生成,但对于颜色较为复杂的图片用处不大。
3.未知原因,RGB为 255,255,255 (纯白)的单元格在生成后会变成黑色,故对于此类单元格直接作不填充处理。