一、前言

嗨,大家好,我是林新发。
之前有同事找我问我能不能通过json逆向切分图集,我之前写过一个python脚本,但是没有存起来,
在这里插入图片描述
直到最近又有同事找我,给我发了jsonpng图集,
在这里插入图片描述
我决定写篇文章讲一下,也方便下次又有同事问我的时候直接丢出这篇文章~

二、TexturePacker

1、特别说明

首先,本文的python代码是针对TexturePacker工具Phaser 3框架生成出来的图集进行逆向切分,不过只要你懂了原理,其他格式的配置都可以一通百通。
如何知道图集是使用TexturePacker工具制作的呢?我们可以打开图集同名的配置文件,如果能看到texturepacker的字样,就说明是使用TexturePacker工具生成的,例:
在这里插入图片描述

2、TexturePacker下载

TexturePacker官网地址:https://www.codeandweb.com/texturepacker
在这里插入图片描述
下载下来后,我们进入bin目录,可以看到TexturePackerGUI.exe,双击即可打开,
在这里插入图片描述
如下
在这里插入图片描述

为了演示,我先使用搞一些散图,然后使用TexturePacker打个图集出来。

3、准备精灵散图(小图)

以下是我随便搜的一些精灵小图,
在这里插入图片描述

4、使用TexturePacker打图集

首先点击界面右侧的 框架,根据需求选择一个框架,
在这里插入图片描述

它支持非常多的框架,如下
请添加图片描述
我选择的是通用的Phaser 3,如下,(因为我那两个同事发给我的图集格式都是用的这个-_-,那我就用这个来做演示吧)
在这里插入图片描述

接着,我们把小图的整个文件夹拖到界面左侧中,它自动帮我们执行了图集排版,如下,

请添加图片描述
接着,我们点击发布精灵表
在这里插入图片描述
填写保存路径,即可生成图集png和一个json配置文件,如下
在这里插入图片描述

5、json结构分析

其中test.atlas.json配置表内容如下:

