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"; } /** * GIF解析 */ 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 = []; private _spriteFrames: Array = []; 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; } /** * 将buffer 解析为gif 核心 * @param item * @param callback */ handle(item, callback) { this.buffer = item; this.getHeader(); this.getScrDesc(); this.getTexture(); if (this._spriteFrames.length == 0) { callback(new Error("gif加载失败,帧长度为0")) } 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) { //此处填坑,当byte因为符合位导致数值为负时候,需要对数据进行处理 tmp = (255 + num + 1).toString(16); } else { tmp = num.toString(16); } if (tmp.length == 1) { tmp = "0" + tmp; } str += tmp; } return str; } /** * 解析GIF得到所有的纹理 */ 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); } /** * 解析frame数据为ImageData * 最耗时的操作(80%耗时归究这里) * @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((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; }); //测试数据 // 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; } /** * 合并ImageData数据 * @param lastImageData 上一帧frame解析出来的ImageData * @param curImageData 当前的ImageData */ 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; } /** * 网页版转换 * 将DataUrl的数据转换为cc.SpriteFrame * @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; } /** * native版转换 * 用renderTexture将二进制数据制作为cc.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; } /** * 图片数据叠加 * 根据显示模式更新图片数据 * 模型0 1 叠加图片 * 模式2 清理画布 显示新的图片 * 模式3 保持上一个状态 * 模式4-7 。。。。 * @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) //新像素点的透明度不是0就替换掉旧像素 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) { //显示模式1 叠加图片 replay() } else if (frame.ctrl.disp == 2) { //显示模式2 清理画布显示新的 clearAndReplay() } else { if (checkAlpha()) { clearAndReplay() } else { replay() } } } /** * 模型0 1 叠加图片 * 模式2 清理画布 显示新的图片 * 模式3 保持上一个状态 * 模式4-7 。。。。 * @param imageData * @param frame */ putImageDataWeb(imageData, frame) { let finalImageData if (frame.ctrl.disp == 1 || frame.ctrl.disp == 0) { //叠加图形 // 3、将当前frame的ImageData设置到canvas上(必须,否则会因为ImageData的尺寸大小可能不一样造成拉伸等错乱现象) this._context.putImageData(imageData, frame.img.x, frame.img.y, 0, 0, frame.img.w, frame.img.h); // 4、把当前imageData和上一帧imageData合并(必须,因为GIF的当前帧可能只提供了像素发生变化位置的信息) 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) // 3、将当前frame的ImageData设置到canvas上(必须,否则会因为ImageData的尺寸大小可能不一样造成拉伸等错乱现象) this._context.putImageData(imageData, frame.img.x, frame.img.y, 0, 0, frame.img.w, frame.img.h); // 4、把当前imageData和上一帧imageData合并(必须,因为GIF的当前帧可能只提供了像素发生变化位置的信息) finalImageData = this._context.getImageData(0, 0, this._canvas.width, this._canvas.height); } // 5、把最终的ImageData设置到canvas上(形成合成之后的最终图像) this._context.putImageData(finalImageData, 0, 0); this._lastData = finalImageData; return this._canvas.toDataURL(); } /** * 将frame数据转化为cc.Texture * @param frame 当前frame的数据 * @param index 当前frame的顺序 */ private decodeFrame2Texture(frame, index) { // 1、初始化canvas的相关信息 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; } // 2、解析当前frame的ImageData数据(frame中存在的IamgeData数据) 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 { //web平台 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); } /** * 获取文件头部分(Header) * GIF署名(Signature)和版本号(Version) */ private getHeader() { this._info.header = ''; this.read(6).forEach((e, i, arr) => { this._info.header += String.fromCharCode(e); }); } /** * 获取逻辑屏幕标识符(Logical Screen Descriptor) * GIF数据流部分(GIF Data Stream) */ 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(); } /** * 解析GIF数据流 */ 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((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((e, i, arr) => { this._info.appVersion += String.fromCharCode(e); }); while (1) { arr = this.read(1); if (arr[0]) { this.read(arr[0]); } else { this.decode(); break; } }; } else { throw new 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('解析出错'); } } else { throw new Error('解析出错'); } break; case 254: //注释块 arr = this.read(1); if (arr[0]) { this.read(arr[0]).forEach((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; } } /** * gif缓存系统 * 资源再利用 */ class GIFCache { private static instance: GIFCache = null; gifFrameMap = {} static getInstance() { if (!GIFCache.instance) { cc.macro.ALLOW_IMAGE_BITMAP = true; GIFCache.instance = new GIFCache(); cc.assetManager.parser.register('.gif', async (file: Blob, options, onComplete) => { let gif = new GIF(); let buffer = await file.arrayBuffer(); gif.handle(buffer, onComplete) }) } 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 } /** * onDestroy 释放资源 * 资源引用计数为0的时候释放资源 * @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 = {} } } /** * gif资源 */ interface GIFFrameData { /*每一帧延时 */ delays: Array, spriteFrames: Array, length: number } interface GIFCaheItem { /*资源引用计数*/ referenceCount: number, /*文件类型*/ type: FileType, /*gif解析后的数据 */ frameData: GIFFrameData } export { GIF, GIFCache, GIFFrameData, GIFCaheItem }