GIF.ts 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702
  1. import LZW from "./LZW";
  2. export enum FileType {
  3. UNKNOWN,
  4. PNG,
  5. JPG,
  6. GIF,
  7. WEBP
  8. }
  9. export class FileHead {
  10. static IMAGE_PNG = "89504e47";
  11. static IMAGE_JPG = "ffd8ff";
  12. static IMAGE_GIF = "474946";
  13. /**
  14. * Webp
  15. */
  16. static RIFF = "52494646";
  17. static WEBP_RIFF = FileHead.RIFF;
  18. static WEBP_WEBP = "57454250";
  19. }
  20. /**
  21. * GIF解析
  22. */
  23. class GIF {
  24. private _tab: any;
  25. private _view: Uint8Array;
  26. private _frame: any;
  27. private _buffer: ArrayBuffer;
  28. private _offset: number = 0;
  29. private _lastData: ImageData;
  30. private _info: any = {
  31. header: '',
  32. frames: [],
  33. comment: ''
  34. };
  35. private _delays: Array<number> = [];
  36. private _spriteFrames: Array<cc.SpriteFrame> = [];
  37. private _canvas: HTMLCanvasElement = null;
  38. private _context: CanvasRenderingContext2D = null;
  39. public id = "GIF"
  40. public async = true
  41. set buffer(buffer: ArrayBuffer) {
  42. this.clear();
  43. this._buffer = buffer;
  44. this._view = new Uint8Array(buffer);
  45. }
  46. get buffer() {
  47. return this._buffer;
  48. }
  49. /**
  50. * 将buffer 解析为gif 核心
  51. * @param item
  52. * @param callback
  53. */
  54. handle(item, callback) {
  55. this.buffer = item;
  56. this.getHeader();
  57. this.getScrDesc();
  58. this.getTexture();
  59. if (this._spriteFrames.length == 0) {
  60. callback(new Error("gif加载失败,帧长度为0"))
  61. } else {
  62. callback(null, { delays: this._delays, spriteFrames: this._spriteFrames, length: this._info.frames.length })
  63. }
  64. }
  65. /**
  66. * 文件类型识别
  67. * @param data
  68. */
  69. static detectFormat(data): FileType {
  70. if (data.indexOf(FileHead.IMAGE_GIF) != -1) {
  71. return FileType.GIF;
  72. } else if (data.indexOf(FileHead.IMAGE_PNG) != -1) {
  73. return FileType.PNG;
  74. } else if (data.indexOf(FileHead.IMAGE_JPG) != -1) {
  75. return FileType.JPG;
  76. } else if (data.indexOf(FileHead.WEBP_RIFF) != -1 && data.indexOf(FileHead.WEBP_WEBP) != -1) {
  77. return FileType.WEBP;
  78. } else {
  79. return FileType.UNKNOWN
  80. }
  81. }
  82. /**
  83. * 二进制转换为十六进制字符串
  84. * @param arrBytes
  85. */
  86. static bytes2HexString(arrBytes) {
  87. var str = "";
  88. for (var i = 0; i < arrBytes.length; i++) {
  89. var tmp;
  90. var num = arrBytes[i];
  91. if (num < 0) {
  92. //此处填坑,当byte因为符合位导致数值为负时候,需要对数据进行处理
  93. tmp = (255 + num + 1).toString(16);
  94. } else {
  95. tmp = num.toString(16);
  96. }
  97. if (tmp.length == 1) {
  98. tmp = "0" + tmp;
  99. }
  100. str += tmp;
  101. }
  102. return str;
  103. }
  104. /**
  105. * 解析GIF得到所有的纹理
  106. */
  107. private getTexture() {
  108. // console.log(this._info)
  109. let index = 0;
  110. for (const frame of this._info.frames) {
  111. this.decodeFrame2Texture(frame, index++);
  112. }
  113. // this.getSpriteFrame(0);
  114. }
  115. /**
  116. * 得到对应索引的精灵帧
  117. * @param index
  118. */
  119. public getSpriteFrame(index) {
  120. if (this._spriteFrames[index]) return this._spriteFrames[index];
  121. return this.decodeFrame2Texture(this._info.frames[index], index);
  122. }
  123. /**
  124. * 解析frame数据为ImageData
  125. * 最耗时的操作(80%耗时归究这里)
  126. * @param frame frame数据
  127. */
  128. private decodeFrame(frame) {
  129. let imageData = this._context.getImageData(frame.img.x, frame.img.y, frame.img.w, frame.img.h)
  130. frame.img.m ? this._tab = frame.img.colorTab : this._tab = this._info.colorTab;
  131. LZW.decode(frame.img.srcBuf, frame.img.codeSize).forEach((j, k) => {
  132. imageData.data[k * 4] = this._tab[j * 3];
  133. imageData.data[k * 4 + 1] = this._tab[j * 3 + 1];
  134. imageData.data[k * 4 + 2] = this._tab[j * 3 + 2];
  135. imageData.data[k * 4 + 3] = 255;
  136. frame.ctrl.t ? (j == frame.ctrl.tranIndex ? imageData.data[k * 4 + 3] = 0 : 0) : 0;
  137. });
  138. //测试数据
  139. // for (var i = 0; i < imageData.data.length; i += 4) {
  140. // imageData.data[i + 0] = 255;
  141. // imageData.data[i + 1] = 0;
  142. // imageData.data[i + 2] = 0;
  143. // imageData.data[i + 3] = 255;
  144. // }
  145. return imageData;
  146. }
  147. /**
  148. * 合并ImageData数据
  149. * @param lastImageData 上一帧frame解析出来的ImageData
  150. * @param curImageData 当前的ImageData
  151. */
  152. private mergeFrames(lastImageData, curImageData) {
  153. let imageData = curImageData;
  154. if (lastImageData) {
  155. for (var i = 0; i < imageData.data.length; i += 4) {
  156. if (imageData.data[i + 3] == 0) {
  157. imageData.data[i] = this._lastData.data[i];
  158. imageData.data[i + 1] = this._lastData.data[i + 1];
  159. imageData.data[i + 2] = this._lastData.data[i + 2];
  160. imageData.data[i + 3] = this._lastData.data[i + 3];
  161. }
  162. }
  163. }
  164. return imageData;
  165. }
  166. /**
  167. * 网页版转换
  168. * 将DataUrl的数据转换为cc.SpriteFrame
  169. * @param dataUrl
  170. */
  171. private dataUrl2SpriteFrame(dataUrl) {
  172. let texture = new cc.Texture2D()
  173. let spriteFrame = new cc.SpriteFrame();
  174. let image = new Image();
  175. image.src = dataUrl;
  176. texture.initWithElement(image);
  177. spriteFrame.setTexture(texture);
  178. return spriteFrame;
  179. }
  180. /**
  181. * native版转换
  182. * 用renderTexture将二进制数据制作为cc.SpriteFrame
  183. * @param data
  184. * @param w
  185. * @param h
  186. */
  187. private date2SpriteFrame(data, w, h) {
  188. let texture = new cc.RenderTexture();
  189. let spriteFrame = new cc.SpriteFrame();
  190. texture.initWithData(data.data, cc.Texture2D.PixelFormat.RGBA8888, w, h);
  191. spriteFrame.setTexture(texture);
  192. return spriteFrame;
  193. }
  194. /**
  195. * 图片数据叠加
  196. * 根据显示模式更新图片数据
  197. * 模型0 1 叠加图片
  198. * 模式2 清理画布 显示新的图片
  199. * 模式3 保持上一个状态
  200. * 模式4-7 。。。。
  201. * @param imageData 新数据
  202. * @param x 左上角横向偏移
  203. * @param y 左上角纵向偏移
  204. */
  205. putImageDataJSB(imageData, x, y, frame) {
  206. let cheeckNullPixel = () => {
  207. if (imageData.data[0] == 4
  208. && imageData.data[1] == 0
  209. && imageData.data[2] == 0
  210. && imageData.data[3] == 0) {
  211. return true
  212. }
  213. return false
  214. }
  215. let checkAlpha = () => {
  216. let alphaCount = 0
  217. for (let i = 0; i < imageData.height; i += 2) {
  218. let lineCount = 0
  219. for (let j = 0; j < imageData.width; j++) {
  220. let indexData = i * 4 * imageData.width + 4 * j
  221. if (imageData.data[indexData + 3] == 0) {
  222. lineCount++
  223. }
  224. }
  225. if (lineCount / imageData.width > 0.1) {
  226. alphaCount++
  227. }
  228. if (alphaCount / (imageData.height / 2) > 0.6) return true
  229. }
  230. return false
  231. }
  232. //叠加图形
  233. let replay = () => {
  234. for (let i = 0; i < imageData.height; i++) {
  235. for (let j = 0; j < imageData.width; j++) {
  236. let indexData = i * 4 * imageData.width + 4 * j
  237. let indexLastData = (i + y) * 4 * this._lastData.width + 4 * (j + x)
  238. //新像素点的透明度不是0就替换掉旧像素
  239. if (imageData.data[indexData + 3] != 0) {
  240. this._lastData.data[indexLastData] = imageData.data[indexData]
  241. this._lastData.data[indexLastData + 1] = imageData.data[indexData + 1]
  242. this._lastData.data[indexLastData + 2] = imageData.data[indexData + 2]
  243. this._lastData.data[indexLastData + 3] = imageData.data[indexData + 3]
  244. }
  245. }
  246. }
  247. }
  248. //清理画布从新绘制
  249. let clearAndReplay = () => {
  250. for (let i = 0; i < this._lastData.height; i++) {
  251. for (let j = 0; j < this._lastData.width; j++) {
  252. let indexLastData = i * 4 * this._lastData.width + 4 * j
  253. let indexData = (i - y) * 4 * imageData.width + 4 * (j - x)
  254. let clear = false
  255. if (j < x || j > (x + imageData.width)) {
  256. clear = true
  257. }
  258. if (i < y || i > (y + imageData.height)) {
  259. clear = true
  260. }
  261. if (clear) {
  262. this._lastData.data[indexLastData + 0] = 0;
  263. this._lastData.data[indexLastData + 1] = 0;
  264. this._lastData.data[indexLastData + 2] = 0;
  265. this._lastData.data[indexLastData + 3] = 0;
  266. } else {
  267. this._lastData.data[indexLastData + 0] = imageData.data[indexData + 0]
  268. this._lastData.data[indexLastData + 1] = imageData.data[indexData + 1]
  269. this._lastData.data[indexLastData + 2] = imageData.data[indexData + 2]
  270. this._lastData.data[indexLastData + 3] = imageData.data[indexData + 3]
  271. }
  272. }
  273. }
  274. }
  275. //如果和上一帧一样的不更新画布
  276. if (cheeckNullPixel()) {
  277. return
  278. }
  279. if (frame.ctrl.disp == 1 || frame.ctrl.disp == 0) {
  280. //显示模式1 叠加图片
  281. replay()
  282. } else if (frame.ctrl.disp == 2) {
  283. //显示模式2 清理画布显示新的
  284. clearAndReplay()
  285. } else {
  286. if (checkAlpha()) {
  287. clearAndReplay()
  288. } else {
  289. replay()
  290. }
  291. }
  292. }
  293. /**
  294. * 模型0 1 叠加图片
  295. * 模式2 清理画布 显示新的图片
  296. * 模式3 保持上一个状态
  297. * 模式4-7 。。。。
  298. * @param imageData
  299. * @param frame
  300. */
  301. putImageDataWeb(imageData, frame) {
  302. let finalImageData
  303. if (frame.ctrl.disp == 1 || frame.ctrl.disp == 0) {
  304. //叠加图形
  305. // 3、将当前frame的ImageData设置到canvas上(必须,否则会因为ImageData的尺寸大小可能不一样造成拉伸等错乱现象)
  306. this._context.putImageData(imageData, frame.img.x, frame.img.y, 0, 0, frame.img.w, frame.img.h);
  307. // 4、把当前imageData和上一帧imageData合并(必须,因为GIF的当前帧可能只提供了像素发生变化位置的信息)
  308. let curImageData = this._context.getImageData(0, 0, this._canvas.width, this._canvas.height);
  309. let lastImageData = this._lastData;
  310. finalImageData = this.mergeFrames(lastImageData, curImageData);
  311. } else {
  312. //清理画布从新绘制
  313. this._context.clearRect(0, 0, this._canvas.width, this._canvas.height)
  314. // 3、将当前frame的ImageData设置到canvas上(必须,否则会因为ImageData的尺寸大小可能不一样造成拉伸等错乱现象)
  315. this._context.putImageData(imageData, frame.img.x, frame.img.y, 0, 0, frame.img.w, frame.img.h);
  316. // 4、把当前imageData和上一帧imageData合并(必须,因为GIF的当前帧可能只提供了像素发生变化位置的信息)
  317. finalImageData = this._context.getImageData(0, 0, this._canvas.width, this._canvas.height);
  318. }
  319. // 5、把最终的ImageData设置到canvas上(形成合成之后的最终图像)
  320. this._context.putImageData(finalImageData, 0, 0);
  321. this._lastData = finalImageData;
  322. return this._canvas.toDataURL();
  323. }
  324. /**
  325. * 将frame数据转化为cc.Texture
  326. * @param frame 当前frame的数据
  327. * @param index 当前frame的顺序
  328. */
  329. private decodeFrame2Texture(frame, index) {
  330. // 1、初始化canvas的相关信息
  331. if (!this._context) {
  332. this._canvas = document.createElement('canvas');
  333. this._context = this._canvas.getContext('2d');
  334. this._canvas.width = frame.img.w;
  335. this._canvas.height = frame.img.h;
  336. }
  337. // 2、解析当前frame的ImageData数据(frame中存在的IamgeData数据)
  338. let imageData = this.decodeFrame(frame);
  339. this._delays[index] = frame.ctrl.delay;
  340. if (CC_JSB) {
  341. //原生平台
  342. if (!this._lastData) {
  343. this._lastData = imageData
  344. } else {
  345. this.putImageDataJSB(imageData, frame.img.x, frame.img.y, frame);
  346. }
  347. this._spriteFrames[index] = this.date2SpriteFrame(this._lastData, this._canvas.width, this._canvas.height);
  348. } else {
  349. //web平台
  350. let dataUrl = this.putImageDataWeb(imageData, frame)
  351. this._spriteFrames[index] = this.dataUrl2SpriteFrame(dataUrl);
  352. }
  353. return this._spriteFrames[index];
  354. }
  355. /**
  356. * 读文件流
  357. * @param len 读取的长度
  358. */
  359. private read(len) {
  360. return this._view.slice(this._offset, this._offset += len);
  361. }
  362. /**
  363. * 获取文件头部分(Header)
  364. * GIF署名(Signature)和版本号(Version)
  365. */
  366. private getHeader() {
  367. this._info.header = '';
  368. this.read(6).forEach((e, i, arr) => {
  369. this._info.header += String.fromCharCode(e);
  370. });
  371. }
  372. /**
  373. * 获取逻辑屏幕标识符(Logical Screen Descriptor)
  374. * GIF数据流部分(GIF Data Stream)
  375. */
  376. private getScrDesc() {
  377. // await 0;
  378. var arr = this.read(7), i;
  379. this._info.w = arr[0] + (arr[1] << 8);
  380. this._info.h = arr[2] + (arr[3] << 8);
  381. this._info.m = 1 & arr[4] >> 7;
  382. this._info.cr = 7 & arr[4] >> 4;
  383. this._info.s = 1 & arr[4] >> 3;
  384. this._info.pixel = arr[4] & 0x07;
  385. this._info.bgColor = arr[5];
  386. this._info.radio = arr[6];
  387. if (this._info.m) {
  388. this._info.colorTab = this.read((2 << this._info.pixel) * 3);
  389. }
  390. this.decode();
  391. }
  392. /**
  393. * 解析GIF数据流
  394. */
  395. private decode() {
  396. let srcBuf = [];
  397. let arr = this.read(1);
  398. switch (arr[0]) {
  399. case 33: //扩展块
  400. this.extension();
  401. break;
  402. case 44: //图象标识符
  403. arr = this.read(9);
  404. this._frame.img = {
  405. x: arr[0] + (arr[1] << 8),
  406. y: arr[2] + (arr[3] << 8),
  407. w: arr[4] + (arr[5] << 8),
  408. h: arr[6] + (arr[7] << 8),
  409. colorTab: 0
  410. };
  411. this._frame.img.m = 1 & arr[8] >> 7;
  412. this._frame.img.i = 1 & arr[8] >> 6;
  413. this._frame.img.s = 1 & arr[8] >> 5;
  414. this._frame.img.r = 3 & arr[8] >> 3;
  415. this._frame.img.pixel = arr[8] & 0x07;
  416. if (this._frame.img.m) {
  417. this._frame.img.colorTab = this.read((2 << this._frame.img.pixel) * 3);
  418. }
  419. this._frame.img.codeSize = this.read(1)[0];
  420. srcBuf = [];
  421. while (1) {
  422. arr = this.read(1);
  423. if (arr[0]) {
  424. this.read(arr[0]).forEach((e, i, arr) => {
  425. srcBuf.push(e);
  426. });
  427. } else {
  428. this._frame.img.srcBuf = srcBuf;
  429. this.decode();
  430. break;
  431. }
  432. };
  433. break;
  434. case 59:
  435. // console.log('The end.', this._offset, this.buffer.byteLength)
  436. break;
  437. default:
  438. // console.log(arr);
  439. break;
  440. }
  441. }
  442. /**
  443. * 扩展块部分
  444. */
  445. private extension() {
  446. var arr = this.read(1), o, s;
  447. switch (arr[0]) {
  448. case 255: //应用程序扩展
  449. if (this.read(1)[0] == 11) {
  450. this._info.appVersion = '';
  451. this.read(11).forEach((e, i, arr) => {
  452. this._info.appVersion += String.fromCharCode(e);
  453. });
  454. while (1) {
  455. arr = this.read(1);
  456. if (arr[0]) {
  457. this.read(arr[0]);
  458. } else {
  459. this.decode();
  460. break;
  461. }
  462. };
  463. } else {
  464. throw new Error('解析出错');
  465. }
  466. break;
  467. case 249: //图形控制扩展
  468. if (this.read(1)[0] == 4) {
  469. arr = this.read(4);
  470. this._frame = {};
  471. this._frame.ctrl = {
  472. disp: 7 & arr[0] >> 2,
  473. i: 1 & arr[0] >> 1,
  474. t: arr[0] & 0x01,
  475. delay: arr[1] + (arr[2] << 8),
  476. tranIndex: arr[3]
  477. };
  478. this._info.frames.push(this._frame);
  479. if (this.read(1)[0] == 0) {
  480. this.decode();
  481. } else {
  482. throw new Error('解析出错');
  483. }
  484. } else {
  485. throw new Error('解析出错');
  486. }
  487. break;
  488. case 254: //注释块
  489. arr = this.read(1);
  490. if (arr[0]) {
  491. this.read(arr[0]).forEach((e, i, arr) => {
  492. this._info.comment += String.fromCharCode(e);
  493. });
  494. if (this.read(1)[0] == 0) {
  495. this.decode();
  496. };
  497. }
  498. break;
  499. default:
  500. // console.log(arr);
  501. break;
  502. }
  503. }
  504. /**
  505. * 初始化参数
  506. */
  507. private clear() {
  508. this._tab = null;
  509. this._view = null;
  510. this._frame = null;
  511. this._offset = 0;
  512. this._info = {
  513. header: '',
  514. frames: [],
  515. comment: ''
  516. };
  517. this._lastData = null;
  518. this._delays = [];
  519. this._spriteFrames = [];
  520. this._canvas = null;
  521. this._context = null;
  522. }
  523. }
  524. /**
  525. * gif缓存系统
  526. * 资源再利用
  527. */
  528. class GIFCache {
  529. private static instance: GIFCache = null;
  530. gifFrameMap = {}
  531. static getInstance() {
  532. if (!GIFCache.instance) {
  533. cc.macro.ALLOW_IMAGE_BITMAP = true;
  534. GIFCache.instance = new GIFCache();
  535. cc.assetManager.parser.register('.gif', async (file: Blob, options, onComplete) => {
  536. let gif = new GIF();
  537. let buffer = await file.arrayBuffer();
  538. gif.handle(buffer, onComplete)
  539. })
  540. }
  541. return GIFCache.instance;
  542. }
  543. preloadGif(data) {
  544. try {
  545. if (data.words) {
  546. data.words.forEach(item => {
  547. if (item.indexOf(".gif") != -1)
  548. cc.loader.load(item.img, (error, data) => { })
  549. });
  550. }
  551. if (data.classes) {
  552. data.classes.forEach(item => {
  553. if (item.indexOf(".gif") != -1)
  554. cc.loader.load(item.img, (error, data) => { })
  555. });
  556. }
  557. } catch (e) {
  558. cc.log(e)
  559. }
  560. }
  561. addItemFrame(key: any, frameData: GIFFrameData) {
  562. if (this.has(key) == true) {
  563. let item = this.get(key)
  564. item.referenceCount++
  565. item.frameData = frameData
  566. } else {
  567. let gifCaheItem = { referenceCount: 0, type: FileType.GIF, frame: {} }
  568. this.gifFrameMap[key] = gifCaheItem
  569. }
  570. }
  571. addItemType(key: any, type: FileType) {
  572. if (this.has(key)) {
  573. let item = this.get(key)
  574. item.type = type
  575. } else {
  576. let gifCaheItem = { referenceCount: 0, type: type, frame: null }
  577. this.gifFrameMap[key] = gifCaheItem
  578. }
  579. }
  580. add(key: any, value: GIFCaheItem) {
  581. if (!this.has(key)) {
  582. this.gifFrameMap[key] = value
  583. }
  584. }
  585. get(key: any): GIFCaheItem {
  586. return this.gifFrameMap[key]
  587. }
  588. has(key: any): boolean {
  589. if (this.gifFrameMap[key] == undefined) {
  590. return false
  591. }
  592. return true
  593. }
  594. hasFrame(key: any) {
  595. let item = this.get(key)
  596. if (item != undefined) {
  597. let itemFrame = item.frameData
  598. if (itemFrame != null) {
  599. return true
  600. }
  601. }
  602. return false
  603. }
  604. /**
  605. * onDestroy 释放资源
  606. * 资源引用计数为0的时候释放资源
  607. * @param key
  608. */
  609. relase(key: any) {
  610. if (this.has(key)) {
  611. this.gifFrameMap[key] = undefined
  612. cc.loader.release(key)
  613. }
  614. }
  615. releaseAll() {
  616. for (const key in this.gifFrameMap) {
  617. cc.loader.release(key)
  618. }
  619. this.gifFrameMap = {}
  620. }
  621. }
  622. /**
  623. * gif资源
  624. */
  625. interface GIFFrameData {
  626. /*每一帧延时 */
  627. delays: Array<number>,
  628. spriteFrames: Array<cc.SpriteFrame>,
  629. length: number
  630. }
  631. interface GIFCaheItem {
  632. /*资源引用计数*/
  633. referenceCount: number,
  634. /*文件类型*/
  635. type: FileType,
  636. /*gif解析后的数据 */
  637. frameData: GIFFrameData
  638. }
  639. export { GIF, GIFCache, GIFFrameData, GIFCaheItem }