{
	"textures": [
		{
			"image": "test_atlas.png",
			"format": "RGBA8888",
			"size": {
				"w": 309,
				"h": 118
			},
			"scale": 1,
			"frames": [
				{
					"filename": "car.png",
					"rotated": false,
					"trimmed": true,
					"sourceSize": {
						"w": 128,
						"h": 128
					},
					"spriteSourceSize": {
						"x": 0,
						"y": 6,
						"w": 128,
						"h": 116
					},
					"frame": {
						"x": 1,
						"y": 1,
						"w": 128,
						"h": 116
					},
					"anchor": {
						"x": 0.5,
						"y": 0.5
					}
				},
				{
					"filename": "bookcase.png",
					"rotated": false,
					"trimmed": true,
					"sourceSize": {
						"w": 64,
						"h": 64
					},
					"spriteSourceSize": {
						"x": 8,
						"y": 0,
						"w": 55,
						"h": 64
					},
					"frame": {
						"x": 131,
						"y": 1,
						"w": 55,
						"h": 64
					},
					"anchor": {
						"x": 0.5,
						"y": 0.5
					}
				},
				{
					"filename": "table_tennis.png",
					"rotated": false,
					"trimmed": false,
					"sourceSize": {
						"w": 48,
						"h": 48
					},
					"spriteSourceSize": {
						"x": 0,
						"y": 0,
						"w": 48,
						"h": 48
					},
					"frame": {
						"x": 131,
						"y": 67,
						"w": 48,
						"h": 48
					},
					"anchor": {
						"x": 0.5,
						"y": 0.5
					}
				},
				{
					"filename": "pencil.png",
					"rotated": false,
					"trimmed": true,
					"sourceSize": {
						"w": 48,
						"h": 48
					},
					"spriteSourceSize": {
						"x": 3,
						"y": 3,
						"w": 42,
						"h": 42
					},
					"frame": {
						"x": 188,
						"y": 1,
						"w": 42,
						"h": 42
					},
					"anchor": {
						"x": 0.5,
						"y": 0.5
					}
				},
				{
					"filename": "cate.png",
					"rotated": false,
					"trimmed": true,
					"sourceSize": {
						"w": 48,
						"h": 48
					},
					"spriteSourceSize": {
						"x": 5,
						"y": 3,
						"w": 39,
						"h": 41
					},
					"frame": {
						"x": 232,
						"y": 1,
						"w": 39,
						"h": 41
					},
					"anchor": {
						"x": 0.5,
						"y": 0.5
					}
				},
				{
					"filename": "stone.png",
					"rotated": false,
					"trimmed": true,
					"sourceSize": {
						"w": 48,
						"h": 48
					},
					"spriteSourceSize": {
						"x": 6,
						"y": 4,
						"w": 35,
						"h": 40
					},
					"frame": {
						"x": 273,
						"y": 1,
						"w": 35,
						"h": 40
					},
					"anchor": {
						"x": 0.5,
						"y": 0.5
					}
				},
				{
					"filename": "brinjaul.png",
					"rotated": false,
					"trimmed": true,
					"sourceSize": {
						"w": 48,
						"h": 48
					},
					"spriteSourceSize": {
						"x": 2,
						"y": 6,
						"w": 42,
						"h": 36
					},
					"frame": {
						"x": 188,
						"y": 45,
						"w": 42,
						"h": 36
					},
					"anchor": {
						"x": 0.5,
						"y": 0.5
					}
				},
				{
					"filename": "carota.png",
					"rotated": false,
					"trimmed": true,
					"sourceSize": {
						"w": 48,
						"h": 48
					},
					"spriteSourceSize": {
						"x": 5,
						"y": 6,
						"w": 38,
						"h": 36
					},
					"frame": {
						"x": 232,
						"y": 44,
						"w": 38,
						"h": 36
					},
					"anchor": {
						"x": 0.5,
						"y": 0.5
					}
				},
				{
					"filename": "bulb.png",
					"rotated": false,
					"trimmed": true,
					"sourceSize": {
						"w": 48,
						"h": 48
					},
					"spriteSourceSize": {
						"x": 6,
						"y": 4,
						"w": 36,
						"h": 40
					},
					"frame": {
						"x": 272,
						"y": 44,
						"w": 36,
						"h": 40
					},
					"anchor": {
						"x": 0.5,
						"y": 0.5
					}
				},
				{
					"filename": "pizza.png",
					"rotated": false,
					"trimmed": true,
					"sourceSize": {
						"w": 48,
						"h": 48
					},
					"spriteSourceSize": {
						"x": 4,
						"y": 6,
						"w": 41,
						"h": 34
					},
					"frame": {
						"x": 181,
						"y": 83,
						"w": 41,
						"h": 34
					},
					"anchor": {
						"x": 0.5,
						"y": 0.5
					}
				},
				{
					"filename": "coconut.png",
					"rotated": false,
					"trimmed": true,
					"sourceSize": {
						"w": 48,
						"h": 48
					},
					"spriteSourceSize": {
						"x": 3,
						"y": 10,
						"w": 42,
						"h": 27
					},
					"frame": {
						"x": 224,
						"y": 83,
						"w": 42,
						"h": 27
					},
					"anchor": {
						"x": 0.5,
						"y": 0.5
					}
				}
			]
		}
	],
	"meta": {
		"app": "https://www.codeandweb.com/texturepacker",
		"version": "3.0",
		"smartupdate": "$TexturePacker:SmartUpdate:79dd9a57d14f6938f1ea38d2225ac0ce:6ec25dea8a4c4491ba119c28bbdefaf3:2a084c904c80f72428569743fdf0f51d$"
	}
}

我们分析一下json配置的结构,画个图方便一目了然,
(注意:这个json结构仅针对Phaser 3这个框架,其他框架的格式需要自行分析)
在这里插入图片描述
知道了json的结构,以及每个字段的含义,我们就可以开始逆向了~

