401 lines
10 KiB
JavaScript
401 lines
10 KiB
JavaScript
function Library (ronin) {
|
|
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.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,/, '').replace(/^data:image\/jpeg;base64,/, '')
|
|
fs.writeFileSync(path, data, 'base64')
|
|
return path
|
|
}
|
|
|
|
this.open = async (path) => { // Imports a graphic file and resizes the frame.
|
|
return ronin.surface.open(path)
|
|
}
|
|
|
|
// Shapes
|
|
|
|
this.pos = (x, y, t = 'pos') => { // Returns a position shape.
|
|
return { x, y, t }
|
|
}
|
|
|
|
this.size = (w, h, t = 'size') => { // Returns a size shape.
|
|
return { w, h, t }
|
|
}
|
|
|
|
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') => { // Returns a circle shape.
|
|
return { x, y, r, t }
|
|
}
|
|
|
|
this.line = (a, b, t = 'line') => { // Returns a line shape.
|
|
return { a, b, t }
|
|
}
|
|
|
|
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') => { // Returns a svg shape.
|
|
return { d, t }
|
|
}
|
|
|
|
// Actions
|
|
|
|
this.stroke = (shape = this.frame(), thickness, color) => { // Strokes a shape.
|
|
ronin.surface.stroke(shape, thickness, color)
|
|
return shape
|
|
}
|
|
|
|
this.fill = (rect = this.frame(), color) => { // Fills a shape.
|
|
ronin.surface.fill(rect, color)
|
|
return rect
|
|
}
|
|
|
|
this.clear = (rect = this.frame()) => { // Clears a rect.
|
|
ronin.surface.clear(rect)
|
|
return rect
|
|
}
|
|
|
|
// Strings
|
|
|
|
this.concat = function (...items) { // Concat multiple strings.
|
|
return items.reduce((acc, item) => { return `${acc}${item}` }, '')
|
|
}
|
|
|
|
// Math
|
|
|
|
this.add = (...args) => { // Adds values.
|
|
return args.reduce((sum, val) => sum + val)
|
|
}
|
|
|
|
this.sub = (...args) => { // Subtracts values.
|
|
return args.reduce((sum, val) => sum - val)
|
|
}
|
|
|
|
this.mul = (...args) => { // Multiplies values.
|
|
return args.reduce((sum, val) => sum * val)
|
|
}
|
|
|
|
this.div = (...args) => { // Divides values.
|
|
return args.reduce((sum, val) => sum / val)
|
|
}
|
|
|
|
this.mod = (a, b) => { // Returns the modulo of a and b.
|
|
return a % b
|
|
}
|
|
|
|
this.clamp = (val, min, max) => { // Clamps a value between min and max.
|
|
return Math.min(max, Math.max(min, val))
|
|
}
|
|
|
|
this.step = (val, step) => {
|
|
return Math.round(val / step) * step
|
|
}
|
|
|
|
this.min = Math.min
|
|
|
|
this.max = Math.max
|
|
|
|
this.ceil = Math.ceil
|
|
|
|
this.floor = Math.floor
|
|
|
|
this.sin = Math.sin
|
|
|
|
this.cos = Math.cos
|
|
|
|
this.PI = Math.PI
|
|
|
|
this.TWO_PI = Math.PI * 2
|
|
|
|
this.random = (...args) => {
|
|
if (args.length >= 2) {
|
|
// (random start end)
|
|
return args[0] + Math.random() * (args[1] - args[0])
|
|
} else if (args.length === 1) {
|
|
// (random max)
|
|
return Math.random() * args[0]
|
|
}
|
|
return Math.random()
|
|
}
|
|
|
|
// 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) => { // Gets object parameters with names.
|
|
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.resize = async (w, h, fit = true) => { // 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()
|
|
await ronin.surface.resizeImage(a, b)
|
|
ronin.surface.resize(rect, fit)
|
|
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()
|
|
await ronin.surface.resizeImage(a, b)
|
|
ronin.surface.resize(rect, true)
|
|
return ronin.surface.draw(b, rect)
|
|
}
|
|
|
|
this.crop = async (rect) => { // Crop canvas to 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(rect.x, rect.y, 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.set(processed, i);
|
|
}
|
|
ronin.surface.context.putImageData(img, rect.x, rect.y)
|
|
return rect
|
|
}
|
|
|
|
this.saturation = (pixel, q = 1) => {
|
|
const color = 0.2126 * pixel.r + 0.7152 * pixel.g + 0.0722 * pixel.b * (1 - q)
|
|
return [color + (pixel.r * q), color + (pixel.g * q), color + (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]
|
|
}
|
|
|
|
// Convolve
|
|
|
|
this.convolve = (rect, kernel) => {
|
|
const sigma = kernel.flat().reduce((a, x) => (a + x))
|
|
const kw = kernel[0].length, kh = kernel.length
|
|
const img = ronin.surface.context.getImageData(rect.x, rect.y, rect.w, rect.h)
|
|
const out = new Uint8ClampedArray(rect.w * 4 * rect.h)
|
|
for (let i = 0, outer = img.data.length; i < outer; i++) { // bytes
|
|
const ix = Math.floor(i/4) % rect.w, iy = Math.floor((i/4) / rect.w)
|
|
let acc = 0.0
|
|
for (let k = 0, inner = kw*kh; k < inner; k++) { // kernel
|
|
const kx = (k%kw), ky = (Math.floor(k/kw))
|
|
const x = Math.ceil(ix + kx - kw/2), y = Math.ceil(iy + ky - kh/2)
|
|
if( x < 0 || x >= rect.w || y < 0 || y >= rect.h ) continue; // edge case
|
|
acc += img.data[x*4 + y*rect.w*4 + i%4] * kernel[kx][ky] / sigma
|
|
}
|
|
out[i] = acc
|
|
if (i%4 == 3) out[i] = 255
|
|
}
|
|
img.data.set(out, 0)
|
|
ronin.surface.context.putImageData(img, rect.x, rect.y)
|
|
return rect
|
|
}
|
|
|
|
this.blur = [[1, 2, 1],
|
|
[2, 4, 2],
|
|
[1, 2, 2]]
|
|
|
|
this.sharpen = [[ 0, -1, 0],
|
|
[-1, 5, -1],
|
|
[ 0, -1, 0]]
|
|
|
|
this.edge = [[-1, -1, -1],
|
|
[-1, 9, -1],
|
|
[-1, -1, -1]]
|
|
|
|
// File System
|
|
|
|
this.dir = (path = this.dirpath()) => { // Returns the content of a directory.
|
|
return fs.existsSync(path) ? fs.readdirSync(path) : []
|
|
}
|
|
|
|
this.file = (path = this.filepath()) => { // Returns the content of a file.
|
|
return fs.existsSync(path) ? fs.readFileSync(path, 'utf8') : ''
|
|
}
|
|
|
|
this.dirpath = (path = this.filepath()) => { // Returns the path of a directory.
|
|
return require('path').dirname(path)
|
|
}
|
|
|
|
this.filepath = (path = ronin.source.path) => { // Returns the path of a file.
|
|
return path
|
|
}
|
|
|
|
this.exit = (force = false) => { // Exits Ronin.
|
|
ronin.source.quit(force)
|
|
}
|
|
|
|
this.echo = (...args) => {
|
|
ronin.log(args)
|
|
return args
|
|
}
|
|
|
|
this.time = (rate = 1) => { // Returns timestamp in milliseconds.
|
|
return (Date.now() * rate)
|
|
}
|
|
|
|
this.animate = (play = true) => { // Toggles animation.
|
|
ronin.animate(play)
|
|
}
|
|
|
|
this.js = () => { // Javascript interop.
|
|
return window
|
|
}
|
|
|
|
this.test = (name, a, b) => {
|
|
if (`${a}` !== `${b}`) {
|
|
console.warn('failed ' + name, a, b)
|
|
} else {
|
|
console.log('passed ' + name, a)
|
|
}
|
|
return a === b
|
|
}
|
|
|
|
this.benchmark = async (fn) => { // logs time taken to execute a function.
|
|
const start = Date.now()
|
|
const result = await fn()
|
|
console.log(`time taken: ${Date.now() - start}ms`)
|
|
return result
|
|
}
|
|
}
|