Skip to content

Generating colored Julia sets in the browser using wasm.

Notifications You must be signed in to change notification settings

d2718/jset-wasm

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

27 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

jset-wasm

Generating colored Julia sets in the browser using wasm.

Use

To draw a trippy "fractal" on an HTML <canvas> using this module requires the following steps:

  • Load the wasm module into your Javascript script. If you want it to signal to the browser when it panics, expose your panic singnalling script to the pnk() function, like so:
    let wasm_mod = {};
    WebAssembly.instantiateStreaming(
        fetch("http:// ... /jset_wasm.wasm"),
        {
            "env": {
                "pnk": your_panic_alert_function,
            },
        }
    ).then(obj => wasm_mod = obj);
  • Call set_gradient() for each color gradient in your trippy fractal. For example, to have it fade from black to blue to cyan to white to black again:
//                   |-start color--|  |-end color----|
// args: gradient #, red, green, blue, red, green, blue, # of steps
wasm_mod.instance.exports.set_gradient(0,   0,   0,   0,   0,   0, 255, 256);
wasm_mod.instance.exports.set_gradient(1,   0,   0, 255,   0, 255, 255, 256);
wasm_mod.instance.exports.set_gradient(2,   0, 255, 255, 255, 255, 255, 256);
wasm_mod.instance.exports.set_gradient(3, 255, 255, 255,   0,   0,   0, 256);
  • Call set_n_gradients() to inform the module of the number of gradients.
wasm_mod.instance.exports.set_n_gradients(4); // We set gradients 0-3 above.
  • Call update_color_map() to make a color map out of the gradient information you have set.
wasm_mod.instance.exports.update_color_map();
  • If you want to use the polynomial iterator, specify the polynomial function to use with set_coeff() to set each coefficient and to tell the module how many coefficients you want to use set_n_coeffs().
    // Specify iterator as f(z) = z^2 + 0.7e^(i0.63π)

    // Calculate the real and imaginary parts of the constant term from
    // its polar coordinates:
    const re_part = 0.7 * Math.cos(0.63 * Math.PI);
    const im_part = 0.7 * Math.sin(0.63 * Math.PI);

    wasm_mod.instance.exports.set_coeff(0, re_part, im_part);
    wasm_mod.instance.exports.set_coeff(1, 0, 0);
    wasm_mod.instance.exports.set_coeff(2, 1.0, 0);
    wasm_mod.instance.exports.set_n_coeffs(3);

Be careful with the polynomial iterator. While the Mandlebrot iterator is guaranteed to produce an interesting image, most sets of polynomial coefficients will not. The one specified in the example above will, so if you want quick results, start with that, and make small incremental changes until you have something you like. Keep your coefficients small, especially the higher-degree ones.

  • Call redraw() with the appropriate image parameters to churn through all the calculations and write image data to the exposed IMAGE buffer.
    wasm_mod.instance.exports.redraw(
        xpix,   // width of canvas in pixels
        ypix,   // height of canvas in pixels
        x,      // real coordinate of upper-left-hand corner of image
        y,      // imaginary coordinate of upper-left-hand corner of image
        width,  // width of image on the Complex plane
        poly_p, // boolean indicating whenther to use the polynomial iterator
        smooth  // desired intensity of smoothing (from 0.0 to 1.0)
    );
  • Wrap the IMAGE buffer in a Uint8ClampedArray, wrap it in an ImageData object, and put it in the <canvas>'s "2d" context.
    const data = new ImageData(
        new UInt8ClampedArray(
            wasm_mod.instance.exports.memory.buffer,
            wasm_mod.instance.exports.BUFFER.value,
            4 * xpix * ypix     // 4 bytes of data per pixel
        ),
        xpix                    // image width in pixels
    );
    const canvas = document.getElementById("your-canvas");
    canvas.getContext("2d").putImageData(data, 0, 0);

The most time-consuming step is the call to redraw()--it's the one that iterates a complex value associated with each pixel until it either diverges or runs out of color map. If you don't need to redo the iteration, but just want to recolor the image, you can use the exposed function recolor():

wasm_mod.instance.exports.set_gradient(0, 0,   0, 0,   0, 255,   0, 128);
wasm_mod.instance.exports.set_gradient(0, 0, 255, 0,   0,   0, 255, 256);
wasm_mod.instance.exports.set_gradient(0, 0, 255, 255, 0,   0,   0, 256);
wasm_mod.instance.exports.set_n_gradients(3); // We just specified 3 gradients.

wasm_mod.instance.exports.recolor(); // <-- This call right here.

// Then we need to shove the data into the <canvas> again.
const data = new ImageData(
    new UInt8ClampedArray(
        wasm_mod.instance.exports.memory.buffer,
        wasm_mod.instance.exports.BUFFER.value,
        4 * xpix * ypix
    ),
    xpix
);
const canvas = document.getElementById("your-canvas");
canvas.getContext("2d").putImageData(data, 0, 0);

There is one caveat here: If you specify a new color map that is longer than the old color map (that is, the sum of the number of shades in the collection of gradients is larger), it won't display properly until you call redraw() again. I don't think it's possible to fix this while keeping the recolor() call fast. This has been changed so that if the color map is extended, the iterator will make another pass through the image, but only operate on the points that would have iterated off the end of the old color map. I think this is a good compromise; it keeps recoloring relatively fast unless you have a lot of slow-diverging (or non-diverging) points in the image.

Plans

All the core functionality is now implemented.

  • user-specifiable polynomial iteration done!
  • user-specifiable color map done!
  • user-specifiable "default" color (the one used when the iterator runs out of colormap before a point diverges)
  • perhaps some type of smoothing, blurring, or downsampling I added a parameter for a very simple smoothing pass. It's not as bad as I though it might have been; it's very subtle, even at full intensity.
  • improved UI of the web interface
    • drag-resizable canvas
    • better ergonomics/visuals for entering polynomial coefficients
    • fixed-size controls <div>
    • sucking less on mobile