pixi.js 圖像資源(svg)轉紋理


當Pixi使用WebGL去調用GPU渲染圖像時,需要先將圖像轉化為GPU可以處理的版本。而能夠被GPU處理的圖像就叫做紋理,在pixi中使用紋理緩存來存儲和引用所有紋理。通過將紋理分配給精靈,再將精靈添加到舞台上,從而顯示圖像。

圖像轉化為紋理的方式

1. app的loader對象

Pixi強大的loader對象可以加載任何種類的圖像資源,並保存在紋理緩存中。后續如果需要繼續獲取紋理,就不用再重復加載圖像,直接從緩存中獲取即可,減輕GPU內存占用。

app.loader
  .add("imgs/1.jpg")
  .load(setup);

function setup() {
  //This code will run when the loader has finished loading the image
  let sprite = new PIXI.Sprite(app.loader.resources["imgs/1.jpg"].texture);
  app.stage.add(spirte)
}
2. Pixi的Texture類型

Pixi的Texture類型,實現了加載圖像的靜態方法。
static from(source: string | HTMLImageElement | HTMLCanvasElement | HTMLVideoElement | PIXI.BaseTexture, options?: any, strict?: boolean): PIXI.Texture;

從允許的參數類型可以看出,我們可以通過直接傳入圖像地址、img標簽、canvas標簽、video標簽,將圖像轉化為紋理。

  • 通過圖像鏈接加載
var texture = PIXI.Texture.from("imgs/1.jpg");
var sprite = new PIXI.Sprite(PIXI.utils.TextureCache["imgs/1.jpg"]);
app.stage.addChild(sprite);

根據鏈接加載圖像,更推薦這種方式,可以捕獲到圖像加載失敗

ps:其實fromURL(),內部調用的還是from()

 PIXI.Texture.fromURL(data)
.then((texture) => {
    const spirte = new PIXI.Sprite(texture);
    app.stage.addChild(spirte);
})
.catch((e) => {
    console.log("load error", e);
});
  • 通過標簽加載
var img = document.getElementById("img");
var texture = PIXI.Texture.from(img);
var sprite = new PIXI.Sprite(texture)
app.stage.addChild(sprite)

將SVG Dom節點轉為紋理

如果只是單純的把svg作為一個單獨的外部文件,其實只要按照上面兩種轉換紋理的方式,傳入svg圖像鏈接就可以實現了。但是如果這個svg是在同一個html頁上的dom節點呢?這時候還能將其轉為紋理嗎?答案是可以的。

注意觀察Texture.from()的參數,可以傳入圖像的鏈接。那么base64編碼后的圖像地址,按理來說也可以。所以只要將頁面上的svg節點,轉化為base64編碼即可。

function getSvgBase64(id) {
    var svg = document.getElementById(id)
    return "data:image/svg+xml;base64," +  window.btoa(svg.outerHTML);
}

關鍵代碼:window.btoa() 創建一個base64編碼的字符串,解碼方法 window.atob()

源碼解析

首先,從Texture.from()開始入手,我們具體看看pixi是如何加載圖像紋理的。

在from方法中有這么一句話texture = new Texture(new BaseTexture(source, options));。所有的Texture對應的還有一個BaseTexture,他們之間的關系可以這么解釋

BaseTexture : The base texture source to create the texture from

接下來看一下 BaseTexture類的構造函數,其中調用了autoDetectResource()方法,在這個方法中真正的對資源進行了檢測分類,並根據不同類型的資源調用不同的資源插件(ResourcePlugin)。

function autoDetectResource(source: unknown, options?: IAutoDetectOptions): Resource
{
    if (!source)
    {
        return null;
    }

    let extension = '';
    if (typeof source === 'string')
    {
        // search for file extension: period, 3-4 chars, then ?, # or EOL
        const result = (/\.(\w{3,4})(?:$|\?|#)/i).exec(source);
        if (result)
        {
            extension = result[1].toLowerCase();
        }
    }

    for (let i = INSTALLED.length - 1; i >= 0; --i)
    {
        const ResourcePlugin = INSTALLED[i];
        if (ResourcePlugin.test && ResourcePlugin.test(source, extension))
        {
            return new ResourcePlugin(source, options);
        }
    }
    throw new Error('Unrecognized source type to auto-detect Resource');
}

INSTALLED在index.ts中已經初始化注入所有的ResourcePlugin

INSTALLED.push(
    ImageResource,
    ImageBitmapResource,
    CanvasResource,
    VideoResource,
    SVGResource,
    BufferResource,
    CubeResource,
    ArrayResource
);

在這里可以看到,pixi中有一個SVGResource,我們就以這個為例繼續深入看下內部的處理機制。

簡化版SVGResource類:

export class SVGResource extends BaseImageResource
{
    constructor(sourceBase64: string, options?: ISVGResourceOptions)
    {
        //...
        super(document.createElement('canvas'));
        if (options.autoLoad !== false)
        {
            this.load();
        }
    }

    load(): Promise<SVGResource>
    {
        // Convert SVG inline string to data-uri
        if ((/^\<svg/).test(this.svg.trim()))
        {
            if (!btoa)
            {
                throw new Error('Your browser doesn\'t support base64 conversions.');
            }
            (this as any).svg = `data:image/svg+xml;base64,${btoa(unescape(encodeURIComponent(this.svg)))}`;
        }

        this._loadSvg();
        return this._load;
    }

    /**
     * Loads an SVG image from `imageUrl` or `data URL`.
     *
     * @private
     */
    private _loadSvg(): void
    {
        const tempImage = new Image();

        BaseImageResource.crossOrigin(tempImage, this.svg, this._crossorigin);
        tempImage.src = this.svg;//將base64編碼的Svg字符串,創建為Image對象

        tempImage.onload = (): void =>
        {
            // Draw the Svg to the canvas
            canvas
                .getContext('2d')
                .drawImage(tempImage, 0, 0, svgWidth, svgHeight, 0, 0, width, height);

        };
    }

    static test(source: unknown, extension?: string): boolean
    {
        // url file extension is SVG
        return extension === 'svg'
            // source is SVG data-uri
            || (typeof source === 'string' && (/^data:image\/svg\+xml(;(charset=utf8|utf8))?;base64/).test(source))
            // source is SVG inline
            || (typeof source === 'string' && source.indexOf('<svg') === 0);
    }

}

看完這里就差不多明白了,對於傳入的Source來說,當在autoDetectResource()中通過test()方法檢測到資源為SVG格式后,將其轉換為Base64字符串(也就是說直接傳入拼接好的svg字符串也是可以被解析的~),然后再load為Image對象,加載到臨時canvas中。最后再通過BaseImageResourceupload()調用GPU輸出圖像資源。

總結

一步步了解一個東西過程還是很有意思的,每走一步都會有新的發現。事實上還是有很多東西沒有搞懂,就留着以后去發現了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM