首页 > 其他分享 >带你实现TexturePacker中的网格打包功能

带你实现TexturePacker中的网格打包功能

时间:2024-05-25 14:09:41浏览次数:14  
标签:arr image 网格 打包 TexturePacker path numpy best 图片

现在手头上有一堆相同大小的图片,如下图所示:

我想将它们打包进一张大图片中,主要是便于资源管理。本着能偷懒就偷懒的原则,看看市面上有没有类似功能的软件,果然,找到了一款名为TexturePacker的软件。但很可惜,这个功能是收费的。毕竟需求比较简单,还是自己动手,丰衣足食,试着用Python实现下吧。

1. 图片的读取与保存

在Python中,我们可以使用pillow库处理图片,例如读写图片;此外,可以使用numpy将读取的图片转化为numpy数组,这样处理起来会很方便。为此,首先运行pip install numpypip install pillow安装这两个库。安装完成后,导入它们:

import numpy as np
from PIL import Image

首先实现图片的读取功能:指定图片的路径,将其转化为numpy数组:

def read_image_as_numpy(image_path:str) -> np.ndarray:
    """读取一张图片,并转化为numpy数组
    Args:
        image_path: 图片路径
    """
    image = Image.open(image_path)
    arr = np.array(image, dtype=np.uint8)
    return arr

 接下来是图片的保存功能:将numpy数组保存为图片:

def save_numpy_as_image(arr: np.ndarray, save_path:str):
    """将numpy数组保存为图片
    Args:
        arr: numpy数组
        save_path: 保存图片的路径
    """
    image = Image.fromarray(arr)
    image.save(save_path)

2. 图片的打包

将小图片合并很简单,无非是数组的切片操作。现在的问题是,如果有21张图片,我们可以将其打包为3x7的大图片;但如果有19张呢?可以有不同的选择,例如4x5或是3x7, 这样分别会产生1个或2个位置的浪费。

当然,可以让用户决定按照怎样的大小打包。我想了想自己的需求:打包后的图片,水平方向上最好有5~8个网格,而且浪费的内存越少越好。从而,实现一个函数,给定图片的张数n,找到满足要求的分解大小:

def best_size(n:int) -> tuple[int,int]:
    """为正整数n找到最佳的分解大小"""
    if n <= 8:
        return 1, n
    else:
        for t in [8, 7, 6, 5]:
            if n % t == 0:
                return n // t, t
        
        best_w, best_h = n // 8 + 1, 8
        for t in [7, 6, 5]:
            w, h = n // t + 1, t
            if best_w * best_h > w * h:
                best_w, best_h = w, h
        return best_w, best_h

接下来就是为每张小图片找到它在大图片中的位置(这里假设处理的都是png图片):

def grid_packer(dir_path:str):
	"""网格打包
	Args:
		dir_path: 需要打包的文件夹路径
	"""
    # 1. 加载文件夹中的png图片(要求所有图片必须大小相同)
	image_arrs = [
		read_image_as_numpy(os.path.join(dir_path, filename))
		for filename in os.listdir(dir_path) if filename.endswith('.png')
	]
    
    # 2. 计算打包后的最佳大小
	best_x, best_y = best_size(len(image_arrs))
    
    # 3. 将小图片写入大图片中对应的位置
	image_w, image_h, image_channel = image_arrs[0].shape
	target_image = np.zeros((best_x * image_w, best_y * image_h, image_channel), dtype=np.uint8)
	for i, image_arr in enumerate(image_arrs):
		i_x, i_y = i // best_y, i % best_y
		target_image[i_x*image_w:(i_x+1)*image_w, i_y*image_h:(i_y+1)*image_h, :] = image_arr
	save_numpy_as_image(target_image, f'{os.path.basename(dir_path).lower()}_{best_x}x{best_y}_{len(image_arrs)}.png')

大功告成,最终效果如下图所示:

完整代码如下:

grid_packer.py
import sys, os
import numpy as np
from PIL import Image


def read_image_as_numpy(image_path:str) -> np.ndarray:
    """读取一张图片,并转化为numpy数组
    Args:
        image_path: 图片路径
    """
    image = Image.open(image_path)
    arr = np.array(image, dtype=np.uint8)
    return arr


def save_numpy_as_image(arr: np.ndarray, save_path:str):
    """将numpy数组保存为图片
    Args:
        arr: numpy数组
        save_path: 保存图片的路径
    """
    image = Image.fromarray(arr)
    image.save(save_path)


def best_size(n:int) -> tuple[int,int]:
    """为正整数n找到最佳的分解大小"""
    if n <= 8:
        return 1, n
    else:
        for t in [8, 7, 6, 5]:
            if n % t == 0:
                return n // t, t
        
        best_w, best_h = n // 8 + 1, 8
        for t in [7, 6, 5]:
            w, h = n // t + 1, t
            if best_w * best_h > w * h:
                best_w, best_h = w, h
        return best_w, best_h


