當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
中。最后再通過BaseImageResource
的upload()
調用GPU輸出圖像資源。
總結
一步步了解一個東西過程還是很有意思的,每走一步都會有新的發現。事實上還是有很多東西沒有搞懂,就留着以后去發現了。