用Python在Excel里画出蒙娜丽莎
文的文字及图片来源于网络,仅供学习、交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理。
作者: 麦麦麦造
ps:如有需要python学习资料的小伙伴可以加点击下方链接自行获取
基本思路
实现这个需求的基本思路是读取这张图片每一个像素的色彩值,然后给excel里的每一个单元格填充上颜色。所以主要用到的是pil、openpyxl这两个库。
pil使用
pil是python里面做图像处理的时候十分常用的一个库,功能也是十分的强大,这里只需要用到pil里一小部分的功能。
1 from pil import image 2 img = image.open(img_path) # 读取图片 3 width, height = img.size # 获取图片大小 4 r, g, b = img.getpixel((w - 1, h - 1)) # 获取像素色彩值
-
image.open()
是pil里面打开一张图片的函数,支持多种图片类型 -
img_path
是图片路径,可以是相对路径,也可以是绝对路径 -
img.size
是获取图片的size属性,包含图片的宽和高 -
img.getpixel()
是获取图片色彩值的函数,需传入一个tuple或list,值为像素坐标xy
openpyxl使用
openpyxl
几乎是python里功能最全的操作excel文件的库了,这里也只需要用到它的一小部分功能。
1 import openpyxl 2 from openpyxl.styles import fills 3 4 workbook = openpyxl.workbook() 5 worksheet = workbook.active 6 cell.fill = fills.patternfill(fill_type="solid", fgcolor=hex_rgb) 7 workbook.save(out_file)
-
openpyxl.workbook()
新建一个excel文件 -
workbook.active
激活一个工作表 -
cell.fill = fills.patternfill(fill_type="solid", fgcolor=hex_rgb)
填充一个单元格,fill_type="solid"
是填充类型,fgcolor=hex_rgb
是填充的颜色 -
workbook.save()
保存文件,需传入要保存的文件名
写一段代码
写这一个画图的需求需要用到的核心就是上面介绍的pil跟openpyxl的几种用法。但是在实际写的时候,还会有一些其他问题,比如:
1、getpixel()获取的颜色值是rgb十进制的,但fills.patternfill 里的fgcolor`参数接收到的颜色值是十六进制的值 这个问题其实就是十进制转十六进制,很容易解决
1 def int_to_16(num): 2 num1 = hex(num).replace('0x', '') 3 num2 = num1 if len(num1) > 1 else '0' + num1 # 位数只有一位的时候在前面补零 4 return num2
2、excel的单元格默认是长方形,修改为正方形才不会使图片变形
1 if h == 1: 2 _w = cell.column 3 _h = cell.col_idx 4 # 调整列宽 5 worksheet.column_dimensions[_w].width = 1 6 7 # 调整行高 8 worksheet.row_dimensions[h].height = 6
这里用到了双重for循环,外层是width,里层是height,是一列一列的填充颜色,因此判断if h == 1,避免多次调整列宽。
3、excel支持的样式数量有限
这个问题比较严重。如果直接对高清大图进行操作,最后输出的excel文件在打开的时候,可能会提示我们文件有问题,需要自动修复。
但是等它修复完成之后,会发现填充的所有颜色都消失了!
开始以为是使用的行列数过多了原因。
查询资料后发现,13版excel支持的大行数是1048576,最大列数是16384,我们使用的单元格数量还远没达到限制。
在经过更换图片、更换excel版本,修改代码等不充分各种测试,才找到问题的原因所在。
原来是因为,excel的原形是由多个xml文件,填充的颜色都存储在一个style.xml文件里面,当这个文件过大就会导致打开的时候报错。
所以为了解决这个问题,有两个解决方案,第一是缩小图片,第二是减少图片颜色。缩小图片的时候自带减少图片颜色的功能,减少图片颜色的方法可以采用灰度化、二值化等方法。
总体上来讲,就是需要控制颜色数量*单元格数<阈值(3300w左右)。
1 max_width = 300 2 max_height = 300 3 def resize(img): 4 w, h = img.size 5 if w > max_width: 6 h = max_width / w * h 7 w = max_width 8 9 if h > max_height: 10 w = max_height / h * w 11 h = max_height 12 return img.resize((int(w), int(h)), image.antialias)
最终效果
苍天不负有心人,打开最后输出的excel已经可以看到效果了!
所以说,一切能用python解决的问题,最终都会用python来解决。
全部代码
1 # draw_excel.py 2 3 from pil import image 4 import openpyxl 5 from openpyxl.styles import fills 6 import os 7 8 max_width = 300 9 max_height = 300 10 11 def resize(img): 12 w, h = img.size 13 if w > max_width: 14 h = max_width / w * h 15 w = max_width 16 17 if h > max_height: 18 w = max_height / h * w 19 h = max_height 20 return img.resize((int(w), int(h)), image.antialias) 21 22 23 def int_to_16(num): 24 num1 = hex(num).replace('0x', '') 25 num2 = num1 if len(num1) > 1 else '0' + num1 26 return num2 27 28 29 def draw_jpg(img_path): 30 31 img_pic = resize(image.open(img_path)) 32 img_name = os.path.basename(img_path) 33 out_file = './result/' + img_name.split('.')[0] + '.xlsx' 34 if os.path.exists(out_file): 35 os.remove(out_file) 36 37 workbook = openpyxl.workbook() 38 worksheet = workbook.active 39 40 width, height = img_pic.size 41 42 for w in range(1, width + 1): 43 44 for h in range(1, height + 1): 45 if img_pic.mode == 'rgb': 46 r, g, b = img_pic.getpixel((w - 1, h - 1)) 47 elif img_pic.mode == 'rgba': 48 r, g, b, a = img_pic.getpixel((w - 1, h - 1)) 49 50 hex_rgb = int_to_16(r) + int_to_16(g) + int_to_16(b) 51 52 cell = worksheet.cell(column=w, row=h) 53 54 if h == 1: 55 _w = cell.column 56 _h = cell.col_idx 57 # 调整列宽 58 worksheet.column_dimensions[_w].width = 1 59 # 调整行高 60 worksheet.row_dimensions[h].height = 6 61 62 cell.fill = fills.patternfill(fill_type="solid", fgcolor=hex_rgb) 63 64 print('write in:', w, ' | all:', width + 1) 65 print('saving...') 66 workbook.save(out_file) 67 print('success!') 68 69 if __name__ == '__main__': 70 draw_jpg('mona-lisa.jpg')