- Messages
- 126
- Reaction score
- 189
Chroma4j 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.
This project is originally a port of this project which is written in C# and renders .png files of furniture .swf files.
Requirements
- JDK 17 or newer.
- Gradle 8.7 or newer, or use the checked-in Gradle wrapper.
- Node.js for browser JavaScript syntax checks run by the Gradle verification task.
- A modern browser with WebAssembly GC support.
The 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.
Downloads
You can download prebuilt releases from the GitHub Releases page:
Download Chroma4j from GitHub Releases
Source code: https://github.com/Quackster/Chroma4j
The WASM release package includes the browser files needed to run the static demo locally or host it on a static web server.
Preview
Build The WASM Release
From the repository root:
.\gradlew.bat :chroma-wasm:clean :chroma-wasm:buildWasmGC
This compiles the TeaVM module and writes:
- web/dist/wasm-gc/chroma-wasm.wasm
- web/dist/wasm-gc/chroma-wasm.wasm-runtime.js
The static deployment directory contains:
- web/dist/index.html
- web/dist/main.js
- web/dist/chroma4j.js
- web/dist/styles.css
- web/dist/bg.png
The ADD overlay test page lives in web/dist-add-overlay-test and imports the WASM release from web/dist.
web/dist is generated/served output and is ignored by Git.
Run The Static Demo
Serve
web/dist with any static HTTP server:(Requires Python for to test the demo, or use the Node.js
Code:
serve
Code:
python -m http.server 5177 --directory web/dist
Open:
Code:
http://localhost:5177
Paste 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.To try the ADD overlay demonstration, serve the
web directory and open:
Code:
python -m http.server 5178 --directory web
Code:
http://localhost:5178/dist-add-overlay-test/
JavaScript API
JavaScript:
import { loadChroma4j } from "./chroma4j.js";
const chroma = await loadChroma4j();
const result = await chroma.renderFromUrl("https://example.com/hof_furni/chair.swf", {
state: 0,
direction: 2,
color: 0,
crop: true,
canvas: "transparent"
});
document.body.append(result.canvas);
const png = await result.blob();
PNG 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.To paint PNG output into an existing canvas:
JavaScript:
const canvas = document.querySelector("#preview");
const result = await chroma.renderFromUrl("https://example.com/hof_furni/chair.swf", {
state: 0,
direction: 2
}, canvas);
console.log(result.canvas === canvas); // true
To show the PNG in a normal
<img>:
JavaScript:
const img = document.createElement("img");
img.src = await result.dataUrl();
document.body.append(img);
To render an animated GIF, pass
gif: true:
JavaScript:
const result = await chroma.renderFromUrl("https://example.com/hof_furni/rare_dragonlamp.swf", {
state: 1,
direction: 4,
color: 0,
crop: true,
canvas: "transparent",
gif: true,
loop: true
});
console.log(result.format); // "gif"
console.log(result.mime); // "image/gif"
console.log(result.isAnimated); // true when the selected state has multiple frames
To render animated PNG instead, pass
apng: true or format: "apng":
JavaScript:
const result = await chroma.renderFromUrl("https://example.com/hof_furni/rare_dragonlamp.swf", {
state: 1,
direction: 4,
apng: true,
loop: true
});
console.log(result.format); // "apng"
console.log(result.mime); // "image/png"
Use an
<img> for animated GIF or APNG playback:
JavaScript:
const img = document.createElement("img");
img.src = await result.dataUrl();
document.body.append(img);
Or use an object URL:
JavaScript:
const blob = await result.blob();
const img = document.createElement("img");
img.src = URL.createObjectURL(blob);
document.body.append(img);
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 <img> when you want the animation.You can also provide SWF bytes directly:
JavaScript:
const bytes = await file.arrayBuffer();
const result = await chroma.renderFromBytes(bytes, { sprite: "chair" });
Supported first-release options mirror the server endpoint where applicable:
small/sstatedirection/rotationcolor/colourcropbg/backgroundshadowcanvas: a hex colour,transparent, or an HTTP/HTTPS image URL in the browser build. URL backgrounds must allow CORS pixel reads.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.separateAddis still accepted as a low-level alias.icongif:falseby default for PNG output;truereturns GIF bytes when the selected state has animation frames.apng:falseby default for PNG output;truereturns APNG bytes. APNG is served as PNG-compatibleimage/png.format: optional"png","gif", or"apng"selector.apngwins if both animated formats are requested.loop:trueby default for animated output; setfalseto emit a non-looping GIF or APNG.
Render With The Java Library
Use
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.
Java:
import com.quackster.chroma.ChromaFurniture;
import java.nio.file.Files;
import java.nio.file.Path;
public class RenderFurniture {
public static void main(String[] args) throws Exception {
ChromaFurniture furni = new ChromaFurniture(
"swfs/hof_furni/chair.swf",
false, // small furni
0, // state
2, // direction
-1, // colour id, or -1 for default
true, // render shadows
false, // render bg.png background
"transparent", // canvas colour
true, // crop image
false // render icon
);
furni.run();
Files.write(Path.of("chair.png"), furni.createImage());
}
}
run() parses the SWF and writes extracted assets under furni_export/<sprite>, 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.Build The Spring Webapp
The existing server-side renderer can still be built with:
Code:
.\gradlew.bat :chroma-webapp:clean :chroma-webapp:bootJar
Run it with:
java -jar chroma-webapp\build\libs\chroma-webapp-1.0.0.jar
The 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.