Merge pull request #1 from hundredrabbits/master

sync
This commit is contained in:
Nikolaus Gradwohl 2019-07-18 05:06:53 +02:00 committed by GitHub
commit 77d1aea227
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 755 additions and 357 deletions

View File

@ -1,19 +1,90 @@
# Ronin # Ronin
_All I wanted, was a way of resizing photos._ _"All I wanted, was a quick way of resizing a few photos.."_
Ronin is a LISP repl to create generative graphics currently under development. You can follow the daily progress on [Mastodon](https://merveilles.town/@neauoire/). Until we have documented the library, you can find a list of available functions [here](https://github.com/hundredrabbits/Ronin/blob/master/desktop/sources/scripts/library.js). Ronin is a [LISP](https://en.wikipedia.org/wiki/Lisp_(programming_language)) repl to create generative graphics currently under development. You can follow the daily progress on [Mastodon](https://merveilles.town/@neauoire/).
<img src='https://raw.githubusercontent.com/hundredrabbits/Ronin/master/PREVIEW.jpg' width='600'/> ## Install & Run
## Electron Build You can download [builds](https://hundredrabbits.itch.io/ronin) for **OSX, Windows and Linux**, or if you wish to build it yourself, follow these steps:
``` ```
cd desktop git clone https://github.com/hundredrabbits/Ronin.git
cd Ronin/desktop/
npm install npm install
npm start npm start
``` ```
<img src='https://raw.githubusercontent.com/hundredrabbits/Ronin/master/PREVIEW.jpg' width='600'/>
## Library
- `(import path rect)` Imports a graphic file with format.
- `(export path ~format ~quality)` Exports a graphic file with format.
- `(pos x y ~t)` Returns a position shape.
- `(size w h ~t)` Returns a size shape.
- `(rect x y w h ~t)` Returns a rect shape.
- `(circle x y r ~t)` Returns a circle shape.
- `(line a b ~t)` Returns a line shape.
- `(text x y g s ~f ~t)` Returns a text shape.
- `(svg d ~t)` Returns a svg shape.
- `(stroke ~shape)` Strokes a shape.
- `(fill ~rect)` Fills a shape.
- `(clear ~rect)` Clears a rect.
- `(add ...args)` Adds values.
- `(sub ...args)` Subtracts values.
- `(mul ...args)` Multiplies values.
- `(div ...args)` Divides values.
- `(mod a b)` Returns the modulo of a and b.
- `(clamp val min max)` Clamps a value between min and max.
- `(step val step)`
- `(min)`
- `(max)`
- `(ceil)`
- `(floor)`
- `(sin)`
- `(cos)`
- `(PI)`
- `(TWO_PI)`
- `(random ...args)`
- `(gt a b)` Returns true if a is greater than b, else false.
- `(lt a b)` Returns true if a is less than b, else false.
- `(eq a b)` Returns true if a is equal to b, else false.
- `(and a b ...rest)` Returns true if all conditions are true.
- `(or a b ...rest)` Returns true if at least one condition is true.
- `(map fn arr)`
- `(filter fn arr)`
- `(reduce fn arr ~acc)`
- `(len item)` Returns the length of a list.
- `(first arr)` Returns the first item of a list.
- `(last arr)` Returns the last
- `(rest [_ ...arr])`
- `(range start end ~step)`
- `(get item key)` Gets an object's parameter with name.
- `(set item key val)` Sets an object's parameter with name as value.
- `(frame)` Returns a rect of the frame.
- `(center)` Returns a position of the center of the frame.
- `(scale rect w h)`
- `(resize ~w ~h)`
- `(crop rect)`
- `(clone a b)`
- `(of h ...keys)`
- `(theme variable ~el)`
- `(gradient [x1 y1 x2 y2] ~colors 'black'])`
- `(pixels rect fn q)`
- `(saturation pixel ~q)`
- `(contrast pixel ~q)`
- `(echo ...args)`
- `(str ...args)`
- `(open path)` Imports a graphic file and resizes the frame.
- `(folder ~path)` Returns the content of a folder path.
- `(exit ~force)` Exits Ronin.
- `(ronin)`
- `(time)` Returns timestamp in milliseconds.
- `(animate ~play)` Toggles animation.
- `(js)`
- `(test name a b)`
## Extras ## Extras
- This application supports the [Ecosystem Theme](https://github.com/hundredrabbits/Themes). - This application supports the [Ecosystem Theme](https://github.com/hundredrabbits/Themes).

View File

@ -40,6 +40,7 @@
ronin.controller.addRole('default', 'Edit', 'paste') ronin.controller.addRole('default', 'Edit', 'paste')
ronin.controller.addRole('default', 'Edit', 'delete') ronin.controller.addRole('default', 'Edit', 'delete')
ronin.controller.addRole('default', 'Edit', 'selectall') ronin.controller.addRole('default', 'Edit', 'selectall')
ronin.controller.add("default","Edit","Re-Indent",() => { ronin.commander.reindent() },"CmdOrCtrl+Shift+I")
ronin.controller.add("default","View","Zoom In",() => { ronin.modZoom(0.25) },"CmdOrCtrl+=") ronin.controller.add("default","View","Zoom In",() => { ronin.modZoom(0.25) },"CmdOrCtrl+=")
ronin.controller.add("default","View","Zoom Out",() => { ronin.modZoom(-0.25) },"CmdOrCtrl+-") ronin.controller.add("default","View","Zoom Out",() => { ronin.modZoom(-0.25) },"CmdOrCtrl+-")
ronin.controller.add("default","View","Zoom Reset",() => { ronin.modZoom(1,true) },"CmdOrCtrl+0") ronin.controller.add("default","View","Zoom Reset",() => { ronin.modZoom(1,true) },"CmdOrCtrl+0")

View File

@ -5,12 +5,12 @@ body { margin:0px; padding:0px; overflow:hidden; font-family:"input_mono_regular
#ronin { height: calc(100vh - 60px); width:calc(100vw - 60px); -webkit-app-region: drag; padding: 30px;overflow: hidden; } #ronin { height: calc(100vh - 60px); width:calc(100vw - 60px); -webkit-app-region: drag; padding: 30px;overflow: hidden; }
#ronin #wrapper { overflow: hidden; position: relative; } #ronin #wrapper { overflow: hidden; position: relative; }
#ronin #wrapper #commander { z-index: 9000;position: relative;width: 310px;height: calc(100vh - 60px);-webkit-app-region: no-drag;padding-right: 30px;transition: margin-left 250ms;} #ronin #wrapper #commander { z-index: 9000;position: relative;width: 310px;height: calc(100vh - 60px);-webkit-app-region: no-drag;padding-right: 30px;transition: margin-left 250ms;}
#ronin #wrapper #commander textarea { background: none; width: 100%; height: calc(100vh - 80px); resize: none; font-size: 12px;line-height: 15px; padding-right: 15px} #ronin #wrapper #commander textarea { background: none; width: 100%; height: calc(100vh - 105px); resize: none; font-size: 12px;line-height: 15px; padding-right: 15px}
#ronin #wrapper #commander div#status { position: absolute; bottom: 0px; } #ronin #wrapper #commander div#status { position: absolute; bottom: 0px; text-transform: lowercase;}
#ronin.hidden #wrapper #commander { margin-left:-331px; } #ronin.hidden #wrapper #commander { margin-left:-331px; }
#ronin canvas#surface,#ronin canvas#guide { position: absolute; top:0px; -webkit-user-select: none;-webkit-app-region: no-drag; background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='20' height='20'><circle cx='10' cy='10' r='1' fill='%23555'></circle></svg>"); background-size: 10px 10px; background-position: -4px -4px; width:100%; height:100%; left:340px; transition: left 250ms} #ronin canvas#surface,#ronin canvas#guide { position: absolute; top:0px; -webkit-user-select: none;-webkit-app-region: no-drag; background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='20' height='20'><circle cx='10' cy='10' r='1' fill='%23555'></circle></svg>"); background-size: 10px 10px; background-position: -4px -4px; width:100%; height:100%; left:340px; transition: left 250ms}
#ronin canvas#guide { background:none; } #ronin canvas#guide { background:none; }
#ronin canvas#surface { border-radius: 2px } #ronin canvas#surface { border-radius: 2px }
#ronin.hidden canvas#surface, #ronin.hidden canvas#guide { left:0px; } #ronin.hidden canvas#surface, #ronin.hidden canvas#guide { left:10px; }

View File

@ -4,18 +4,28 @@ function Commander (ronin) {
this._input = document.createElement('textarea') this._input = document.createElement('textarea')
this._status = document.createElement('div') this._status = document.createElement('div')
this._status.id = 'status' this._status.id = 'status'
this._log = document.createElement('div')
this._log.id = 'log'
this._source = document.createElement('div')
this._source.id = 'source'
this._docs = document.createElement('div')
this._docs.id = 'help'
this.isVisible = true this.isVisible = true
this.install = function (host) { this.install = function (host) {
this.el.appendChild(this._input) this.el.appendChild(this._input)
this._status.appendChild(this._log)
this._status.appendChild(this._source)
this._status.appendChild(this._docs)
this.el.appendChild(this._status) this.el.appendChild(this._status)
host.appendChild(this.el) host.appendChild(this.el)
this._input.addEventListener('input', this.onInput) this._input.addEventListener('input', this.onInput)
this._input.addEventListener('click', this.onClick)
this.docs.install()
} }
this.start = function () { this.start = function () {
this._status.textContent = 'Idle. (zoom 100%)' this.setStatus('Ready.')
this._input.focus() this._input.focus()
this.run() this.run()
this.hide() this.hide()
@ -33,21 +43,59 @@ function Commander (ronin) {
this.run() this.run()
} }
this.reindent = function () {
let val = this._input.value.replace(/\n/g, '').replace(/ +(?= )/g, '').replace(/\( \(/g, '((').replace(/\) \)/g, '))').trim()
let depth = 0
for (let i = 0; i < val.length; i++) {
const c = val.charAt(i)
if (c === '(') { depth++ } else if (c === ')') { depth-- }
if (c === ';') {
const indent = '\n' + (' '.repeat(depth))
val = val.insert(indent, i)
i += indent.length
}
if (c === '(') {
const indent = '\n' + (' '.repeat(depth - 1))
val = val.insert(indent, i)
i += indent.length
}
}
this._input.value = val.trim()
}
this.setStatus = function (msg) { this.setStatus = function (msg) {
if (!msg) { return } // Logs
this._status.textContent = `${(msg + '').substr(0, 40)}` if (msg && msg !== this._log.textContent) {
this._log.textContent = `${msg}`
console.log(msg)
}
// Source
const _source = `${ronin.source} ${this._input.value.split('\n').length} lines`
if (_source !== this._source.textContent) {
this._source.textContent = _source
}
// Docs
const _docs = this.docs.print(this.getLastfn())
if (_docs !== this._docs.textContent) {
this._docs.textContent = `${_docs}`
}
} }
this.update = function () { this.update = function () {
} }
this.onInput = function () { this.onInput = () => {
this.setStatus()
} }
this.getQuery = function () { this.onClick = () => {
this.setStatus()
}
this.getLastfn = function () {
const pos = this._input.value.substr(0, this._input.selectionStart).lastIndexOf('(')
return this._input.value.substr(pos).split(' ')[0].replace(/\(/g, '').replace(/\)/g, '').trim()
} }
// Mouse // Mouse
@ -62,7 +110,6 @@ function Commander (ronin) {
this.mouseRect.a.x = e.offsetX this.mouseRect.a.x = e.offsetX
this.mouseRect.a.y = e.offsetY this.mouseRect.a.y = e.offsetY
this.mouseRect.t = 'pos' this.mouseRect.t = 'pos'
this._status.textContent = `${this.mouseRect.x},${this.mouseRect.y} ${this.mouseRect.w},${this.mouseRect.h}`
this.capture() this.capture()
this.show() this.show()
} }
@ -73,7 +120,6 @@ function Commander (ronin) {
this.mouseRect.h = e.offsetY - this.mouseRect.y this.mouseRect.h = e.offsetY - this.mouseRect.y
this.mouseRect.b.x = e.offsetX this.mouseRect.b.x = e.offsetX
this.mouseRect.b.y = e.offsetY this.mouseRect.b.y = e.offsetY
this._status.textContent = `${this.mouseRect.x},${this.mouseRect.y} ${this.mouseRect.w},${this.mouseRect.h}`
this.commit() this.commit()
} }
} }
@ -85,7 +131,6 @@ function Commander (ronin) {
this.mouseRect.b.x = e.offsetX this.mouseRect.b.x = e.offsetX
this.mouseRect.b.y = e.offsetY this.mouseRect.b.y = e.offsetY
this.mouseRect.t = '' this.mouseRect.t = ''
this._status.textContent = `${this.mouseRect.x},${this.mouseRect.y} ${this.mouseRect.w},${this.mouseRect.h}`
this.commit() this.commit()
this._input.focus() this._input.focus()
ronin.surface.clearGuide() ronin.surface.clearGuide()
@ -162,4 +207,44 @@ function Commander (ronin) {
this.hide() this.hide()
} }
} }
// Docs micro-module
this.docs = {
dict: {},
load: function () {
const fs = require('fs')
const path = require('path')
const p = path.join(__dirname, 'scripts/', 'library.js')
if (!fs.existsSync(p)) { console.warn('Docs', 'File does not exist: ' + p); return }
const lines = fs.readFileSync(p, 'utf8').split('\n').filter((line) => { return line.substr(0, 7) === ' this.' })
return lines.map((line) => { return line.trim().substr(5).trim() })
},
install: function (payload = this.load()) {
for (const id in payload) {
const name = payload[id].substr(0, payload[id].indexOf(' = '))
const parent = payload[id].substr(payload[id].indexOf(' = ')).match(/\(([^)]+)\)/)
const params = parent ? parent[1].split(',').map((word) => { return word.indexOf(' = ') > -1 ? '~' + (word.split(' = ')[0]).trim() : word.trim() }) : []
const note = payload[id].indexOf('// ') > -1 ? payload[id].split('//')[1].trim() : ''
this.dict[name] = { note, params }
if (params.length < 1) { console.warn('Docs', 'Missing params for ' + name) }
if (note === '') { console.warn('Docs', 'Missing note for ' + name) }
}
console.log('Docs', `Loaded ${Object.keys(this.dict).length} functions.`)
console.log(this.toMarkdown())
},
toMarkdown: function () {
return Object.keys(this.dict).reduce((acc, item, key) => {
const example = `${item} ${this.dict[item].params.reduce((acc, item) => {
return `${acc}${item} `
}, '').trim()}`
return `${acc}- \`(${example.trim()})\` ${this.dict[item].note}\n`
}, '')
},
print: function (name) {
return this.dict[name] ? `(${name} ${this.dict[name].params.reduce((acc, item) => { return `${acc}${item} ` }, '').trim()})` : ''
}
}
String.prototype.insert = function (s, i) { return [this.slice(0, i), `${s}`, this.slice(i)].join('') }
} }

