【游戏开发小技】TexturePacker生成的图集逆向切分成精灵小图(json | python | PIL | TextureUnPacker | 逆向 | 切图)

这篇具有很好参考价值的文章主要介绍了【游戏开发小技】TexturePacker生成的图集逆向切分成精灵小图(json | python | PIL | TextureUnPacker | 逆向 | 切图)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、前言

嗨,大家好,我是林新发。
之前有同事找我问我能不能通过json逆向切分图集,我之前写过一个python脚本,但是没有存起来,
【游戏开发小技】TexturePacker生成的图集逆向切分成精灵小图(json | python | PIL | TextureUnPacker | 逆向 | 切图)
直到最近又有同事找我,给我发了jsonpng图集,
【游戏开发小技】TexturePacker生成的图集逆向切分成精灵小图(json | python | PIL | TextureUnPacker | 逆向 | 切图)
我决定写篇文章讲一下,也方便下次又有同事问我的时候直接丢出这篇文章~

二、TexturePacker

1、特别说明

首先,本文的python代码是针对TexturePacker工具Phaser 3框架生成出来的图集进行逆向切分,不过只要你懂了原理,其他格式的配置都可以一通百通。
如何知道图集是使用TexturePacker工具制作的呢?我们可以打开图集同名的配置文件,如果能看到texturepacker的字样,就说明是使用TexturePacker工具生成的,例:
【游戏开发小技】TexturePacker生成的图集逆向切分成精灵小图(json | python | PIL | TextureUnPacker | 逆向 | 切图)

2、TexturePacker下载

TexturePacker官网地址:https://www.codeandweb.com/texturepacker
【游戏开发小技】TexturePacker生成的图集逆向切分成精灵小图(json | python | PIL | TextureUnPacker | 逆向 | 切图)
下载下来后,我们进入bin目录,可以看到TexturePackerGUI.exe,双击即可打开,
【游戏开发小技】TexturePacker生成的图集逆向切分成精灵小图(json | python | PIL | TextureUnPacker | 逆向 | 切图)
如下
【游戏开发小技】TexturePacker生成的图集逆向切分成精灵小图(json | python | PIL | TextureUnPacker | 逆向 | 切图)

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

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

以下是我随便搜的一些精灵小图,
【游戏开发小技】TexturePacker生成的图集逆向切分成精灵小图(json | python | PIL | TextureUnPacker | 逆向 | 切图)

4、使用TexturePacker打图集

首先点击界面右侧的 框架,根据需求选择一个框架,
【游戏开发小技】TexturePacker生成的图集逆向切分成精灵小图(json | python | PIL | TextureUnPacker | 逆向 | 切图)

它支持非常多的框架,如下
【游戏开发小技】TexturePacker生成的图集逆向切分成精灵小图(json | python | PIL | TextureUnPacker | 逆向 | 切图)
我选择的是通用的Phaser 3,如下,(因为我那两个同事发给我的图集格式都是用的这个-_-,那我就用这个来做演示吧)
【游戏开发小技】TexturePacker生成的图集逆向切分成精灵小图(json | python | PIL | TextureUnPacker | 逆向 | 切图)

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

【游戏开发小技】TexturePacker生成的图集逆向切分成精灵小图(json | python | PIL | TextureUnPacker | 逆向 | 切图)
接着,我们点击发布精灵表
【游戏开发小技】TexturePacker生成的图集逆向切分成精灵小图(json | python | PIL | TextureUnPacker | 逆向 | 切图)
填写保存路径,即可生成图集png和一个json配置文件,如下
【游戏开发小技】TexturePacker生成的图集逆向切分成精灵小图(json | python | PIL | TextureUnPacker | 逆向 | 切图)

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这个框架,其他框架的格式需要自行分析)
【游戏开发小技】TexturePacker生成的图集逆向切分成精灵小图(json | python | PIL | TextureUnPacker | 逆向 | 切图)
知道了json的结构,以及每个字段的含义,我们就可以开始逆向了~

三、图集逆向切分精灵图

1、环境准备

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

pip install Pillow

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

