Python 拼图成心【大小,图数可调节】

最新版本,点这里


在学习PIL库Image用法的时候,瞅见了个函数,Image.paste(),突然有个点子,来拼一个小心心怎么样?说干就干!

Image.paste(image, box)
Image一个图像对象,img=Image.open(img_path)
box这里就是粘贴时图像img的左上角‘坐标’

那么就需要一系列的坐标,然后逐个给粘贴上去,最终才能出来。

这是成果图,以前爬了好多表情包,就顺手拿来用了....hhhhhhh~

思来想去,凭现在的能力,就只能嫁接一下以前的一个小东西,那就是画家驹的那一篇说的。
说来就是先画一个小心心,或者找一张心形图,然后挨行遍历,存储小心心每一行像素的纵坐标 y,以及起始、末尾横坐标x0, x1。(为了方便比较,就把除了小心心之外的背景搞成白色的)。就像下面这样的一张图:

在这里插入图片描述

def heart_get(self, source_path):
    img = Image.open(source_path) # 打开小心心
    pix = img.load()
    self.s_size = size = img.size
    for y in range(0, size[1]):
        index = 0
        for x in range(0, size[0] - 1):
            if (pix[x, y]!=pix[x + 1, y] and (255, 255, 255) in [pix[x, y], pix[x + 1, y]]) or (
                    pix[x, y]!=pix[x + 1, y] and (255, 255, 255, 255) in [pix[x, y], pix[x + 1, y]]):
                if index:
                    self.lst[-1].append(x)
                    index = 0
                else:
                    self.lst.append([y, x + 1])
                    index = 1
这样就得到了所有线条的坐标,每个元素的形式为 [y, x0, x1]

接下来就是要如何运用这些线条的坐标来贴出心形了

如果每一行像素条都贴的话,不是不行,只是最终的图有点大,那么可以这样:隔n行取用。怎么贴呢?先设置好每张图的大小mm的方图,然后像素条每m个像素贴一张图(即:贴图左上角横坐标Xn=x0+nm)为了美观一点,还可以隔开一点(Xn=x0+n*(m+a))。想必还是图示直观:(蓝色块表示贴的图)

在这里插入图片描述

问题来了,这样直接来就不能保证贴出来心形的比例。但也不要方,根据选取的像素条数,以及每张图片尺寸,可以计算出心形的高度,由于心形素材图片的留白部分较少,心形的比例可以近似于图片的长宽比。那么就可以推算出心形的宽度,那么就可以将每条像素条按照比例做相应的放大缩小。

梳理思路:
  • 根据选取的像素条数计算出心形的高度。H=条数x(图片宽度+每行间隙)
  • 计算出心形应有的宽度。W=H·(图宽/图高)
  • 算出每条像素的放大系数。
    [首先找出像素条中最宽的一条,宽度记为w],那么W/w就可以近似看做每条像素的放大(缩小)倍数
  • 接着借用numpy库的函数直接完成像素条的缩放。
