Code Monkey home page Code Monkey logo

canvas-sketch's Introduction

canvas-sketch

canvas-sketch is a loose collection of tools, modules and resources for creating generative art in JavaScript and the browser.

example of canvas-sketch running in Chrome

Quick Start with Node.js & npm

To jump directly into canvas-sketch, try the following terminal commands with [email protected] and [email protected] or newer:

# Make a new folder to hold all your generative sketches
mkdir my-sketches

# Move into that folder
cd my-sketches

# Scaffold a new 'sketch.js' file and open the browser
npx canvas-sketch-cli sketch.js --new --open

💡 Notice the x in npx, and the -cli in canvas-sketch-cli

Now, while in the browser, hit Cmd + S or Ctrl + S to export a high-resolution PNG of your artwork to your ~/Downloads folder.

More Commands

Some other commands to try:

# Start the tool on an existing file and change PNG export folder
npx canvas-sketch-cli src/foobar.js --output=./tmp/

# Start a new sketch from the Three.js template
npx canvas-sketch-cli --new --template=three --open

# Build your sketch to a sharable HTML + JS website
npx canvas-sketch-cli src/foobar.js --build

# Develop with "Hot Reloading" instead of full page reload
npx canvas-sketch-cli src/foobar.js --hot

For more features and details, see the Documentation.

Installation Guide

The examples above use npx which is a convenient way to install and run a local CLI tool, but you might want to setup canvas-sketch as a global command. You can see more details in the Installation Guide.

Code Example

Once you have the CLI tool running, you can try this example of an A4 print artwork.

const canvasSketch = require('canvas-sketch');

// Sketch parameters
const settings = {
  dimensions: 'a4',
  pixelsPerInch: 300,
  units: 'in'
};

// Artwork function
const sketch = () => {
  return ({ context, width, height }) => {
    // Margin in inches
    const margin = 1 / 4;

    // Off-white background
    context.fillStyle = 'hsl(0, 0%, 98%)';
    context.fillRect(0, 0, width, height);

    // Gradient foreground
    const fill = context.createLinearGradient(0, 0, width, height);
    fill.addColorStop(0, 'cyan');
    fill.addColorStop(1, 'orange');

    // Fill rectangle
    context.fillStyle = fill;
    context.fillRect(margin, margin, width - margin * 2, height - margin * 2);
  };
};

// Start the sketch
canvasSketch(sketch, settings);

When exporting the image in browser with Cmd + S or Ctrl + S keystrokes, the saved PNG file matches 21 x 29.7 cm at 300 DPI, and can be printed with archival ink on quality paper.

Resulting image looks something like this:

Note: The above PNG file has been scaled/optimized for web.

Roadmap

There are many features still outstanding, such as:

  • API & CLI Docs
  • Easy & beginner-friendly examples
  • Website/frontend
  • HUD/GUI controls
  • "Gallery Mode" for viewing many local sketches
  • External Module for utilities (randomness, geometry, etc)
  • Unit tests
  • More??

License

MIT, see LICENSE.md for details.

canvas-sketch's People

Contributors

alvinometric avatar alvinsight avatar camilleroux avatar debone avatar dlespiau avatar guidoschmidt avatar huntercaron avatar jckw avatar jinksi avatar jonobr1 avatar jzting avatar kellymilligan avatar makio135 avatar mattdesl avatar pbeshai avatar rachelnicole avatar stombeur avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

canvas-sketch's Issues

Determine mouse position on canvas

Hi Matt.

I was wondering whether there is a built-in way to determine the mouse position on the canvas -- so far I haven't spotted anything along those lines in the docs or examples.

FWIW, here's an example sandbox how I currently do this.

More generally, I am not sure how to best create a reusable "plugin". As you can see, I am extending render props -- not sure this is necessarily the best idea (might clash with "official" props that you might add in the future). Any recommendations?

Thanks,
Markus

Throws error when using `canvas-sketch --new --open`

config:
❯ node -v
v11.0.0

❯ npm -v
6.4.1

the error is :

❯ canvas-sketch --new --open
internal/modules/cjs/loader.js:589
    throw err;
    ^

Error: Cannot find module './clone.js'
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:587:15)
    at Function.Module._load (internal/modules/cjs/loader.js:513:25)
    at Module.require (internal/modules/cjs/loader.js:643:17)
    at require (internal/modules/cjs/helpers.js:22:18)
    at Object.<anonymous> (/Users/magops/.nvm/versions/node/v11.0.0/lib/node_modules/canvas-sketch-cli/node_modules/graceful-fs/graceful-fs.js:4:13)
    at Module._compile (internal/modules/cjs/loader.js:707:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:718:10)
    at Module.load (internal/modules/cjs/loader.js:605:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:544:12)
    at Function.Module._load (internal/modules/cjs/loader.js:536:3)

Export Settings: add ability to use a function

name, suffix and prefix could potentially benefit from being computed on export similarly to file.

Use case: user try to exports with a new preset name as suffix.

let myCurrentGUIPreset = "preset1";

const settings = {
  // ...
  suffix: () => myCurrentGUIPreset,
}

const saveNewPreset = () => {
  myCurrentGUIPreset = "preset2";
}

saveNewPreset();

Probably related to GUI/HUD.

Is that something I could do with manager.update(newSettings)?

Automatically add .js extension when creating new sketch

