之前看到很多头条,说某国某某自学了多少年用excel画画,效果惊人。我非常钦佩他们的耐心。但是作为程序员,自然要挑战自己。我们可以在十分钟内完成!基本思路实现这个需求的基本思路是读取这张图片每个像素的颜色值,然后给excel中的每个单元格填充颜色。所以主要用到PIL和openpyxl这两个库。PILUsingPIL是Python中非常常用的图像处理库,功能也非常强大。这里只需要用到PIL中的一小部分函数。fromPILimportImageimg=Image.open(img_path)width,height=img.sizer,g,b=img.getpixel((w-1,h-1))Image.open()是在PIL中打开一张图片支持多种图像类型的功能。img_path为图片路径,可以是相对路径也可以是绝对路径。img.size是获取到的图片的尺寸属性,包括图片的宽高。img.getpixel()用于获取图像的颜色值该函数需要传入一个元组或列表,其值为像素坐标xyopenpyxl使用openpyxl几乎是Python中操作excel文件最全面的库,这里只需要它的一小部分功能。importopenpyxlfromopenpyxl.stylesimportfillsworkbook=openpyxl.Workbook()worksheet=workbook.activecell.fill=fills.PatternFill(fill_type="solid",fgColor=hex_rgb)workbook.save(out_file)openpyxl.Workbook()创建一个newexcelfileworkbook.active激活一个工作表cell.fill=fills.PatternFill(fill_type="solid",fgColor=hex_rgb)填充一个单元格,fill_type="solid"是填充类型,fgColor=hex_rgb是填充颜色工作簿。save()保存文件,需要传入要保存的文件名,写一段代码写这张图。需要用到的核心就是上面介绍的PIL和openpyxl的几种用法。但是实际写的时候会出现一些其他的问题,比如:1.getpixel()得到的颜色值是rgb十进制,但是fills.PatternFill中的fgColor参数接收到的颜色值是十六进制value的问题其实是十进制转十六进制,这个很容易解决defint_to_16(num):num1=hex(num).replace('0x','')num2=num1iflen(num1)>1else'0'+num1#When位数只有一位,前面补零returnnum22.excel的cell默认是矩形,如果h==1,改成正方形不会变形图片:_w=cell.column_h=cell.col_idx#调整列宽worksheet.column_dimensions[_w].width=1#调整行高worksheet.row_dimensions[h].height=6这里用了双for循环,外层是`width`,内层是`height`,是逐列填充颜色,所以判断`ifh==1`,避免多次调整列宽。3、excel支持的样式数量有限是一个严重的问题。如果我们直接对高清大图进行操作,当打开最终输出的excel文件时,可能会提示我们文件有问题,需要自动修复。但是修复后,填充的所有颜色都消失了!起初,错误消息被认为是使用太多行和列的原因。查询资料后发现excel13版本支持的最大行数是1048576,最大列数是16384,我们使用的单元格数远远没有达到极限。经过换图、换excel版本、修改代码等不充分的测试,找到了问题的原因。原来excel的原始形状是由多个xml文件组成的,填充的颜色都存储在一个style.xml文件中。当文件过大时,会导致打开时出错。所以为了解决这个问题,有两个解决办法,第一个是缩小图片,第二个是减少图片的颜色。缩小图片时自带减少图片颜色的功能。降低图片颜色的方法可以是灰度化、二值化等方法。一般来说,需要控制颜色数*单元格数<阈值(3300w左右)。MAX_WIDTH=300MAX_HEIGHT=300defresize(img):w,h=img.size如果w>MAX_WIDTH:h=MAX_WIDTH/w*hw=MAX_WIDTH如果h>MAX_HEIGHT:w=MAX_HEIGHT/h*wh=MAX_HEIGHTreturnimg.resize((int(w),int(h)),Image.ANTIALIAS)最终效果不负众望,打开最终输出的excel就可以看到效果了!所以,凡是能用Python解决的问题,最终都会用Python来解决。全部代码#draw_excel.pyfromPILimportImageimportopenpyxlfromopenpyxl.stylesimportfillsimportosMAX_WIDTH=300MAX_HEIGHT=300defresize(img):w,h=img.sizeifw>MAX_WIDTH:h=MAX_WIDTH/w*hw=MAX_WIDTHifh>MAX_HEIGHT:w=MAX_HEIGHT/h*wh=MAX_HEIGHT返回img.resize((int(w),int(h)),Image.ANTIALIAS)defint_to_16(num):num1=hex(num).replace('0x','')num2=num1iflen(num1)>1else'0'+num1returnnum2defdraw_jpg(img_path):img_pic=resize(Image.open(img_path))img_name=os.path.basename(img_path)out_file='./result/'+img_name.split('.')[0]+'.xlsx'如果os.path.exists(out_file):os.remove(out_file)workbook=openpyxl.Workbook()worksheet=workbook.activewidth,height=img_pic.sizeforwinrange(1,width+1):forhinrange(1,height+1):如果img_pic.mode=='RGB':r,g,b=img_pic.getpixel((w-1,h-1))elifimg_pic.mode=='RGBA':r,g,b,a=img_pic.getpixel((w-1,h-1))hex_rgb=int_to_16(r)+int_to_16(g)+int_to_16(b)cell=worksheet.cell(column=w,row=h)ifh==1:_w=cell.column_h=cell.col_idx#调整列宽worksheet.column_dimensions[_w].width=1#调整行高worksheet.row_dimensions[h].height=6cell.fill=fills.PatternFill(fill_type="solid",fgColor=hex_rgb)print('writein:',w,'|all:',width+1)print('saving...')workbook.save(out_file)print('success!')if__name__=='__main__':draw_jpg('mona-lisa.jpg')attached=上面说的当颜色数*单元格数<阈值(约2564)时,可能有人会有疑惑,这2564是怎么来的?当然,我是从我的测试中得到的。由于有两个变量,颜色的数量和单元格的数量,自然会有两种以上的测试方法。一个观察颜色的数量,一个观察细胞的数量。但是我在这里只对颜色的数量进行了一次测试。(最大的原因是生成几万行*几万列的excel耗时太长。。。懒。。。)count=0MAX_WIDTH=255forwinrange(1,MAX_WIDTH+1):forhinrange(1,MAX_WIDTH+1):cell=worksheet.cell(column=w,row=h)ifh==1:_w=cell.column_h=cell.col_idx#调整列宽worksheet.column_dimensions[_w].width=1#调整行高worksheet.row_dimensions[h].height=6ifcount<255**3:back=int_to_16(num=count)back='0'*(6-len(back))+backelse:back=''.join([int_to_16(random.randint(0,255))for_inrange(3)])cell.fill=fills.PatternFill(fill_type="solid",fgColor=back)count+=1count是一个记录颜色的变量,保证每种颜色不重复,但是目前计算机RGB最多只能表示256^3种颜色。通过调整MAX_WIDTH的值来测试excel的阈值,最终生成的测试excel如下:see.!??最后由于精力有限,~懒~,能力有限~菜~,所以我没有测试单一颜色,可能还有其他方法没有这个阈值限制。如果您喜欢本文,请关注并前往~感谢您的阅读。