【游戏开发小技】TexturePacker生成的图集逆向切分成精灵小图(json | python | PIL | TextureUnPacker | 逆向 | 切图)

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我标注了黄色出来,如下
【游戏开发小技】TexturePacker生成的图集逆向切分成精灵小图(json | python | PIL | TextureUnPacker | 逆向 | 切图)
其中json配置的小图解析,我封装在parse_as_json方法中,如下,需要注意rotated旋转,
【游戏开发小技】TexturePacker生成的图集逆向切分成精灵小图(json | python | PIL | TextureUnPacker | 逆向 | 切图)

生成小图的逻辑封装在generate_little_image方法中,这个方法中用到了PIL库的一些API
【游戏开发小技】TexturePacker生成的图集逆向切分成精灵小图(json | python | PIL | TextureUnPacker | 逆向 | 切图)

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

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

3.1、PIL.Image.open方法

【游戏开发小技】TexturePacker生成的图集逆向切分成精灵小图(json | python | PIL | TextureUnPacker | 逆向 | 切图)
打开图像,返回图像对象,
示例:

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

【游戏开发小技】TexturePacker生成的图集逆向切分成精灵小图(json | python | PIL | TextureUnPacker | 逆向 | 切图)
创建image对象,示例:

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

【游戏开发小技】TexturePacker生成的图集逆向切分成精灵小图(json | python | PIL | TextureUnPacker | 逆向 | 切图)
裁剪图像的某个矩形区域,示例

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方法

【游戏开发小技】TexturePacker生成的图集逆向切分成精灵小图(json | python | PIL | TextureUnPacker | 逆向 | 切图)
粘贴另一个image到自身image上,示例:

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

【游戏开发小技】TexturePacker生成的图集逆向切分成精灵小图(json | python | PIL | TextureUnPacker | 逆向 | 切图)
image保存为文件,示例:

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

python代码保存为TextureUnpacker.py,放在图集文件同级目录中,
【游戏开发小技】TexturePacker生成的图集逆向切分成精灵小图(json | python | PIL | TextureUnPacker | 逆向 | 切图)
执行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

可以看到生成了一个文件夹,
【游戏开发小技】TexturePacker生成的图集逆向切分成精灵小图(json | python | PIL | TextureUnPacker | 逆向 | 切图)
文件夹里就可以看到生成出来的精灵小图啦
【游戏开发小技】TexturePacker生成的图集逆向切分成精灵小图(json | python | PIL | TextureUnPacker | 逆向 | 切图)
完美,收工~
我是林新发,https://blog.csdn.net/linxinfa
一个在小公司默默奋斗的Unity开发者,希望可以帮助更多想学Unity的人,共勉~文章来源地址https://www.toymoban.com/news/detail-442001.html

