cocos做一个图片空白区域颜色填充(colorFillTexture)

本人技术一般,从论坛看到一个大佬写的图片填充思路,拿过来用了一下,
效果挺不错,在这分享一下, 版本 cocos3.8.1

cocos论坛链接: https://forum.cocos.org/t/cocos-creator/82612/22

效果如图
请添加图片描述

思路

对于里面涉及的关于图片的原理我接触的也比较少,但是思路如下:
1.通过点击的位置,将cocos坐标系位置转换为在图片坐标系位置
(转成到图片坐标系,Cocos默认坐标系是图片的中心点,X方向向右,Y方向向上,而图片格式的坐标默认是在左上角,X方向向右,Y方向向下。)
2.从图片坐标系位置开始遍历其上下左右各一个像素的Alpha值是否小于自己设置的Alpha值(我用的是255),如果是,将其颜色利用texture内部函数改变自己指定的color,从而达到改变颜色的效果;如果遇到的Alpha值等于255,则return
目的是找到途中黑色边框所框住的范围,然后上色即可(此处我是写的固定的生成绿色,可以动态去切换颜色)
因为是通过Alpha值去判断的,所以底图必须是png的透明图才可以
我是从花瓣找的一张线稿图,如图:
请添加图片描述

背景是cocos自带的单色图,我设置成橙色了

代码

代码基本上是和论坛里的代码一样,就两三个脚本,非常简洁

1.此脚本主要是处理texture中的内容

import { _decorator, Color, Component, director, gfx, IVec2, log, Node, Texture2D, v2 } from 'cc';
const { ccclass, property } = _decorator;

enum GridIndex { Center, Up, Down, Left, Right }
const gridIndexOffset: readonly IVec2[] = [null, v2(0, +1), v2(0, -1), v2(-1, 0), v2(+1, 0)];
const gridIndexNeighbors: Array<GridIndex>[] = [];
gridIndexNeighbors[GridIndex.Center] = [GridIndex.Up, GridIndex.Down, GridIndex.Left, GridIndex.Right];
gridIndexNeighbors[GridIndex.Up] = [GridIndex.Up, GridIndex.Left, GridIndex.Right];
gridIndexNeighbors[GridIndex.Down] = [GridIndex.Down, GridIndex.Left, GridIndex.Right];
gridIndexNeighbors[GridIndex.Left] = [GridIndex.Left, GridIndex.Up, GridIndex.Down];
gridIndexNeighbors[GridIndex.Right] = [GridIndex.Right, GridIndex.Up, GridIndex.Down];
@ccclass('ColorFillTexture')
export class ColorFillTexture extends Component {
    public readonly texture: Texture2D;
    public readonly sourceData: Uint8Array;
    public readonly targetData: Uint8Array;
    public readonly width: number;
    public readonly height: number;

    public constructor(source: Texture2D, public alphaThreshold: number = 100, public maxFillCount: number = 30) {
        super();
        this.sourceData = ColorFillTexture.readTexturePixels(source);
        this.targetData = Uint8Array.from(this.sourceData);
        this.texture = ColorFillTexture.createTextureFromPixels(this.targetData, source.width, source.height);
        this.width = this.texture.width;
        this.height = this.texture.height;
    }

    public setTextureColor(color: Color, x: number, y: number): boolean {
        x = ColorFillTexture.translateX(this.width, x);
        y = ColorFillTexture.translateY(this.height, y);
        return this.setColor(color, x, y);
    }

    private setColor(color: Color, x: number, y: number): boolean {
        if (x < 0 || y < 0 || x > this.width || y > this.height) return false;

        ColorFillTexture.setBufferColor(this.targetData, this.width, x, y, color);
        if (ColorFillTexture.getAlphaColor(this.sourceData, this.width, x, y) > this.alphaThreshold) return false;
        return true;
    }

    public updateTextureData(): void {
        this.texture.uploadData(this.targetData);
    }

    public fillTextureColor(color: Color, x: number, y: number) {
        x = ColorFillTexture.translateX(this.width, x);
        y = ColorFillTexture.translateY(this.height, y);
        this.fillColor(color, x, y, gridIndexNeighbors[GridIndex.Center]);
    }

    private async fillColor(color: Color, x: number, y: number, gridIndexTypes: readonly GridIndex[]) {
        let colorPointList1 = [{ x, y, neighborTypes: gridIndexTypes }];
        let colorPointList2 = [];
        let filleCount = 0;
        do {
            for (const item of colorPointList1) {
                const result = this.setColor(color, item.x, item.y);
                for (const type of item.neighborTypes) {
                    const offset = gridIndexOffset[type];
                    const nx = item.x + offset.x, ny = item.y + offset.y;

                    if (ColorFillTexture.getAlphaColor(this.targetData, this.width, nx, ny) == color.a) continue;
                    if (colorPointList2.find(v => v.x == nx && v.y == ny) == null) {
                        colorPointList2.push({ x: nx, y: ny, neighborTypes: gridIndexNeighbors[type] });
                    }
                }
                if (!result) continue;
            }

            let temp = colorPointList1;
            colorPointList1 = colorPointList2;
            colorPointList2 = temp;
            colorPointList2.length = 0;
            //
            if (++filleCount > this.maxFillCount) {
                filleCount = 0;
                this.updateTextureData();
                //Timer.instance.waitForTime(0);
            }
        } while (colorPointList1.length > 0);

        this.updateTextureData();
    }