三、图集逆向切分精灵图

1、环境准备

人生苦短,我用python,不要跟我争辩~
如果你没有python环境,安装一下,我用的是python 3
另外因为要进行图形处理,需要安装Pillow库(PIL)。
通过pip命令进行安装

pip install Pillow

如果在pythonimport PIL没有报错,则说明安装PIL库成功。

在这里插入图片描述

2、python代码:TextureUnpacker.py

这里我先直接上代码,注释我写得比较详细,大家应该能看懂,下文我会讲解核心的步骤,

# TexturePacker图集逆向工具,本代码只针对Phaser 3框架的json配置
# 只要你知道原理,其他配置都可自行分析处理,希望你能有自行修改和优化的能力
# Author: 林新发 https://blog.csdn.net/linxinfa
# Create: 2022-03-24

import os
from PIL import Image
import json

# 封装一个TextureUnpacker类
class TextureUnpacker(object):
    @classmethod
    def split_with_json(cls, f_json, save_dir=None):
        f_json = os.path.abspath(f_json)
        if save_dir is None:
            save_dir = f_json + '_split'
        else:
            save_dir = os.path.abspath(save_dir)
        # 读取json配置表
        f = open(f_json, 'r')
        txt = f.read()
        dt = json.loads(txt)
        f.close()
        # 大图集文件名
        big_texture_file_name = dt['textures'][0]['image']
        # 小图序列
        frames =  dt['textures'][0]['frames']
        # 打开大图
        big_img = Image.open(big_texture_file_name)
        # 遍历生成小图
        for index in range(0, len(frames)):
            info = frames[index]
            # 解析配置
            info = cls.parse_as_json(info)
            print(info)
            # 小图的保存路径
            little_image_save_path = os.path.join(save_dir, info['filename'])
            # 生成小图
            cls.generate_little_image(big_img, info, little_image_save_path)

    @classmethod
    def generate_little_image(cls, big_img, info, path):
        # 创建小图
        little_img = Image.new('RGBA', info['sz'])
        # PIL.Image.crop()方法用于裁剪任何图像的矩形部分
        # box –定义左,上,右和下像素坐标的4元组
        region = big_img.crop(info['box'])
        if info['rotated']:
            region = region.transpose(Image.ROTATE_90)
        # 把裁剪出来的图片粘贴到小图上
        little_img.paste(region, info['xy'])
        save_dir = os.path.dirname(path)
        if not os.path.exists(save_dir):
            os.makedirs(save_dir)
        # 保存
        little_img.save(path)



    @classmethod
    def parse_as_json(cls, info):
        """
        "filename": "player_1.png",
        "rotated": false,
        "trimmed": false,
        "sourceSize": { "w": 1, "h": 1 },
        "spriteSourceSize": { "x": 0, "y": 0, "w": 1, "h": 1 },
        "frame": { "x": 1, "y": 1, "w": 1, "h": 1 }
        """
        # 小图宽高
        width = info['sourceSize']['w']
        height = info['sourceSize']['h']
        # 小图矩形信息
        frame = info['frame'] 
        # 是否旋转 (顺时针方向90度)
        rotated = info['rotated']
        if rotated:
            # box 定义左、上、右和下像素坐标的4元组
            box = (frame['x'], frame['y'], 
                    frame['x'] + frame['h'],
                    frame['y'] + frame['w'])
        else:
            box = (frame['x'], frame['y'],
                    frame['x'] + frame['w'],
                    frame['y'] + frame['h'])
        # 图形在小图中的偏移
        x = int((width - frame['w']) / 2)
        y = int((height - frame['h']) / 2)

        return {
            'box': box,
            'rotated': rotated,
            'sz': [width, height],
            'xy': (x, y),
            'filename' : info['filename']
        }

if __name__ == '__main__':
    unpacker = TextureUnpacker()
    unpacker.split_with_json('test_atlas.json')
    print('done')

3、代码讲解