到了这里,关于【游戏开发小技】TexturePacker生成的图集逆向切分成精灵小图(json | python | PIL | TextureUnPacker | 逆向 | 切图)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 64.网游逆向分析与插件开发-游戏增加自动化助手接口-优化自动助手与游戏焦点的切换

    内容来源于: 易道云信息技术研究院VIP课 上一个内容:自动化助手UI与游戏菜单的对接-CSDN博客 码云地址(master分支):https://gitee.com/dye_your_fingers/sro_-ex.git 码云版本号:617ac3477ef18273fb9cc281be3c04052304b965 代码下载地址,在 SRO_EX 目录下,文件名为:SRO_Ex-优化自动助手与游戏焦

    2024年01月15日
    浏览(54)
  • 63.网游逆向分析与插件开发-游戏增加自动化助手接口-自动化助手UI与游戏菜单的对接

    内容来源于: 易道云信息技术研究院VIP课 上一个内容:游戏公告类的C++还原-CSDN博客 码云地址(master分支):https://gitee.com/dye_your_fingers/sro_-ex.git 码云版本号:19a2828def451a280ee211c62dcd1074ed422054 代码下载地址,在 SRO_EX 目录下,文件名为:SRO_Ex-自动化助手UI与游戏菜单的对接.

    2024年02月02日
    浏览(43)
  • 【Unity编辑器扩展】包体优化神器,图片压缩,批量生成图集/图集变体,动画压缩

    功能介绍: 1. 压缩工具支持对图片原文件压缩(支持png/jpg),也支持使用Unity内置图片压缩批量对图片设置压缩参数。 2. 支持以文件夹或及其子文件夹为单位批量生成图集(SpriteAtlas), 支持同时生成图集变体(SpriteAtlas Variant),支持忽略像素宽高大于限定值的图片打进图集。 3. 批

    2023年04月10日
    浏览(46)
  • Unity SpriteAtlas(图集)自动生成工具

    图集是一种将多个纹理合并为一个组合纹理的资源。 可以调用此单个纹理来发出单个绘制调用而不是发出多个绘制调用,能够以较小的性能开销一次性访问压缩的纹理 减少DrawCall,一张图集只需要一次DrawCall 图集将一张或者多张图片合成一张2的幂次方的图片,减少资源大小

    2024年02月13日
    浏览(50)
  • 【Android 逆向】程序员高危开发方向 ( 违法软件类型 | 赌博游戏 | 色情类应用 | 涉及金融类软件 | 爬虫类软件 | 区块链货币 | 甄别是否合法 )

    棋牌类 游戏开发 , 写这类游戏的程序员 很容易被抓 , 只要 涉及到了 充值 以及 提现 , 就是涉嫌赌博 ; 常见的 就是 麻将类游戏 , 纸牌类游戏 , 具体的地方麻将或扑克玩法 , 德州扑克 , 21 点 , 老虎机 等 类型的 游戏 ; 抽卡类的游戏 , 充值 然后 赌概率 , 比如原神这种 , 只充值

    2024年01月19日
    浏览(47)
  • 【游戏逆向】Lua游戏逆向及破解方法介绍

    随着手游的发展,越来越多的Cocos-lua端游开发者转移到手游平台。Lua脚本编写逻辑的手游也是越来越多,如梦幻西游、刀塔传奇、开心消消乐、游龙英雄、奇迹暖暖、疾风猎人、万万没想到等手游。随着Lua手游的增加,其安全性更值得关注,在此归纳一些常用的分析方法,同

    2024年02月04日
    浏览(50)
  • x86游戏逆向之实战游戏线程发包与普通发包的逆向

    网游找Call的过程中难免会遇到不方便通过数据来找的或者仅仅查找数据根本找不到的东西,但是网游中一般的工程肯定要发给服务器,比如你打怪,如果都是在本地处理的话就特别容易产生变态功能,而且不方便与其他玩家通信,所以找到了游戏发包的地方,再找功能就易如

    2024年02月06日
    浏览(46)
  • 9.网络游戏逆向分析与漏洞攻防-游戏网络架构逆向分析-接管游戏连接服务器的操作

    内容参考于:易道云信息技术研究院VIP课 上一个内容:游戏底层功能对接类GameProc的实现 码云地址(master 分支):https://gitee.com/dye_your_fingers/titan 码云版本号:44c54d30370d3621c1e9ec3d7fa1e2a028e773e9 代码下载地址,在 titan 目录下,文件名为:titan-接管游戏连接服务器的操作.zip 链接

    2024年03月08日
    浏览(47)
  • 游戏逆向_Android读写游戏内容

    一、背景 Android外挂的实现,需要涉及相应游戏内容的读写。读写的游戏内容包括代码和数据 针对不同的读写对象,通用的步骤就是寻找对象地址(位置)→获取相应权限→读写。下面将更详细介绍下相关实现。 二、实现方式 实现方式可以分为两大类:注入式和非注入式。

    2023年04月13日
    浏览(41)
  • [游戏开发][Unity] Xlua生成wrap文件报错、打AB包Wrap报错

     Xlua生成wrap文件,自带添加了ref字段报错 例如Material生成MaterialWrap时,EnableKeyword(in LocalKeyword keyword);带着in,所以在Wrap文件中会自动在参数前生成ref导致编译不过 解决办法: 换Xlua版本就好了,也不知道我xlua当时从哪个版本copy过来的,换了xlua-master里的Xlua源码

    2024年02月04日
    浏览(54)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包