Merge branch 'master' of github.com:hundredrabbits/Ronin

This commit is contained in:
Quentin Leonetti 2019-07-18 10:59:32 +02:00
commit 533c4d3f27
20 changed files with 574 additions and 394 deletions

View File

@ -1,22 +1,89 @@
# 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 start
```
## Documentation
<img src='https://raw.githubusercontent.com/hundredrabbits/Ronin/master/PREVIEW.jpg' width='600'/>
Documentation is accessible [here](./documentation.md)
## 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.
- `(of h ...keys)`
- `(frame)` Returns a rect of the frame.
- `(center)` Returns a position of the center of the frame.
- `(scale rect w h)`
- `(resize w h)` Resizes the canvas to target w and h, returns the rect.
- `(rescale w h)` Rescales the canvas to target ratio of w and h, returns the rect.
- `(crop rect)`
- `(clone a b)`
- `(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.
- `(time)` Returns timestamp in milliseconds.
- `(animate ~play)` Toggles animation.
- `(js)` Javascript interop.
- `(test name a b)`
## Extras

View File

@ -40,6 +40,7 @@
ronin.controller.addRole('default', 'Edit', 'paste')
ronin.controller.addRole('default', 'Edit', 'delete')
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 Out",() => { ronin.modZoom(-0.25) },"CmdOrCtrl+-")
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 #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 textarea { background: none; width: 100%; height: calc(100vh - 80px); resize: none; font-size: 12px;line-height: 15px; padding-right: 15px}
#ronin #wrapper #commander div#status { position: absolute; bottom: 0px; }
#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; text-transform: lowercase;}
#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#guide { background:none; }
#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._status = document.createElement('div')
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.install = function (host) {
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)
host.appendChild(this.el)
this._input.addEventListener('input', this.onInput)
this._input.addEventListener('click', this.onClick)
this.docs.install()
}
this.start = function () {
this._status.textContent = 'Idle. (zoom 100%)'
this.setStatus('Ready.')
this._input.focus()
this.run()
this.hide()
@ -33,21 +43,59 @@ function Commander (ronin) {
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) {
if (!msg) { return }
this._status.textContent = `${(msg + '').substr(0, 40)}`
// Logs
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.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
@ -62,7 +110,6 @@ function Commander (ronin) {
this.mouseRect.a.x = e.offsetX
this.mouseRect.a.y = e.offsetY
this.mouseRect.t = 'pos'
this._status.textContent = `${this.mouseRect.x},${this.mouseRect.y} ${this.mouseRect.w},${this.mouseRect.h}`
this.capture()
this.show()
}
@ -73,7 +120,6 @@ function Commander (ronin) {
this.mouseRect.h = e.offsetY - this.mouseRect.y
this.mouseRect.b.x = e.offsetX
this.mouseRect.b.y = e.offsetY
this._status.textContent = `${this.mouseRect.x},${this.mouseRect.y} ${this.mouseRect.w},${this.mouseRect.h}`
this.commit()
}
}
@ -85,7 +131,6 @@ function Commander (ronin) {
this.mouseRect.b.x = e.offsetX
this.mouseRect.b.y = e.offsetY
this.mouseRect.t = ''
this._status.textContent = `${this.mouseRect.x},${this.mouseRect.y} ${this.mouseRect.w},${this.mouseRect.h}`
this.commit()
this._input.focus()
ronin.surface.clearGuide()
@ -162,4 +207,44 @@ function Commander (ronin) {
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,278 +1,88 @@
function Library (ronin) {
console.log(ronin)
this.open = async (path) => {
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) => {
this.import = async (path, rect) => { // Imports a graphic file with format.
const img = new Image()
img.src = path
return ronin.surface.draw(img, rect)
}
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)
}
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) => {
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 = async (fn, arr, acc) => {
const length = arr.length
let result = acc === undefined ? subject[0] : acc
for (let i = acc === undefined ? 1 : 0; i < length; i++) {
result = await fn(result, arr[i], i, arr)
}
return result;
}
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
this.export = (path, format = 'image/png', quality = 1.0) => { // Exports a graphic file with format.
if (!path) { console.warn('Missing export path'); return path }
var dataUrl = ronin.surface.el.toDataURL(format, quality)
const data = dataUrl.replace(/^data:image\/png;base64,/, '')
fs.writeFileSync(path, data, 'base64')
return path
}
// Shapes
this.pos = (x, y, t = 'pos') => {
this.pos = (x, y, t = 'pos') => { // Returns a position shape.
return { x, y, t }
}
this.size = (w, h, t = 'size') => {
this.size = (w, h, t = 'size') => { // Returns a size shape.
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 }
}
this.circle = (x, y, r, t = 'circle') => {
this.circle = (x, y, r, t = 'circle') => { // Returns a circle shape.
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 }
}
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 }
}
this.svg = (d, t = 'svg') => {
this.svg = (d, t = 'svg') => { // Returns a svg shape.
return { d, t }
}
// Helpers
// Actions
this.frame = () => {
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) => {
this.stroke = (shape = this.frame(), thickness, color) => { // Strokes a shape.
ronin.surface.stroke(shape, thickness, color)
return shape
}
this.fill = (rect = this.frame(), color) => {
this.fill = (rect = this.frame(), color) => { // Fills a shape.
ronin.surface.fill(rect, color)
return rect
}
this.clear = (rect = this.frame()) => {
this.clear = (rect = this.frame()) => { // Clears a rect.
ronin.surface.clear(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}`)
}
// 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]
}
// Math
this.add = (...args) => {
this.add = (...args) => { // Adds values.
return args.reduce((sum, val) => sum + val)
}
this.sub = (...args) => {
this.sub = (...args) => { // Subtracts values.
return args.reduce((sum, val) => sum - val)
}
this.mul = (...args) => {
this.mul = (...args) => { // Multiplies values.
return args.reduce((sum, val) => sum * val)
}
this.div = (...args) => {
this.div = (...args) => { // Divides values.
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
}
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))
}
@ -307,7 +117,194 @@ function Library (ronin) {
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 = async (fn, arr, acc) => {
const length = arr.length
let result = acc === undefined ? subject[0] : acc
for (let i = acc === undefined ? 1 : 0; i < length; i++) {
result = await fn(result, arr[i], i, arr)
}
return result;
}
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]
}
this.of = (h, ...keys) => {
return keys.reduce((acc, key) => {
return acc[key]
}, h)
}
// 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, h) => { // Resizes the canvas to target w and h, returns the rect.
const rect = { 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.rescale = async (w, h) => { // Rescales the canvas to target ratio of w and h, returns the rect.
const rect = { x: 0, y: 0, w: this.frame().w * w, h: this.frame().h * 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)
}
this.clone = (a, b) => {
ronin.surface.clone(a, b)
return [a, b]
}
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) => {
ronin.log(args)
@ -318,29 +315,36 @@ function Library (ronin) {
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)
}
this.time = () => { // Returns timestamp in milliseconds.
return Date.now
}
this.animate = (play = true) => { // Toggles animation.
ronin.animate(play)
}
this.js = () => { // Javascript interop.
return window
}
this.test = (name, a, b) => {
if (Array.isArray(a)) {
// TODO: make testing more solid
a = a.toString()
b = b.toString()
}
if (a !== b) {
if (`${a}` !== `${b}`) {
console.warn('failed ' + name, a, b)
} else {
console.log('passed ' + name, a)
}
return a === b
}
// Client
this.ronin = ronin
// Livecoding
this.time = Date.now
this.animate = (b = true) => ronin.animate(b)
// javascript interop
this.js = window
}

View File

@ -20,9 +20,8 @@ function Lisp (input, lib) {
const special = {
include: (input, context) => {
const p = input[1].value
if (!fs.existsSync(p)) { console.warn('Source', p); return [] }
const file = fs.readFileSync(p, { encoding: 'utf-8' })
if (!input[1].value || !fs.existsSync(input[1].value)) { console.warn('Source', input[1].value); return [] }
const file = fs.readFileSync(input[1].value, { encoding: 'utf-8' })
return interpret(this.parse(file), context)
},
let: function (input, context) {
@ -34,29 +33,21 @@ function Lisp (input, lib) {
},
def: function (input, context) {
const identifier = input[1].value
const value = (input[2].type === TYPES.string) ? input[3] : input[2]
if (input[2].type === TYPES.string) {
// docstring
console.log(input[2].value)
}
const value = input[2].type === TYPES.string && input[3] ? input[3] : input[2]
context.scope[identifier] = interpret(value, context)
return value
},
defn: function (input, context) {
const identifier = input[1].value
const argumentNames = (input[2].type === TYPES.string) ? input[3] : input[2]
const functionBody = (input[2].type === TYPES.string) ? input[4] : input[3]
if (input[2].type === TYPES.string) {
// docstring
console.log(input[2].value)
}
context.scope[identifier] = async function () {
const fnName = input[1].value
const fnParams = input[2].type === TYPES.string && input[3] ? input[3] : input[2]
const fnBody = input[2].type === TYPES.string && input[4] ? input[4] : input[3]
context.scope[fnName] = async function () {
const lambdaArguments = arguments
const lambdaScope = argumentNames.reduce(function (acc, x, i) {
const lambdaScope = fnParams.reduce(function (acc, x, i) {
acc[x.value] = lambdaArguments[i]
return acc
}, {})
return interpret(functionBody, new Context(lambdaScope, context))
return interpret(fnBody, new Context(lambdaScope, context))
}
},
lambda: function (input, context) {

View File

@ -15,7 +15,6 @@ function Ronin () {
this.el.id = 'ronin'
this.theme = new Theme(defaultTheme)
this.source = new Source(this)
this.commander = new Commander(this)
this.surface = new Surface(this)
@ -52,7 +51,6 @@ function Ronin () {
}
this.log = function (...msg) {
console.log(...msg)
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..')
this.path = null
ronin.surface.clear()
ronin.log(`New file.`)
}
this.open = function () {
@ -57,6 +58,7 @@ function Source (ronin) {
if (quitAfter === true) {
app.exit()
}
ronin.log(`Writing file.`)
}
this.read = function (loc = this.path) {
@ -65,6 +67,7 @@ function Source (ronin) {
console.log('Source', 'Reading ' + loc)
this.path = loc
this.load(fs.readFileSync(this.path, 'utf8'))
ronin.log(`Reading file.`)
}
this.run = function () {
@ -75,8 +78,8 @@ function Source (ronin) {
ronin.commander._input.value = data
}
this.quit = function () {
if (this.hasChanges() === true) {
this.quit = function (force = false) {
if (this.hasChanges() === true && force === false) {
this.verify()
} else {
app.exit()
@ -144,7 +147,7 @@ function Source (ronin) {
}
this.toString = function () {
return this.path ? this.name() : 'unsaved'
return this.path ? this.name() + '.lisp' : 'unsaved'
}
function isDifferent (a, b) {

View File

@ -61,11 +61,11 @@ function Surface (ronin) {
context.closePath()
}
this.linearGradient = function(x1, y1, x2, y2, colors, context = this.context) {
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)
const step = 1 / (colors.length - 1)
colors.forEach((color, i) => {
gradient.addColorStop(i * step, color)
})
return gradient
}
@ -159,6 +159,8 @@ function Surface (ronin) {
}
this.resize = function (size, fit = false) {
const frame = this.getFrame()
if (frame.w === size.w && frame.h === size.h) { return }
console.log('Surface', `Resize: ${size.w}x${size.h}`)
this.el.width = size.w
this.el.height = size.h
@ -173,10 +175,14 @@ function Surface (ronin) {
}
}
this.getFrame = function () {
return { x: 0, y: 0, w: this.el.width, h: this.el.height, t: 'rect' }
}
this.fitWindow = function (size) {
const win = require('electron').remote.getCurrentWindow()
const pad = { w: ronin.commander.isVisible === true ? 400 : 60, h: 60 }
win.setSize(size.w + pad.w, size.h + pad.h, false)
win.setSize(size.w + pad.w, size.h + pad.h, true)
}
this.maximize = function () {
@ -191,10 +197,6 @@ function Surface (ronin) {
ronin.log(`resize ${f.w}x${f.h}`)
}
this.getFrame = function () {
return { x: 0, y: 0, w: this.el.width, h: this.el.height, t: 'rect' }
}
this.getCrop = function (rect) {
const newCanvas = document.createElement('canvas')
newCanvas.width = rect.w
@ -203,7 +205,7 @@ function Surface (ronin) {
return newCanvas
}
this.resizeImage = function (src, dst, type = 'image/jpeg', quality = 0.92) {
this.resizeImage = function (src, dst, type = 'image/png', quality = 1.0) {
const tmp = new Image()
let canvas
let context

9
examples/basics.lisp Normal file
View File

@ -0,0 +1,9 @@
; basics
(
; define a variable
(def a 25)
(echo a)
; define a function
(defn add-two (a) (add 2 a))
(echo (add-two 4)))

View File

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

View File

@ -1,36 +1,57 @@
; guides file
((clear)
(stroke (frame) 1 "red")
; guides
(
(clear)
(stroke
(frame) 1 "red")
(stroke
(line
(pos 0 0)
(pos (of (frame) "w") (of (frame) "h")))
1 "red")
(pos
(of
(frame) "w")
(of
(frame) "h"))) 1 "red")
(stroke
(line
(pos (of (frame) "w") 0)
(pos 0 (of (frame) "h")))
1 "red")
(pos
(of
(frame) "w") 0)
(pos 0
(of
(frame) "h"))) 1 "red")
(stroke
(line
(pos (div (of (frame) "w") 2) 0)
(pos (div (of (frame) "w") 2) (of (frame) "h")))
1 "red")
(pos
(div
(of
(frame) "w") 2) 0)
(pos
(div
(of
(frame) "w") 2)
(of
(frame) "h"))) 1 "red")
(stroke
(line
(pos 0 (div (of (frame) "h") 2))
(pos (div (of (frame) "w") 2) (of (frame) "h")))
1 "#72dec2")
(pos 0
(div
(of
(frame) "h") 2))
(pos
(div
(of
(frame) "w") 2)
(of
(frame) "h"))) 1 "#72dec2")
(stroke
(line
(pos (div (of (frame) "w") 2) 0)
(pos (of (frame) "w") (div (of (frame) "h") 2)))
1 "#72dec2")
)
(pos
(div
(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
(
(clear)
(draw
"../../PREVIEW.jpg"
(import "../../PREVIEW.jpg"
(frame))
(pixels
(rect 0 0 500 500)
saturation
0.5)
)
(rect 0 0 500 500) saturation 0.5))

View File

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

View File

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

View File

@ -1,7 +1,5 @@
; resize
(
(clear)
(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")
(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"))
(
(clear)
; ronin path
(stroke
(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)
(def col
(lambda
(i)
(of
((theme "f_high")
(theme "f_med")
(theme "f_low")
(theme "f_inv")
(theme "b_high")
(theme "b_med")
(theme "b_low")
(theme "b_inv"))
(mod i 8))))
(def rec
(lambda
(v i)
(if (gt v 0)
((fill
(circle
(add
(div (of (frame) "w") 1.6)
(mul 1.5 v))
(mul 10 v)
(mul v (div v 5)))
(col i))
(rec
(sub v 3)
(add i 1))))))
(rec 40 0))
; theme
(
(clear)
(def col
(lambda
(i)
(of
(
(theme "f_high")
(theme "f_med")
(theme "f_low")
(theme "f_inv")
(theme "b_high")
(theme "b_med")
(theme "b_low")
(theme "b_inv"))
(mod i 8))))
(def rec
(lambda
(v i)
(if
(gt v 0)
(
(fill
(circle
(add
(div
(of
(frame) "w") 1.6)
(mul 1.5 v))
(mul 10 v)
(mul v
(div v 5)))
(col i))
(rec
(sub v 3)
(add i 1))))))
(rec 40 0))