这样,就基本搞定了比例的问题。
        #每行像素条贴图后所占的像素 图片宽度 x per_list[index]
        per_list = np.arange(1.1, 1.9, .2)
        index = self.lst[-1][0]//55
        if index >= len(per_list):
            index = -1
        per = per_list[index]

        # 图片大小 per_size x per_size
        if not per_size:
            per_size = 100

        # 图片宽度yy
        yy = (2 + self.lst[-1][0]//leap)*(per_size*per)
        #计算图片应有的宽度 xx = yy*(图宽/图高)
        xx = int(yy*(self.s_size[0]/self.s_size[1]))

        #先对像素条的结束坐标排序,以最大值即l1[-1][2]近似于最大像素条长度
        l1 = sorted(self.lst, key=lambda x:x[2])
        # 用应有宽度xx除以最大宽度l1[-1][2]计算出放大/缩小的比例
        up_size = xx//l1[-1][2]
        # 将像素条列表转换为array对象,然后只直接缩放像素条的起末坐标达到缩放像素条的效果
        lst = np.array(self.lst)
        lst = lst*[1, up_size, up_size]

比例虽然弄好的,但是心形的位置还需要稍微调整一下

代码附带解析如下
        # 对放大后的像素条的起末坐标分别排序
        l0 = sorted(lst, key=lambda x: x[1])
        l1 = sorted(lst, key=lambda x: x[2])
        # 用最大末坐标减去最小起始坐标除以 图宽+间隙,稍微缩放加个1
        # 然后再乘上 图宽+间隙, 计算出贴图后心形的最大宽度
        max_x = (per_size+20)*(((l1[-1][2]-l0[0][1])//(per_size+20))+1)
        # 根据底图宽度xx、以及心形宽度 max_x 计算出两者差值即为留白宽度,除以2算出最宽行起始坐标dx
        if xx>=max_x:
            x_zh = (xx-max_x)//2
            dx = x_zh - l0[0][1]
        else:
            xx = max_x*1.05
            x_zh = int(max_x*0.05)
            dx = x_zh - l0[0][1]

        # 平移像素条,顺便把像素条的行号操作一下让它从0开始
        lst = lst + [-lst[0][0], dx, dx]

        # 同样,简单计算出像素条起始的Y坐标
        dy = ((yy - (per_size*per)*(lst[-1][0]//leap))//(per_size*per))//2

最后,就可以开始遍历贴图了

代码如下
        canv = Image.new('RGB', (int(xx*1.01), int(yy)), bg)
        for line in lst:
            #根据leap值筛去不贴图的像素条,达到控制贴图像素条数
            if not line[0] % leap:
                y = int(dy) + line[0]//leap
                print(f'\rget__{y}/{lst[-1][0]//leap}__', end='')
                for x in range(line[1], line[2]+1, per_size+22):
                    s_img = Image.open(next(imgs)).resize((per_size, per_size))
                    canv.paste(s_img, (x, y*int(per_size*per)))

来个完整代码

import os
import os.path as op
import turtle as t
import numpy as np
from PIL import Image
from random import *

class Heart():
    def __init__(self):
        self.lst = []
        self.s_size = None

    def img_generator(self, imgs_list):
        while 1:
            for path in imgs_list:
                yield path

    def heart_get(self, source_path):
        img = Image.open(source_path)
        pix = img.load()
        self.s_size = size = img.size
        for y in range(0, size[1]):
            index = 0
            for x in range(0, size[0]-1):
                if (pix[x, y] != pix[x+1, y] and (255, 255, 255) in [pix[x, y], pix[x+1, y]]) or (pix[x, y] != pix[x+1, y] and (255, 255, 255, 255) in [pix[x, y], pix[x+1, y]]):
                    if index:
                        self.lst[-1].append(x)
                        index = 0
                    else:
                        self.lst.append([y, x+1])
                        index = 1

    def mkit(self, leap, imgs_path, source_path=r'c:\users\pxo\desktop\heart.png', out=None, show=True, per_size=None, bg=(0, 0, 0)):
        self.heart_get(source_path)
        #self.paint(self.lst)
        if not out['path']:
            out['path'] = r'c:\users\pxo\desktop\{}.jpg'.format(randint(0, 99999))
        imgs_list = [op.join(imgs_path, name) for name in os.listdir(imgs_path)]
        imgs = self.img_generator(imgs_list)

        #每行像素条贴图后所占的像素 图片宽度 x per_list[index]
        per_list = np.arange(1.1, 1.9, .2)
        index = self.lst[-1][0]//55
        if index >= len(per_list):
            index = -1
        per = per_list[index]

        # 图片大小 per_size x per_size
        if not per_size:
            per_size = 100

        # 图片宽度yy
        yy = (2 + self.lst[-1][0]//leap)*(per_size*per)
        #计算图片应有的宽度 xx = yy*(图宽/图高)
        xx = int(yy*(self.s_size[0]/self.s_size[1]))

        #先对像素条的结束坐标排序,以最大值即l1[-1][2]近似于最大像素条长度
        l1 = sorted(self.lst, key=lambda x:x[2])
        # 用应有宽度xx除以最大宽度l1[-1][2]计算出放大/缩小的比例
        up_size = xx//l1[-1][2]
        # 将像素条列表转换为array对象,然后只直接缩放像素条的起末坐标达到缩放像素条的效果
        lst = np.array(self.lst)
        lst = lst*[1, up_size, up_size]

        # 对放大后的像素条的起末坐标分别排序
        l0 = sorted(lst, key=lambda x: x[1])
        l1 = sorted(lst, key=lambda x: x[2])
        # 用最大末坐标减去最小起始坐标除以 图宽+间隙,稍微缩放加个1
        # 然后再乘上 图宽+间隙, 计算出贴图后心形的最大宽度
        max_x = (per_size+20)*(((l1[-1][2]-l0[0][1])//(per_size+20))+1)

        # 根据底图宽度xx、以及心形宽度 max_x 计算出两者差值即为留白宽度,除以2算出最宽行起始坐标dx
        if xx>=max_x:
            x_zh = (xx-max_x)//2
            dx = x_zh - l0[0][1]
        else:
            xx = max_x*1.05
            x_zh = int(max_x*0.05)
            dx = x_zh - l0[0][1]

        # 平移像素条,顺便把像素条的行号操作一下让它从0开始
        lst = lst + [-lst[0][0], dx, dx]

        # 同样,简单计算出像素条起始的Y坐标
        dy = ((yy - (per_size*per)*(lst[-1][0]//leap))//(per_size*per))//2

        #print(f'{self.s_size[0]/self.s_size[1]=}, {self.s_size[0]=}, {self.s_size[1]=}')
        #print(f'x={int(xx * 1.01)}, y={int(yy)}, up_sizee={up_size}, per={per}, percentage={1.01 * xx / yy}')

        canv = Image.new('RGB', (int(xx*1.01), int(yy)), bg)
        for line in lst:
            #根据leap值筛去不贴图的像素条,达到控制贴图像素条数
            if not line[0] % leap:
                y = int(dy) + line[0]//leap
                print(f'\rget__{y}/{lst[-1][0]//leap}__', end='')
                for x in range(line[1], line[2]+1, per_size+22):
                    s_img = Image.open(next(imgs)).resize((per_size, per_size))
                    canv.paste(s_img, (x, y*int(per_size*per)))
        if out:
            canv.save(out['path'], quality=out['quality'])
        if show:
            canv.show()

if __name__ == '__main__':
    imgs_path = r'c:\users\pxo\desktop\杂\toutiao_pic\斗图'
    imgs_path1 = r'c:\users\pxo\desktop\tem'
    bg = (0, 0, 0)
    out = {
        'path': None,
        'quality': 70
    }
    first_t = Heart()
    first_t.mkit(leap=20, imgs_path=imgs_path, show=True, out=out, per_size=60)

== 这是心形图素材 ==

在这里插入图片描述

这里创建了一个类方法Heart(), 要贴出小心心,就只用调用一下mkit()方法就可以贴出一个小心心了。

Heart.mkit(leap, imgs_path, source_path, out, show, per_size, bg)
leap整数。可以理解为跳跃选取贴图像素条的步长,数值越大,选择的像素条数越少,相应的贴图数就少。
imgs_path文件路径。用于贴图的图片文件的存储路径。例如:c:\users\lalala\pictures
source_path心形图的存储地址
out字典型。out[‘path’]为最终文件输出的路径,out[‘quality’]为存储图片的质量(好像是5-95),越大图片质量越高。默认为None值,即不输出。
show用于控制贴完图是否显示,默认为真
per_size整数。用来控制每张小贴图的大小。
bg三元元组。贴图的背景颜色,默认(0,0,0)黑色
不止可以表白啊,用来做表情包不 更好?接下来放一张我舍友的表情包,估计他是刷不到这片博客了,哈哈哈~~

如此含蓄的表(骂)白(人),岂不是好有意思

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

薛定谔的壳

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值