2025-11-03
C#
00

目录

📊 你是否遇到过这些痛点?
💡 为什么需要Excel转图片?
🎯 实际应用场景分析
🛠️ 解决方案:EPPlus + GDI+ 完美组合
📋 技术选型分析
🔧 环境准备
🚀 核心代码实战解析
🎨 完整解决方案代码
💡 使用示例
⚡ 性能优化技巧
🔥 关键优化点
💻 最佳实践建议
🚨 常见坑点与解决方案
⚠️ 坑点1:许可证问题
⚠️ 坑点2:中文字体显示问题
⚠️ 坑点3:图片质量不佳
💬 互动时间

📊 你是否遇到过这些痛点?

作为一名C#开发者,你是否经常遇到这样的需求:老板要求将Excel报表数据以图片形式展示在网页上,或者需要将数据表格生成图片用于邮件发送?传统的截图方式不仅效率低下,而且图片质量参差不齐。

这个方案主要还是GDI+其实问题还是不少,没法高保真实现。

💡 为什么需要Excel转图片?

🎯 实际应用场景分析

在实际开发中,Excel转图片的需求主要来自以下场景:

  1. 报表系统:将动态生成的Excel报表转换为图片,便于在Web页面展示
  2. 邮件营销:将数据表格转为图片,避免邮件客户端兼容性问题
  3. 移动端应用:手机屏幕有限,图片格式更适合展示复杂表格
  4. 文档归档:将重要数据表格转为图片,防止后续编辑造成的数据风险

🛠️ 解决方案:EPPlus + GDI+ 完美组合

📋 技术选型分析

我们选择EPPlus作为Excel处理库,配合**.NET的GDI+**进行图片渲染,原因如下:

  • EPPlus:轻量级、高性能的Excel处理库,支持.NET Core
  • GDI+:.NET内置的图形处理API,无需额外依赖
  • 组合优势:既能精确读取Excel数据,又能灵活控制图片输出质量

🔧 环境准备

首先安装EPPlus包:

Bash
Install-Package EPPlus

🚀 核心代码实战解析

🎨 完整解决方案代码

