BARS.defineActions(function() { function getTextures() { if (Texture.selected) { return [Texture.selected]; } else { return Texture.all; } } let show_preview = true; new Action('invert_colors', { icon: 'invert_colors', category: 'textures', condition: {modes: ['paint'], method: () => Texture.all.length}, click() { let textures = getTextures(); Undo.initEdit({textures, bitmap: true}); textures.forEach(texture => { texture.edit((canvas) => { let ctx = canvas.getContext('2d'); ctx.clearRect(0, 0, texture.width, texture.height); ctx.filter = 'invert(1)'; ctx.drawImage(texture.img, 0, 0); }, {no_undo: true}); }) Undo.finishEdit('Invert colors') } }) new Action('adjust_brightness_contrast', { icon: 'brightness_high', category: 'textures', condition: {modes: ['paint'], method: () => Texture.all.length}, click() { let textures = getTextures(); let original_imgs = textures.map(tex => { return tex.img.cloneNode(); }) Undo.initEdit({textures, bitmap: true}); new Dialog({ id: 'adjust_brightness_contrast', title: 'action.adjust_brightness_contrast', darken: false, component: { data() {return { show_preview, brightness: 100, contrast: 100, textures }}, methods: { change() { textures.forEach((texture, i) => { texture.edit((canvas) => { let ctx = canvas.getContext('2d'); ctx.clearRect(0, 0, texture.width, texture.height); ctx.filter = `brightness(${this.brightness / 100}) contrast(${this.contrast / 100})`; ctx.drawImage(original_imgs[i], 0, 0); let ref_ctx = this.$refs.canvas[i].getContext('2d'); ref_ctx.clearRect(0, 0, texture.width, texture.height); ref_ctx.drawImage(canvas, 0, 0); }, {no_undo: true, use_cache: true}); }) }, togglePreview() { this.show_preview = show_preview = !this.show_preview; } }, template: `
{{ show_preview ? 'expand_more' : 'expand_less' }}
`, mounted() { textures.forEach((texture, i) => { let ref_ctx = this.$refs.canvas[i].getContext('2d'); ref_ctx.clearRect(0, 0, texture.width, texture.height); ref_ctx.drawImage(texture.img, 0, 0); }) } }, onConfirm() { Undo.finishEdit('Adjust brightness and contrast'); }, onCancel() { Undo.cancelEdit(); } }).show(); } }) new Action('adjust_saturation_hue', { icon: 'fa-tint', category: 'textures', condition: {modes: ['paint'], method: () => Texture.all.length}, click() { let textures = getTextures(); let original_imgs = textures.map(tex => { return tex.img.cloneNode(); }) Undo.initEdit({textures, bitmap: true}); new Dialog({ id: 'adjust_saturation_hue', title: 'action.adjust_saturation_hue', darken: false, component: { data() {return { show_preview, saturation: 100, hue: 0, textures }}, methods: { change() { textures.forEach((texture, i) => { texture.edit((canvas) => { let ctx = canvas.getContext('2d'); ctx.clearRect(0, 0, texture.width, texture.height); ctx.filter = `saturate(${this.saturation / 100}) hue-rotate(${this.hue}deg)`; ctx.drawImage(original_imgs[i], 0, 0); let ref_ctx = this.$refs.canvas[i].getContext('2d'); ref_ctx.clearRect(0, 0, texture.width, texture.height); ref_ctx.drawImage(canvas, 0, 0); }, {no_undo: true, use_cache: true}); }) }, togglePreview() { this.show_preview = show_preview = !this.show_preview; } }, template: `
{{ show_preview ? 'expand_more' : 'expand_less' }}
`, mounted() { textures.forEach((texture, i) => { let ref_ctx = this.$refs.canvas[i].getContext('2d'); ref_ctx.clearRect(0, 0, texture.width, texture.height); ref_ctx.drawImage(texture.img, 0, 0); }) } }, onConfirm() { Undo.finishEdit('Adjust saturation and hue'); }, onCancel() { Undo.cancelEdit(); } }).show(); } }) new Action('adjust_curves', { icon: 'fa-chart-line', category: 'textures', condition: {modes: ['paint'], method: () => Texture.all.length}, click() { let textures = getTextures(); let original_image_data = textures.map(tex => { let canvas = Painter.getCanvas(tex); let image_data = canvas.getContext('2d').getImageData(0, 0, canvas.width, canvas.height); image_data.original_data = image_data.data.slice(); return image_data; }) Undo.initEdit({textures, bitmap: true}); let image_data = original_image_data[0]; let light_points = {}; let highest_light_point = 0; for (let i = 0; i < 256; i++) { light_points[i] = 0; } for (let i = 0; i < image_data.data.length; i += 4) { let R = image_data.data[i+0]; let G = image_data.data[i+1]; let B = image_data.data[i+2]; let A = image_data.data[i+3]; if (A == 0) continue; let brightness = (0.2126*R + 0.7152*G + 0.0722*B); light_points[Math.round(brightness)] += 1; highest_light_point = Math.max(highest_light_point, light_points[Math.round(brightness)]); } let curves = { rgb: null, r: null, g: null, b: null, a: null, }; function toCatmullRomBezier( points, tension = 0.5) { if (points.length == 2) { return `M${points[0][0]},${points[0][1]} L${points[1][0]},${points[1][1]}` } let tens = (tension !== 0) ? tension * 12 : 0.5 * 12 let floats = points.map(x => x.map(x => parseFloat(x))) let firstMoveto = ['M' + floats[0][0] + ' ' + floats[0][1] + ' '] let matrixPoints = floats.map((point, i, arr) => { if (i == 0) { return getMatrix([arr[i],arr[i],arr[i+1],arr[i+2]]) } else if (i == arr.length - 2) { return getMatrix([arr[i-1],arr[i],arr[i+1],arr[i+1]]) } else { return getMatrix([arr[i-1],arr[i],arr[i+1],arr[i+2]]) } }).filter(mx => mx[3] !== undefined) let matrixMathToBezier = matrixPoints.map(p => { return [ { x: p[1].x, y: p[1].y }, { x: Math.clamp((-p[0].x + tens * p[1].x + p[2].x) / tens, p[1].x, p[2].x), y: ((-p[0].y + tens * p[1].y + p[2].y) / tens) }, { x: Math.clamp(( p[1].x + tens * p[2].x - p[3].x) / tens, p[1].x, p[2].x), y: ((p[1].y + tens * p[2].y - p[3].y) / tens) }, { x: p[2].x, y: p[2].y } ] }) let toSVGNotation = matrixMathToBezier.map(bp => { return "C" + bp[1].x + "," + bp[1].y + " " + bp[2].x + "," + bp[2].y + " " + bp[3].x + "," + bp[3].y + " " }) return firstMoveto.concat(toSVGNotation).join(' ') function getMatrix(arr) { return arr.map(p => { if (p !== undefined) { return { x : p[0], y : p[1] }} }) } } new Dialog({ id: 'adjust_curves', title: 'action.adjust_curves', darken: false, width: 460, component: { data() {return { light_data: '', graph: 'rgb', graphs: { rgb: {data: '', points: [[0, 0], [1, 1]]}, r: {data: '', points: [[0, 0], [1, 1]]}, g: {data: '', points: [[0, 0], [1, 1]]}, b: {data: '', points: [[0, 0], [1, 1]]}, a: {data: '', points: [[0, 0], [1, 1]]}, }, height: 400, width: 400, textures }}, methods: { change() { let values = {}; for (let key in this.graphs) { let graph = this.graphs[key]; if (graph.points.length === 2 && graph.points[0].allEqual(0) && graph.points[1].allEqual(1)) { delete curves[key]; } else { let vectors = []; values[key] = {}; graph.points.forEach(point => { vectors.push(new THREE.Vector2(point[0], point[1])); }) curves[key] = new THREE.SplineCurve(vectors); } } textures.forEach((texture, i) => { texture.edit((canvas) => { let ctx = canvas.getContext('2d'); let image_data = original_image_data[i]; for (let i = 0; i < image_data.data.length; i += 4) { let R = image_data.original_data[i+0] let G = image_data.original_data[i+1] let B = image_data.original_data[i+2] let A = image_data.original_data[i+3] let brightness = Math.round(0.2126*R + 0.7152*G + 0.0722*B); let rgb = !curves.rgb ? brightness : (values.rgb[brightness] !== undefined ? values.rgb[brightness] : values.rgb[brightness] = curves.rgb.getPointAt(brightness / 255).y * 255); let r = !curves.r ? brightness : (values.r[brightness] !== undefined ? values.r[brightness] : values.r[brightness] = curves.r.getPointAt(brightness / 255).y * 255); let g = !curves.g ? brightness : (values.g[brightness] !== undefined ? values.g[brightness] : values.g[brightness] = curves.g.getPointAt(brightness / 255).y * 255); let b = !curves.b ? brightness : (values.b[brightness] !== undefined ? values.b[brightness] : values.b[brightness] = curves.b.getPointAt(brightness / 255).y * 255); let a = !curves.a ? brightness : (values.a[brightness] !== undefined ? values.a[brightness] : values.a[brightness] = curves.a.getPointAt(brightness / 255).y * 255); image_data.data[i+0] = R * (r / brightness) * (rgb / brightness); image_data.data[i+1] = G * (g / brightness) * (rgb / brightness); image_data.data[i+2] = B * (b / brightness) * (rgb / brightness); image_data.data[i+3] = A * (a / brightness); } ctx.putImageData(image_data, 0, 0); }, {no_undo: true, use_cache: true}); }) }, setGraph(graph) { this.graph = graph; this.updateGraph(); }, dragPoint(point, e1) { let scope = this; let {points} = this.graphs[this.graph]; if (point == points[0] || point == points.last()) return; let original_point = point.slice(); function drag(e2) { point[0] = Math.clamp(original_point[0] + (e2.clientX - e1.clientX) / scope.width, 0, 1); point[1] = Math.clamp(original_point[1] - (e2.clientY - e1.clientY) / scope.height, 0, 1); scope.updateGraph(); scope.change(); } function stop() { removeEventListeners(document, 'mousemove touchmove', drag); removeEventListeners(document, 'mouseup touchend', stop); } addEventListeners(document, 'mousemove touchmove', drag); addEventListeners(document, 'mouseup touchend', stop); }, createNewPoint(event) { if (event.target.id !== 'contrast_graph' || event.which == 3) return; let point = [ (event.offsetX - 5) / this.width, 1 - ((event.offsetY - 5) / this.width), ] let {points} = this.graphs[this.graph]; points.push(point); this.updateGraph(); this.change(); this.dragPoint(point, event); }, updateGraph(id = this.graph) { let offset = 5; let {points} = this.graphs[this.graph]; points.sort((a, b) => a[0] - b[0]); this.graphs[id].data = toCatmullRomBezier(points.map(p => { return [p[0] * this.width + offset, (1-p[1]) * this.height + offset]; })); }, contextMenu(point, event) { new Menu([{ id: 'remove', name: 'Remove', icon: 'clear', click: () => { let {points} = this.graphs[this.graph]; points.remove(point); this.updateGraph(); this.change(); } }]).open(event.target); } }, template: `
RGB
R
G
B
A
`, mounted() { for (let key in this.graphs) { this.updateGraph(key); } this.light_data = `${5},${this.height + 5}`; for (let key in light_points) { this.light_data += ` ${Math.round((key / 255) * this.width) + 5},${Math.round((1 - light_points[key] / highest_light_point) * this.height) + 5}`; } this.light_data += ` ${this.width + 5},${this.height + 5}`; } }, onConfirm() { Undo.finishEdit('Invert colors'); }, onCancel() { Undo.cancelEdit(); } }).show(); } }) new Action('flip_texture_x', { icon: 'icon-mirror_x', category: 'textures', condition: {modes: ['paint'], method: () => Texture.all.length}, click() { let textures = getTextures(); Undo.initEdit({textures, bitmap: true}); textures.forEach(texture => { texture.edit((canvas) => { let ctx = canvas.getContext('2d'); ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.scale(-1, 1); ctx.drawImage(texture.img, -canvas.width, 0); }, {no_undo: true}); }) Undo.finishEdit('Flip texture X') } }) new Action('flip_texture_y', { icon: 'icon-mirror_y', category: 'textures', condition: {modes: ['paint'], method: () => Texture.all.length}, click() { let textures = getTextures(); Undo.initEdit({textures, bitmap: true}); textures.forEach(texture => { texture.edit((canvas) => { let ctx = canvas.getContext('2d'); ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.scale(1, -1); ctx.drawImage(texture.img, 0, -canvas.height); }, {no_undo: true}); }) Undo.finishEdit('Flip texture Y') } }) new Action('resize_texture', { icon: 'photo_size_select_large', category: 'textures', condition: () => Texture.all.length, click() { let texture = Texture.getDefault(); texture.resizeDialog(); } }) })