{"id":20114602,"url":"https://github.com/alienzhou/giframe","last_synced_at":"2025-05-06T13:32:04.360Z","repository":{"id":107435321,"uuid":"234589997","full_name":"alienzhou/giframe","owner":"alienzhou","description":"extract the first frame in GIF without reading whole bytes,  support both browser and nodejs  📸","archived":false,"fork":false,"pushed_at":"2020-02-03T10:27:03.000Z","size":6774,"stargazers_count":19,"open_issues_count":0,"forks_count":6,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-09T12:22:53.501Z","etag":null,"topics":["decoder","extract-data","frame","gif","gif87a","gif89a","progressive","stream-like"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/alienzhou.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-01-17T16:32:10.000Z","updated_at":"2025-02-07T11:19:27.000Z","dependencies_parsed_at":"2023-06-16T18:52:52.237Z","dependency_job_id":null,"html_url":"https://github.com/alienzhou/giframe","commit_stats":{"total_commits":49,"total_committers":2,"mean_commits":24.5,"dds":0.3877551020408163,"last_synced_commit":"5a88dde19674a26face51ade56e51c13df523c2d"},"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alienzhou%2Fgiframe","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alienzhou%2Fgiframe/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alienzhou%2Fgiframe/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alienzhou%2Fgiframe/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/alienzhou","download_url":"https://codeload.github.com/alienzhou/giframe/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252693615,"owners_count":21789724,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["decoder","extract-data","frame","gif","gif87a","gif89a","progressive","stream-like"],"created_at":"2024-11-13T18:30:44.294Z","updated_at":"2025-05-06T13:31:59.348Z","avatar_url":"https://github.com/alienzhou.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv\u003e\n    \u003ch1 align=\"center\"\u003e\u003ccode\u003eGIFrame\u003c/code\u003e\u0026nbsp;\u0026nbsp;📸\u003c/h1\u003e\n    \u003cp align=\"center\"\u003e\n        \u003cstrong\u003eGIFrame.js can extract the GIF's first frame without reading whole bytes in both browsers and NodeJS environments.\u003c/strong\u003e\n    \u003c/p\u003e\n    \u003cp align=\"center\"\u003e\n        \u003ca href=\"https://travis-ci.org/alienzhou/giframe\" target=\"_blank\"\u003e\n            \u003cimg src=\"https://api.travis-ci.org/alienzhou/giframe.svg?branch=master\" alt=\"Build status\" /\u003e\n        \u003c/a\u003e\n        \u003ca href=\"https://www.npmjs.com/package/giframe\" target=\"_blank\"\u003e\n            \u003cimg src=\"https://img.shields.io/npm/v/giframe.svg\" alt=\"NPM version\" /\u003e\n        \u003c/a\u003e\n        \u003ca href=\"https://coveralls.io/github/alienzhou/giframe\" target=\"_blank\"\u003e\n            \u003cimg src=\"https://coveralls.io/repos/github/alienzhou/giframe/badge.svg\" alt=\"Coverage Status\" /\u003e\n        \u003c/a\u003e\n        \u003ca href=\"https://unpkg.com/giframe\" target=\"_blank\"\u003e\n            \u003cimg src=\"https://img.badgesize.io/https://unpkg.com/giframe/dist/umd/giframe.js?compression=gzip\" alt=\"Gzip size\" /\u003e\n        \u003c/a\u003e\n        \u003ca href=\"https://codebeat.co/projects/github-com-alienzhou-giframe-master\" target=\"_blank\"\u003e\n            \u003cimg src=\"https://codebeat.co/badges/f2b1747e-3cd8-46a8-83e4-cda6ba8b3498\" alt=\"Codebeat\" /\u003e\n        \u003c/a\u003e\n        \u003ca href=\"https://opensource.org/licenses/mit-license.php\" target=\"_blank\"\u003e\n            \u003cimg src=\"https://img.shields.io/github/license/alienzhou/giframe\" alt=\"MIT Licence\" /\u003e\n        \u003c/a\u003e\n    \u003c/p\u003e\n\u003c/div\u003e\n\n---\n\nIt's fast (decode on-demand) and compatible (both in browsers and nodejs).\n\nNo need to wait for and read all bytes and decode chunk by chunk, especially when only extracting the first frame. So it may be used for improving GIFs loading experiences, providing more controllable GIF loading strategies and so on.\n\n\u003cimg src=\"./doc/img/example.gif\" alt=\"exmaple\" width=\"100%\" /\u003e\n\n## Motivation\n\nSome websites contain a lot of [GIF images](https://en.wikipedia.org/wiki/GIF). Displaying animation images in your homepage, item list and so on may attract users' attention. However, GIF images are much larger than static images (sometimes 20x~30x depends on how many frames).\n\n![](./doc/img/1.jpg)\n\nAs a result, users need to wait for a long time to see GIF images. A common method is to extract the first frame as a placeholder and load GIF lazily when in view or clicked. There are lots of libraries to extract frames in the server-side. However, it has some limitations:\n\n- Most libs need to read whole bytes in GIF for extracting frames, even though we only need the first one. It's a waste of computing and time. For example, the first frame only use about 16% bytes in [`example/img/4.gif`](./example/img/4.gif) (8-frames) and .\n- This solution needs the support of the server-side or CDN. Is there any frontend-only solution to improve user experience?\n\nThis repository aims to provide a stream-like (decode chunk by chunk) GIF decoder which can run in both browsers (client-side) and NodeJS (server-side).\n\n- It will try to extract the needed frame without reading all bytes. You can read bytes and decode at the same time. It is useful especially when using stream in I/O.\n- Running in browsers means you can display a early static frame when downloading GIF, or use the client itself to calculate.\n\nBelow is an browser example. _**The first frame is extracted and used as a placeholder while the GIF image is still loading.**_\n\n![](./doc/img/example.gif)\n\nYou can also play with it by yourself. [Go to the `Example` section \u003e\u003e](#Example)\n\n## Basic Usage\n\nSupport both in browsers and NodeJS,\n\n```JavaScript\nimport GIFrame from 'giframe';\nconst giframe = new GIFrame();\ngiframe.getBase64().then(base64 =\u003e {\n    // finally get the base64 string of the first frame\n    console.log(base64);\n});\n\n// then read GIF bytes from network, local file and so on\nconst source = readGIF('xxx.gif');\n// chunk need to be Uint8Array\nsource.on('data', chunk =\u003e giframe.feed(chunk));\n```\n\nMore complex usages can be found in `example/` directory. You can also run examples below ↓↓↓\n\n## Example\n\n\u003e [Nodejs](https://nodejs.org/) required.\n\nThis repo provides some examples in `example/` which give you a quick start. \n\nFirstly, clone the repo and install dependencies.\n\n```bash\ngit clone git@github.com:alienzhou/giframe.git\ncd giframe\n\n# install dependencies\nnpm i\n```\n\nRun in browsers,\n\n```bash\nnpm run example:browser\n```\n\nThen it will open http://127.0.0.1:8080 (default port 8080), you will see a demo page ↓\n\n![](./doc/img/example.jpg)\n\nOr run in NodeJS,\n\n```bash\n# extract the first frame image\n# you can change the gif filename (1.gif ~ 5.gif)\nnpm run example:node:stream 1.gif\n\n# or you can run\nnpm run example:node:limit 1.gif\n\n# then the first frame image will be written in example/output\n```\n\n![](./doc/img/example-node.jpg)\n\n\n## How it works\n\nFor a quick and robust start, the decoder is mostly a folk of [omggif](https://github.com/deanm/omggif). GIF is composed of [many blocks](http://matthewflickinger.com/lab/whatsinagif/bits_and_bytes.asp). Giframe treats every block as a valid unit and resets the position to the previous block's end when meet an incomplete block. It will try to continue to decoding when receiving another chunk (more bytes). It's like stream.\n\nTo generate the image's base64, Giframe uses the Canvas API - [node-canvas](https://github.com/Automattic/node-canvas) in NodeJS and [native canvas](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API) in browsers. The canvas uses all RGBA pixels which are provided by Giframe to render a image and exports base64 string by `.toDataURL()`.\n\nBy the way, the example in `example/browser` uses [Service Worker](https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker), [`fetch` event](https://developer.mozilla.org/en-US/docs/Web/API/FetchEvent), [fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) and [Readable Stream](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream/ReadableStream). It tees a stream from the response in fetch API and read it. Every chunk received will be used to decode progressively. Once the first frame is ready, it will be displayed on screen as a static preview.\n\n## Compatibility\n\n\u003e **Discarded**: *~~Now GIFrame.js uses [`Proxy` object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) to throw an error when accessing an out-of-range item in `Uin8Array` and catch it in the decoder to reset to the previous valid block's tail position. `Proxy` object [isn't compatible in some browsers](https://caniuse.com/#search=proxy).~~*\n\nAfter v0.2.0, GIFrame uses a basic [`get` function](./src/utils/proxy.ts) instead of the `Proxy` object. So mostly its compatibility depends on `Uint8Array` and `Int32Array` which [are supported in most browsers](https://caniuse.com/#search=Uint8Array).\n\n![compatibility](./doc/img/compatibility.png)\n\n## License\n\n[MIT](./LICENCE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falienzhou%2Fgiframe","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falienzhou%2Fgiframe","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falienzhou%2Fgiframe/lists"}