A couple of times I've forgotten to include the .js and have had to rename my file afterward and restart the canvas-sketch server. Would be nice if the CLI automatically added it for you if not provided.

For example, I wrote:

canvas-sketch sketches/molnar-quads --hot --new --output=output/

Then rename to molnar-quads.js, kill the server, start again as:

canvas-sketch sketches/molnar-quads.js --hot --output=output/

(btw I'm happy to submit PRs for any of the issues I've submitted if you think they're worthwhile!)

p5js example should demonstrate accessing p5 in setup

In the p5js examples, there are comments indicating where the equivalent of setup is:

canvasSketch(() => {
  // Inside this is a bit like p5.js 'setup' function
  // ...

At least for the instanced version, you need access to the p5 instance there, but it's not clear how to get it from the current example. Updating the code to have:

canvasSketch(({ p5 }) => {
  // Inside this is a bit like p5.js 'setup' function
  // ...

would help clarify it.

Glslify breaks when used alongside async / await

If glslify is used in the same file as a sketch with async / await, it will fail to compile the shaders. This is a glslify issue but will leave this ticket open until it’s fixed upstream.

FPS rate only works if 'playbackRate' is set to throttle

I was trying to force a lower FPS by setting the property fps: 4 in my settings (as shown in the docs https://github.com/mattdesl/canvas-sketch/blob/master/docs/animated-sketches.md) but I couldn't make it work unless I also defined the playbackRate to throttle.

I dug a bit in the source code and I found that without the playbackRate the fps value was never taken into account.

} else if (playbackRate === 'throttle') {

I don't know if it should be set to throttle as a default but I think it should be mentioned in the docs :)

Thanks for the library btw 💙

Rendering differences in export and browser on high DPI monitor

👋 Thanks for your work on this, it looks promising! I wanted to report an issue I noticed when working with a high dpi canvas. Particularly, I noticed some rendering differences when viewing in the browser vs when exporting on my high DPI screen (Macbook Pro Retina). Here's the sample sketch:

const canvasSketch = require('canvas-sketch')

const settings = {
  dimensions: [500, 500]
}

const sketch = () => {
  return ({context: ctx, width, height}) => {
    ctx.fillStyle = 'white'
    ctx.strokeStyle = 'black'
    ctx.lineWidth = 0.5

    const rings = width / 50
    const step = 25
    for (let i = 0; i < rings; i++) {
      const size = width - step * i * 2
      ctx.rect(step * i, step * i, size, size)
      ctx.stroke()
    }
  }
}

canvasSketch(sketch, settings)

What it looks like in the browser (Chrome)

Note that the line width is much larger than what would typically be considered a lineWidth of 0.5.
Also the color fades towards the center, even though strokeStyle remains constant.

screen shot 2018-08-10 at 10 29 40 pm

What the export looks like

When doing a ⌘+S save, the stroke color appears to be correct, but the line width still looks a bit too thick.

2018 08 10-22 25 07

Hijacking the pixel ratio

The issue appears to be related to how the style attribute is calculated. Typically I've used something like the function below to set a canvas to "high dpi mode." You can call this function at the top of your sketch function to apply it.

function hdpi(canvas, ctx, width, height) {
  canvas.width = width * devicePixelRatio
  canvas.height = height * devicePixelRatio
  canvas.style.width = `${width}px`
  canvas.style.height = `${height}px`
  ctx.scale(devicePixelRatio, devicePixelRatio)
}

The main difference is that that x is always devicePixelRatio * x where x is width or height. When applying this method to a sketch, I get a render closer to what I would expect.

screen shot 2018-08-10 at 10 41 39 pm

Though, the save file is now 2x the desired size and you can't resize the browser window because the hijack is lost.

2018 08 10-22 43 19

The core issue appears to be that the width and height attribute should always be 2x (devicePixelRatio) of the style width and height for an accurate representation on high DPI monitors.

<canvas width="500" height="500" style="width: 500px; height: 500px;"></canvas>

Auto-Installing Modules

I’d like to unify auto-installation across startup & during development so the user doesn’t need to ‘npm install’. This would also cover glsl deps hopefully.

One concern is that sometimes you will type the wrong package name in require and suddenly you end up pulling in a package you weren’t expecting, maybe even something that causes issues with your machine.

Simple way of supporting auto install: Any time a missing package is found, it gets auto installed and saved as a dependency.

Another, maybe “safer” way: always confirm with the user before installing dependencies. On startup, confirmation would have to happen in the terminal. During dev when a new module is found, confirmation could happen through a browser page with a button to “Install Missing Dependencies”.

I’m leaning toward KISS and just doing the automatic way, but allowing a CLI flag to disable auto installation for more seasoned devs.

Export a sketch's canvas element/context

Hey!

Digging into some sketches using THREE.js, I was thinking of using another canvas as a texture uniform sent in a RawShaderMaterial. Basically would like to draw some text on a canvas and be able to use it in the current sketch.

Exporting a sketch's canvas element would maybe make this possible?

const canvasSketch = require('canvas-sketch')
const anotherSketch = require('./another-sketch')

const currentSketch = ({ context }) => {
  // set up THREE.js renderer, etc...
  // then grab the anotherSketch's canvas and create a texture
  const texture = new THREE.Texture(anotherSketch.canvas)
  // etc...

  return {
    resize ({ pixelRatio, viewportWidth, viewportHeight }) {
      anotherSketch.resize({ pixelRatio, viewportWidth, viewportHeight })
    },
    render ({ time, deltaTime }) {
      anotherSketch.render({ time, deltaTime })
    }
  }
}

canvasSketch(currentSketch, settings)

Would be awesome to take advantage of this module and not having to re-create another canvas and set-up resize events. Maybe having another option in the settings of the sketch to prevent the canvas to be added to the body and functions to be called automatically?
Cheers!

[cli] MP4 utility – loss of color richness

Not sure what's going on but I've noticed a pretty significant loss of colour richness when converting frames into MP4. I'm not sure if this is some limitation of the H264 but it seems like it should be possible to get just as rich colours as with GIF.

For reference:

problem

Paper Size Presets

Something I hate doing is googling for common sizes and converting to/from various units, so I think it could be handy to have a simple way of starting from a 'preset'.

const canvasSketch = require('canvas-sketch');

const settings = {
  ...canvasSketch.preset('A4')
};

Which leads to { units: 'cm', dimensions: [ 21, 29.7 ] }.

Could support:

  • A1-8, B, C, D...
  • letter
  • legal
  • tabloid
  • postcard (4x6")
  • business-card
  • poster (18x24" according to Photoshop and this random site)

The object can be merged in with your settings using ... or Object.assign({}), and/or the function could also accept other values and merge them into the result:

const settings = canvasSketch.preset('a4', {
  animate: true, // a looping print
  units: 'in', // if you want to work in inches
});

If you pass in units it will do the conversion for you, so you can work in your preferred unit.

Could also support some web defaults like:

  • 1080p, 720p, 480p, 320p, 240p

Which returns { units: 'px', dimensions: [ x, y ] }.

Questions:

  • Is this too much scope creep? I don't think so since the tool is aiming to be an all-around convenience for generative art and this seems like a common task.
  • Is the above syntax clean/easy or is there another way the function should be imported and used?

project manager

Hey 👋 ,

I really like the project but was wondering if there has been any talk about adding a GUI to view projects without having to restart the server. I have used https://github.com/chiunhau/p5-manager and I think that I can whip something like it but I wasn't sure if there are plans for it already underway. Ideally the user would also be able to keep the server running and build either the project manager and your projects or just the stand alone project.

Any thoughts?

Exporting Multiple Dimensions

I am working on a print series and realizing I probably want to give buyers the option between multiple sizes, rather than forcing them to a single size. I'm also realizing I usually export multiple sizes: print, Behance, IG, Twitter sometimes all get slightly different sizes, sometimes working in inches or in pixels.

Will have to think about syntax... but should be possible so that Cmd + S will save all sizes in a single go.

EDIT: Just thinking, maybe this could somehow be done via a UI or somehow without code changes. I do not really want to associate exact combination of sizes with a git commit, because some time later I may revisit the same artwork to re-print it at a different size, and I would like to be able to just git checkout that SHA and re-export without tweaking any code.

Settings: prefix and suffix

I just tried exporting with a custom filename, as well as prefix and suffix as you described in Exporting media and found them missing in the exported *.png file. As a first shot, I got it working by adding the properties in the assign statement at line 182 in index.js. If that'll be ok for you, @mattdesl I'd happy investigate the package a bit further and create a PR? 🤔

TypeError: promisify is not a function

Hey Matt! After completing the last installation step:

# Scaffold a new 'sketch.js' file and open the browser
canvas-sketch sketch.js --new --open

I get the following Error:

/usr/local/lib/node_modules/canvas-sketch-cli/src/util.js:10
const readFile = promisify(fs.readFile);
^

TypeError: promisify is not a function
at Object. (/usr/local/lib/node_modules/canvas-sketch-cli/src/util.js:10:18)
at Module._compile (module.js:571:32)
at Object.Module._extensions..js (module.js:580:10)
at Module.load (module.js:488:32)
at tryModuleLoad (module.js:447:12)
at Function.Module._load (module.js:439:3)
at Module.require (module.js:498:17)
at require (internal/module.js:20:19)
at Object. (/usr/local/lib/node_modules/canvas-sketch-cli/src/index.js:14:66)
at Module._compile (module.js:571:32)

Not sure why?

I completed the first three steps:

# Install the CLI tool globally
npm install canvas-sketch-cli -g

# Make a new folder to hold all your generative sketches
mkdir my-sketches

# Move into that folder
cd my-sketches

Floating Playback Controls

There has been some suggestions to build a mini floating HUD to control the sketch – see #8. This could be useful in the interim until a more complete GUI system is added (see #20).

It would probably need to handle the following:

  • Buttons to export a single frame and start/stop recording a frame sequence
  • Button to play/pause loop
  • Button to stop loop (going back to frame 0)
  • Button to git commit & export (Ctrl + K)
  • Skip to first frame / last frame
  • Skip forward / backward by one frame

This seems like a good idea. One potential issue is that canvas-sketch is designed to work with multiple instances of itself (for example – if you are building a blog post with many canvas elements throughout), whereas HUD generally implies a singleton design. One solution might be to absolutely position the HUD just underneath the canvas element (or in the corner if the canvas fills the page) so each can be controlled independently.

If you'd like to help contribute, I think what would be most useful is posting different sketches and ideas in terms of the UX, layout and design of this sort of HUD/toolbar.

If it is floating below the canvas, it could be great to show a little ruler with ticks and dimensions (in user units, like inches) along the width and height of the canvas.

Typescript/Flow support

After having used this tool for a bit, I'm starting to really miss the type-safety I'm used to on other projects.

I definitely think the default should be vanilla JS, since it's much more familiar and lower-friction, but I wonder if it'd be worth setting it up to transpile Typescript or Flow? I think ideally, the user experience wouldn't change at all, except for it processing .ts files as well as .js.

(I've actually never used Typescript, I've used Flow extensively... but TS is much more popular than Flow, so it probably makes more sense to focus on that)

Saving triggers canvas to re-draw above current artwork?

I'm not quite sure if this is me using the tool in the wrong way — I am just working my way through https://generativeartistry.com and it seems that when I save, the artwork gets redrawn on top of the previous canvas, then saves this double-exposure.

Is it something to do with the for loops included in the return?

My code is as follows:

const canvasSketch = require("canvas-sketch");
const { lerp } = require("canvas-sketch-util/math");
const random = require("canvas-sketch-util/random");

const settings = {
  dimensions: [1000, 1618]
};

const sketch = () => {
  const step = 50;
  const lines = [];
  return ({ context, width, height }) => {
    context.fillStyle = "white";
    context.fillRect(0, 0, width, height);
    context.lineWidth = 10;

    // Create the lines
    for (let i = step; i <= height - step; i += step) {
      const line = [];
      for (let j = step; j <= width - step; j += step) {
        const offset = Math.random() * 50;
        const point = { x: j, y: i + offset };
        line.push(point);
      }
      lines.push(line);
    }
    console.log(lines);
    // Draw the lines
    for (let i = 0; i < lines.length; i++) {
      console.log("why");
      context.beginPath();
      context.moveTo(lines[i][0].x, lines[i][0].y);
      console.log(lines[i].length);
      for (let j = 0; j < lines[i].length; j++) {
        context.lineTo(lines[i][j].x, lines[i][j].y);
      }
      context.stroke();
    }
  };
};

canvasSketch(sketch, settings);

canvasSketch is not a function

When trying to run the basic scaffolding code:

const canvasSketch = require('canvas-sketch');

const settings = {
  dimensions: [ 2048, 2048 ]
};

const sketch = () => {
  return ({ context, width, height }) => {
    context.fillStyle = 'white';
    context.fillRect(0, 0, width, height);
  };
};

canvasSketch(sketch, settings);

I get this error in Firefox dev 62.0b11:

TypeError: canvasSketch is not a function[Learn More] sketch.js:15:1
[1]<
http://192.168.0.173:9966/sketch.js:15:1
o
http://192.168.0.173:9966/sketch.js:1:257
r
http://192.168.0.173:9966/sketch.js:1:431
<anonymous>
http://192.168.0.173:9966/sketch.js:1:2

and in Chrome:

Uncaught TypeError: canvasSketch is not a function
    at Object.1.canvas-sketch (sketch.js? [sm]:14)
    at o (_prelude.js:1)
    at r (_prelude.js:1)
    at _prelude.js:1

Node - v8.9.3
npm - 6.1.0
macOS - 10.13.6

Any thoughts?

Canvas context cleared before running async code

const sketch = () => {
  return ({ context: ctx, width, height }) => {
    ctx.fillStyle = 'red';

    setTimeout(() => {
      console.log('I expect red');
      ctx.fillRect(200, 200, 200, 200);
    }, 100);
  };
};

I have the above code. I was expect to see a red rect drawn to the screen after 100ms, however it was instead black.

It looks like we call canvas.restore during postRender here:
https://github.com/mattdesl/canvas-sketch/blob/master/lib/core/SketchManager.js#L355
which results in my setTimeout callback no longer having the expected context state.

I'm not familiar enough with all the usage scenarios to understand if/why the canvas.restore is necessary here, but I wonder if it could at least be made configurable.

Another option might be to just make it so that our sketch method returns a promise in this case and SketchManager doesn't call postRender until the promise has resolved. Not sure if makingSubmit async would lead to additional challenges further down the line.

polylinesToSVG accepts only lines?

Hey Matt,

I'm looking to generate pen plotter art files that includes arcs, but I must be missing something in your two --penplot examples. Both generate arrays of coordinate arrays that are passed to your polylinesToSVG() helper function. Looking at your penplot.js in canvas-sketch-util, it looks like here you just iterate through each coordinate pair and generate the relevant svg tags. Those are then handed down into a pre-baked svg template here that only allows <path ... > elements

I'm going to play around with implementing this myself, but it seems like with the relatively straightforward mapping from canvas to svg arcs, maybe #L56 earlier could be made more flexible to include <circle> svg tags without much hassle?

context.arc(375, 252, 75)

<circle cx="375" cy="252" r="75" style="fill:rgb(235,235,235);"/>

Cannot start canvas-sketch-cli: SyntaxError

Hey Matt! Wanted to give this a try, but I'm getting a SyntaxError when running canvas-sketch sketch.js --new --open — I just followed the install steps in the README and ran into this issue.

screen shot 2018-09-28 at 3 45 28 pm

Not sure if I missed something 😅

Web version

It would be kind of cool to have a version that could run and be edited on the web, either as it's own little editor thing or as something that could easily be used with things like codepen, etc.

I think imports and exporting would be the most challenging parts of that?

But otherwise, hi! I'm a web developer looking for things to build experience and my portfolio. I would love to help with canvas-sketch, especially for a project website :).

Possibly outdated example?

In the canvas-penplot.js example, a utility is imported, exportPolylines, and used in a return array (https://github.com/mattdesl/canvas-sketch/blob/master/examples/canvas-penplot.js#L28) to generate an SVG. Another example, pen-plotter-patchwork.js, imports it from canvas-sketch-util/penplot.

I tried doing this in my own project, but the import didn't resolve. But, I soon realized that it was OK, since I was indeed getting an SVG when saving the file. So I'm guessing this utility isn't needed, it happens automatically?

I'm happy to open a PR to update the examples, but I wanted to solidify my understanding first. Are SVGs always generated, or does it have to be returning a format compatible with SVG rendering, like polylines?

EDIT: just saw the relevant docs, exporting-artwork.md. It answers some of my questions... although my work is getting an SVG without specifying an encoding in settings, so it's still not clear to me what the conditions are for SVG output.

Setting for changing the background color of the window

As far as I can tell, there is currently no setting to change the background color of the web page that the canvas sits on. I found myself wanting a darker background when working on a canvas with a dark bg as opposed to the default white. It's fairly easy to work around, but could be a handy option to have available.

My workaround:

const settings = {
   ...
};
document.body.style.background = '#111';

GUI/HUD Feature

I'm working in a feature/hud branch to test out the idea of a built-in GUI system for canvas-sketch. It would be like dat.gui but a bit more opinionated, a lot more declarative, and will integrate easily with canvas-sketch as well as features like exporting/importing serialized data (for example, making prints parameters reproducible).

Here is an example, no fancy styling yet:

screen shot 2018-10-01 at 5 19 13 pm

And a video in this tweet.

It would be built with Preact to keep the canvas-sketch library small.

Syntax

I don't want to introduce a lot of new API concepts to users, and I want it to be declarative and in line with the rest of the ethos of pure render functions. My current thought is to have something like this:

  • When params is passed to settings or update() function, the sketch will be added to a global HUD panel, and the panel will be made visible if it has one or more sketches attached to it.
  • If the param is an object it can include settings like display name, min/max, etc.
  • In the sketch & renderer functions, the param prop is converted into literal values (e.g. instead of a descriptor with options, you just get the raw number value).
const canvasSketch = require('canvas-sketch');

const settings = {
  dimensions: [ 640, 640 ],
  params: {
    background: 'pink',
    time: {
      value: 0.5,
      min: 0,
      max: 1,
      step: 0.001
    },
    number: 0.25,
    text: 'some text',
    download: ({ exportFrame }) => exportFrame()
  }
};

canvasSketch(() => {
  return ({ params }) => {
    const { background, radius } = params;
    console.log('Current background:', background); // e.g. #ff0000
    console.log('Current radius:', radius); // e.g. 0.523
  };
}, settings);

Motivation

It won't be all that different than dat.gui, but:

  • It will work well out of the box, and will require zero "wiring" to get properties hooked up to GUIs or render events
  • It will be declarative, so it can technically be stripped away by a higher-order function that passes props down (like React props & components)
  • It will integrate well with canvas-sketch already, but can also make some nice considerations for exporting, e.g. serialize JSON to a file so that each frame/export is saved with the parameters, or adding a --params flag to the CLI tool to override params with a JSON file
  • The global HUD can be a place for other requested features to canvas-sketch, like a play/pause, export button, etc

Features

Should support:

  • Text input, number spinners, sliders, color input, 2D XY pad, 3D orbit rotation (?), drop-down, checkbox, buttons, file drag & drop (images etc), ...?
  • Fuzzy searching of parameters to quickly drill down big lists?
  • Folders or some other way of letting the user organize things?

Questions

  • Syntax for buttons/click events? Often users will want to handle the event within the sketch function, rather than before the sketch loads. Maybe some sort of event system?
  • Should the parameters be persisted with localStorage? etc.
  • How to serialize/unserialize the parameters?
  • How to map params to built-in sketch properties like time, dimensions, duration etc?
  • Is this a can of worms I don't even want to get into?

Add Examples to CLI

I've got some examples so far, but most of them are more 'unit tests' to make sure it all works.

It would be great if the CLI could run a "Example Viewer" mode before generating a new sketch. Such as:

canvas-sketch --new --examples

The browser will list a thumbnail grid with examples, and clicking one will generate it from the template and start the server on that file.

Auto-install glslify deps

This is a 'nice to have' feature. Right now auto-install works pretty well, except with glslify modules since they are not picked up by detective.

Test case, save the following as my-sketch.js and run canvas-sketch my-sketch.js --open from an empty repo. Everything is installed except the GLSL modules.

const sketcher = require('canvas-sketch');

// Import geometry & utilities
const createRegl = require('regl');
const createPrimitive = require('primitive-icosphere');
const createCamera = require('perspective-camera');
const glslify = require('glslify');
const hexRgb = require('hex-rgb');

// Utility to convert hex string to [ r, g, b] floats
const hexToRGB = hex => {
  const rgba = hexRgb(hex, { format: 'array' });
  return rgba.slice(0, 3).map(n => n / 255);
};

const settings = {
  // Output size
  dimensions: [ 256, 256 ],

  // Setup render loop
  animation: true,
  duration: 7,
  fps: 24,

  // Ensure we set up a canvas with WebGL context, not 2D
  context: 'webgl',

  // We can pass down some properties to the WebGL context...
  attributes: {
    antialias: true // turn on MSAA
  }
};

const sketch = ({ gl, canvasWidth, canvasHeight }) => {
  // Background & foreground colors
  const color = '#f2bac4';
  const foregroundRGB = hexToRGB(color);
  const backgroundRGBA = [ ...foregroundRGB, 1.0 ];

  // Setup REGL with our canvas context
  const regl = createRegl({ gl });

  // Create a simple sphere mesh
  const sphere = createPrimitive(1, { subdivisions: 5 });

  // Create a perspective camera
  const camera = createCamera({
    fov: 45 * Math.PI / 180
  });

  // Place our camera
  camera.translate([ 0, 0, 6 ]);
  camera.lookAt([ 0, 0, 0 ]);
  camera.update();

  // Build a draw command for the mesh
  const drawMesh = regl({
    // Fragment & Vertex shaders
    frag: glslify(`
      precision highp float;
      uniform vec3 color;
      uniform float time;
      uniform vec2 resolution;
      varying vec3 vNormal;
      varying vec3 vPosition;
      varying vec2 screenUV;

      #pragma glslify: dither = require('glsl-dither/8x8');

      void main () {
        // Spin light around a bit
        float angle = sin(time * 0.25) * 2.0 + 3.14 * 0.5;
        vec3 lightPosition = vec3(cos(angle), sin(time * 1.0), sin(angle));
        vec3 L = normalize(lightPosition);
        vec3 N = normalize(vNormal);

        // Get diffuse contribution
        float diffuse = max(0.0, dot(N, L));
        diffuse = smoothstep(0.0, 1.25, diffuse);
        diffuse = pow(diffuse, 0.25);
        diffuse *= max(0.0, 1.0 - distance(vPosition, lightPosition) / 2.0) * 1.0;
        diffuse += pow(vNormal.z, 0.95) * 0.05;
        diffuse = clamp(diffuse, 0.0, 1.0);

        float ditherSize = 256.0;
        diffuse = dither(gl_FragCoord.xy / resolution.xy * ditherSize, diffuse);
        
        gl_FragColor = vec4(color * diffuse, 1.0);
      }
    `),
    vert: glslify(`
      precision highp float;
      attribute vec3 position;
      uniform mat4 projectionMatrix;
      uniform mat4 modelViewMatrix;
      uniform float time;
      varying vec3 vPosition;
      varying vec3 vNormal;
      varying vec2 screenUV;

      #pragma glslify: noise = require('glsl-noise/classic/4d');

      void main () {
        // Get initial normal
        vNormal = normalize(position);

        // Contribute noise
        vec3 pos = position;
        pos += vNormal * pow(noise(vec4(position.xyz * 1.0, time * 0.25)), 0.75) * 1.0;
        pos += vNormal * pow(noise(vec4(position.xyz * 0.75, time * 0.25)), 0.75) * 0.5;
        pos += vNormal * pow(noise(vec4(position.xyz * 40.0, time * 0.5)), 0.1) * 0.3;
        pos *= 0.75;

        // Update normal
        vNormal = normalize(pos);
        vPosition = pos;

        // Final position
        gl_Position = projectionMatrix * modelViewMatrix * vec4(pos.xyz, 1.0);
        screenUV = gl_Position.xy;
      }
    `),
    uniforms: {
      projectionMatrix: regl.prop('projectionMatrix'),
      modelViewMatrix: regl.prop('modelViewMatrix'),
      color: foregroundRGB,
      resolution: [ canvasWidth, canvasHeight ],
      time: regl.prop('time')
    },
    attributes: {
      position: regl.buffer(sphere.positions),
      normal: regl.buffer(sphere.normals)
    },
    elements: regl.elements(sphere.cells)
  });

  return ({ viewportWidth, viewportHeight, time, playhead }) => {
    // On each tick, update regl timers and sizes
    regl.poll();

    // Clear backbuffer with black
    regl.clear({
      color: backgroundRGBA,
      depth: 1,
      stencil: 0
    });

    camera.viewport = [ 0, 0, viewportWidth, viewportHeight ];
    camera.update();

    drawMesh({
      projectionMatrix: camera.projection,
      modelViewMatrix: camera.view,
      time
    });
  };
};

sketcher(sketch, settings);

[Discussion] FFMPEG Handling

I've added streaming MP4 and GIF exports with the --stream flag, which is pretty handy.

I'm curious about how I could integrate @ffmpeg-intsaller/ffmpeg into this project without forcing it upon the user. Basically, giving users a single npm approach to installing ffmpeg, rather than telling them to install through brew or whatever, however still allowing canvas-sketch to support the PATH-installed ffmpeg if needed. By default, it seems too heavy to add as a dependency to canvas-sketch-cli, which might get used in a per-project basis.

Basically looking for a UX solve... open to discussion!

Willing to Partcipate

Hi Matt, you mentioned in the lead you’re looking for collaborators. Here I am. Been into generative artistry for ten years (CFDG, some Processing, native JS and Python), so I instantly became enthusiastic about this project. Feel free to contact me at [email protected] – some works and experiments are to be found at https://www.behance.net/royaljerry Cheers, Adam

Export contains transparent pixels when dimensions > "a3" in Chrome

Here is the sketch, I am using:

import canvasSketch from "canvas-sketch";
import createRegl from "regl";

const settings = {
  dimensions: "a3",
  pixelsPerInch: 300,
  scaleToView: true,
  units: "in",
  orientation: "landscape",
  context: "webgl",
  attributes: {
    antialias: true // turn on MSAA
  }
};

const sketchRender = ({ context, gl, width, height, time }) => {
  let regl = createRegl({ gl });

  // Draw
  regl.clear({
    color: [0.93, 0.93, 0.93, 1],
    depth: 1
  });
};

const onResize = ({ width, height }) => {
  console.log("Canvas has resized to %d x %d", width, height);
};

// Sketch
canvasSketch(async ({ render, update }) => {
  const renderer = { render: sketchRender, resize: onResize };

  return renderer;
}, settings);

When exporting with Cmd+S with "a3" dimension preset, a nudge of transparent pixels appears on top/right (tried in Incognito):

2019 01 14-19 04 43-default

It works fine in Safari and also fine when using "a4". scaleToView has no effect either.

Looping a track with Tone.js

Hi!

I’ve been working on videos that loop with an audio track. I’ve been using Tone.js to load, play, and extract an fft from the audio.

One issue I’ve been having is I can’t seem to get the audio to restart when I press command+shift+s to record the video frames. I’m not quite sure the best way to sort this.

This is my isolated test case:

let canvasSketch = require('canvas-sketch');
let Tone = require('tone')

let settings = {
    animate: true,
    duration: 8.39,
    dimensions: [ 1024, 1024 ]
}

let sketch = () => {

    let player = new Tone.Player('/assets/2019_05_24.mp3').toMaster()
    let fft = new Tone.FFT(128)
    player.autostart = true
    player.loop = true
    player.connect(fft)

    return ({ context, width, height }) => {
        context.fillStyle = 'white'
        context.fillRect(0, 0, width, height)

        context.fillStyle = 'hsla(0, 0%, 0%, 1)'
        let fft_v = fft.getValue()
        for (let i = 0; i < fft_v.length; ++i) {
            let x = i/(fft_v.length-1)*width
            let y = (fft_v[i]/-200)*height/2
            context.save()
            context.translate(x, (height/4)+y)
            context.fillRect(-4, -4, 8, 8)
            context.restore()
        }
    }
}

canvasSketch(sketch, settings)

I have also tried with an async/await setup (which I may be doing wrong here):

let canvasSketch = require('canvas-sketch');
let Tone = require('tone')

let settings = {
    animate: true,
    duration: 8.39,
    dimensions: [ 1024, 1024 ]
}

let sketch = async () => {

    let load_music = function(src) {
        let actx = new AudioContext()
        let source = actx.createBufferSource()

        let request = new Request(src)

        return new Promise(function(resolve, reject) {
            return fetch(request).then(function(response) {
                return response.arrayBuffer()
            }).then(function(buffer) {
                actx.decodeAudioData(buffer, function(decodedata) {
                    source.buffer = decodedata
                    source.connect(actx.destination)
                    resolve(source)
                })
            })
        })
    }

    let mp3 = await load_music('/assets/2019_05_24.mp3')

    let player = new Tone.Player()
    let fft = new Tone.FFT(128)
    player.buffer = new Tone.Buffer(mp3.buffer)
    player.loop = true
    player.connect(fft)
    player.toMaster()
    player.start()

    return ({ context, width, height }) => {
        context.fillStyle = 'white'
        context.fillRect(0, 0, width, height)

        context.fillStyle = 'hsla(0, 0%, 0%, 1)'
        let fft_v = fft.getValue()
        for (let i = 0; i < fft_v.length; ++i) {
            let x = i/(fft_v.length-1)*width
            let y = (fft_v[i]/-200)*height/2
            context.save()
            context.translate(x, (height/4)+y)
            context.fillRect(-4, -4, 8, 8)
            context.restore()
        }
    }
}

canvasSketch(sketch, settings)

Hotkeys for Exporting & Controlling Sketches

As brought up by this thread by @fturcheti, it might be nice to get some feedback on hotkeys and "how to control the sketch" in a nice and easy way.

Currently these hotkeys work:

  • Cmd + S / Ctrl + S = Export Single Frame
  • Cmd + Shift + S / Ctrl + Shift S = Start/Stop Recording Multiple Frames
  • Cmd + K = Commit & Export Single Frame
  • Space to play/pause (only for animated sketches)

One issue with Cmd + S is that it sometimes triggers the native save dialog, i.e. when done from within DevTools. Unfortunately it doesn't seem like a simple solve, since keydown events aren't triggered in the window when DevTools are in focus.

I'm not sure if there are better default keystrokes for these – maybe some additional hotkeys could be added (e.g. stop/reset playhead), or some removed (the play/pause does not really fit right now). Maybe there is also some other method of exporting & controlling sketches that I have not considered.

Add warning with typos on settings keys

Ran into a problem when setting up a new sketch since I wrote dimension: [500, 500] and the actual key is dimensions. A nice-to-have would be to notice the near miss and output to the console a suggested alternative.

Hooks

Hi Matt,

This morning I came across Kyle Simpson's TNG-Hooks library, and this reminded me of #42 and your idea to provide a Hooks API for canvas-sketch.

I adapted my previous mousemove example to use tng-hooks: https://codesandbox.io/s/github/mhyfritz/canvas-sketch-mouse-tng-hook.

Maybe a first implementation for canvas-sketch could use tng-hooks internally, automatically wrap the render function in TNG() and expose the TNG hooks (useState etc.) via, say, canvas-sketch/hooks?

Just something to consider 😄

Happy 2019!

Syntax for custom templates

I was happy to see that one can easily create their own templates for sketches, though I was a little surprised by the syntax. For others who might be running into this, the approach is to do the following:

canvas-sketch --new --open < path/to/my-custom-template.js

FWIW, my initial expectation was that I would be able to pass the path to my template as an argument, i.e. canvas-sketch --new --open --template=./path/to/my-custom-template.js.

As the documentation gets more flushed out, I imagine this will be covered. Feel free to close this issue in the interim.

Thanks!

Default: Animated or Still?

Should the default mode be animated (i.e. 60 FPS requestAnimationFrame loop) or still (i.e. render once and then again on resize)?

Although most of my work lately is still (prints/penplotter/etc) I still think the majority of users would expect animated to be default, so they can use { time } without any additional settings...

Git commit shortcut (Cmd+K) breaks Firefox Web Console shortcut (Cmd+Opt+K)

The Firefox keyboard shortcut to open its Web Console is Cmd+Opt+K, but the current implementation of Cmd+K to trigger git commit prevents this shortcut to work correcty. To fix that, I think you just need to add a && !ev.altKey to the conditional below:

} else if (client && ev.keyCode === 75 && (ev.metaKey || ev.ctrlKey)) {
// Cmd + K, only when canvas-sketch-cli is used
ev.preventDefault();
this.exportFrame({ commit: true });
}

[Experimental] Hot Reloading Support ✨

I've added Hot Reloading support to the latest version. You will need to update to both [email protected] and [email protected] or newer (for help updating, see here).

After you've updated both, you can use it like so:

canvas-sketch my-sketch.js --hot

You can read more about the feature here:

You can see a video of it in action here:

The feature is probably most useful with animated sketches as the { time } and { frame } props persist across code updates.

Let me know if you have any issues/comments/etc! This is still experimental but so far seems to be working well in my sketches.

Export doubles the frame on save

Hey @mattdesl, love the library, and I'm really appreciative of your support for generative art!

I'm not totally sure this is the same issue as #25, so I thought I'd open a new issue.

I've created a script that creates random christmas trees 🎄

Code here (don't look too closely it's ugly :(

Edit: For some reason I made a small tweak to the code (random colors) and now it doesn't do the same thing. I've updated the link above to point to the code at the time that was behaving strangely. The current commit works fine.

Unfortunately, it isn't exporting correctly. It appears to be leaving the old render in place and then rendering an additional frame on top of it.

What's shown on screen:
screen shot 2018-12-24 at 11 08 15 pm

What's exported to PNG
screen shot 2018-12-24 at 11 08 30 pm

As far as I can tell this wasn't happening on my previous ornament-generating code

Any ideas?

Thanks!

[Idea] Prevent frames overwriting with timestamps

When exporting images while sketching, we get timestamped image files, so we don't have to worry about overwriting them while we iterate over our sketching and exporting process.

But when we are exporting animated frames, this is different. As frames are exported as [frame-number].[extension], if we export the frames for a certain state, then tweak the code and export frames again, we'll overwrite the previous frame files with the new ones.

To prevent this overwriting, I have some ideas:

  1. Export the frames to a timestamped folder in the current export path. This way, instead of getting frames like this:

    ~/Downloads/0001.png  
    ~/Downloads/0002.png  
    ~/Downloads/0003.png
    

    we'd get this:

    ~/Downloads/2018.08.01-11.56.00/0001.png  
    ~/Downloads/2018.08.01-11.56.00/0002.png  
    ~/Downloads/2018.08.01-11.56.00/0003.png
    
  2. Or export the frames with a timestamp prefix. This way we'd get files like this:

    ~/Downloads/2018.08.01-11.56.00-0001.png  
    ~/Downloads/2018.08.01-11.56.00-0002.png  
    ~/Downloads/2018.08.01-11.56.00-0003.png
    

Render function called twice while exporting

I noticed while experimenting with canvas-sketch that when you export an image using cmd + s or an animation using cmd + shift + s, the render function is called twice for each frame.

To reproduce:

// bug-demo.js
const SKETCH = require('canvas-sketch')

const sketch = () => {
  return ({ frame }) => {
    console.log(`render frame ${frame}`)
  }
}

SKETCH(sketch)

Then launch:

canvas-sketch bug-demo.js --open

Open the browser console and then try saving with cmd + s and you should see “render frame 0” logged twice.

Here are the relevant lines from my console (Firefox 62.0.3):

browser console output demonstrating error

“render frame 0” is logged once on page load, then twice more on export/save. When exporting an animation the duplication happens for each frame.

I imagine this has performance implications, but I also tripped up on it because I was using a generator function to get the next value to display for each frame. Each frame was being rendered twice (and saved once), which meant every other value returned from the generator was lost.


P.S. Generally the experience getting started with this has been really great. Thanks for the thoughtful library and clear documentation!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.