C#
using System; using System.Collections.Generic; using System.Drawing.Imaging; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using OfficeOpenXml; namespace AppEPPlus { public class ExcelToImageConverter { /// <summary> /// 将Excel工作表的指定区域转换为图片 /// </summary> /// <param name="excelFilePath">Excel文件路径</param> /// <param name="outputImagePath">输出图片路径</param> /// <param name="worksheetIndex">工作表索引(从1开始)</param> /// <param name="rangeAddress">区域地址,如"A1:C10",为空则转换整个工作表</param> public static void ConvertExcelToImage(string excelFilePath, string outputImagePath, int worksheetIndex = 1, string rangeAddress = null) { // 设置EPPlus许可上下文(非商业用途) ExcelPackage.LicenseContext = LicenseContext.NonCommercial; using (var package = new ExcelPackage(new FileInfo(excelFilePath))) { // 获取指定工作表 var worksheet = package.Workbook.Worksheets[worksheetIndex - 1]; // 数组索引从0开始 // 确定要转换的区域 ExcelRange targetRange; if (string.IsNullOrEmpty(rangeAddress)) { // 如果没有指定区域,使用整个工作表的使用区域 targetRange = worksheet.Cells[worksheet.Dimension.Address]; } else { // 使用指定的区域 targetRange = worksheet.Cells[rangeAddress]; } // 转换指定区域为图片 SaveRangeAsImage(worksheet, targetRange, outputImagePath); } } /// <summary> /// 重载方法:使用行列坐标指定区域 /// </summary> /// <param name="excelFilePath">Excel文件路径</param> /// <param name="outputImagePath">输出图片路径</param> /// <param name="worksheetIndex">工作表索引(从1开始)</param> /// <param name="startRow">起始行(从1开始)</param> /// <param name="startCol">起始列(从1开始)</param> /// <param name="endRow">结束行</param> /// <param name="endCol">结束列</param> public static void ConvertExcelToImage(string excelFilePath, string outputImagePath, int worksheetIndex, int startRow, int startCol, int endRow, int endCol) { ExcelPackage.LicenseContext = LicenseContext.NonCommercial; using (var package = new ExcelPackage(new FileInfo(excelFilePath))) { var worksheet = package.Workbook.Worksheets[worksheetIndex - 1]; var targetRange = worksheet.Cells[startRow, startCol, endRow, endCol]; SaveRangeAsImage(worksheet, targetRange, outputImagePath); } } /// <summary> /// 核心方法:将指定区域保存为图片 /// </summary> private static void SaveRangeAsImage(ExcelWorksheet worksheet, ExcelRange range, string outputPath) { if (range == null) { Console.WriteLine("指定的区域为空"); return; } // 获取区域的行列信息 int startRow = range.Start.Row; int startCol = range.Start.Column; int endRow = range.End.Row; int endCol = range.End.Column; int rowCount = endRow - startRow + 1; int colCount = endCol - startCol + 1; Console.WriteLine($"转换区域:{range.Address} ({rowCount}行 × {colCount}列)"); // 计算图片尺寸 int cellWidth = 100; // 每列像素宽度 int cellHeight = 25; // 每行像素高度 int width = colCount * cellWidth; int height = rowCount * cellHeight; // 确保最小尺寸 width = Math.Max(width, 200); height = Math.Max(height, 100); using (var bitmap = new Bitmap(width, height)) using (var graphics = Graphics.FromImage(bitmap)) { // 设置高质量渲染 graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit; graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; // 填充背景色 graphics.Clear(Color.White); // 绘制指定区域内容 DrawRangeContent(graphics, worksheet, range, width, height); // 确保输出目录存在 var directory = Path.GetDirectoryName(outputPath); if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) { Directory.CreateDirectory(directory); } // 保存图片 bitmap.Save(outputPath, ImageFormat.Png); Console.WriteLine($"区域图片已保存到:{outputPath}"); } } /// <summary> /// 绘制指定区域的内容 /// </summary> private static void DrawRangeContent(Graphics graphics, ExcelWorksheet worksheet, ExcelRange range, int totalWidth, int totalHeight) { int startRow = range.Start.Row; int startCol = range.Start.Column; int endRow = range.End.Row; int endCol = range.End.Column; int rowCount = endRow - startRow + 1; int colCount = endCol - startCol + 1; float cellWidth = (float)totalWidth / colCount; float cellHeight = (float)totalHeight / rowCount; // 创建字体和画笔 using (var font = new Font("微软雅黑", 9, FontStyle.Regular)) using (var borderPen = new Pen(Color.Gray, 1)) using (var textBrush = new SolidBrush(Color.Black)) using (var headerBrush = new SolidBrush(Color.LightGray)) { // 遍历指定区域的所有单元格 for (int row = startRow; row <= endRow; row++) { for (int col = startCol; col <= endCol; col++) { var cell = worksheet.Cells[row, col]; var cellValue = cell.Text; // 计算在图片中的位置(相对位置) float x = (col - startCol) * cellWidth; float y = (row - startRow) * cellHeight; // 检查是否有背景色或特殊格式 var fillColor = Color.White; if (cell.Style.Fill.BackgroundColor.Rgb != null) { try { string rgbHex = cell.Style.Fill.BackgroundColor.Rgb; if (rgbHex.Length == 8) // ARGB格式 { fillColor = Color.FromArgb( Convert.ToInt32(rgbHex.Substring(0, 2), 16), Convert.ToInt32(rgbHex.Substring(2, 2), 16), Convert.ToInt32(rgbHex.Substring(4, 2), 16), Convert.ToInt32(rgbHex.Substring(6, 2), 16)); } } catch { fillColor = Color.White; } } // 填充单元格背景 if (fillColor != Color.White) { using (var backgroundBrush = new SolidBrush(fillColor)) { graphics.FillRectangle(backgroundBrush, x, y, cellWidth, cellHeight); } } // 绘制单元格边框 graphics.DrawRectangle(borderPen, x, y, cellWidth, cellHeight); // 绘制单元格内容 if (!string.IsNullOrEmpty(cellValue)) { var rect = new RectangleF(x + 3, y + 3, cellWidth - 6, cellHeight - 6); var stringFormat = new StringFormat { Alignment = StringAlignment.Near, LineAlignment = StringAlignment.Center, FormatFlags = StringFormatFlags.NoWrap, Trimming = StringTrimming.EllipsisCharacter }; // 根据单元格样式设置文本颜色 var textColor = Color.Black; if (cell.Style.Font.Color.Rgb != null) { try { string rgbHex = cell.Style.Font.Color.Rgb; if (rgbHex.Length == 8) { textColor = Color.FromArgb( Convert.ToInt32(rgbHex.Substring(2, 2), 16), Convert.ToInt32(rgbHex.Substring(4, 2), 16), Convert.ToInt32(rgbHex.Substring(6, 2), 16)); } } catch { textColor = Color.Black; } } using (var currentTextBrush = new SolidBrush(textColor)) { graphics.DrawString(cellValue, font, currentTextBrush, rect, stringFormat); } } } } } } } }

💡 使用示例

C#
namespace AppEPPlus { internal class Program { static void Main(string[] args) { // 调用方法 string excelFile = @"./report.xlsx"; string outputImage = @"./report.png"; ExcelToImageConverter.ConvertExcelToImage(excelFile, outputImage, 1, "A1:C12"); Console.WriteLine("Excel转换完成!"); } } }

image.png

⚡ 性能优化技巧

🔥 关键优化点

  1. 资源管理:使用using语句确保图形资源正确释放
  2. 内存控制:避免处理超大Excel文件时内存溢出
  3. 渲染质量:合理设置Graphics渲染参数平衡质量与性能

💻 最佳实践建议

C#
// ✅ 推荐:处理大文件时的内存优化 public static void ConvertLargeExcelToImage(string excelPath, string outputPath, int maxRows = 1000) { // 分批处理,避免内存溢出 // 具体实现可根据实际需求调整 }

🚨 常见坑点与解决方案

⚠️ 坑点1:许可证问题

问题:EPPlus 5.0+版本需要设置许可证上下文

解决:添加ExcelPackage.LicenseContext = LicenseContext.NonCommercial;

⚠️ 坑点2:中文字体显示问题

问题:生成的图片中文显示异常

解决:明确指定中文字体如"微软雅黑"

⚠️ 坑点3:图片质量不佳

问题:输出图片模糊或锯齿严重

解决:设置高质量渲染参数(代码中已包含)

💬 互动时间

你在项目中是如何处理Excel数据展示需求的?遇到过哪些技术难点?欢迎在评论区分享你的经验!

觉得有用请转发给更多同行,让更多C#开发者受益!


关注我,获取更多C#开发实战技巧和最佳实践!

本文作者:技术老小子

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!