A Java graphics effects library inspired by the demoscene of the 80s and 90s. Produces animated images from pixel sources, tile maps, and chainable effects — with no dependency on any GUI toolkit.
| Effect | Method | Description |
|---|---|---|
| Horizontal scroll | scrollH(ParamInt offset) |
Shifts the source horizontally |
| Vertical scroll | scrollV(ParamInt offset) |
Shifts the source vertically |
| Bilinear zoom | zoom(ParamDouble factor) |
Zoom in/out centred on the Stage |
| Explicit-centre zoom | zoom(ParamDouble factor, ParamDouble cx, ParamDouble cy) |
Explicit centre |
| Rotation | rotate(ParamDouble angle) |
Clockwise rotation, Stage centre |
| Explicit-centre rotation | rotate(ParamDouble angle, ParamDouble cx, ParamDouble cy) |
Explicit centre |
Applied per pixel after sampling, before the transparent-colour test. Multiple transforms chain in declaration order.
| Effect | Method | Description |
|---|---|---|
| Palette cycling | exactCycling(int[] palette, ParamDouble phase) |
Rotates palette entries by exact ARGB match; non-palette pixels pass through |
| Fade to black | fade(ParamDouble t) |
t=0 identity → t=1 full black; values clamped to [0, 1] |
| Fade to colour | fade(ParamDouble t, int targetArgb) |
Same as above, fades toward an explicit ARGB colour |
| Negative | negative() |
Inverts R, G, B channels (255 - c); stateless |
| Greyscale | grayscale() |
BT.601 luma: (77R + 150G + 29B) >> 8; stateless |
| LUT remap | lut(int[] red, int[] green, int[] blue) |
Per-channel lookup table, 256 entries each; output masked to [0, 255] |
A sprite combines a PixelSource with a position, a depth, and an optional anchor point.
Position and depth are controlled via ParamDouble / ParamInt and can be updated between frames.
ParamDouble x = new ParamDouble(100);
ParamDouble y = new ParamDouble(80);
ParamInt depth = new ParamInt(0);
Sprite ship = new Sprite(new ImageSource(shipImage), x, y, depth);
// centred anchor:
Sprite bullet = new Sprite(new ImageSource(bulletImage), x, y, depth, 0.5, 0.5);A mutable container of sprites rendered back-to-front (painter's algorithm) into a Stage.
Lower depth values appear in front; equal depths are drawn in reverse insertion order
(first-added in front). A layer-level transparent colour (color key) can be set once and
applies to all sprites in the layer.
SpriteLayer layer = new SpriteLayer()
.transparentColor(0xFF00FF00); // color key: transparent green
layer.addSprite(ship);
layer.addSprite(bullet);
// Register in pipeline at the desired compositing position
EffectPipeline pipeline = new EffectPipeline()
.addSource(background)
.scrollH(bgScroll)
.addSpriteLayer(layer) // composited on top of the background
.build();
// Each frame — update params, then render
x.set(newX);
y.set(newY);
pipeline.render(stage);Sprites can be added or removed between frames; the layer content is mutable after build().
Sprites that extend partially outside the stage are clipped silently.
// 1. Pixel sources
TileSet tileSet = new TileSet(spriteSheet, 16, 16);
TileMap background = new TileMap(tileSet, 40, 30, TileMap.EdgePolicy.WRAP);
background.setTiles(0, 0, levelData);
ImageSource overlay = new ImageSource(overlayImage);
// 2. Mutable parameters (updated each frame)
ParamInt bgScroll = new ParamInt(0);
ParamInt fgScroll = new ParamInt(0);
ParamDouble fgZoom = new ParamDouble(1.0);
// 3. Pipeline (built once, reused every frame)
EffectPipeline pipeline = new EffectPipeline()
.addSource(background)
.scrollH(bgScroll)
.addSource(overlay)
.transparentColor(0xFF00FF00) // color key: transparent green
.scrollH(fgScroll)
.zoom(fgZoom)
.build();
// 4. Stage (output buffer)
Stage stage = new Stage(640, 480); // black background by default
// 5. Animation loop
while (running) {
bgScroll.add(1);
fgScroll.add(3);
pipeline.render(stage);
// Display — your choice of toolkit
Graphics g = canvas.getGraphics();
g.drawImage(stage.getImage(), 0, 0, null);
}PixelSource ← interface: any pixel source
├── ImageSource ← wraps a BufferedImage
└── TileMap ← tile grid (WRAP / CLIP / FEED)
└── TileSet ← spritesheet split into fixed-size tiles
ParamInt ← mutable discrete parameter (pixel offset, ...)
ParamDouble ← mutable continuous parameter (zoom factor, angle, ...)
EffectPipeline ← effect chain — stateless, renders into a Stage
Stage ← 32-bit ARGB pixel buffer — output to the toolkit of your choice
StagePool ← pool of N stages for double/triple buffering
Orchestrator ← render loop on a dedicated thread, paced at targetFps
FrameCallback ← interface: callback invoked once per frame before rendering
PerformanceSampler ← optional tumbling-window accumulator for FPS and render-time stats
- Portable — no dependency on Swing, JavaFX, or SWT. The output is a
BufferedImage, displayable in any toolkit or exportable to a file. - Stateless — the pipeline is built once, reused every frame.
Only
ParamInt/ParamDoublevalues change between frames. - Direct array access — pixels are read and written via the underlying
int[]array (DataBufferInt), bypassinggetRGB/setRGB. - Bilinear by default — zoom and rotation use bilinear interpolation. The integer path (pure scroll) short-circuits interpolation.
For each pixel (x, y) of the Stage:
- Initial value:
stage.getBackgroundColor()(opaque black by default) - For each layer, in order:
- Transforms compute the source coordinate
(sx, sy) - If the source is bounded (
CLIP/FEED) and(sx, sy)is out of bounds → no write - The source pixel is sampled (bilinear interpolation for fractional coordinates)
- Colour transforms are applied in declaration order (
exactCycling,fade,negative,grayscale,lut) - If the result matches the layer's
transparentColor→ no write - Otherwise → the result overwrites the current Stage value
- Transforms compute the source coordinate
| Value | Out-of-bounds behaviour |
|---|---|
WRAP |
Coordinates wrapped modulo the source dimensions (Math.floorMod) |
CLIP |
IndexOutOfBoundsException — the pipeline never crosses the boundary |
FEED |
Same runtime behaviour as CLIP — signals semantically that tiles are fed dynamically |
Examples are located in fr.dufrenoy.imagefx.examples. Each is a standalone
fullscreen application (AWT, no Swing). Press SPACE to quit.
| Class | Effect | Image |
|---|---|---|
FleursDemoExample |
Continuous rotation + sinusoidal zoom (mandala effect) | flowers.jpg |
PaysageDemoExample |
Multi-directional scrolling on a 3:2 Lissajous curve | landscape.png |
ShadowDemo |
5-layer parallax inspired by Shadow of the Beast — gradient sky + moon, clouds, ochre rock spires, slate menhirs | generative |
PlatformDemo |
Platformer level — fixed desert backdrop, tile map (2×2 screens), simulated player with gravity and scripted jumps | cloudsinthedesert.png + tiles_spritesheet.png |
FractalColorDemo |
6 colour effects cycling every 5 s (negative, greyscale, fade, solarize LUT, palette cycling) with continuous rotation + zoom | fractal_rainbow_swirl.jpg |
Build the self-contained demo JAR (includes all resources):
mvn package -P demos -DskipTestsThen run any demo:
java -jar target/denise4j-demos.jar shadow
java -jar target/denise4j-demos.jar paysage
java -jar target/denise4j-demos.jar fleurs
java -jar target/denise4j-demos.jar platform
java -jar target/denise4j-demos.jar fractalPress SPACE to quit any demo.
Pre-built binaries are published on the Releases page.
<dependency>
<groupId>fr.dufrenoy.imagefx</groupId>
<artifactId>denise4j</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency># Compile and run tests
mvn clean install
# Run tests only
mvn test
# Formal JML verification (requires OpenJML — Linux/macOS)
mvn verify -P openjml-unix
# Formal JML verification (Windows via WSL)
mvn verify -P openjml-windowsRequirements: Java 11+, Maven 3.8+
- Positioned objects — iteration 1:
Sprite+SpriteLayer(basic positioning, depth sort, clipping, transparent colour) - Positioned objects — iterations 2–4: animation, transforms, composite trees, copper/raster bars
- Deformations (wave, distortion, tunnel, plasma)
- Advanced compositing (blending, masking, overlay)
GNU Lesser General Public License v3 — see LICENCE.