2022-05-26 11:46:36 +08:00

673 lines
19 KiB
TypeScript

import LZW from "./LZW";
export enum FileType {
UNKNOWN,
PNG,
JPG,
GIF,
WEBP
}
export class FileHead {
static IMAGE_PNG = "89504e47";
static IMAGE_JPG = "ffd8ff";
static IMAGE_GIF = "474946";
/**
* Webp
*/
static RIFF = "52494646";
static WEBP_RIFF = FileHead.RIFF;
static WEBP_WEBP = "57454250";
}
class GIF {
private _tab: any;
private _view: Uint8Array;
private _frame: any;
private _buffer: ArrayBuffer;
private _offset: number = 0;
private _lastData: ImageData;
private _info: any = {
header: '',
frames: [],
comment: ''
};
private _delays: Array<number> = [];
private _spriteFrames: Array<cc.SpriteFrame> = [];
private _canvas: HTMLCanvasElement = null;
private _context: CanvasRenderingContext2D = null;
public id = "GIF"
public async = true
set buffer(buffer: ArrayBuffer) {
this.clear();
this._buffer = buffer;
this._view = new Uint8Array(buffer);
}
get buffer() {
return this._buffer;
}
/**
*
* @param item
* @param callback
*/
handle(item, callback) {
this.buffer = item.content
this.getHeader();
this.getScrDesc();
this.getTexture();
if (this._spriteFrames.length == 0) {
callback(new Error("gif loading error"))
} else {
callback(null, { delays: this._delays, spriteFrames: this._spriteFrames, length: this._info.frames.length })
}
}
/**
*
* @param data
*/
static detectFormat(data): FileType {
if (data.indexOf(FileHead.IMAGE_GIF) != -1) {
return FileType.GIF;
} else if (data.indexOf(FileHead.IMAGE_PNG) != -1) {
return FileType.PNG;
} else if (data.indexOf(FileHead.IMAGE_JPG) != -1) {
return FileType.JPG;
} else if (data.indexOf(FileHead.WEBP_RIFF) != -1 && data.indexOf(FileHead.WEBP_WEBP) != -1) {
return FileType.WEBP;
} else {
return FileType.UNKNOWN
}
}
/**
*
* @param arrBytes
*/
static bytes2HexString(arrBytes) {
var str = "";
for (var i = 0; i < arrBytes.length; i++) {
var tmp;
var num = arrBytes[i];
if (num < 0) {
tmp = (255 + num + 1).toString(16);
} else {
tmp = num.toString(16);
}
if (tmp.length == 1) {
tmp = "0" + tmp;
}
str += tmp;
}
return str;
}
/**
*
*/
private getTexture() {
// console.log(this._info)
let index = 0;
for (const frame of this._info.frames) {
this.decodeFrame2Texture(frame, index++);
}
// this.getSpriteFrame(0);
}
/**
*
* @param index
*/
public getSpriteFrame(index) {
if (this._spriteFrames[index]) return this._spriteFrames[index];
return this.decodeFrame2Texture(this._info.frames[index], index);
}
/**
*
*
* @param frame frame
*/
private decodeFrame(frame) {
let imageData = this._context.getImageData(frame.img.x, frame.img.y, frame.img.w, frame.img.h)
frame.img.m ? this._tab = frame.img.colorTab : this._tab = this._info.colorTab;
LZW.decode(frame.img.srcBuf, frame.img.codeSize).forEach(function (j, k) {
imageData.data[k * 4] = this._tab[j * 3];
imageData.data[k * 4 + 1] = this._tab[j * 3 + 1];
imageData.data[k * 4 + 2] = this._tab[j * 3 + 2];
imageData.data[k * 4 + 3] = 255;
frame.ctrl.t ? (j == frame.ctrl.tranIndex ? imageData.data[k * 4 + 3] = 0 : 0) : 0;
}.bind(this));
// for (var i = 0; i < imageData.data.length; i += 4) {
// imageData.data[i + 0] = 255;
// imageData.data[i + 1] = 0;
// imageData.data[i + 2] = 0;
// imageData.data[i + 3] = 255;
// }
return imageData;
}
/**
*
* @param lastImageData
* @param curImageData
*/
private mergeFrames(lastImageData, curImageData) {
let imageData = curImageData;
if (lastImageData) {
for (var i = 0; i < imageData.data.length; i += 4) {
if (imageData.data[i + 3] == 0) {
imageData.data[i] = this._lastData.data[i];
imageData.data[i + 1] = this._lastData.data[i + 1];
imageData.data[i + 2] = this._lastData.data[i + 2];
imageData.data[i + 3] = this._lastData.data[i + 3];
}
}
}
return imageData;
}
/**
* @param dataUrl
*/
private dataUrl2SpriteFrame(dataUrl) {
let texture = new cc.Texture2D()
let spriteFrame = new cc.SpriteFrame();
let image = new Image();
image.src = dataUrl;
texture.initWithElement(image);
spriteFrame.setTexture(texture);
return spriteFrame;
}
/**
* @param data
* @param w
* @param h
*/
private date2SpriteFrame(data, w, h) {
let texture = new cc.RenderTexture();
let spriteFrame = new cc.SpriteFrame();
texture.initWithData(data.data, cc.Texture2D.PixelFormat.RGBA8888, w, h);
spriteFrame.setTexture(texture);
return spriteFrame;
}
/**
* @param imageData
* @param x
* @param y
*/
putImageDataJSB(imageData, x, y, frame) {
let cheeckNullPixel = () => {
if (imageData.data[0] == 4
&& imageData.data[1] == 0
&& imageData.data[2] == 0
&& imageData.data[3] == 0) {
return true
}
return false
}
let checkAlpha = () => {
let alphaCount = 0
for (let i = 0; i < imageData.height; i += 2) {
let lineCount = 0
for (let j = 0; j < imageData.width; j++) {
let indexData = i * 4 * imageData.width + 4 * j
if (imageData.data[indexData + 3] == 0) {
lineCount++
}
}
if (lineCount / imageData.width > 0.1) {
alphaCount++
}
if (alphaCount / (imageData.height / 2) > 0.6) return true
}
return false
}
let replay = () => {
for (let i = 0; i < imageData.height; i++) {
for (let j = 0; j < imageData.width; j++) {
let indexData = i * 4 * imageData.width + 4 * j
let indexLastData = (i + y) * 4 * this._lastData.width + 4 * (j + x)
if (imageData.data[indexData + 3] != 0) {
this._lastData.data[indexLastData] = imageData.data[indexData]
this._lastData.data[indexLastData + 1] = imageData.data[indexData + 1]
this._lastData.data[indexLastData + 2] = imageData.data[indexData + 2]
this._lastData.data[indexLastData + 3] = imageData.data[indexData + 3]
}
}
}
}
let clearAndReplay = () => {
for (let i = 0; i < this._lastData.height; i++) {
for (let j = 0; j < this._lastData.width; j++) {
let indexLastData = i * 4 * this._lastData.width + 4 * j
let indexData = (i - y) * 4 * imageData.width + 4 * (j - x)
let clear = false
if (j < x || j > (x + imageData.width)) {
clear = true
}
if (i < y || i > (y + imageData.height)) {
clear = true
}
if (clear) {
this._lastData.data[indexLastData + 0] = 0;
this._lastData.data[indexLastData + 1] = 0;
this._lastData.data[indexLastData + 2] = 0;
this._lastData.data[indexLastData + 3] = 0;
} else {
this._lastData.data[indexLastData + 0] = imageData.data[indexData + 0]
this._lastData.data[indexLastData + 1] = imageData.data[indexData + 1]
this._lastData.data[indexLastData + 2] = imageData.data[indexData + 2]
this._lastData.data[indexLastData + 3] = imageData.data[indexData + 3]
}
}
}
}
if (cheeckNullPixel()) {
return
}
if (frame.ctrl.disp == 1 || frame.ctrl.disp == 0) {
replay()
} else if (frame.ctrl.disp == 2) {
clearAndReplay()
} else {
if (checkAlpha()) {
clearAndReplay()
} else {
replay()
}
}
}
/**
* @param imageData
* @param frame
*/
putImageDataWeb(imageData, frame) {
let finalImageData
if (frame.ctrl.disp == 1 || frame.ctrl.disp == 0) {
this._context.putImageData(imageData, frame.img.x, frame.img.y, 0, 0, frame.img.w, frame.img.h);
let curImageData = this._context.getImageData(0, 0, this._canvas.width, this._canvas.height);
let lastImageData = this._lastData;
finalImageData = this.mergeFrames(lastImageData, curImageData);
} else {
this._context.clearRect(0, 0, this._canvas.width, this._canvas.height)
this._context.putImageData(imageData, frame.img.x, frame.img.y, 0, 0, frame.img.w, frame.img.h);
finalImageData = this._context.getImageData(0, 0, this._canvas.width, this._canvas.height);
}
this._context.putImageData(finalImageData, 0, 0);
this._lastData = finalImageData;
return this._canvas.toDataURL();
}
/**
* @param frame
* @param index
*/
private decodeFrame2Texture(frame, index) {
if (!this._context) {
this._canvas = document.createElement('canvas');
this._context = this._canvas.getContext('2d');
this._canvas.width = frame.img.w;
this._canvas.height = frame.img.h;
}
let imageData = this.decodeFrame(frame);
this._delays[index] = frame.ctrl.delay;
if (CC_JSB) {
if (!this._lastData) {
this._lastData = imageData
} else {
this.putImageDataJSB(imageData, frame.img.x, frame.img.y, frame);
}
this._spriteFrames[index] = this.date2SpriteFrame(this._lastData, this._canvas.width, this._canvas.height);
} else {
let dataUrl = this.putImageDataWeb(imageData, frame)
this._spriteFrames[index] = this.dataUrl2SpriteFrame(dataUrl);
}
return this._spriteFrames[index];
}
/**
* @param len
*/
private read(len) {
return this._view.slice(this._offset, this._offset += len);
}
/**
*/
private getHeader() {
this._info.header = '';
this.read(6).forEach(function (e, i, arr) {
this._info.header += String.fromCharCode(e);
}.bind(this));
}
/**
*/
private getScrDesc() {
// await 0;
var arr = this.read(7), i;
this._info.w = arr[0] + (arr[1] << 8);
this._info.h = arr[2] + (arr[3] << 8);
this._info.m = 1 & arr[4] >> 7;
this._info.cr = 7 & arr[4] >> 4;
this._info.s = 1 & arr[4] >> 3;
this._info.pixel = arr[4] & 0x07;
this._info.bgColor = arr[5];
this._info.radio = arr[6];
if (this._info.m) {
this._info.colorTab = this.read((2 << this._info.pixel) * 3);
}
this.decode();
}
/**
*/
private decode() {
let srcBuf = [];
let arr = this.read(1);
switch (arr[0]) {
case 33: //
this.extension();
break;
case 44: //
arr = this.read(9);
this._frame.img = {
x: arr[0] + (arr[1] << 8),
y: arr[2] + (arr[3] << 8),
w: arr[4] + (arr[5] << 8),
h: arr[6] + (arr[7] << 8),
colorTab: 0
};
this._frame.img.m = 1 & arr[8] >> 7;
this._frame.img.i = 1 & arr[8] >> 6;
this._frame.img.s = 1 & arr[8] >> 5;
this._frame.img.r = 3 & arr[8] >> 3;
this._frame.img.pixel = arr[8] & 0x07;
if (this._frame.img.m) {
this._frame.img.colorTab = this.read((2 << this._frame.img.pixel) * 3);
}
this._frame.img.codeSize = this.read(1)[0];
srcBuf = [];
while (1) {
arr = this.read(1);
if (arr[0]) {
this.read(arr[0]).forEach(function (e, i, arr) {
srcBuf.push(e);
});
} else {
this._frame.img.srcBuf = srcBuf;
this.decode();
break;
}
};
break;
case 59:
console.log('The end.', this._offset, this.buffer.byteLength)
break;
default:
// console.log(arr);
break;
}
}
/**
*/
private extension() {
var arr = this.read(1), o, s;
switch (arr[0]) {
case 255:
if (this.read(1)[0] == 11) {
this._info.appVersion = '';
this.read(11).forEach(function (e, i, arr) {
this._info.appVersion += String.fromCharCode(e);
}.bind(this));
while (1) {
arr = this.read(1);
if (arr[0]) {
this.read(arr[0]);
} else {
this.decode();
break;
}
};
} else {
throw new Error('error');
}
break;
case 249: //
if (this.read(1)[0] == 4) {
arr = this.read(4);
this._frame = {};
this._frame.ctrl = {
disp: 7 & arr[0] >> 2,
i: 1 & arr[0] >> 1,
t: arr[0] & 0x01,
delay: arr[1] + (arr[2] << 8),
tranIndex: arr[3]
};
this._info.frames.push(this._frame);
if (this.read(1)[0] == 0) {
this.decode();
} else {
throw new Error('error');
}
} else {
throw new Error('error');
}
break;
case 254:
arr = this.read(1);
if (arr[0]) {
this.read(arr[0]).forEach(function (e, i, arr) {
this._info.comment += String.fromCharCode(e);
});
if (this.read(1)[0] == 0) {
this.decode();
};
}
break;
default:
// console.log(arr);
break;
}
}
/**
*
*/
private clear() {
this._tab = null;
this._view = null;
this._frame = null;
this._offset = 0;
this._info = {
header: '',
frames: [],
comment: ''
};
this._lastData = null;
this._delays = [];
this._spriteFrames = [];
this._canvas = null;
this._context = null;
}
}
/**
*/
class GIFCache {
private static instance: GIFCache = null;
gifFrameMap = {}
static getInstance() {
if (!GIFCache.instance) {
GIFCache.instance = new GIFCache();
cc.loader.addDownloadHandlers({ "gif": cc.loader.downloader["extMap"].binary });
cc.loader.addLoadHandlers({
"gif": function (item, callback) {
let gif = new GIF();
gif.handle(item, callback)
}
})
}
return GIFCache.instance;
}
preloadGif(data) {
try {
if (data.words) {
data.words.forEach(item => {
if (item.indexOf(".gif") != -1)
cc.loader.load(item.img, (error, data) => { })
});
}
if (data.classes) {
data.classes.forEach(item => {
if (item.indexOf(".gif") != -1)
cc.loader.load(item.img, (error, data) => { })
});
}
} catch (e) {
cc.log(e)
}
}
addItemFrame(key: any, frameData: GIFFrameData) {
if (this.has(key) == true) {
let item = this.get(key)
item.referenceCount++
item.frameData = frameData
} else {
let gifCaheItem = { referenceCount: 0, type: FileType.GIF, frame: {} }
this.gifFrameMap[key] = gifCaheItem
}
}
addItemType(key: any, type: FileType) {
if (this.has(key)) {
let item = this.get(key)
item.type = type
} else {
let gifCaheItem = { referenceCount: 0, type: type, frame: null }
this.gifFrameMap[key] = gifCaheItem
}
}
add(key: any, value: GIFCaheItem) {
if (!this.has(key)) {
this.gifFrameMap[key] = value
}
}
get(key: any): GIFCaheItem {
return this.gifFrameMap[key]
}
has(key: any): boolean {
if (this.gifFrameMap[key] == undefined) {
return false
}
return true
}
hasFrame(key: any) {
let item = this.get(key)
if (item != undefined) {
let itemFrame = item.frameData
if (itemFrame != null) {
return true
}
}
return false
}
/**
* @param key
*/
relase(key: any) {
if (this.has(key)) {
this.gifFrameMap[key] = undefined
cc.loader.release(key)
}
}
releaseAll() {
for (const key in this.gifFrameMap) {
cc.loader.release(key)
}
this.gifFrameMap = {}
}
}
/**
*/
interface GIFFrameData {
delays: Array<number>,
spriteFrames: Array<cc.SpriteFrame>,
length: number
}
interface GIFCaheItem {
referenceCount: number,
type: FileType,
frameData: GIFFrameData
}
export { GIF, GIFCache, GIFFrameData, GIFCaheItem }