    /**
     * 转成到图片坐标系,Cocos默认坐标系是图片的中心点,X方向向右,Y方向向上,而图片格式的坐标默认是在左上角,X方向向右,Y方向向下。
     * @param width 
     * @param x 
     * @returns 
     */
    public static translateX(width: number, x: number): number {
        return Math.trunc(x + width * 0.5);
    }

    /**
     * 转成到图片坐标系,Cocos默认坐标系是图片的中心点,X方向向右,Y方向向上,而图片格式的坐标默认是在左上角,X方向向右,Y方向向下。
     * @param height 
     * @param y 
     * @returns 
     */
    public static translateY(height: number, y: number): number {
        return Math.trunc(-y + height * 0.5);
    }

    /**
     * 获得该颜色buffer的alpha
     * @param buffer 颜色buffer
     * @param width 图片宽度
     * @param x x坐标
     * @param y y坐标
     * @returns 该坐标的alpha
     */
    public static getAlphaColor(buffer: Uint8Array, width: number, x: number, y: number): number {
        const index = ColorFillTexture.positionToBufferIndex(width, x, y);
        return buffer[index + 3];
    }

    public static setBufferColor(buffer: Uint8Array, width: number, x: number, y: number, color: Color): void {
        const index = ColorFillTexture.positionToBufferIndex(width, x, y);
        buffer[index + 0] = color.r;
        buffer[index + 1] = color.g;
        buffer[index + 2] = color.b;
        buffer[index + 3] = color.a;
    }

    /**
     * 点坐标转换成图片buffer的索引
     * @param width 图片的宽度
     * @param x x坐标
     * @param y y坐标
     * @param colorSize 颜色是RGB还是RGBA组成
     * @returns buffer索引
     */
    public static positionToBufferIndex(width: number, x: number, y: number, colorSize: 3 | 4 = 4): number {
        return Math.trunc(x + y * width) * colorSize;
    }

    /**
     * 读取texture的像素到一个buffer
     * @param texture texture原图
     * @param width 
     * @param height 
     * @param x 
     * @param y 
     * @returns 
     */
    public static readTexturePixels(texture: Texture2D, x = 0, y = 0): Uint8Array {
        const gfxTexture = texture.getGFXTexture();
        if (gfxTexture == null) return null;

        const bufferSize = 4 * texture.width * texture.height;
        const buffer = new Uint8Array(bufferSize);

        const region = new gfx.BufferTextureCopy();
        region.texOffset.x = x;
        region.texOffset.y = y;
        region.texExtent.width = texture.width;
        region.texExtent.height = texture.height;

        director.root.device.copyTextureToBuffers(gfxTexture, [buffer], [region]);
        return buffer;
    }

    /**
     * 从像素的buffer中创建一个texture
     * @param buffer 像素buffer
     * @param width 图片宽度
     * @param height 图片高度
     * @param format 图片格式
     * @param mipmapLevel 图片的mipmap等级
     * @returns 新的texture
     */
    public static createTextureFromPixels(buffer: ArrayBufferView, width: number, height: number, format = Texture2D.PixelFormat.RGBA8888, mipmapLevel?: number): Texture2D {
        const texture = new Texture2D();
        texture.reset({ width, height, format, mipmapLevel });
        texture.uploadData(buffer);
        return texture;
    }
}

2.通过控制点击事件,实现上色功能

import { _decorator, Color, Component, EventTouch, ImageAsset, Node, Sprite, SpriteFrame, Texture2D, UITransform, v3 } from 'cc';
import { ColorFillTexture } from './ColorFillTexture';
import logicConfig from './LogicConfig';
const { ccclass, property } = _decorator;

@ccclass('Scene')
export class Scene extends Component {
    @property(Texture2D)
    texture2D: Texture2D = null;
    @property(Sprite)
    sprite: Sprite = null;
    colorFillTexture: ColorFillTexture = null;
    start() {
        this.sprite.node.on(Node.EventType.TOUCH_START, this.onTouchStart, this);
        this.createColorFill(this.texture2D);
    }

    private createColorFill(texture: Texture2D): void {
        this.colorFillTexture = new ColorFillTexture(texture, logicConfig.alphaThreshold, logicConfig.maxFillCount);
        let spriteFrame = new SpriteFrame();
        spriteFrame.texture = this.colorFillTexture.texture;
        this.sprite.spriteFrame = spriteFrame;
    }

    private onImageLoad(data: string): void {
        const image = new Image();
        image.src = data;
        image.addEventListener('load', () => {
            const texture = new Texture2D();
            texture.image = new ImageAsset(image);
            this.createColorFill(texture);
        });
    }

    private onTouchStart(event: EventTouch): void {
        const location = event.touch.getUILocation();
        const uiTransform = this.sprite.getComponent(UITransform);
        const position = uiTransform.convertToNodeSpaceAR(v3(location.x, location.y, 0));
        let color = new Color(0, 255, 0, 255);
        this.colorFillTexture.fillTextureColor(color, position.x, position.y);
    }
}

3.配置文件脚本

import { _decorator, Component, Node } from 'cc';
const { ccclass, property } = _decorator;

@ccclass('LogicConfig')
class LogicConfig {
    alphaThreshold: number = 200;
    maxFillCount: number = 30;
}
let logicConfig = new LogicConfig();
export default logicConfig;

扩展

我在demo中将颜色固定填充成绿色,需要的话可以在画面中生成几种颜色可供选择,这样图片就变得丰富多彩起来,类似于画板填充的功能就实现了

结束语

内容比较少,里面主要涉及对图片的处理,需要了解一些纹理方面的知识

觉得有用的可以点个赞,留个言,我平时会分享一些简单好玩的功能!

demo工程比较简单,需要参考的话,私信我!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值