View File

@ -1,269 +1,88 @@
function Library (ronin) { function Library (ronin) {
this.open = async (path) => { this.import = async (path, rect) => { // Imports a graphic file with format.
return ronin.surface.open(path)
}
this.export = (path, type = 'image/png', quality = 1.0) => {
if (!path) { console.warn('Missing export path'); return path }
var dataUrl = ronin.surface.el.toDataURL(type, quality)
const data = dataUrl.replace(/^data:image\/png;base64,/, '')
fs.writeFileSync(path, data, 'base64')
return path
}
this.draw = async (path, rect) => {
const img = new Image() const img = new Image()
img.src = path img.src = path
return ronin.surface.draw(img, rect) return ronin.surface.draw(img, rect)
} }
this.resize = async (w = 1, h = 1) => { this.export = (path, format = 'image/png', quality = 1.0) => { // Exports a graphic file with format.
const rect = w <= 1 || h <= 1 ? { x: 0, y: 0, w: this.frame().w * w, h: this.frame().h * h } : { x: 0, y: 0, w, h } if (!path) { console.warn('Missing export path'); return path }
const a = document.createElement('img') var dataUrl = ronin.surface.el.toDataURL(format, quality)
const b = document.createElement('img') const data = dataUrl.replace(/^data:image\/png;base64,/, '')
a.src = ronin.surface.el.toDataURL() fs.writeFileSync(path, data, 'base64')
ronin.surface.resizeImage(a, b) return path
ronin.surface.resize(rect, true)
return ronin.surface.draw(b, rect)
}
this.crop = async (rect) => {
return ronin.surface.crop(rect)
}
this.folder = (path = ronin.source.path) => {
return fs.existsSync(path) ? fs.readdirSync(path) : []
}
this.exit = () => {
ronin.source.quit()
}
// Logic
this.gt = (a, b) => {
return a > b
}
this.lt = (a, b) => {
return a < b
}
this.eq = (a, b) => {
return a === b
}
this.and = (a, b, ...rest) => {
let args = [a, b].concat(rest)
for (let i = 0; i < args.length; i++) {
if (!args[i]) {
return args[i]
}
}
return args[args.length - 1]
}
this.or = (a, b, ...rest) => {
let args = [a, b].concat(rest)
for (let i = 0; i < args.length; i++) {
if (args[i]) {
return args[i]
}
}
return args[args.length - 1]
}
// Arrays
this.map = async (fn, arr) => {
return Promise.all(arr.map(fn))
}
this._filter = (fn, arr) => {
return arr.filter(fn)
}
this.filter = (fn, arr) => {
const list = Array.from(arr)
return Promise.all(list.map((element, index) => fn(element, index, list)))
.then(result => {
return list.filter((_, index) => {
return result[index]
})
})
}
this.reduce = (fn, arr, acc = 0) => {
return arr.reduce(fn, acc)
}
this.len = (item) => {
return item.length
}
this.first = (arr) => {
return arr[0]
}
this.last = (arr) => {
return arr[arr.length - 1]
}
this.rest = ([_, ...arr]) => {
return arr
}
this.range = (start, end, step = 1) => {
let arr = []
if (step > 0) {
for (let i = start; i <= end; i += step) {
arr.push(i)
}
} else {
for (let i = start; i >= end; i += step) {
arr.push(i)
}
}
return arr
} }
// Shapes // Shapes
this.pos = (x, y, t = 'pos') => { this.pos = (x, y, t = 'pos') => { // Returns a position shape.
return { x, y, t } return { x, y, t }
} }
this.size = (w, h, t = 'size') => { this.size = (w, h, t = 'size') => { // Returns a size shape.
return { w, h, t } return { w, h, t }
} }
this.rect = (x, y, w, h, t = 'rect') => { this.rect = (x, y, w, h, t = 'rect') => { // Returns a rect shape.
return { x, y, w, h, t } return { x, y, w, h, t }
} }
this.circle = (x, y, r, t = 'circle') => { this.circle = (x, y, r, t = 'circle') => { // Returns a circle shape.
return { x, y, r, t } return { x, y, r, t }
} }
this.line = (a, b, t = 'line') => { this.line = (a, b, t = 'line') => { // Returns a line shape.
return { a, b, t } return { a, b, t }
} }
this.text = (x, y, g, s, f = 'Arial', t = 'text') => { this.text = (x, y, g, s, f = 'Arial', t = 'text') => { // Returns a text shape.
return { x, y, g, s, f, t } return { x, y, g, s, f, t }
} }
this.svg = (d, t = 'svg') => { this.svg = (d, t = 'svg') => { // Returns a svg shape.
return { d, t } return { d, t }
} }
// Helpers // Actions
this.frame = () => { this.stroke = (shape = this.frame(), thickness, color) => { // Strokes a shape.
return ronin.surface.getFrame()
}
this.center = () => {
const rect = this.frame()
return this.pos(rect.w / 2, rect.h / 2)
}
this.scale = (rect, w, h) => {
return { x: rect.x, y: rect.y, w: rect.w * w, h: rect.h * h }
}
// Copy/Paste
this.clone = (a, b) => {
ronin.surface.clone(a, b)
return [a, b]
}
this.stroke = (shape = this.frame(), thickness, color) => {
ronin.surface.stroke(shape, thickness, color) ronin.surface.stroke(shape, thickness, color)
return shape return shape
} }
this.fill = (rect = this.frame(), color) => { this.fill = (rect = this.frame(), color) => { // Fills a shape.
ronin.surface.fill(rect, color) ronin.surface.fill(rect, color)
return rect return rect
} }
this.clear = (rect = this.frame()) => { this.clear = (rect = this.frame()) => { // Clears a rect.
ronin.surface.clear(rect) ronin.surface.clear(rect)
return rect return rect
} }
this.get = (item, key) => {
return item[key]
}
this.set = (item, key, val) => {
item[key] = val
return item[key]
}
// TODO: Should remove (of) for (get)?
this.of = (h, ...keys) => {
return keys.reduce((acc, key) => {
return acc[key]
}, h)
}
this.theme = (variable, el = document.documentElement) => {
// ex. (theme "f_main") -> :root { --f_main: "#fff" }
return getComputedStyle(el).getPropertyValue(`--${variable}`)
}
// Pixels
this.pixels = (rect, fn, q) => {
const img = ronin.surface.context.getImageData(0, 0, rect.w, rect.h)
for (let i = 0, loop = img.data.length; i < loop; i += 4) {
const pixel = { r: img.data[i], g: img.data[i + 1], b: img.data[i + 2], a: img.data[i + 3] }
const processed = fn(pixel, q)
img.data[i] = processed[0]
img.data[i + 1] = processed[1]
img.data[i + 2] = processed[2]
img.data[i + 3] = processed[3]
}
ronin.surface.context.putImageData(img, 0, 0)
return rect
}
this.saturation = (pixel, q = 1) => {
const color = 0.2126 * pixel.r + 0.7152 * pixel.g + 0.0722 * pixel.b
return [(color * (1 - q)) + (pixel.r * q), (color * (1 - q)) + (pixel.g * q), (color * (1 - q)) + (pixel.b * q), pixel.a]
}
this.contrast = (pixel, q = 1) => {
const intercept = 128 * (1 - q)
return [pixel.r * q + intercept, pixel.g * q + intercept, pixel.b * q + intercept, pixel.a]
}
// Math // Math
this.add = (...args) => { this.add = (...args) => { // Adds values.
return args.reduce((sum, val) => sum + val) return args.reduce((sum, val) => sum + val)
} }
this.sub = (...args) => { this.sub = (...args) => { // Subtracts values.
return args.reduce((sum, val) => sum - val) return args.reduce((sum, val) => sum - val)
} }
this.mul = (...args) => { this.mul = (...args) => { // Multiplies values.
return args.reduce((sum, val) => sum * val) return args.reduce((sum, val) => sum * val)
} }
this.div = (...args) => { this.div = (...args) => { // Divides values.
return args.reduce((sum, val) => sum / val) return args.reduce((sum, val) => sum / val)
} }
this.mod = (a, b) => { this.mod = (a, b) => { // Returns the modulo of a and b.
return a % b return a % b
} }
this.clamp = (val, min, max) => { this.clamp = (val, min, max) => { // Clamps a value between min and max.
return Math.min(max, Math.max(min, val)) return Math.min(max, Math.max(min, val))
} }
@ -298,7 +117,183 @@ function Library (ronin) {
return Math.random() return Math.random()
} }
// Generics // Logic
this.gt = (a, b) => { // Returns true if a is greater than b, else false.
return a > b
}
this.lt = (a, b) => { // Returns true if a is less than b, else false.
return a < b
}
this.eq = (a, b) => { // Returns true if a is equal to b, else false.
return a === b
}
this.and = (a, b, ...rest) => { // Returns true if all conditions are true.
let args = [a, b].concat(rest)
for (let i = 0; i < args.length; i++) {
if (!args[i]) {
return args[i]
}
}
return args[args.length - 1]
}
this.or = (a, b, ...rest) => { // Returns true if at least one condition is true.
let args = [a, b].concat(rest)
for (let i = 0; i < args.length; i++) {
if (args[i]) {
return args[i]
}
}
return args[args.length - 1]
}
// Arrays
this.map = async (fn, arr) => {
return Promise.all(arr.map(fn))
}
this.filter = (fn, arr) => {
const list = Array.from(arr)
return Promise.all(list.map((element, index) => fn(element, index, list)))
.then(result => {
return list.filter((_, index) => {
return result[index]
})
})
}
this.reduce = (fn, arr, acc = 0) => {
return arr.reduce(fn, acc)
}
this.len = (item) => { // Returns the length of a list.
return item.length
}
this.first = (arr) => { // Returns the first item of a list.
return arr[0]
}
this.last = (arr) => { // Returns the last
return arr[arr.length - 1]
}
this.rest = ([_, ...arr]) => {
return arr
}
this.range = (start, end, step = 1) => {
let arr = []
if (step > 0) {
for (let i = start; i <= end; i += step) {
arr.push(i)
}
} else {
for (let i = start; i >= end; i += step) {
arr.push(i)
}
}
return arr
}
// Objects
this.get = (item, key) => { // Gets an object's parameter with name.
return item[key]
}
this.set = (item, key, val) => { // Sets an object's parameter with name as value.
item[key] = val
return item[key]
}
// Frame
this.frame = () => { // Returns a rect of the frame.
return ronin.surface.getFrame()
}
this.center = () => { // Returns a position of the center of the frame.
const rect = this.frame()
return this.pos(rect.w / 2, rect.h / 2)
}
this.scale = (rect, w, h) => {
return { x: rect.x, y: rect.y, w: rect.w * w, h: rect.h * h }
}
this.resize = async (w = 1, h = 1) => {
const rect = w <= 1 || h <= 1 ? { x: 0, y: 0, w: this.frame().w * w, h: this.frame().h * h } : { x: 0, y: 0, w, h }
const a = document.createElement('img')
const b = document.createElement('img')
a.src = ronin.surface.el.toDataURL()
ronin.surface.resizeImage(a, b)
ronin.surface.resize(rect, true)
return ronin.surface.draw(b, rect)
}
this.crop = async (rect) => {
return ronin.surface.crop(rect)
}
// Copy/Paste
this.clone = (a, b) => {
ronin.surface.clone(a, b)
return [a, b]
}
// TODO: Should remove (of) for (get)?
this.of = (h, ...keys) => {
return keys.reduce((acc, key) => {
return acc[key]
}, h)
}
this.theme = (variable, el = document.documentElement) => {
// ex. (theme "f_main") -> :root { --f_main: "#fff" }
return getComputedStyle(el).getPropertyValue(`--${variable}`)
}
// Gradients
this.gradient = ([x1, y1, x2, y2], colors = ['white', 'black']) => {
return ronin.surface.linearGradient(x1, y1, x2, y2, colors)
}
// Pixels
this.pixels = (rect, fn, q) => {
const img = ronin.surface.context.getImageData(0, 0, rect.w, rect.h)
for (let i = 0, loop = img.data.length; i < loop; i += 4) {
const pixel = { r: img.data[i], g: img.data[i + 1], b: img.data[i + 2], a: img.data[i + 3] }
const processed = fn(pixel, q)
img.data[i] = processed[0]
img.data[i + 1] = processed[1]
img.data[i + 2] = processed[2]
img.data[i + 3] = processed[3]
}
ronin.surface.context.putImageData(img, 0, 0)
return rect
}
this.saturation = (pixel, q = 1) => {
const color = 0.2126 * pixel.r + 0.7152 * pixel.g + 0.0722 * pixel.b
return [(color * (1 - q)) + (pixel.r * q), (color * (1 - q)) + (pixel.g * q), (color * (1 - q)) + (pixel.b * q), pixel.a]
}
this.contrast = (pixel, q = 1) => {
const intercept = 128 * (1 - q)
return [pixel.r * q + intercept, pixel.g * q + intercept, pixel.b * q + intercept, pixel.a]
}
// Misc
this.echo = (...args) => { this.echo = (...args) => {
ronin.log(args) ronin.log(args)
@ -309,26 +304,39 @@ function Library (ronin) {
return args.reduce((acc, val) => { return acc + val }, '') return args.reduce((acc, val) => { return acc + val }, '')
} }
this.open = async (path) => { // Imports a graphic file and resizes the frame.
return ronin.surface.open(path)
}
this.folder = (path = ronin.source.path) => { // Returns the content of a folder path.
return fs.existsSync(path) ? fs.readdirSync(path) : []
}
this.exit = (force = false) => { // Exits Ronin.
ronin.source.quit(force)
}
// Client
this.ronin = ronin
// Livecoding
this.time = () => { // Returns timestamp in milliseconds.
return Date.now
}
this.animate = (play = true) => { // Toggles animation.
ronin.animate(play)
}
// javascript interop
this.js = window
this.test = (name, a, b) => { this.test = (name, a, b) => {
if (Array.isArray(a)) { if (`${a}` !== `${b}`) {
// TODO: make testing more solid
a = a.toString()
b = b.toString()
}
if (a !== b) {
console.warn('failed ' + name, a, b) console.warn('failed ' + name, a, b)
} else { } else {
console.log('passed ' + name, a) console.log('passed ' + name, a)
} }
return a === b return a === b
} }
// Livecoding
this.time = Date.now
// javascript interop
this.js = window
// Client
this.ronin = ronin
} }