def grid_packer(dir_path:str):
	"""网格打包
	Args:
		dir_path: 需要打包的文件夹路径
	"""
	image_arrs = [
		read_image_as_numpy(os.path.join(dir_path, filename))
		for filename in os.listdir(dir_path) if filename.endswith('.png')
	]
	best_x, best_y = best_size(len(image_arrs))
	image_w, image_h, image_channel = image_arrs[0].shape
	target_image = np.zeros((best_x * image_w, best_y * image_h, image_channel), dtype=np.uint8)
	for i, image_arr in enumerate(image_arrs):
		i_x, i_y = i // best_y, i % best_y
		target_image[i_x*image_w:(i_x+1)*image_w, i_y*image_h:(i_y+1)*image_h, :] = image_arr
	save_numpy_as_image(target_image, f'{os.path.basename(dir_path).lower()}_{best_x}x{best_y}_{len(image_arrs)}.png')


if __name__ == '__main__':
	if len(sys.argv) != 2:
		print('usage: packer [directory path]')
	elif not (os.path.exists(sys.argv[1]) and os.path.isdir(sys.argv[1])):
		print(f'error: {sys.argv[1]} does not exist or is not a directory')
	else:
		grid_packer(sys.argv[1])

 

标签:arr,image,网格,打包,TexturePacker,path,numpy,best,图片
From: https://www.cnblogs.com/overxus/p/18210177

相关文章

  • 安装笔记本应用商店的pycharm,再安排pandas等模块,说是没有打包工具?
    大家好,我是Python进阶者。一、前言前几天在Python最强王者交流群【斌】问了一个Python库安装的问题。求教大佬:华为笔记本,麒麟系统,安装笔记本应用商店的pycharm,再安排pandas等模块,说是没有打包工具,再安装打包工具冒出来这个故障,求教这是什么故障?怎么解决呢?二、实现过程这里【......
  • android中怎么将一个aar打包进另一个aar
    怎么将一个aar打包进另一个aar方法一、使用fat-aar插件,不过由于fat-aar插件较长时间未更新,导致无法支持最新的Android版本(已不推荐使用)第一步:在你的工程根目录下的build.gradle文件中添加以下代码:buildscript{​ repositories{  google()  mavenCentral()  jce......
  • Flutter Andriod打包发布和更新
    目录前言相关链接软件推荐Andriod打包更改软件icon未完待续前言软件发布和更新是肯定要做的。Flutter在安卓和IOS上面发布是不一样的。而且我也没有IOS的手机,只有一个破小米。所以我们这里以Android为例。相关链接flutterAndroid打包和发布:https://zhuanlan.zhihu.com/p/60......
  • RPM打包
    yum-yinstall  rpm-build生成rpmbuild目录结构rpmbuild-banginx.spec             //报错后会自动在家目录root下创建一个子目录ls/root/rpmbuild                    //自动生成的目录结构BUILD  BUILDROOT  RPMS  SOURCES......
  • 关于如何使用JNI将C语言接口打包成可供java环境调用的so库文件
    一、环境检查在linux下打包.so文件,首先需要确认是否有安装java环境,可通过在终端中输入指令java的方式来进行查看。如下图所示,则为已安装java环境。  若当前未安装java环境,则可通过在终端中输入如下指令进行安装,我这里使用的java环境为1.8.0版本。sudoapt-getinstallo......
  • docker添加文件重新打包
    dockersave归档dockercommint按照运行状态打包一个镜像dockerbuild打包一个新镜像 [root@ecs-353585sdkjs-plugins]#dockerpsCONTAINERIDIMAGECOMMANDCREATEDSTATUSPORTS......
  • utools插件开发踩坑记录 - vite+recat搭建打包到utools环境时运行页面报错unexpected
    问题现象在本地开发环境时,运行无问题,一但打包到utools环境运行时,就出现了下面的现象依赖"dependencies":{"@ant-design/icons":"^5.3.7","antd":"^5.17.3","react":"^18.2.0","react-dom":"^......
  • 在springboot项目中,打包本地的外部jar包,到运行的jar包中
    1、配置依赖<dependency><groupId>com.genesyslab</groupId><artifactId>genesyslab</artifactId><version>1.0.0</version><scope>system</scope><systemPath>${project.basedir}/src/main/re......
  • 如何全程使用docker部署jeecg平台,无需安装开发环境(主要是如何使用Docker来进行Maven打
    在部署jeecg平台时,文档中即使通过docker部署,也需要安装开发环境编译一部分内容,本文记录使用docker替代安装环境的过程。使用docker的目的是在平台选型的过程中,不希望麻烦的安装环境,同时如果选型不满意,无需卸载环境就能恢复一个干净的系统。部署环境:UbuntuServer20.04docker,......
  • Unity安卓IOS一键打包
    添加菜单构建按钮,使用下面API进行构建,注意设置和配置等usingSystem;usingSystem.IO;usingAssetBundles;usingLiXiaoQian.Common.Editor.Tools;usingUnityEditor;usingUnityEngine;///打包工具publicclassBuildTool{[MenuItem("Tools/构建/Android平台")]......