核心就是封装的TextureUnpacker类,它做了以下几件事情,其中用到的PIL库的API我标注了黄色出来,如下
在这里插入图片描述
其中json配置的小图解析,我封装在parse_as_json方法中,如下,需要注意rotated旋转,
在这里插入图片描述

生成小图的逻辑封装在generate_little_image方法中,这个方法中用到了PIL库的一些API
在这里插入图片描述

关于PILAPI可以参见官方文档:https://pillow.readthedocs.io/en/latest/

这里我帮大家贴以下我用到的API的文档吧。

3.1、PIL.Image.open方法

在这里插入图片描述
打开图像,返回图像对象,
示例:

from PIL import Image
big_img = Image.open('test_atlas.png')
3.2、PIL.Image.new方法

在这里插入图片描述
创建image对象,示例:

from PIL import Image
little_img = Image.new('RGBA', (48, 48))
3.3、PIL.Image.crop方法

在这里插入图片描述
裁剪图像的某个矩形区域,示例

from PIL import Image
with Image.open("test_atlas.png") as im:
    (left, upper, right, lower) = (20, 20, 100, 100)
    im_crop = im.crop((left, upper, right, lower))
3.4、PIL.Image.paste方法

在这里插入图片描述
粘贴另一个image到自身image上,示例:

this_img.paste(other_image)
3.5、PIL.Image.save方法

在这里插入图片描述
image保存为文件,示例:

img_obj.save('test.png')
4、执行python脚本

python代码保存为TextureUnpacker.py,放在图集文件同级目录中,
在这里插入图片描述
执行python脚本,它会解析test_atlas.json并生成小图精灵,

if __name__ == '__main__':
    unpacker = TextureUnpacker()
    unpacker.split_with_json('test_atlas.json')
    print('done')

执行输出log如下

{'box': (1, 1, 129, 117), 'rotated': False, 'sz': [128, 128], 'xy': (0, 6), 'filename': 'car.png'}
{'box': (131, 1, 186, 65), 'rotated': False, 'sz': [64, 64], 'xy': (4, 0), 'filename': 'bookcase.png'}      
{'box': (131, 67, 179, 115), 'rotated': False, 'sz': [48, 48], 'xy': (0, 0), 'filename': 'table_tennis.png'}
{'box': (188, 1, 230, 43), 'rotated': False, 'sz': [48, 48], 'xy': (3, 3), 'filename': 'pencil.png'}        
{'box': (232, 1, 271, 42), 'rotated': False, 'sz': [48, 48], 'xy': (4, 3), 'filename': 'cate.png'}
{'box': (273, 1, 308, 41), 'rotated': False, 'sz': [48, 48], 'xy': (6, 4), 'filename': 'stone.png'}
{'box': (188, 45, 230, 81), 'rotated': False, 'sz': [48, 48], 'xy': (3, 6), 'filename': 'brinjaul.png'}
{'box': (232, 44, 270, 80), 'rotated': False, 'sz': [48, 48], 'xy': (5, 6), 'filename': 'carota.png'}
{'box': (272, 44, 308, 84), 'rotated': False, 'sz': [48, 48], 'xy': (6, 4), 'filename': 'bulb.png'}
{'box': (181, 83, 222, 117), 'rotated': False, 'sz': [48, 48], 'xy': (3, 7), 'filename': 'pizza.png'}
{'box': (224, 83, 266, 110), 'rotated': False, 'sz': [48, 48], 'xy': (3, 10), 'filename': 'coconut.png'}
done

可以看到生成了一个文件夹,
在这里插入图片描述
文件夹里就可以看到生成出来的精灵小图啦
在这里插入图片描述
完美,收工~
我是林新发,https://blog.csdn.net/linxinfa
一个在小公司默默奋斗的Unity开发者,希望可以帮助更多想学Unity的人,共勉~

GitHub 加速计划 / js / json
41.72 K
6.61 K
下载
适用于现代 C++ 的 JSON。
最近提交(Master分支:2 个月前 )
960b763e 4 个月前
8c391e04 7 个月前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