├── .gitignore ├── README.md ├── assets ├── CCGIFTest.fire ├── CCGIFTest.fire.meta ├── GIF.meta ├── GIF │ ├── CCGIF.ts │ ├── CCGIF.ts.meta │ ├── CCGIFTest.ts │ ├── CCGIFTest.ts.meta │ ├── GIF.ts │ ├── GIF.ts.meta │ ├── LZW.ts │ └── LZW.ts.meta ├── resources.meta └── resources │ ├── gif.meta │ └── gif │ ├── duck1.gif │ ├── duck1.gif.meta │ ├── duck2.gif │ ├── duck2.gif.meta │ ├── duck3.gif │ └── duck3.gif.meta ├── creator.d.ts ├── jsconfig.json ├── project.json ├── sample.gif ├── settings ├── project.json └── services.json └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | #///////////////////////////////////////////////////////////////////////////// 2 | # Fireball Projects 3 | #///////////////////////////////////////////////////////////////////////////// 4 | 5 | /library/ 6 | /temp/ 7 | /local/ 8 | /build/ 9 | 10 | #///////////////////////////////////////////////////////////////////////////// 11 | # npm files 12 | #///////////////////////////////////////////////////////////////////////////// 13 | 14 | npm-debug.log 15 | node_modules/ 16 | 17 | #///////////////////////////////////////////////////////////////////////////// 18 | # Logs and databases 19 | #///////////////////////////////////////////////////////////////////////////// 20 | 21 | *.log 22 | *.sql 23 | *.sqlite 24 | 25 | #///////////////////////////////////////////////////////////////////////////// 26 | # files for debugger 27 | #///////////////////////////////////////////////////////////////////////////// 28 | 29 | *.sln 30 | *.csproj 31 | *.pidb 32 | *.unityproj 33 | *.suo 34 | 35 | #///////////////////////////////////////////////////////////////////////////// 36 | # OS generated files 37 | #///////////////////////////////////////////////////////////////////////////// 38 | 39 | .DS_Store 40 | ehthumbs.db 41 | Thumbs.db 42 | 43 | #///////////////////////////////////////////////////////////////////////////// 44 | # WebStorm files 45 | #///////////////////////////////////////////////////////////////////////////// 46 | 47 | .idea/ 48 | 49 | #////////////////////////// 50 | # VS Code files 51 | #////////////////////////// 52 | 53 | .vscode/ 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cocos-creator支持2.4.3 2 | 支持PC端和移动端 3 | ![Alt Text](sample.gif) 4 | -------------------------------------------------------------------------------- /assets/CCGIFTest.fire: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "__type__": "cc.SceneAsset", 4 | "_name": "", 5 | "_objFlags": 0, 6 | "_native": "", 7 | "scene": { 8 | "__id__": 1 9 | } 10 | }, 11 | { 12 | "__type__": "cc.Scene", 13 | "_objFlags": 0, 14 | "_parent": null, 15 | "_children": [ 16 | { 17 | "__id__": 2 18 | } 19 | ], 20 | "_active": true, 21 | "_components": [], 22 | "_prefab": null, 23 | "_opacity": 255, 24 | "_color": { 25 | "__type__": "cc.Color", 26 | "r": 255, 27 | "g": 255, 28 | "b": 255, 29 | "a": 255 30 | }, 31 | "_contentSize": { 32 | "__type__": "cc.Size", 33 | "width": 0, 34 | "height": 0 35 | }, 36 | "_anchorPoint": { 37 | "__type__": "cc.Vec2", 38 | "x": 0, 39 | "y": 0 40 | }, 41 | "_trs": { 42 | "__type__": "TypedArray", 43 | "ctor": "Float64Array", 44 | "array": [ 45 | 0, 46 | 0, 47 | 0, 48 | 0, 49 | 0, 50 | 0, 51 | 1, 52 | 1, 53 | 1, 54 | 1 55 | ] 56 | }, 57 | "_is3DNode": true, 58 | "_groupIndex": 0, 59 | "groupIndex": 0, 60 | "autoReleaseAssets": true, 61 | "_id": "d5c14380-1e43-4890-aa6e-97193bdd186f" 62 | }, 63 | { 64 | "__type__": "cc.Node", 65 | "_name": "Canvas", 66 | "_objFlags": 0, 67 | "_parent": { 68 | "__id__": 1 69 | }, 70 | "_children": [ 71 | { 72 | "__id__": 3 73 | }, 74 | { 75 | "__id__": 5 76 | }, 77 | { 78 | "__id__": 13 79 | }, 80 | { 81 | "__id__": 21 82 | } 83 | ], 84 | "_active": true, 85 | "_components": [ 86 | { 87 | "__id__": 23 88 | }, 89 | { 90 | "__id__": 24 91 | } 92 | ], 93 | "_prefab": null, 94 | "_opacity": 255, 95 | "_color": { 96 | "__type__": "cc.Color", 97 | "r": 255, 98 | "g": 255, 99 | "b": 255, 100 | "a": 255 101 | }, 102 | "_contentSize": { 103 | "__type__": "cc.Size", 104 | "width": 960, 105 | "height": 640 106 | }, 107 | "_anchorPoint": { 108 | "__type__": "cc.Vec2", 109 | "x": 0.5, 110 | "y": 0.5 111 | }, 112 | "_trs": { 113 | "__type__": "TypedArray", 114 | "ctor": "Float64Array", 115 | "array": [ 116 | 480, 117 | 320, 118 | 0, 119 | 0, 120 | 0, 121 | 0, 122 | 1, 123 | 1, 124 | 1, 125 | 1 126 | ] 127 | }, 128 | "_eulerAngles": { 129 | "__type__": "cc.Vec3", 130 | "x": 0, 131 | "y": 0, 132 | "z": 0 133 | }, 134 | "_skewX": 0, 135 | "_skewY": 0, 136 | "_is3DNode": false, 137 | "_groupIndex": 0, 138 | "groupIndex": 0, 139 | "_id": "a5esZu+45LA5mBpvttspPD" 140 | }, 141 | { 142 | "__type__": "cc.Node", 143 | "_name": "Main Camera", 144 | "_objFlags": 0, 145 | "_parent": { 146 | "__id__": 2 147 | }, 148 | "_children": [], 149 | "_active": true, 150 | "_components": [ 151 | { 152 | "__id__": 4 153 | } 154 | ], 155 | "_prefab": null, 156 | "_opacity": 255, 157 | "_color": { 158 | "__type__": "cc.Color", 159 | "r": 255, 160 | "g": 255, 161 | "b": 255, 162 | "a": 255 163 | }, 164 | "_contentSize": { 165 | "__type__": "cc.Size", 166 | "width": 960, 167 | "height": 640 168 | }, 169 | "_anchorPoint": { 170 | "__type__": "cc.Vec2", 171 | "x": 0.5, 172 | "y": 0.5 173 | }, 174 | "_trs": { 175 | "__type__": "TypedArray", 176 | "ctor": "Float64Array", 177 | "array": [ 178 | 0, 179 | 0, 180 | 220.83647796503186, 181 | 0, 182 | 0, 183 | 0, 184 | 1, 185 | 1, 186 | 1, 187 | 1 188 | ] 189 | }, 190 | "_eulerAngles": { 191 | "__type__": "cc.Vec3", 192 | "x": 0, 193 | "y": 0, 194 | "z": 0 195 | }, 196 | "_skewX": 0, 197 | "_skewY": 0, 198 | "_is3DNode": false, 199 | "_groupIndex": 0, 200 | "groupIndex": 0, 201 | "_id": "e1WoFrQ79G7r4ZuQE3HlNb" 202 | }, 203 | { 204 | "__type__": "cc.Camera", 205 | "_name": "", 206 | "_objFlags": 0, 207 | "node": { 208 | "__id__": 3 209 | }, 210 | "_enabled": true, 211 | "_cullingMask": 4294967295, 212 | "_clearFlags": 7, 213 | "_backgroundColor": { 214 | "__type__": "cc.Color", 215 | "r": 0, 216 | "g": 0, 217 | "b": 0, 218 | "a": 255 219 | }, 220 | "_depth": -1, 221 | "_zoomRatio": 1, 222 | "_targetTexture": null, 223 | "_fov": 60, 224 | "_orthoSize": 10, 225 | "_nearClip": 1, 226 | "_farClip": 4096, 227 | "_ortho": true, 228 | "_rect": { 229 | "__type__": "cc.Rect", 230 | "x": 0, 231 | "y": 0, 232 | "width": 1, 233 | "height": 1 234 | }, 235 | "_renderStages": 1, 236 | "_alignWithScreen": true, 237 | "_id": "81GN3uXINKVLeW4+iKSlim" 238 | }, 239 | { 240 | "__type__": "cc.Node", 241 | "_name": "btnPlay", 242 | "_objFlags": 0, 243 | "_parent": { 244 | "__id__": 2 245 | }, 246 | "_children": [ 247 | { 248 | "__id__": 6 249 | } 250 | ], 251 | "_active": true, 252 | "_components": [ 253 | { 254 | "__id__": 11 255 | } 256 | ], 257 | "_prefab": null, 258 | "_opacity": 255, 259 | "_color": { 260 | "__type__": "cc.Color", 261 | "r": 255, 262 | "g": 255, 263 | "b": 255, 264 | "a": 255 265 | }, 266 | "_contentSize": { 267 | "__type__": "cc.Size", 268 | "width": 100, 269 | "height": 40 270 | }, 271 | "_anchorPoint": { 272 | "__type__": "cc.Vec2", 273 | "x": 0.5, 274 | "y": 0.5 275 | }, 276 | "_trs": { 277 | "__type__": "TypedArray", 278 | "ctor": "Float64Array", 279 | "array": [ 280 | 0, 281 | -100, 282 | 0, 283 | 0, 284 | 0, 285 | 0, 286 | 1, 287 | 1, 288 | 1, 289 | 1 290 | ] 291 | }, 292 | "_eulerAngles": { 293 | "__type__": "cc.Vec3", 294 | "x": 0, 295 | "y": 0, 296 | "z": 0 297 | }, 298 | "_skewX": 0, 299 | "_skewY": 0, 300 | "_is3DNode": false, 301 | "_groupIndex": 0, 302 | "groupIndex": 0, 303 | "_id": "73UfNECCBHuKKK+4tBG0WH" 304 | }, 305 | { 306 | "__type__": "cc.Node", 307 | "_name": "Background", 308 | "_objFlags": 512, 309 | "_parent": { 310 | "__id__": 5 311 | }, 312 | "_children": [ 313 | { 314 | "__id__": 7 315 | } 316 | ], 317 | "_active": true, 318 | "_components": [ 319 | { 320 | "__id__": 9 321 | }, 322 | { 323 | "__id__": 10 324 | } 325 | ], 326 | "_prefab": null, 327 | "_opacity": 255, 328 | "_color": { 329 | "__type__": "cc.Color", 330 | "r": 255, 331 | "g": 255, 332 | "b": 255, 333 | "a": 255 334 | }, 335 | "_contentSize": { 336 | "__type__": "cc.Size", 337 | "width": 100, 338 | "height": 40 339 | }, 340 | "_anchorPoint": { 341 | "__type__": "cc.Vec2", 342 | "x": 0.5, 343 | "y": 0.5 344 | }, 345 | "_trs": { 346 | "__type__": "TypedArray", 347 | "ctor": "Float64Array", 348 | "array": [ 349 | 0, 350 | 0, 351 | 0, 352 | 0, 353 | 0, 354 | 0, 355 | 1, 356 | 1, 357 | 1, 358 | 1 359 | ] 360 | }, 361 | "_eulerAngles": { 362 | "__type__": "cc.Vec3", 363 | "x": 0, 364 | "y": 0, 365 | "z": 0 366 | }, 367 | "_skewX": 0, 368 | "_skewY": 0, 369 | "_is3DNode": false, 370 | "_groupIndex": 0, 371 | "groupIndex": 0, 372 | "_id": "68GXaEwbBPUp6GSdU+586v" 373 | }, 374 | { 375 | "__type__": "cc.Node", 376 | "_name": "Label", 377 | "_objFlags": 512, 378 | "_parent": { 379 | "__id__": 6 380 | }, 381 | "_children": [], 382 | "_active": true, 383 | "_components": [ 384 | { 385 | "__id__": 8 386 | } 387 | ], 388 | "_prefab": null, 389 | "_opacity": 255, 390 | "_color": { 391 | "__type__": "cc.Color", 392 | "r": 0, 393 | "g": 0, 394 | "b": 0, 395 | "a": 255 396 | }, 397 | "_contentSize": { 398 | "__type__": "cc.Size", 399 | "width": 100, 400 | "height": 40 401 | }, 402 | "_anchorPoint": { 403 | "__type__": "cc.Vec2", 404 | "x": 0.5, 405 | "y": 0.5 406 | }, 407 | "_trs": { 408 | "__type__": "TypedArray", 409 | "ctor": "Float64Array", 410 | "array": [ 411 | 0, 412 | 0, 413 | 0, 414 | 0, 415 | 0, 416 | 0, 417 | 1, 418 | 1, 419 | 1, 420 | 1 421 | ] 422 | }, 423 | "_eulerAngles": { 424 | "__type__": "cc.Vec3", 425 | "x": 0, 426 | "y": 0, 427 | "z": 0 428 | }, 429 | "_skewX": 0, 430 | "_skewY": 0, 431 | "_is3DNode": false, 432 | "_groupIndex": 0, 433 | "groupIndex": 0, 434 | "_id": "454+LOPFFOmYBeIoyvxRgK" 435 | }, 436 | { 437 | "__type__": "cc.Label", 438 | "_name": "", 439 | "_objFlags": 0, 440 | "node": { 441 | "__id__": 7 442 | }, 443 | "_enabled": true, 444 | "_materials": [ 445 | { 446 | "__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432" 447 | } 448 | ], 449 | "_srcBlendFactor": 770, 450 | "_dstBlendFactor": 771, 451 | "_string": "play all", 452 | "_N$string": "play all", 453 | "_fontSize": 20, 454 | "_lineHeight": 40, 455 | "_enableWrapText": false, 456 | "_N$file": null, 457 | "_isSystemFontUsed": true, 458 | "_spacingX": 0, 459 | "_batchAsBitmap": false, 460 | "_styleFlags": 0, 461 | "_underlineHeight": 0, 462 | "_N$horizontalAlign": 1, 463 | "_N$verticalAlign": 1, 464 | "_N$fontFamily": "Arial", 465 | "_N$overflow": 1, 466 | "_N$cacheMode": 1, 467 | "_id": "610z5OWkZJzKHodEiW7Jjh" 468 | }, 469 | { 470 | "__type__": "cc.Sprite", 471 | "_name": "", 472 | "_objFlags": 0, 473 | "node": { 474 | "__id__": 6 475 | }, 476 | "_enabled": true, 477 | "_materials": [ 478 | { 479 | "__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432" 480 | } 481 | ], 482 | "_srcBlendFactor": 770, 483 | "_dstBlendFactor": 771, 484 | "_spriteFrame": { 485 | "__uuid__": "f0048c10-f03e-4c97-b9d3-3506e1d58952" 486 | }, 487 | "_type": 1, 488 | "_sizeMode": 0, 489 | "_fillType": 0, 490 | "_fillCenter": { 491 | "__type__": "cc.Vec2", 492 | "x": 0, 493 | "y": 0 494 | }, 495 | "_fillStart": 0, 496 | "_fillRange": 0, 497 | "_isTrimmedMode": true, 498 | "_atlas": null, 499 | "_id": "dawVFsETdANowqaF16hVxD" 500 | }, 501 | { 502 | "__type__": "cc.Widget", 503 | "_name": "", 504 | "_objFlags": 0, 505 | "node": { 506 | "__id__": 6 507 | }, 508 | "_enabled": true, 509 | "alignMode": 0, 510 | "_target": null, 511 | "_alignFlags": 45, 512 | "_left": 0, 513 | "_right": 0, 514 | "_top": 0, 515 | "_bottom": 0, 516 | "_verticalCenter": 0, 517 | "_horizontalCenter": 0, 518 | "_isAbsLeft": true, 519 | "_isAbsRight": true, 520 | "_isAbsTop": true, 521 | "_isAbsBottom": true, 522 | "_isAbsHorizontalCenter": true, 523 | "_isAbsVerticalCenter": true, 524 | "_originalWidth": 100, 525 | "_originalHeight": 40, 526 | "_id": "6f8086/KhFcJWVd+K8+uPU" 527 | }, 528 | { 529 | "__type__": "cc.Button", 530 | "_name": "", 531 | "_objFlags": 0, 532 | "node": { 533 | "__id__": 5 534 | }, 535 | "_enabled": true, 536 | "_normalMaterial": null, 537 | "_grayMaterial": null, 538 | "duration": 0.1, 539 | "zoomScale": 1.2, 540 | "clickEvents": [ 541 | { 542 | "__id__": 12 543 | } 544 | ], 545 | "_N$interactable": true, 546 | "_N$enableAutoGrayEffect": false, 547 | "_N$transition": 2, 548 | "transition": 2, 549 | "_N$normalColor": { 550 | "__type__": "cc.Color", 551 | "r": 230, 552 | "g": 230, 553 | "b": 230, 554 | "a": 255 555 | }, 556 | "_N$pressedColor": { 557 | "__type__": "cc.Color", 558 | "r": 200, 559 | "g": 200, 560 | "b": 200, 561 | "a": 255 562 | }, 563 | "pressedColor": { 564 | "__type__": "cc.Color", 565 | "r": 200, 566 | "g": 200, 567 | "b": 200, 568 | "a": 255 569 | }, 570 | "_N$hoverColor": { 571 | "__type__": "cc.Color", 572 | "r": 255, 573 | "g": 255, 574 | "b": 255, 575 | "a": 255 576 | }, 577 | "hoverColor": { 578 | "__type__": "cc.Color", 579 | "r": 255, 580 | "g": 255, 581 | "b": 255, 582 | "a": 255 583 | }, 584 | "_N$disabledColor": { 585 | "__type__": "cc.Color", 586 | "r": 120, 587 | "g": 120, 588 | "b": 120, 589 | "a": 200 590 | }, 591 | "_N$normalSprite": { 592 | "__uuid__": "f0048c10-f03e-4c97-b9d3-3506e1d58952" 593 | }, 594 | "_N$pressedSprite": { 595 | "__uuid__": "e9ec654c-97a2-4787-9325-e6a10375219a" 596 | }, 597 | "pressedSprite": { 598 | "__uuid__": "e9ec654c-97a2-4787-9325-e6a10375219a" 599 | }, 600 | "_N$hoverSprite": { 601 | "__uuid__": "f0048c10-f03e-4c97-b9d3-3506e1d58952" 602 | }, 603 | "hoverSprite": { 604 | "__uuid__": "f0048c10-f03e-4c97-b9d3-3506e1d58952" 605 | }, 606 | "_N$disabledSprite": { 607 | "__uuid__": "29158224-f8dd-4661-a796-1ffab537140e" 608 | }, 609 | "_N$target": { 610 | "__id__": 6 611 | }, 612 | "_id": "4aieHS6k1LPKdKa3cAere3" 613 | }, 614 | { 615 | "__type__": "cc.ClickEvent", 616 | "target": { 617 | "__id__": 13 618 | }, 619 | "component": "", 620 | "_componentId": "95292FzWddAUopOS5LSKBKW", 621 | "handler": "playAll", 622 | "customEventData": "" 623 | }, 624 | { 625 | "__type__": "cc.Node", 626 | "_name": "CCGIFTest", 627 | "_objFlags": 0, 628 | "_parent": { 629 | "__id__": 2 630 | }, 631 | "_children": [ 632 | { 633 | "__id__": 14 634 | }, 635 | { 636 | "__id__": 16 637 | }, 638 | { 639 | "__id__": 18 640 | } 641 | ], 642 | "_active": true, 643 | "_components": [ 644 | { 645 | "__id__": 20 646 | } 647 | ], 648 | "_prefab": null, 649 | "_opacity": 255, 650 | "_color": { 651 | "__type__": "cc.Color", 652 | "r": 255, 653 | "g": 255, 654 | "b": 255, 655 | "a": 255 656 | }, 657 | "_contentSize": { 658 | "__type__": "cc.Size", 659 | "width": 0, 660 | "height": 0 661 | }, 662 | "_anchorPoint": { 663 | "__type__": "cc.Vec2", 664 | "x": 0.5, 665 | "y": 0.5 666 | }, 667 | "_trs": { 668 | "__type__": "TypedArray", 669 | "ctor": "Float64Array", 670 | "array": [ 671 | 0, 672 | 0, 673 | 0, 674 | 0, 675 | 0, 676 | 0, 677 | 1, 678 | 1, 679 | 1, 680 | 1 681 | ] 682 | }, 683 | "_eulerAngles": { 684 | "__type__": "cc.Vec3", 685 | "x": 0, 686 | "y": 0, 687 | "z": 0 688 | }, 689 | "_skewX": 0, 690 | "_skewY": 0, 691 | "_is3DNode": false, 692 | "_groupIndex": 0, 693 | "groupIndex": 0, 694 | "_id": "e5bOpQxjxNW60LJodUkh9j" 695 | }, 696 | { 697 | "__type__": "cc.Node", 698 | "_name": "CCGIF", 699 | "_objFlags": 0, 700 | "_parent": { 701 | "__id__": 13 702 | }, 703 | "_children": [], 704 | "_active": true, 705 | "_components": [ 706 | { 707 | "__id__": 15 708 | } 709 | ], 710 | "_prefab": null, 711 | "_opacity": 255, 712 | "_color": { 713 | "__type__": "cc.Color", 714 | "r": 255, 715 | "g": 255, 716 | "b": 255, 717 | "a": 255 718 | }, 719 | "_contentSize": { 720 | "__type__": "cc.Size", 721 | "width": 0, 722 | "height": 0 723 | }, 724 | "_anchorPoint": { 725 | "__type__": "cc.Vec2", 726 | "x": 0.5, 727 | "y": 0.5 728 | }, 729 | "_trs": { 730 | "__type__": "TypedArray", 731 | "ctor": "Float64Array", 732 | "array": [ 733 | -100, 734 | 0, 735 | 0, 736 | 0, 737 | 0, 738 | 0, 739 | 1, 740 | 1, 741 | 1, 742 | 1 743 | ] 744 | }, 745 | "_eulerAngles": { 746 | "__type__": "cc.Vec3", 747 | "x": 0, 748 | "y": 0, 749 | "z": 0 750 | }, 751 | "_skewX": 0, 752 | "_skewY": 0, 753 | "_is3DNode": false, 754 | "_groupIndex": 0, 755 | "groupIndex": 0, 756 | "_id": "61dBkY7j1HGYGULB5l6TIg" 757 | }, 758 | { 759 | "__type__": "c0ff5NcIN5BPITgP1IQiCbP", 760 | "_name": "", 761 | "_objFlags": 0, 762 | "node": { 763 | "__id__": 14 764 | }, 765 | "_enabled": true, 766 | "path": "gif/duck1.gif", 767 | "_id": "b2QnWZA4lDe6/YEXvaXz7h" 768 | }, 769 | { 770 | "__type__": "cc.Node", 771 | "_name": "CCGIF", 772 | "_objFlags": 0, 773 | "_parent": { 774 | "__id__": 13 775 | }, 776 | "_children": [], 777 | "_active": true, 778 | "_components": [ 779 | { 780 | "__id__": 17 781 | } 782 | ], 783 | "_prefab": null, 784 | "_opacity": 255, 785 | "_color": { 786 | "__type__": "cc.Color", 787 | "r": 255, 788 | "g": 255, 789 | "b": 255, 790 | "a": 255 791 | }, 792 | "_contentSize": { 793 | "__type__": "cc.Size", 794 | "width": 0, 795 | "height": 0 796 | }, 797 | "_anchorPoint": { 798 | "__type__": "cc.Vec2", 799 | "x": 0.5, 800 | "y": 0.5 801 | }, 802 | "_trs": { 803 | "__type__": "TypedArray", 804 | "ctor": "Float64Array", 805 | "array": [ 806 | 0, 807 | 0, 808 | 0, 809 | 0, 810 | 0, 811 | 0, 812 | 1, 813 | 1, 814 | 1, 815 | 1 816 | ] 817 | }, 818 | "_eulerAngles": { 819 | "__type__": "cc.Vec3", 820 | "x": 0, 821 | "y": 0, 822 | "z": 0 823 | }, 824 | "_skewX": 0, 825 | "_skewY": 0, 826 | "_is3DNode": false, 827 | "_groupIndex": 0, 828 | "groupIndex": 0, 829 | "_id": "99c9+VaKJMgav1+lnQVtGI" 830 | }, 831 | { 832 | "__type__": "c0ff5NcIN5BPITgP1IQiCbP", 833 | "_name": "", 834 | "_objFlags": 0, 835 | "node": { 836 | "__id__": 16 837 | }, 838 | "_enabled": true, 839 | "path": "gif/duck2.gif", 840 | "_id": "71zRITpTdH+7r+T3vWNPeL" 841 | }, 842 | { 843 | "__type__": "cc.Node", 844 | "_name": "CCGIF", 845 | "_objFlags": 0, 846 | "_parent": { 847 | "__id__": 13 848 | }, 849 | "_children": [], 850 | "_active": true, 851 | "_components": [ 852 | { 853 | "__id__": 19 854 | } 855 | ], 856 | "_prefab": null, 857 | "_opacity": 255, 858 | "_color": { 859 | "__type__": "cc.Color", 860 | "r": 255, 861 | "g": 255, 862 | "b": 255, 863 | "a": 255 864 | }, 865 | "_contentSize": { 866 | "__type__": "cc.Size", 867 | "width": 0, 868 | "height": 0 869 | }, 870 | "_anchorPoint": { 871 | "__type__": "cc.Vec2", 872 | "x": 0.5, 873 | "y": 0.5 874 | }, 875 | "_trs": { 876 | "__type__": "TypedArray", 877 | "ctor": "Float64Array", 878 | "array": [ 879 | 100, 880 | 0, 881 | 0, 882 | 0, 883 | 0, 884 | 0, 885 | 1, 886 | 1, 887 | 1, 888 | 1 889 | ] 890 | }, 891 | "_eulerAngles": { 892 | "__type__": "cc.Vec3", 893 | "x": 0, 894 | "y": 0, 895 | "z": 0 896 | }, 897 | "_skewX": 0, 898 | "_skewY": 0, 899 | "_is3DNode": false, 900 | "_groupIndex": 0, 901 | "groupIndex": 0, 902 | "_id": "d4DmjnX6ZJ8qNxYPmQoKlb" 903 | }, 904 | { 905 | "__type__": "c0ff5NcIN5BPITgP1IQiCbP", 906 | "_name": "", 907 | "_objFlags": 0, 908 | "node": { 909 | "__id__": 18 910 | }, 911 | "_enabled": true, 912 | "path": "gif/duck3.gif", 913 | "_id": "51gAKrNKJMxolzo5bxCC4+" 914 | }, 915 | { 916 | "__type__": "95292FzWddAUopOS5LSKBKW", 917 | "_name": "", 918 | "_objFlags": 0, 919 | "node": { 920 | "__id__": 13 921 | }, 922 | "_enabled": true, 923 | "_id": "80Q31d25pOepr+ZXJLnMSK" 924 | }, 925 | { 926 | "__type__": "cc.Node", 927 | "_name": "loading", 928 | "_objFlags": 0, 929 | "_parent": { 930 | "__id__": 2 931 | }, 932 | "_children": [], 933 | "_active": true, 934 | "_components": [ 935 | { 936 | "__id__": 22 937 | } 938 | ], 939 | "_prefab": null, 940 | "_opacity": 255, 941 | "_color": { 942 | "__type__": "cc.Color", 943 | "r": 255, 944 | "g": 255, 945 | "b": 255, 946 | "a": 255 947 | }, 948 | "_contentSize": { 949 | "__type__": "cc.Size", 950 | "width": 129, 951 | "height": 50.4 952 | }, 953 | "_anchorPoint": { 954 | "__type__": "cc.Vec2", 955 | "x": 0.5, 956 | "y": 0.5 957 | }, 958 | "_trs": { 959 | "__type__": "TypedArray", 960 | "ctor": "Float64Array", 961 | "array": [ 962 | 0, 963 | 0, 964 | 0, 965 | 0, 966 | 0, 967 | 0, 968 | 1, 969 | 1, 970 | 1, 971 | 1 972 | ] 973 | }, 974 | "_eulerAngles": { 975 | "__type__": "cc.Vec3", 976 | "x": 0, 977 | "y": 0, 978 | "z": 0 979 | }, 980 | "_skewX": 0, 981 | "_skewY": 0, 982 | "_is3DNode": false, 983 | "_groupIndex": 0, 984 | "groupIndex": 0, 985 | "_id": "e8gH4x9OxM26dYqFWQCYAa" 986 | }, 987 | { 988 | "__type__": "cc.Label", 989 | "_name": "", 990 | "_objFlags": 0, 991 | "node": { 992 | "__id__": 21 993 | }, 994 | "_enabled": true, 995 | "_materials": [ 996 | { 997 | "__uuid__": "eca5d2f2-8ef6-41c2-bbe6-f9c79d09c432" 998 | } 999 | ], 1000 | "_srcBlendFactor": 770, 1001 | "_dstBlendFactor": 771, 1002 | "_string": "loading", 1003 | "_N$string": "loading", 1004 | "_fontSize": 40, 1005 | "_lineHeight": 40, 1006 | "_enableWrapText": true, 1007 | "_N$file": null, 1008 | "_isSystemFontUsed": true, 1009 | "_spacingX": 0, 1010 | "_batchAsBitmap": false, 1011 | "_styleFlags": 0, 1012 | "_underlineHeight": 0, 1013 | "_N$horizontalAlign": 1, 1014 | "_N$verticalAlign": 1, 1015 | "_N$fontFamily": "Arial", 1016 | "_N$overflow": 0, 1017 | "_N$cacheMode": 0, 1018 | "_id": "96F40F3UdJebG/Hsi+8N04" 1019 | }, 1020 | { 1021 | "__type__": "cc.Canvas", 1022 | "_name": "", 1023 | "_objFlags": 0, 1024 | "node": { 1025 | "__id__": 2 1026 | }, 1027 | "_enabled": true, 1028 | "_designResolution": { 1029 | "__type__": "cc.Size", 1030 | "width": 960, 1031 | "height": 640 1032 | }, 1033 | "_fitWidth": true, 1034 | "_fitHeight": false, 1035 | "_id": "59Cd0ovbdF4byw5sbjJDx7" 1036 | }, 1037 | { 1038 | "__type__": "cc.Widget", 1039 | "_name": "", 1040 | "_objFlags": 0, 1041 | "node": { 1042 | "__id__": 2 1043 | }, 1044 | "_enabled": true, 1045 | "alignMode": 1, 1046 | "_target": null, 1047 | "_alignFlags": 45, 1048 | "_left": 0, 1049 | "_right": 0, 1050 | "_top": 0, 1051 | "_bottom": 0, 1052 | "_verticalCenter": 0, 1053 | "_horizontalCenter": 0, 1054 | "_isAbsLeft": true, 1055 | "_isAbsRight": true, 1056 | "_isAbsTop": true, 1057 | "_isAbsBottom": true, 1058 | "_isAbsHorizontalCenter": true, 1059 | "_isAbsVerticalCenter": true, 1060 | "_originalWidth": 0, 1061 | "_originalHeight": 0, 1062 | "_id": "29zXboiXFBKoIV4PQ2liTe" 1063 | } 1064 | ] -------------------------------------------------------------------------------- /assets/CCGIFTest.fire.meta: -------------------------------------------------------------------------------- 1 | { 2 | "ver": "1.2.9", 3 | "uuid": "d5c14380-1e43-4890-aa6e-97193bdd186f", 4 | "asyncLoadAssets": false, 5 | "autoReleaseAssets": true, 6 | "subMetas": {} 7 | } -------------------------------------------------------------------------------- /assets/GIF.meta: -------------------------------------------------------------------------------- 1 | { 2 | "ver": "1.1.2", 3 | "uuid": "6e851c5a-692e-4fec-8ea3-453656ac04b9", 4 | "isBundle": false, 5 | "bundleName": "", 6 | "priority": 1, 7 | "compressionType": {}, 8 | "optimizeHotUpdate": {}, 9 | "inlineSpriteFrames": {}, 10 | "isRemoteBundle": {}, 11 | "subMetas": {} 12 | } -------------------------------------------------------------------------------- /assets/GIF/CCGIF.ts: -------------------------------------------------------------------------------- 1 | import { GIFCache } from "./GIF"; 2 | 3 | const { ccclass, property } = cc._decorator; 4 | 5 | @ccclass 6 | export default class CCGIF extends cc.Component { 7 | delays = []; 8 | sp: cc.Sprite; 9 | frames: cc.SpriteFrame[] = []; 10 | @property(cc.String) 11 | path: string = ''; 12 | start() { 13 | this.sp = this.node.addComponent(cc.Sprite); 14 | this.node.active = false; 15 | } 16 | async preload() { 17 | GIFCache.getInstance(); 18 | return new Promise((rs, rj) => { 19 | cc.loader.loadRes(this.path, (err, data) => { 20 | // console.log(err, data); 21 | if (err) { 22 | rj(err); 23 | return; 24 | } 25 | let size = data._nativeAsset.spriteFrames[0]._originalSize; 26 | this.node.setContentSize(size); 27 | this.delays = data._nativeAsset.delays.map(v => v / 1e2); 28 | this.frames = data._nativeAsset.spriteFrames; 29 | rs(); 30 | }) 31 | }) 32 | } 33 | frameIdx = 0; 34 | play(loop = false, playNext = false) { 35 | if (!playNext) { 36 | this.stop(); 37 | } 38 | if (this.frames.length) { 39 | if (this.frameIdx >= this.frames.length) { 40 | this.frameIdx = 0; 41 | if (!loop) { 42 | this.node.active = false; 43 | return; 44 | } 45 | } 46 | this.node.active = true; 47 | this.sp.spriteFrame = this.frames[this.frameIdx]; 48 | this.scheduleOnce(() => { 49 | this.play(loop, true); 50 | }, this.delays[this.frameIdx]); 51 | this.frameIdx++; 52 | } 53 | } 54 | stop() { 55 | this.frameIdx = 0; 56 | this.unscheduleAllCallbacks(); 57 | this.node.active = false; 58 | } 59 | } -------------------------------------------------------------------------------- /assets/GIF/CCGIF.ts.meta: -------------------------------------------------------------------------------- 1 | { 2 | "ver": "1.0.8", 3 | "uuid": "c0ff535c-20de-413c-84e0-3f52108826cf", 4 | "isPlugin": false, 5 | "loadPluginInWeb": true, 6 | "loadPluginInNative": true, 7 | "loadPluginInEditor": false, 8 | "subMetas": {} 9 | } -------------------------------------------------------------------------------- /assets/GIF/CCGIFTest.ts: -------------------------------------------------------------------------------- 1 | import CCGIF from "./CCGIF"; 2 | 3 | const { ccclass, property } = cc._decorator; 4 | 5 | @ccclass 6 | export class CCGIFTest extends cc.Component { 7 | async start() { 8 | cc.find('Canvas/loading').active = true; 9 | cc.find('Canvas/btnPlay').active = false; 10 | await Promise.all(this.node.children.map(n => 11 | n.getComponent(CCGIF).preload() 12 | )) 13 | cc.find('Canvas/loading').active = false; 14 | cc.find('Canvas/btnPlay').active = true; 15 | console.debug('preload success'); 16 | this.playAll(); 17 | } 18 | playAll() { 19 | this.node.children.forEach(v => v.getComponent(CCGIF).play()); 20 | } 21 | } -------------------------------------------------------------------------------- /assets/GIF/CCGIFTest.ts.meta: -------------------------------------------------------------------------------- 1 | { 2 | "ver": "1.0.8", 3 | "uuid": "95292173-59d7-4052-8a4e-4b92d2281296", 4 | "isPlugin": false, 5 | "loadPluginInWeb": true, 6 | "loadPluginInNative": true, 7 | "loadPluginInEditor": false, 8 | "subMetas": {} 9 | } -------------------------------------------------------------------------------- /assets/GIF/GIF.ts: -------------------------------------------------------------------------------- 1 | import LZW from "./LZW"; 2 | 3 | export enum FileType { 4 | UNKNOWN, 5 | PNG, 6 | JPG, 7 | GIF, 8 | WEBP 9 | } 10 | 11 | export class FileHead { 12 | static IMAGE_PNG = "89504e47"; 13 | static IMAGE_JPG = "ffd8ff"; 14 | static IMAGE_GIF = "474946"; 15 | /** 16 | * Webp 17 | */ 18 | static RIFF = "52494646"; 19 | static WEBP_RIFF = FileHead.RIFF; 20 | static WEBP_WEBP = "57454250"; 21 | } 22 | 23 | // iOS / Safari support 24 | Blob.prototype.arrayBuffer ??=function(){ return new Response(this).arrayBuffer() } 25 | function downloadAsBlob(url):Promise { 26 | return new Promise(function(resolve, reject) { 27 | try { 28 | var xhr = new XMLHttpRequest(); 29 | xhr.open("GET", url); 30 | xhr.responseType = "blob"; 31 | xhr.onerror = function() {reject("Network error.")}; 32 | xhr.onload = function() { 33 | if (xhr.status === 200) {resolve(xhr.response)} 34 | else {reject("Loading error:" + xhr.statusText)} 35 | }; 36 | xhr.send(); 37 | } 38 | catch(err) {reject(err.message)} 39 | }); 40 | } 41 | 42 | /** 43 | * GIF解析 44 | */ 45 | class GIF { 46 | 47 | private _tab: any; 48 | private _view: Uint8Array; 49 | private _frame: any; 50 | private _buffer: ArrayBuffer; 51 | private _offset: number = 0; 52 | private _lastData: ImageData; 53 | private _info: any = { 54 | header: '', 55 | frames: [], 56 | comment: '' 57 | }; 58 | 59 | private _delays: Array = []; 60 | private _spriteFrames: Array = []; 61 | private _canvas: HTMLCanvasElement = null; 62 | private _context: CanvasRenderingContext2D = null; 63 | public id = "GIF" 64 | public async = true 65 | 66 | set buffer(buffer: ArrayBuffer) { 67 | this.clear(); 68 | this._buffer = buffer; 69 | this._view = new Uint8Array(buffer); 70 | } 71 | 72 | get buffer() { 73 | return this._buffer; 74 | } 75 | 76 | /** 77 | * 将buffer 解析为gif 核心 78 | * @param item 79 | * @param callback 80 | */ 81 | handle(item, callback) { 82 | this.buffer = item; 83 | this.getHeader(); 84 | this.getScrDesc(); 85 | this.getTexture(); 86 | if (this._spriteFrames.length == 0) { 87 | callback(new Error("gif加载失败,帧长度为0")) 88 | } else { 89 | callback(null, { delays: this._delays, spriteFrames: this._spriteFrames, length: this._info.frames.length }) 90 | } 91 | } 92 | 93 | /** 94 | * 文件类型识别 95 | * @param data 96 | */ 97 | static detectFormat(data): FileType { 98 | if (data.indexOf(FileHead.IMAGE_GIF) != -1) { 99 | return FileType.GIF; 100 | } else if (data.indexOf(FileHead.IMAGE_PNG) != -1) { 101 | return FileType.PNG; 102 | } else if (data.indexOf(FileHead.IMAGE_JPG) != -1) { 103 | return FileType.JPG; 104 | } else if (data.indexOf(FileHead.WEBP_RIFF) != -1 && data.indexOf(FileHead.WEBP_WEBP) != -1) { 105 | return FileType.WEBP; 106 | } else { 107 | return FileType.UNKNOWN 108 | } 109 | } 110 | 111 | /** 112 | * 二进制转换为十六进制字符串 113 | * @param arrBytes 114 | */ 115 | static bytes2HexString(arrBytes) { 116 | var str = ""; 117 | for (var i = 0; i < arrBytes.length; i++) { 118 | var tmp; 119 | var num = arrBytes[i]; 120 | if (num < 0) { 121 | //此处填坑,当byte因为符合位导致数值为负时候,需要对数据进行处理 122 | tmp = (255 + num + 1).toString(16); 123 | } else { 124 | tmp = num.toString(16); 125 | } 126 | if (tmp.length == 1) { 127 | tmp = "0" + tmp; 128 | } 129 | str += tmp; 130 | } 131 | return str; 132 | } 133 | 134 | /** 135 | * 解析GIF得到所有的纹理 136 | */ 137 | private getTexture() { 138 | // console.log(this._info) 139 | let index = 0; 140 | for (const frame of this._info.frames) { 141 | this.decodeFrame2Texture(frame, index++); 142 | } 143 | // this.getSpriteFrame(0); 144 | } 145 | 146 | /** 147 | * 得到对应索引的精灵帧 148 | * @param index 149 | */ 150 | public getSpriteFrame(index) { 151 | if (this._spriteFrames[index]) return this._spriteFrames[index]; 152 | return this.decodeFrame2Texture(this._info.frames[index], index); 153 | } 154 | 155 | /** 156 | * 解析frame数据为ImageData 157 | * 最耗时的操作(80%耗时归究这里) 158 | * @param frame frame数据 159 | */ 160 | private decodeFrame(frame) { 161 | let imageData = this._context.getImageData(frame.img.x, frame.img.y, frame.img.w, frame.img.h) 162 | frame.img.m ? this._tab = frame.img.colorTab : this._tab = this._info.colorTab; 163 | LZW.decode(frame.img.srcBuf, frame.img.codeSize).forEach((j, k) => { 164 | imageData.data[k * 4] = this._tab[j * 3]; 165 | imageData.data[k * 4 + 1] = this._tab[j * 3 + 1]; 166 | imageData.data[k * 4 + 2] = this._tab[j * 3 + 2]; 167 | imageData.data[k * 4 + 3] = 255; 168 | frame.ctrl.t ? (j == frame.ctrl.tranIndex ? imageData.data[k * 4 + 3] = 0 : 0) : 0; 169 | }); 170 | 171 | //测试数据 172 | // for (var i = 0; i < imageData.data.length; i += 4) { 173 | // imageData.data[i + 0] = 255; 174 | // imageData.data[i + 1] = 0; 175 | // imageData.data[i + 2] = 0; 176 | // imageData.data[i + 3] = 255; 177 | // } 178 | return imageData; 179 | } 180 | 181 | 182 | /** 183 | * 合并ImageData数据 184 | * @param lastImageData 上一帧frame解析出来的ImageData 185 | * @param curImageData 当前的ImageData 186 | */ 187 | private mergeFrames(lastImageData, curImageData) { 188 | let imageData = curImageData; 189 | if (lastImageData) { 190 | for (var i = 0; i < imageData.data.length; i += 4) { 191 | if (imageData.data[i + 3] == 0) { 192 | imageData.data[i] = this._lastData.data[i]; 193 | imageData.data[i + 1] = this._lastData.data[i + 1]; 194 | imageData.data[i + 2] = this._lastData.data[i + 2]; 195 | imageData.data[i + 3] = this._lastData.data[i + 3]; 196 | } 197 | } 198 | } 199 | return imageData; 200 | } 201 | 202 | 203 | /** 204 | * 网页版转换 205 | * 将DataUrl的数据转换为cc.SpriteFrame 206 | * @param dataUrl 207 | */ 208 | private dataUrl2SpriteFrame(dataUrl) { 209 | let texture = new cc.Texture2D() 210 | let spriteFrame = new cc.SpriteFrame(); 211 | let image = new Image(); 212 | image.src = dataUrl; 213 | texture.initWithElement(image); 214 | spriteFrame.setTexture(texture); 215 | return spriteFrame; 216 | } 217 | 218 | /** 219 | * native版转换 220 | * 用renderTexture将二进制数据制作为cc.SpriteFrame 221 | * @param data 222 | * @param w 223 | * @param h 224 | */ 225 | private date2SpriteFrame(data, w, h) { 226 | let texture = new cc.RenderTexture(); 227 | let spriteFrame = new cc.SpriteFrame(); 228 | texture.initWithData(data.data, cc.Texture2D.PixelFormat.RGBA8888, w, h); 229 | spriteFrame.setTexture(texture); 230 | return spriteFrame; 231 | } 232 | 233 | 234 | /** 235 | * 图片数据叠加 236 | * 根据显示模式更新图片数据 237 | * 模型0 1 叠加图片 238 | * 模式2 清理画布 显示新的图片 239 | * 模式3 保持上一个状态 240 | * 模式4-7 。。。。 241 | * @param imageData 新数据 242 | * @param x 左上角横向偏移 243 | * @param y 左上角纵向偏移 244 | */ 245 | putImageDataJSB(imageData, x, y, frame) { 246 | 247 | let cheeckNullPixel = () => { 248 | if (imageData.data[0] == 4 249 | && imageData.data[1] == 0 250 | && imageData.data[2] == 0 251 | && imageData.data[3] == 0) { 252 | return true 253 | } 254 | return false 255 | } 256 | 257 | let checkAlpha = () => { 258 | let alphaCount = 0 259 | for (let i = 0; i < imageData.height; i += 2) { 260 | let lineCount = 0 261 | for (let j = 0; j < imageData.width; j++) { 262 | let indexData = i * 4 * imageData.width + 4 * j 263 | if (imageData.data[indexData + 3] == 0) { 264 | lineCount++ 265 | } 266 | } 267 | if (lineCount / imageData.width > 0.1) { 268 | alphaCount++ 269 | } 270 | if (alphaCount / (imageData.height / 2) > 0.6) return true 271 | } 272 | return false 273 | } 274 | 275 | //叠加图形 276 | let replay = () => { 277 | for (let i = 0; i < imageData.height; i++) { 278 | for (let j = 0; j < imageData.width; j++) { 279 | let indexData = i * 4 * imageData.width + 4 * j 280 | let indexLastData = (i + y) * 4 * this._lastData.width + 4 * (j + x) 281 | //新像素点的透明度不是0就替换掉旧像素 282 | if (imageData.data[indexData + 3] != 0) { 283 | this._lastData.data[indexLastData] = imageData.data[indexData] 284 | this._lastData.data[indexLastData + 1] = imageData.data[indexData + 1] 285 | this._lastData.data[indexLastData + 2] = imageData.data[indexData + 2] 286 | this._lastData.data[indexLastData + 3] = imageData.data[indexData + 3] 287 | } 288 | } 289 | } 290 | } 291 | 292 | //清理画布从新绘制 293 | let clearAndReplay = () => { 294 | for (let i = 0; i < this._lastData.height; i++) { 295 | for (let j = 0; j < this._lastData.width; j++) { 296 | let indexLastData = i * 4 * this._lastData.width + 4 * j 297 | let indexData = (i - y) * 4 * imageData.width + 4 * (j - x) 298 | let clear = false 299 | if (j < x || j > (x + imageData.width)) { 300 | clear = true 301 | } 302 | if (i < y || i > (y + imageData.height)) { 303 | clear = true 304 | } 305 | if (clear) { 306 | this._lastData.data[indexLastData + 0] = 0; 307 | this._lastData.data[indexLastData + 1] = 0; 308 | this._lastData.data[indexLastData + 2] = 0; 309 | this._lastData.data[indexLastData + 3] = 0; 310 | } else { 311 | this._lastData.data[indexLastData + 0] = imageData.data[indexData + 0] 312 | this._lastData.data[indexLastData + 1] = imageData.data[indexData + 1] 313 | this._lastData.data[indexLastData + 2] = imageData.data[indexData + 2] 314 | this._lastData.data[indexLastData + 3] = imageData.data[indexData + 3] 315 | } 316 | } 317 | 318 | } 319 | } 320 | 321 | //如果和上一帧一样的不更新画布 322 | if (cheeckNullPixel()) { 323 | return 324 | } 325 | 326 | if (frame.ctrl.disp == 1 || frame.ctrl.disp == 0) { 327 | //显示模式1 叠加图片 328 | replay() 329 | } else if (frame.ctrl.disp == 2) { 330 | //显示模式2 清理画布显示新的 331 | clearAndReplay() 332 | } else { 333 | if (checkAlpha()) { 334 | clearAndReplay() 335 | } else { 336 | replay() 337 | } 338 | } 339 | } 340 | 341 | /** 342 | * 模型0 1 叠加图片 343 | * 模式2 清理画布 显示新的图片 344 | * 模式3 保持上一个状态 345 | * 模式4-7 。。。。 346 | * @param imageData 347 | * @param frame 348 | */ 349 | putImageDataWeb(imageData, frame) { 350 | let finalImageData 351 | if (frame.ctrl.disp == 1 || frame.ctrl.disp == 0) { 352 | //叠加图形 353 | // 3、将当前frame的ImageData设置到canvas上(必须,否则会因为ImageData的尺寸大小可能不一样造成拉伸等错乱现象) 354 | this._context.putImageData(imageData, frame.img.x, frame.img.y, 0, 0, frame.img.w, frame.img.h); 355 | // 4、把当前imageData和上一帧imageData合并(必须,因为GIF的当前帧可能只提供了像素发生变化位置的信息) 356 | let curImageData = this._context.getImageData(0, 0, this._canvas.width, this._canvas.height); 357 | let lastImageData = this._lastData; 358 | finalImageData = this.mergeFrames(lastImageData, curImageData); 359 | } else { 360 | //清理画布从新绘制 361 | this._context.clearRect(0, 0, this._canvas.width, this._canvas.height) 362 | // 3、将当前frame的ImageData设置到canvas上(必须,否则会因为ImageData的尺寸大小可能不一样造成拉伸等错乱现象) 363 | this._context.putImageData(imageData, frame.img.x, frame.img.y, 0, 0, frame.img.w, frame.img.h); 364 | // 4、把当前imageData和上一帧imageData合并(必须,因为GIF的当前帧可能只提供了像素发生变化位置的信息) 365 | finalImageData = this._context.getImageData(0, 0, this._canvas.width, this._canvas.height); 366 | } 367 | // 5、把最终的ImageData设置到canvas上(形成合成之后的最终图像) 368 | this._context.putImageData(finalImageData, 0, 0); 369 | this._lastData = finalImageData; 370 | return this._canvas.toDataURL(); 371 | } 372 | 373 | /** 374 | * 将frame数据转化为cc.Texture 375 | * @param frame 当前frame的数据 376 | * @param index 当前frame的顺序 377 | */ 378 | private decodeFrame2Texture(frame, index) { 379 | // 1、初始化canvas的相关信息 380 | if (!this._context) { 381 | this._canvas = document.createElement('canvas'); 382 | this._context = this._canvas.getContext('2d'); 383 | this._canvas.width = frame.img.w; 384 | this._canvas.height = frame.img.h; 385 | } 386 | 387 | // 2、解析当前frame的ImageData数据(frame中存在的IamgeData数据) 388 | let imageData = this.decodeFrame(frame); 389 | this._delays[index] = frame.ctrl.delay; 390 | 391 | if (CC_JSB) { 392 | //原生平台 393 | if (!this._lastData) { 394 | this._lastData = imageData 395 | } else { 396 | this.putImageDataJSB(imageData, frame.img.x, frame.img.y, frame); 397 | } 398 | this._spriteFrames[index] = this.date2SpriteFrame(this._lastData, this._canvas.width, this._canvas.height); 399 | } else { 400 | //web平台 401 | let dataUrl = this.putImageDataWeb(imageData, frame) 402 | this._spriteFrames[index] = this.dataUrl2SpriteFrame(dataUrl); 403 | } 404 | 405 | return this._spriteFrames[index]; 406 | } 407 | 408 | 409 | /** 410 | * 读文件流 411 | * @param len 读取的长度 412 | */ 413 | private read(len) { 414 | return this._view.slice(this._offset, this._offset += len); 415 | } 416 | 417 | /** 418 | * 获取文件头部分(Header) 419 | * GIF署名(Signature)和版本号(Version) 420 | */ 421 | private getHeader() { 422 | this._info.header = ''; 423 | this.read(6).forEach((e, i, arr) => { 424 | this._info.header += String.fromCharCode(e); 425 | }); 426 | } 427 | 428 | /** 429 | * 获取逻辑屏幕标识符(Logical Screen Descriptor) 430 | * GIF数据流部分(GIF Data Stream) 431 | */ 432 | private getScrDesc() { 433 | // await 0; 434 | var arr = this.read(7), i; 435 | this._info.w = arr[0] + (arr[1] << 8); 436 | this._info.h = arr[2] + (arr[3] << 8); 437 | this._info.m = 1 & arr[4] >> 7; 438 | this._info.cr = 7 & arr[4] >> 4; 439 | this._info.s = 1 & arr[4] >> 3; 440 | this._info.pixel = arr[4] & 0x07; 441 | this._info.bgColor = arr[5]; 442 | this._info.radio = arr[6]; 443 | if (this._info.m) { 444 | this._info.colorTab = this.read((2 << this._info.pixel) * 3); 445 | } 446 | this.decode(); 447 | } 448 | 449 | 450 | /** 451 | * 解析GIF数据流 452 | */ 453 | private decode() { 454 | let srcBuf = []; 455 | let arr = this.read(1); 456 | 457 | switch (arr[0]) { 458 | case 33: //扩展块 459 | this.extension(); 460 | break; 461 | case 44: //图象标识符 462 | arr = this.read(9); 463 | this._frame.img = { 464 | x: arr[0] + (arr[1] << 8), 465 | y: arr[2] + (arr[3] << 8), 466 | w: arr[4] + (arr[5] << 8), 467 | h: arr[6] + (arr[7] << 8), 468 | colorTab: 0 469 | }; 470 | this._frame.img.m = 1 & arr[8] >> 7; 471 | this._frame.img.i = 1 & arr[8] >> 6; 472 | this._frame.img.s = 1 & arr[8] >> 5; 473 | this._frame.img.r = 3 & arr[8] >> 3; 474 | this._frame.img.pixel = arr[8] & 0x07; 475 | if (this._frame.img.m) { 476 | this._frame.img.colorTab = this.read((2 << this._frame.img.pixel) * 3); 477 | } 478 | this._frame.img.codeSize = this.read(1)[0]; 479 | srcBuf = []; 480 | while (1) { 481 | arr = this.read(1); 482 | if (arr[0]) { 483 | this.read(arr[0]).forEach((e, i, arr) => { 484 | srcBuf.push(e); 485 | }); 486 | } else { 487 | this._frame.img.srcBuf = srcBuf; 488 | this.decode(); 489 | break; 490 | } 491 | }; 492 | break; 493 | case 59: 494 | // console.log('The end.', this._offset, this.buffer.byteLength) 495 | break; 496 | default: 497 | // console.log(arr); 498 | break; 499 | } 500 | } 501 | 502 | 503 | /** 504 | * 扩展块部分 505 | */ 506 | private extension() { 507 | var arr = this.read(1), o, s; 508 | switch (arr[0]) { 509 | case 255: //应用程序扩展 510 | if (this.read(1)[0] == 11) { 511 | this._info.appVersion = ''; 512 | this.read(11).forEach((e, i, arr) => { 513 | this._info.appVersion += String.fromCharCode(e); 514 | }); 515 | while (1) { 516 | arr = this.read(1); 517 | if (arr[0]) { 518 | this.read(arr[0]); 519 | } else { 520 | this.decode(); 521 | break; 522 | } 523 | }; 524 | } else { 525 | throw new Error('解析出错'); 526 | } 527 | break; 528 | case 249: //图形控制扩展 529 | if (this.read(1)[0] == 4) { 530 | arr = this.read(4); 531 | this._frame = {}; 532 | this._frame.ctrl = { 533 | disp: 7 & arr[0] >> 2, 534 | i: 1 & arr[0] >> 1, 535 | t: arr[0] & 0x01, 536 | delay: arr[1] + (arr[2] << 8), 537 | tranIndex: arr[3] 538 | }; 539 | this._info.frames.push(this._frame); 540 | if (this.read(1)[0] == 0) { 541 | this.decode(); 542 | } else { 543 | throw new Error('解析出错'); 544 | } 545 | } else { 546 | throw new Error('解析出错'); 547 | } 548 | break; 549 | case 254: //注释块 550 | arr = this.read(1); 551 | if (arr[0]) { 552 | this.read(arr[0]).forEach((e, i, arr) => { 553 | this._info.comment += String.fromCharCode(e); 554 | }); 555 | if (this.read(1)[0] == 0) { 556 | this.decode(); 557 | }; 558 | } 559 | break; 560 | default: 561 | // console.log(arr); 562 | break; 563 | } 564 | } 565 | 566 | 567 | /** 568 | * 初始化参数 569 | */ 570 | private clear() { 571 | this._tab = null; 572 | this._view = null; 573 | this._frame = null; 574 | this._offset = 0; 575 | this._info = { 576 | header: '', 577 | frames: [], 578 | comment: '' 579 | }; 580 | this._lastData = null; 581 | this._delays = []; 582 | this._spriteFrames = []; 583 | this._canvas = null; 584 | this._context = null; 585 | } 586 | 587 | } 588 | 589 | 590 | /** 591 | * gif缓存系统 592 | * 资源再利用 593 | */ 594 | class GIFCache { 595 | private static instance: GIFCache = null; 596 | gifFrameMap = {} 597 | 598 | static getInstance() { 599 | if (!GIFCache.instance) { 600 | cc.macro.ALLOW_IMAGE_BITMAP = true; 601 | GIFCache.instance = new GIFCache(); 602 | cc.assetManager.parser.register('.gif', async (file: Blob | HTMLImageElement, options, onComplete) => { 603 | let buffer; 604 | if('src' in file){ 605 | let _file = await downloadAsBlob(file.src); 606 | buffer = await _file.arrayBuffer(); 607 | }else{ 608 | buffer = await file.arrayBuffer(); 609 | } 610 | let gif = new GIF(); 611 | gif.handle(buffer, onComplete) 612 | }) 613 | } 614 | return GIFCache.instance; 615 | } 616 | 617 | preloadGif(data) { 618 | try { 619 | if (data.words) { 620 | data.words.forEach(item => { 621 | if (item.indexOf(".gif") != -1) 622 | cc.loader.load(item.img, (error, data) => { }) 623 | }); 624 | } 625 | if (data.classes) { 626 | data.classes.forEach(item => { 627 | if (item.indexOf(".gif") != -1) 628 | cc.loader.load(item.img, (error, data) => { }) 629 | }); 630 | } 631 | } catch (e) { 632 | cc.log(e) 633 | } 634 | } 635 | 636 | addItemFrame(key: any, frameData: GIFFrameData) { 637 | if (this.has(key) == true) { 638 | let item = this.get(key) 639 | item.referenceCount++ 640 | item.frameData = frameData 641 | } else { 642 | let gifCaheItem = { referenceCount: 0, type: FileType.GIF, frame: {} } 643 | this.gifFrameMap[key] = gifCaheItem 644 | } 645 | } 646 | 647 | 648 | addItemType(key: any, type: FileType) { 649 | if (this.has(key)) { 650 | let item = this.get(key) 651 | item.type = type 652 | } else { 653 | let gifCaheItem = { referenceCount: 0, type: type, frame: null } 654 | this.gifFrameMap[key] = gifCaheItem 655 | } 656 | } 657 | 658 | add(key: any, value: GIFCaheItem) { 659 | if (!this.has(key)) { 660 | this.gifFrameMap[key] = value 661 | } 662 | } 663 | 664 | get(key: any): GIFCaheItem { 665 | return this.gifFrameMap[key] 666 | } 667 | 668 | 669 | has(key: any): boolean { 670 | if (this.gifFrameMap[key] == undefined) { 671 | return false 672 | } 673 | return true 674 | } 675 | 676 | hasFrame(key: any) { 677 | let item = this.get(key) 678 | if (item != undefined) { 679 | let itemFrame = item.frameData 680 | if (itemFrame != null) { 681 | return true 682 | } 683 | } 684 | return false 685 | } 686 | 687 | /** 688 | * onDestroy 释放资源 689 | * 资源引用计数为0的时候释放资源 690 | * @param key 691 | */ 692 | relase(key: any) { 693 | if (this.has(key)) { 694 | this.gifFrameMap[key] = undefined 695 | cc.loader.release(key) 696 | } 697 | } 698 | 699 | releaseAll() { 700 | for (const key in this.gifFrameMap) { 701 | cc.loader.release(key) 702 | } 703 | this.gifFrameMap = {} 704 | } 705 | } 706 | 707 | /** 708 | * gif资源 709 | */ 710 | interface GIFFrameData { 711 | /*每一帧延时 */ 712 | delays: Array, 713 | 714 | spriteFrames: Array, 715 | length: number 716 | } 717 | 718 | interface GIFCaheItem { 719 | /*资源引用计数*/ 720 | referenceCount: number, 721 | /*文件类型*/ 722 | type: FileType, 723 | /*gif解析后的数据 */ 724 | frameData: GIFFrameData 725 | } 726 | 727 | export { GIF, GIFCache, GIFFrameData, GIFCaheItem } 728 | -------------------------------------------------------------------------------- /assets/GIF/GIF.ts.meta: -------------------------------------------------------------------------------- 1 | { 2 | "ver": "1.0.8", 3 | "uuid": "c63d8577-7363-4fcd-8341-dec3776f6d5c", 4 | "isPlugin": false, 5 | "loadPluginInWeb": true, 6 | "loadPluginInNative": true, 7 | "loadPluginInEditor": false, 8 | "subMetas": {} 9 | } -------------------------------------------------------------------------------- /assets/GIF/LZW.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * LZW编码解析 3 | */ 4 | export default class LZW { 5 | static decode(arr, min) { 6 | let clearCode = 1 << min, 7 | eofCode = clearCode + 1, 8 | size = min + 1, 9 | dict = [], 10 | pos = 0; 11 | 12 | function clear() { 13 | let i; 14 | dict = []; 15 | size = min + 1; 16 | for (i = 0; i < clearCode; i++) { 17 | dict[i] = [i]; 18 | } 19 | dict[clearCode] = []; 20 | dict[eofCode] = null; 21 | } 22 | 23 | function decode() { 24 | let out = [], 25 | code, last; 26 | while (1) { 27 | last = code; 28 | code = read(size); 29 | if (code == clearCode) { 30 | clear(); 31 | continue; 32 | } 33 | if (code == eofCode) { 34 | break; 35 | } 36 | if (code < dict.length) { 37 | if (last !== clearCode) { 38 | dict.push(dict[last].concat(dict[code][0])); 39 | } 40 | } else { 41 | if (code !== dict.length) { 42 | throw new Error('LZW解析出错'); 43 | } 44 | dict.push(dict[last].concat(dict[last][0])); 45 | } 46 | out.push.apply(out, dict[code]); 47 | if (dict.length === (1 << size) && size < 12) { 48 | size++; 49 | } 50 | } 51 | return out; 52 | } 53 | 54 | function read(size) { 55 | let i, code = 0; 56 | for (i = 0; i < size; i++) { 57 | if (arr[pos >> 3] & 1 << (pos & 7)) { 58 | code |= 1 << i; 59 | } 60 | pos++; 61 | } 62 | return code; 63 | } 64 | return decode(); 65 | } 66 | } -------------------------------------------------------------------------------- /assets/GIF/LZW.ts.meta: -------------------------------------------------------------------------------- 1 | { 2 | "ver": "1.0.8", 3 | "uuid": "5cab8751-79e0-45b9-a08b-5f3d0377af05", 4 | "isPlugin": false, 5 | "loadPluginInWeb": true, 6 | "loadPluginInNative": true, 7 | "loadPluginInEditor": false, 8 | "subMetas": {} 9 | } -------------------------------------------------------------------------------- /assets/resources.meta: -------------------------------------------------------------------------------- 1 | { 2 | "ver": "1.1.2", 3 | "uuid": "cf9d512a-2e45-49bd-908c-25fc51242705", 4 | "isBundle": true, 5 | "bundleName": "resources", 6 | "priority": 8, 7 | "compressionType": {}, 8 | "optimizeHotUpdate": {}, 9 | "inlineSpriteFrames": {}, 10 | "isRemoteBundle": {}, 11 | "subMetas": {} 12 | } -------------------------------------------------------------------------------- /assets/resources/gif.meta: -------------------------------------------------------------------------------- 1 | { 2 | "ver": "1.1.2", 3 | "uuid": "55d3d191-2417-4cdf-af62-f31fe87c5935", 4 | "isBundle": false, 5 | "bundleName": "", 6 | "priority": 1, 7 | "compressionType": {}, 8 | "optimizeHotUpdate": {}, 9 | "inlineSpriteFrames": {}, 10 | "isRemoteBundle": {}, 11 | "subMetas": {} 12 | } -------------------------------------------------------------------------------- /assets/resources/gif/duck1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2van/cocos-creator-gif/befec981c391436f200d66fe1b0762cbf995b25f/assets/resources/gif/duck1.gif -------------------------------------------------------------------------------- /assets/resources/gif/duck1.gif.meta: -------------------------------------------------------------------------------- 1 | { 2 | "ver": "1.0.1", 3 | "uuid": "7b977980-ad4a-43b6-a19d-b0adbca55a06", 4 | "subMetas": {} 5 | } -------------------------------------------------------------------------------- /assets/resources/gif/duck2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2van/cocos-creator-gif/befec981c391436f200d66fe1b0762cbf995b25f/assets/resources/gif/duck2.gif -------------------------------------------------------------------------------- /assets/resources/gif/duck2.gif.meta: -------------------------------------------------------------------------------- 1 | { 2 | "ver": "1.0.1", 3 | "uuid": "743f55ae-1782-48a7-a5bd-0280db35d1f7", 4 | "subMetas": {} 5 | } -------------------------------------------------------------------------------- /assets/resources/gif/duck3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2van/cocos-creator-gif/befec981c391436f200d66fe1b0762cbf995b25f/assets/resources/gif/duck3.gif -------------------------------------------------------------------------------- /assets/resources/gif/duck3.gif.meta: -------------------------------------------------------------------------------- 1 | { 2 | "ver": "1.0.1", 3 | "uuid": "3151d52f-8d7e-4baf-9b11-e4ebc5f88f2b", 4 | "subMetas": {} 5 | } -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "experimentalDecorators": true 6 | }, 7 | "exclude": [ 8 | "node_modules", 9 | ".vscode", 10 | "library", 11 | "local", 12 | "settings", 13 | "temp" 14 | ] 15 | } -------------------------------------------------------------------------------- /project.json: -------------------------------------------------------------------------------- 1 | { 2 | "engine": "cocos-creator-js", 3 | "packages": "packages", 4 | "name": "example", 5 | "id": "de903c1b-092d-4de5-bf91-a2bd51235ffb", 6 | "version": "2.4.3", 7 | "isNew": false 8 | } -------------------------------------------------------------------------------- /sample.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2van/cocos-creator-gif/befec981c391436f200d66fe1b0762cbf995b25f/sample.gif -------------------------------------------------------------------------------- /settings/project.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /settings/services.json: -------------------------------------------------------------------------------- 1 | { 2 | "game": { 3 | "name": "未知游戏", 4 | "appid": "UNKNOW" 5 | } 6 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "lib": [ "es2015", "es2017", "dom" ], 5 | "target": "es5", 6 | "experimentalDecorators": true, 7 | "skipLibCheck": true, 8 | "outDir": "temp/vscode-dist", 9 | "forceConsistentCasingInFileNames": true 10 | }, 11 | "exclude": [ 12 | "node_modules", 13 | "library", 14 | "local", 15 | "temp", 16 | "build", 17 | "settings" 18 | ] 19 | } --------------------------------------------------------------------------------