123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702 |
- 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<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;
- }
- /**
- * 将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<number>,
- spriteFrames: Array<cc.SpriteFrame>,
- length: number
- }
- interface GIFCaheItem {
- /*资源引用计数*/
- referenceCount: number,
- /*文件类型*/
- type: FileType,
- /*gif解析后的数据 */
- frameData: GIFFrameData
- }
- export { GIF, GIFCache, GIFFrameData, GIFCaheItem }
|