magic-painting
v0.1.1
Published
JavaScript lightweight library for easy interactive beautiful artistic abstract panting
Downloads
27
Readme
Magic Painting project
JavaScript lightweight library for easy interactive beautiful artistic abstract panting.
See demo image or web app.
Quick navigation
Table of contents
Overview
MagicPainting is a lightweight JavaScript library for easy interactive beautiful artistic abstract panting.
It uses HTML5 Canvas API and its 2D context. It provides a higher-level abstraction allowing a developer to focus on the actual painting.
Classes and features
MagicPainting
The MagicPainting
class takes care of:
- mouse/touch events
- window to canvas coordinate transformation
- schedules the painting (using
requestAnimationFrame
) - undo, redo, save, clear
- communication with the primary
Effect
(settings, transformations, ...)
Context
The Context
class is a wrapper around CanvasRenderingContext2D
.
Its main purpose is to automatically apply transformations (symmetry, rotation etc.), so if something is painted at one place, it is simultanously painted on other relevant places "out of the box", too.
Currently implemented are:
- symmetry along x axis
- symmetry along y axis
- symmetry along x axis, y axis and center
Effect
The Effect
class is the base class for actual painting.
Its subclasses implements what to do based on given events (i.e. their coordinates and times)
Each effect can have a parent effect and descendant child effect(s).
Compatibility
Tested on Google Chrome 94
Usage
Node
Installation
npm install magic-painting --save-dev
Package contains source code as well as bundled and minified javascript files. The index.min.js
file defines MagicPainting
namespace, equivalent to module import * as MagicPainting from "..."
counterpart.
Web
Have a look at examples/web-minimal
directory.
Module import from source code
You can use module import
from source code.
Minimal example:
<script type="module">
import * as MagicPainting from "/path/to/src/index.js"
window.onload = () => {
var painting = new MagicPainting.MagicPainting(document.body)
painting.effect = new MagicPainting.effects.StreamLines()
}
</script>
Using global namespace from bundled file
You can load bundled script defining global namespace. The bundled script may be a local file
<script src="/path/to/index.min.js"></script>
or a file loaded from CDN (e.g. unpkg.com or similar made from NPM package).
<script src="https://unpkg.com/[email protected]/index.min.js"></script>
followed by
<script>
window.onload = () => {
var painting = new MagicPainting.MagicPainting(document.body)
painting.effect = new MagicPainting.effects.StreamLines()
}
</script>
Extending
Let's see how to implement a simple new Effect!
See it live here and its source code here or here .
This is just a minimal example to show the basic usage. Of course, adding colors, styles, opacity, curvature etc. etc. makes the Effect much more effective.
// example primary effect
class ExampleEffect extends MagicPainting.Effect {
constructor() {
super()
// what to do on pointer start
this.onPointerStart((x,y,t) => {
// stores the coordinates
this.xy = [{x,y}]
// add new effect (which will be started just after)
this.add(new ExampleStartEffect())
})
// what to do on pointer move
this.onPointerMove((x,y,t) => {
// updates coordinates
var {x:x0,y:y0} = this.xy
this.xy = {x,y}
var dx = x - x0
var dy = y - y0
var context = this.background
// draw line from previous to current coordinates
context.stroke(() => {
context.moveTo(x0,y0)
context.lineTo(x,y)
context.context.lineWidth = 10
context.context.strokeStyle = "cyan"
})
// draw perpendicular lines
context.stroke(() => {
context.moveTo(x,y)
context.lineTo(x+dy,y-dx)
context.context.lineWidth = 5
context.context.strokeStyle = "white"
})
})
// what to do on pointer end
this.onPointerEnd(t => {
// finish is important for undo / redo
this.finish()
})
}
}
// MagicPainting.effect must be a primary effect. The easiest is
ExampleEffect.bePrimary()
// example secondary effect
class ExampleStartEffect extends MagicPainting.Effect {
constructor() {
super()
// what to do on pointer start
this.onPointerStart((x,y,t) => {
// stores coordinates and time
this.xy = {x,y}
this.t = t
})
// what to do on animation frame
this.onAnimationFrameEnd(t => {
// draw on foreground
this.draw(this.foreground,t)
})
// what to do on pointer end
this.onPointerEnd(t => {
// draw on background
this.draw(this.background,t)
// remove itself from its parent
this.remove()
})
}
// what and how to draw (a circle whose radius depends on time elapsed from pointer start)
draw(context,time) {
var {x,y} = this.xy
var r = 10*(1+Math.cos((time-this.t)/300))
context.fill(() => {
context.arc(x,y,r,0,2*Math.PI)
context.context.fillStyle = "red"
})
}
}
For more information, see existing effects here and here and/or the documentation.
TODO (sorted by priority)
- improve
Context
class w.r.t. transformations (only necessary is done yet) - add more transformations
- add more effects
- improve existing effects
Contributing
is welcome :-)
- via the GitLab pages of the project
- via e-mail
Ideas
Ideas about existing and new effects, UI/UX, ...
Bugs
Code
Please read CONTRIBUTING.md
file