當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輸出影像資源。
總結
一步步瞭解一個東西過程還是很有意思的,每走一步都會有新的發現。事實上還是有很多東西沒有搞懂,就留著以後去發現了。