{"id":51124590,"url":"https://github.com/quackster/chroma4j","last_synced_at":"2026-06-25T06:01:31.987Z","repository":{"id":359919595,"uuid":"1130716463","full_name":"Quackster/Chroma4j","owner":"Quackster","description":"The repository contains the original JVM renderer and a TeaVM/WebAssembly browser deployment that renders from SWF bytes entirely client-side.","archived":false,"fork":false,"pushed_at":"2026-06-23T11:53:20.000Z","size":541,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-06-23T13:30:22.217Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Quackster.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","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,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-01-08T22:52:47.000Z","updated_at":"2026-06-23T11:52:32.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/Quackster/Chroma4j","commit_stats":null,"previous_names":["quackster/chroma4j"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/Quackster/Chroma4j","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Quackster%2FChroma4j","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Quackster%2FChroma4j/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Quackster%2FChroma4j/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Quackster%2FChroma4j/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Quackster","download_url":"https://codeload.github.com/Quackster/Chroma4j/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Quackster%2FChroma4j/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34761847,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-25T02:00:05.521Z","response_time":101,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":[],"created_at":"2026-06-25T06:01:31.180Z","updated_at":"2026-06-25T06:01:31.981Z","avatar_url":"https://github.com/Quackster.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Chroma4j\n\nChroma4j renders Habbo furni SWFs. The repository contains the original JVM renderer and a TeaVM/WebAssembly browser deployment that renders from SWF bytes entirely client-side.\n\n## Requirements\n\n- JDK 17 or newer.\n- Gradle 8.7 or newer, or use the checked-in Gradle wrapper.\n- Python 3 for serving `web/dist` locally.\n- Node.js for browser JavaScript syntax checks run by the Gradle verification task.\n- A modern browser with WebAssembly GC support.\n\nThe browser build fetches SWFs directly from the browser, so HTTP SWF URLs must allow CORS. There is no server-side fetch or rendering fallback in the WASM release.\n\n## Preview\n\n\u003cimg width=\"1275\" height=\"863\" alt=\"chrome_2myBLVlekx\" src=\"https://github.com/user-attachments/assets/d037fc41-6633-4736-8283-35b3ace100f7\" /\u003e\n\n## Build The WASM Release\n\nFrom the repository root:\n\n```powershell\n.\\gradlew.bat :chroma-wasm:clean :chroma-wasm:buildWasmGC\n```\n\nThis compiles the TeaVM module and writes:\n\n- `web/dist/wasm-gc/chroma-wasm.wasm`\n- `web/dist/wasm-gc/chroma-wasm.wasm-runtime.js`\n\nThe static deployment directory contains:\n\n- `web/dist/index.html`\n- `web/dist/main.js`\n- `web/dist/chroma4j.js`\n- `web/dist/styles.css`\n- `web/dist/bg.png`\n\nThe ADD overlay test page lives in `web/dist-add-overlay-test` and imports the WASM release from `web/dist`.\n\n`web/dist` is generated/served output and is ignored by Git.\n\n## Run The Static Demo\n\nServe `web/dist` with any static HTTP server:\n\n```powershell\npython -m http.server 5177 --directory web/dist\n```\n\nOpen:\n\n```text\nhttp://localhost:5177\n```\n\nPaste a CORS-enabled furni SWF URL, choose render options, and click `Render`. The page fetches SWF bytes in the browser, passes them to the TeaVM parser, and exports PNG by default, animated GIF when `gif: true` is selected, or animated PNG when `apng: true` is selected.\n\nTo try the ADD overlay demonstration, serve the `web` directory and open:\n\n```powershell\npython -m http.server 5178 --directory web\n```\n\n```text\nhttp://localhost:5178/dist-add-overlay-test/\n```\n\n## JavaScript API\n\n```js\nimport { loadChroma4j } from \"./chroma4j.js\";\n\nconst chroma = await loadChroma4j();\nconst result = await chroma.renderFromUrl(\"https://example.com/hof_furni/chair.swf\", {\n  state: 0,\n  direction: 2,\n  color: 0,\n  crop: true,\n  canvas: \"transparent\"\n});\n\ndocument.body.append(result.canvas);\nconst png = await result.blob();\n```\n\nPNG is the default output. `result.format` is `png`, `result.mime` is `image/png`, `result.canvas` contains the rendered pixels, and `result.blob()` returns a browser `Blob` containing the PNG bytes.\n\nTo paint PNG output into an existing canvas:\n\n```js\nconst canvas = document.querySelector(\"#preview\");\nconst result = await chroma.renderFromUrl(\"https://example.com/hof_furni/chair.swf\", {\n  state: 0,\n  direction: 2\n}, canvas);\n\nconsole.log(result.canvas === canvas); // true\n```\n\nTo show the PNG in a normal `\u003cimg\u003e`:\n\n```js\nconst img = document.createElement(\"img\");\nimg.src = await result.dataUrl();\ndocument.body.append(img);\n```\n\nTo render an animated GIF, pass `gif: true`:\n\n```js\nconst result = await chroma.renderFromUrl(\"https://example.com/hof_furni/rare_dragonlamp.swf\", {\n  state: 1,\n  direction: 4,\n  color: 0,\n  crop: true,\n  canvas: \"transparent\",\n  gif: true,\n  loop: true\n});\n\nconsole.log(result.format);    // \"gif\"\nconsole.log(result.mime);      // \"image/gif\"\nconsole.log(result.isAnimated); // true when the selected state has multiple frames\n```\n\nTo render animated PNG instead, pass `apng: true` or `format: \"apng\"`:\n\n```js\nconst result = await chroma.renderFromUrl(\"https://example.com/hof_furni/rare_dragonlamp.swf\", {\n  state: 1,\n  direction: 4,\n  apng: true,\n  loop: true\n});\n\nconsole.log(result.format); // \"apng\"\nconsole.log(result.mime);   // \"image/png\"\n```\n\nUse an `\u003cimg\u003e` for animated GIF or APNG playback:\n\n```js\nconst img = document.createElement(\"img\");\nimg.src = await result.dataUrl();\ndocument.body.append(img);\n```\n\nOr use an object URL:\n\n```js\nconst blob = await result.blob();\nconst img = document.createElement(\"img\");\nimg.src = URL.createObjectURL(blob);\ndocument.body.append(img);\n```\n\n`result.canvas` is still populated for animated results, but a canvas cannot play an animated GIF or APNG by itself. It is useful only as a first-frame/static preview. Use `result.blob()` or `result.dataUrl()` with an `\u003cimg\u003e` when you want the animation.\n\nYou can also provide SWF bytes directly:\n\n```js\nconst bytes = await file.arrayBuffer();\nconst result = await chroma.renderFromBytes(bytes, { sprite: \"chair\" });\n```\n\nSupported first-release options mirror the server endpoint where applicable:\n\n- `small` / `s`\n- `state`\n- `direction` / `rotation`\n- `color` / `colour`\n- `crop`\n- `bg` / `background`\n- `shadow`\n- `canvas`: a hex colour, `transparent`, or an HTTP/HTTPS image URL in the browser build. URL backgrounds must allow CORS pixel reads.\n- `addMode`: `\"overlay\"` by default. `\"overlay\"` uses Overlay glow, where ADD ink is returned as a separate layer and composited over the visible page background. `\"baked\"` uses Baked glow, where the renderer blends ADD into the output image. `\"none\"` disables ADD/glow layers completely and always uses the baked renderer path. `separateAdd` is still accepted as a low-level alias.\n- `icon`\n- `gif`: `false` by default for PNG output; `true` returns GIF bytes when the selected state has animation frames.\n- `apng`: `false` by default for PNG output; `true` returns APNG bytes. APNG is served as PNG-compatible `image/png`.\n- `format`: optional `\"png\"`, `\"gif\"`, or `\"apng\"` selector. `apng` wins if both animated formats are requested.\n- `loop`: `true` by default for animated output; set `false` to emit a non-looping GIF or APNG.\n\n## Render With The Java Library\n\nUse `chroma-lib` directly when you want to render from a normal JVM application instead of the browser WASM build or Spring webapp. The main entry point is `com.quackster.chroma.ChromaFurniture`.\n\n```java\nimport com.quackster.chroma.ChromaFurniture;\n\nimport java.nio.file.Files;\nimport java.nio.file.Path;\n\npublic class RenderFurniture {\n    public static void main(String[] args) throws Exception {\n        ChromaFurniture furni = new ChromaFurniture(\n                \"swfs/hof_furni/chair.swf\",\n                false,         // small furni\n                0,             // state\n                2,             // direction\n                -1,            // colour id, or -1 for default\n                true,          // render shadows\n                false,         // render bg.png background\n                \"transparent\", // canvas colour\n                true,          // crop image\n                false          // render icon\n        );\n\n        furni.run();\n        Files.write(Path.of(\"chair.png\"), furni.createImage());\n    }\n}\n```\n\n`run()` parses the SWF and writes extracted assets under `furni_export/\u003csprite\u003e`, so the process needs write access to the working directory. For animated output, call `createGif(true)` or `createApng(true)` after `run()` and write the returned bytes to a `.gif` or `.png` file.\n\n## Build The Spring Webapp\n\nThe existing server-side renderer can still be built with:\n\n```powershell\n.\\gradlew.bat :chroma-webapp:clean :chroma-webapp:bootJar\n```\n\nRun it with:\n\n```powershell\njava -jar chroma-webapp\\build\\libs\\chroma-webapp-1.0.0.jar\n```\n\nThe server listens on port `5000` and expects SWFs under `swfs/hof_furni`. Use `gif=true` for GIF output, `apng=true` or `format=apng` for APNG output, and `loop=false` for a non-looping animation.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fquackster%2Fchroma4j","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fquackster%2Fchroma4j","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fquackster%2Fchroma4j/lists"}