View File

@ -20,9 +20,8 @@ function Lisp (input, lib) {
const special = { const special = {
include: (input, context) => { include: (input, context) => {
const p = input[1].value if (!input[1].value || !fs.existsSync(input[1].value)) { console.warn('Source', input[1].value); return [] }
if (!fs.existsSync(p)) { console.warn('Source', p); return [] } const file = fs.readFileSync(input[1].value, { encoding: 'utf-8' })
const file = fs.readFileSync(p, { encoding: 'utf-8' })
return interpret(this.parse(file), context) return interpret(this.parse(file), context)
}, },
let: function (input, context) { let: function (input, context) {

View File

@ -52,7 +52,6 @@ function Ronin () {
} }
this.log = function (...msg) { this.log = function (...msg) {
console.log(...msg)
this.commander.setStatus(msg.reduce((acc, val) => { return acc + val + ' ' }, '')) this.commander.setStatus(msg.reduce((acc, val) => { return acc + val + ' ' }, ''))
} }

View File

@ -15,6 +15,7 @@ function Source (ronin) {
console.log('Source', 'Make a new file..') console.log('Source', 'Make a new file..')
this.path = null this.path = null
ronin.surface.clear() ronin.surface.clear()
ronin.log(`New file.`)
} }
this.open = function () { this.open = function () {
@ -57,6 +58,7 @@ function Source (ronin) {
if (quitAfter === true) { if (quitAfter === true) {
app.exit() app.exit()
} }
ronin.log(`Writing file.`)
} }
this.read = function (loc = this.path) { this.read = function (loc = this.path) {
@ -65,6 +67,7 @@ function Source (ronin) {
console.log('Source', 'Reading ' + loc) console.log('Source', 'Reading ' + loc)
this.path = loc this.path = loc
this.load(fs.readFileSync(this.path, 'utf8')) this.load(fs.readFileSync(this.path, 'utf8'))
ronin.log(`Reading file.`)
} }
this.run = function () { this.run = function () {
@ -75,8 +78,8 @@ function Source (ronin) {
ronin.commander._input.value = data ronin.commander._input.value = data
} }
this.quit = function () { this.quit = function (force = false) {
if (this.hasChanges() === true) { if (this.hasChanges() === true && force === false) {
this.verify() this.verify()
} else { } else {
app.exit() app.exit()
@ -144,7 +147,7 @@ function Source (ronin) {
} }
this.toString = function () { this.toString = function () {
return this.path ? this.name() : 'unsaved' return this.path ? this.name() + '.lisp' : 'unsaved'
} }
function isDifferent (a, b) { function isDifferent (a, b) {

View File

@ -61,6 +61,15 @@ function Surface (ronin) {
context.closePath() context.closePath()
} }
this.linearGradient = function (x1, y1, x2, y2, colors, context = this.context) {
const gradient = context.createLinearGradient(x1, y1, x2, y2)
const step = 1 / (colors.length - 1)
colors.forEach((color, i) => {
gradient.addColorStop(i * step, color)
})
return gradient
}
// Tracers // Tracers
this.trace = function (shape, context) { this.trace = function (shape, context) {

159
documentation.md Normal file
View File

@ -0,0 +1,159 @@
# Functions
## IO
`(open path)`
`(export path type quality)`
`(draw path rect)`
`(resize width height)`
`(crop rect)`
`(folder path)`
`(exit)`
## Logic
`(gt a b)` check if `a` is greater than `b`
`(lt a b)` check if `a` is lower than `b`
`(eq a b)` check if `a` is equal to `b`
`(and a b <c d...>)` returns true if all conditions are true
`(or a b <cd...>)` returns true if at least one condition is true
## Arrays
`(map function array)`
`(filter function array)`
`(reduce function array accumulator)`
`(len array)`
`(first array)`
`(last array)`
`(rest array)`
`(range start end step)`
## Shapes
`(pos x y)`
`(size w h)`
`(rect x y w h t)`
`(circle x y r)`
`(line start end)`
`(text x y g string font)`
`(svg data)`
## Helpers
`(frame)`
`(center)`
`(scale rect width height)`
## Copy/Paste
`(clone start end)` clone start `rect` into end `rect`
`(stroke shape thickness color)`
`(fill shape color)`
`(clear shape)`
## Objects
`(get item key <keys>)`
`(set item key val)`
## Colors
`(theme variable)`
`(gradient (x1,y1,x2,y2) colors)`
`(pixels rect function q)`
`(saturation pixel q)`
`(contrast pixel q)`
## Math
`(add ...values)`
`(sub...values)`
`(mul ...values)`
`(div ...values)`
`(mod a b)`
`(clamp value min max)`
`(step value step)`
`(min a b)`
`(max a b)`
`(ceil value)`
`(floor value)`
`(sin a)`
`(cos a)`
`PI, TWO_PI`
`(random)`
`(random start end)`
`(random max)`
## Generics
`(echo args)`
`(str args)`
`(test name value expectedValue)`
## Livecoding
`(time)` returns timestamp in milliseconds
`(animate)` start animation
`(animate false)` stop animation
## Javascript interop
`js`
## Client
`ronin`

View File

@ -1,13 +1,12 @@
; animate ; animate
( (
(def start (get ronin "animate"))
(def t (sin (div (time) 100))) (def t (sin (div (time) 100)))
(def pos (add 200 (mul 30 t))) (def pos (add 200 (mul 30 t)))
(defn square (a) (rect a a a a)) (defn square (a) (rect a a a a))
(stroke (square pos) 1 "red") (stroke (square pos) 1 "red")
(start) (animate)
;(animate false) to stop animation
) )

29
examples/g_spiral1.lisp Normal file
View File

@ -0,0 +1,29 @@
; animated recusive spiral
; by @local_guru
(
(def start (get ronin "animate"))
(clear)
(defn rec
(v)
(if (gt v 0)
((stroke
(circle
(add 300
(mul (cos (add (div v 17) (div (time) 2000)))
(div v 2)
)
)
(add 300
(mul (sin (div v 11))
(div v 2)
)
)
(div v 2))
1 "rgba(255,255,255,0.1")
(rec (sub v 0.3))
)
)
)
(start)
(rec 300)
)

View File

@ -17,7 +17,7 @@
; Draw photo ; Draw photo
(draw (import
"../static/crystal.jpg" "../static/crystal.jpg"
(rect 0 0 400 400)) (rect 0 0 400 400))

16
examples/gradient.lisp Normal file
View File

@ -0,0 +1,16 @@
(
; gradients
(clear)
(fill
(svg "M405,15 L405,15 L150,150 L195,90 L240,135 L120,195 L75,90 L135,165 L120,225 L90,240 L60,210 L90,150 L255,180 L285,180 L285,165 ")
(gradient
(0 -50 600 175)
("red" "orange" "blue" "green")))
(stroke
(svg "M255,60 L255,60 L135,180 L75,60 L195,210 L120,225 L105,225 L165,255 L225,195 L255,135 L285,150") 1
(gradient
(50 0 180 0)
("black" "white" "blue" "green")))
)

View File

@ -1,36 +1,57 @@
; guides file ; guides
(
((clear) (clear)
(stroke (frame) 1 "red") (stroke
(frame) 1 "red")
(stroke (stroke
(line (line
(pos 0 0) (pos 0 0)
(pos (of (frame) "w") (of (frame) "h"))) (pos
1 "red") (of
(frame) "w")
(of
(frame) "h"))) 1 "red")
(stroke (stroke
(line (line
(pos (of (frame) "w") 0) (pos
(pos 0 (of (frame) "h"))) (of
1 "red") (frame) "w") 0)
(pos 0
(of
(frame) "h"))) 1 "red")
(stroke (stroke
(line (line
(pos (div (of (frame) "w") 2) 0) (pos
(pos (div (of (frame) "w") 2) (of (frame) "h"))) (div
1 "red") (of
(frame) "w") 2) 0)
(pos
(div
(of
(frame) "w") 2)
(of
(frame) "h"))) 1 "red")
(stroke (stroke
(line (line
(pos 0 (div (of (frame) "h") 2)) (pos 0
(pos (div (of (frame) "w") 2) (of (frame) "h"))) (div
1 "#72dec2") (of
(frame) "h") 2))
(pos
(div
(of
(frame) "w") 2)
(of
(frame) "h"))) 1 "#72dec2")
(stroke (stroke
(line (line
(pos (div (of (frame) "w") 2) 0) (pos
(pos (of (frame) "w") (div (of (frame) "h") 2))) (div
1 "#72dec2") (of
) (frame) "w") 2) 0)
(pos
(of
(frame) "w")
(div
(of
(frame) "h") 2))) 1 "#72dec2"))

4
examples/include.lisp Normal file
View File

@ -0,0 +1,4 @@
; include
(
(include "../examples/recursive.lisp")
(echo line-color))

View File

@ -1,12 +1,7 @@
; pixels ; pixels
( (
(clear) (clear)
(draw (import "../../PREVIEW.jpg"
"../../PREVIEW.jpg"
(frame)) (frame))
(pixels (pixels
(rect 0 0 500 500) (rect 0 0 500 500) saturation 0.5))
saturation
0.5)
)

View File

@ -1,21 +1,17 @@
; random ; random
( (
(clear) (clear)
(defn place (defn place
(rec) (rec)
(if (gt rec 0) (if
(gt rec 0)
( (
(draw "../static/crystal.jpg" (import "../static/crystal.jpg"
(rect (rect
(random 200) (random 200)
(random 200) (random 200)
(random 200) (random 200)
(random 200))) (random 200)))
(place (sub rec 1)) (place
)) (sub rec 1)))))
) (place 30))
(place 30)
)

View File

@ -1,17 +1,16 @@
; recursive ; recursive
( (
(clear) (clear)
(def line-color "red")
(defn rec (defn rec
(v) (v)
(if (gt v 0) (if
((stroke (circle (gt v 0)
(mul 5 v) (
(mul 5 v) (stroke
(mul 5 v)) 1 line-color) (circle
(rec (sub v 5)))) (mul 5 v)
) (mul 5 v)
(mul 5 v)) 1 "red")
(rec 100) (rec
) (sub v 5)))))
(rec 100))

View File

@ -1,7 +1,5 @@
; resize ; resize
( (
(clear) (clear)
(open "../../PREVIEW.jpg") (open "../../PREVIEW.jpg")
(resize 0.5 0.5) (resize 0.5 0.5))
)

View File

@ -1,4 +0,0 @@
(
(include "../examples/recursive.lisp")
(echo line-color)
)

View File

@ -1,5 +1,8 @@
((fill (
(svg "M255,60 L255,60 L135,180 L75,60 L195,210 L120,225 L105,225 L165,255 L225,195 L255,135 L285,150") "white") (clear)
; ronin path
(stroke (stroke
(svg "M405,15 L405,15 L150,150 L195,90 L240,135 L120,195 L75,90 L135,165 L120,225 L90,240 L60,210 L90,150 L255,180 L285,180 L285,165 ") "pink")) (svg "M60,60 L195,60 A45,45 0 0,1 240,105 A45,45 0 0,1 195,150 L60,150 M195,150 A45,45 0 0,1 240,195 L240,240 ") 2 "white")
; outline
(stroke
(svg "M15,15 L15,15 L285,15 L285,285 L15,285 Z") 1 "#555"))

View File

@ -1,30 +1,38 @@
((clear) ; theme
(def col (
(lambda (clear)
(i) (def col
(of (lambda
((theme "f_high") (i)
(theme "f_med") (of
(theme "f_low") (
(theme "f_inv") (theme "f_high")
(theme "b_high") (theme "f_med")
(theme "b_med") (theme "f_low")
(theme "b_low") (theme "f_inv")
(theme "b_inv")) (theme "b_high")
(mod i 8)))) (theme "b_med")
(def rec (theme "b_low")
(lambda (theme "b_inv"))
(v i) (mod i 8))))
(if (gt v 0) (def rec
((fill (lambda
(circle (v i)
(add (if
(div (of (frame) "w") 1.6) (gt v 0)
(mul 1.5 v)) (
(mul 10 v) (fill
(mul v (div v 5))) (circle
(col i)) (add
(rec (div
(sub v 3) (of
(add i 1)))))) (frame) "w") 1.6)
(rec 40 0)) (mul 1.5 v))
(mul 10 v)
(mul v
(div v 5)))
(col i))
(rec
(sub v 3)
(add i 1))))))
(rec 40 0))