mirror of
https://github.com/JannisX11/blockbench.git
synced 2025-02-17 16:20:13 +08:00
Implement some texture adjustment tools
This commit is contained in:
parent
ea5b30183d
commit
6cc59dd058
177
css/dialogs.css
177
css/dialogs.css
@ -4,9 +4,13 @@
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 19;
|
||||
background-color: transparent;
|
||||
opacity: 0;
|
||||
}
|
||||
#blackout.darken {
|
||||
background-color: var(--color-dark);
|
||||
opacity: 0.6;
|
||||
z-index: 19;
|
||||
}
|
||||
.dialog_handle {
|
||||
position: relative;
|
||||
@ -1326,42 +1330,135 @@
|
||||
}
|
||||
|
||||
/* Edit History */
|
||||
#edit_history_list ul {
|
||||
margin-left: 14px;
|
||||
}
|
||||
#edit_history_list ul li {
|
||||
height: 30px;
|
||||
padding: 2px 6px;
|
||||
cursor: pointer;
|
||||
border: 2px solid transparent;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
#edit_history_list ul li.current {
|
||||
border-color: var(--color-accent);
|
||||
}
|
||||
#edit_history_list ul li:hover {
|
||||
color: var(--color-light);
|
||||
}
|
||||
#edit_history_list ul li.selected {
|
||||
background-color: var(--color-accent);
|
||||
color: var(--color-accent_text);
|
||||
position: relative;
|
||||
}
|
||||
#edit_history_list ul li.selected:not(:first-of-type)::before {
|
||||
content: "\f04b";
|
||||
font-family: 'Font Awesome 5 Free';
|
||||
font-weight: 900;
|
||||
font-size: 14px;
|
||||
color: var(--color-accent);
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: -11px;
|
||||
left: -16px;
|
||||
}
|
||||
#edit_history_list .edit_history_time {
|
||||
color: var(--color-subtle_text);
|
||||
}
|
||||
#edit_history_list ul li.selected .edit_history_time {
|
||||
color: inherit;
|
||||
}
|
||||
#edit_history_list ul {
|
||||
margin-left: 14px;
|
||||
}
|
||||
#edit_history_list ul li {
|
||||
height: 30px;
|
||||
padding: 2px 6px;
|
||||
cursor: pointer;
|
||||
border: 2px solid transparent;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
#edit_history_list ul li.current {
|
||||
border-color: var(--color-accent);
|
||||
}
|
||||
#edit_history_list ul li:hover {
|
||||
color: var(--color-light);
|
||||
}
|
||||
#edit_history_list ul li.selected {
|
||||
background-color: var(--color-accent);
|
||||
color: var(--color-accent_text);
|
||||
position: relative;
|
||||
}
|
||||
#edit_history_list ul li.selected:not(:first-of-type)::before {
|
||||
content: "\f04b";
|
||||
font-family: 'Font Awesome 5 Free';
|
||||
font-weight: 900;
|
||||
font-size: 14px;
|
||||
color: var(--color-accent);
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: -11px;
|
||||
left: -16px;
|
||||
}
|
||||
#edit_history_list .edit_history_time {
|
||||
color: var(--color-subtle_text);
|
||||
}
|
||||
#edit_history_list ul li.selected .edit_history_time {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
/* Texture Edit */
|
||||
div.texture_adjust_previews {
|
||||
overflow: auto;
|
||||
max-height: 416px;
|
||||
display: flex;
|
||||
margin-bottom: 10px;
|
||||
width: calc(100% - 38px);
|
||||
float: left;
|
||||
}
|
||||
div.texture_adjust_previews.folded {
|
||||
max-height: 30px;
|
||||
overflow: hidden;
|
||||
}
|
||||
div.texture_adjust_previews img {
|
||||
max-height: 400px;
|
||||
width: 400px;
|
||||
margin: auto;
|
||||
}
|
||||
dialog .slider_input_combo {
|
||||
clear: both;
|
||||
}
|
||||
dialog#adjust_curves .dialog_content {
|
||||
margin-top: 6px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
#contrast_graph {
|
||||
background-color: var(--color-back);
|
||||
border: 1px solid var(--color-border);
|
||||
height: 412px;
|
||||
width: 412px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
cursor: crosshair;
|
||||
}
|
||||
.contrast_graph_selector {
|
||||
clear: both;
|
||||
display: flex;
|
||||
}
|
||||
.contrast_graph_selector div {
|
||||
flex-basis: 0;
|
||||
flex-grow: 1;
|
||||
padding-top: 2px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
.contrast_graph_selector div:hover {
|
||||
color: var(--color-light);
|
||||
}
|
||||
.contrast_graph_selector div.selected {
|
||||
background-color: var(--color-accent);
|
||||
color: var(--color-accent_text);
|
||||
}
|
||||
#contrast_graph svg {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
pointer-events: none;
|
||||
}
|
||||
#contrast_graph svg path {
|
||||
fill: none;
|
||||
stroke-width: 2px;
|
||||
stroke: var(--color-accent);
|
||||
opacity: 0.3;
|
||||
}
|
||||
#contrast_graph svg path.active {
|
||||
opacity: 1.0;
|
||||
}
|
||||
#contrast_graph svg polygon {
|
||||
fill: var(--color-selected);
|
||||
stroke: none;
|
||||
stroke-width: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
.contrast_graph_point {
|
||||
position: absolute;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
background-color: var(--color-accent);
|
||||
margin: -1px;
|
||||
}
|
||||
.contrast_graph_point:hover {
|
||||
background-color: var(--color-light);
|
||||
}
|
||||
.contrast_graph_point::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
left: -6px;
|
||||
top: -6px;
|
||||
cursor: move;
|
||||
}
|
@ -122,6 +122,7 @@
|
||||
<script src="js/texturing/painter.js"></script>
|
||||
<script src="js/texturing/texture_generator.js"></script>
|
||||
<script src="js/texturing/color.js"></script>
|
||||
<script src="js/texturing/edit_texture.js"></script>
|
||||
<script src="js/display_mode.js"></script>
|
||||
<script src="js/animations/animation.js"></script>
|
||||
<script src="js/animations/timeline_animators.js"></script>
|
||||
@ -362,7 +363,7 @@
|
||||
|
||||
<div id="page_wrapper" class="invisible start_screen">
|
||||
|
||||
<div id="blackout"></div>
|
||||
<div id="blackout" class="darken"></div>
|
||||
|
||||
<div id="tab_bar" :class="{drag_mode: drag_target_index !== null}">
|
||||
<div id="tab_bar_list">
|
||||
|
@ -419,6 +419,7 @@ window.Dialog = class Dialog {
|
||||
|
||||
this.width = options.width
|
||||
this.draggable = options.draggable
|
||||
this.darken = options.darken !== false
|
||||
this.singleButton = options.singleButton
|
||||
this.buttons = options.buttons instanceof Array ? options.buttons : (options.singleButton ? ['dialog.close'] : ['dialog.confirm', 'dialog.cancel'])
|
||||
this.form_first = options.form_first;
|
||||
@ -677,7 +678,8 @@ window.Dialog = class Dialog {
|
||||
let jq_dialog = $(this.object);
|
||||
|
||||
$('#dialog_wrapper').append(jq_dialog);
|
||||
$('#blackout').show();
|
||||
$('#blackout').show().toggleClass('darken', this.darken);
|
||||
|
||||
jq_dialog.show().css('display', 'flex');
|
||||
jq_dialog.css('top', limitNumber(window.innerHeight/2-jq_dialog.height()/2, 0, 100)+'px');
|
||||
if (this.width) {
|
||||
@ -704,13 +706,13 @@ window.Dialog = class Dialog {
|
||||
return this;
|
||||
}
|
||||
hide() {
|
||||
$('#blackout').hide();
|
||||
$('#blackout').hide().toggleClass('darken', true);
|
||||
$(this.object).hide();
|
||||
open_dialog = false;
|
||||
open_interface = false;
|
||||
Dialog.open = null;
|
||||
Prop.active_panel = undefined;
|
||||
$(this.object).detach()
|
||||
$(this.object).detach();
|
||||
return this;
|
||||
}
|
||||
delete() {
|
||||
|
@ -692,6 +692,27 @@ const MenuBar = {
|
||||
], {
|
||||
condition: {modes: ['edit']}
|
||||
})
|
||||
new BarMenu('texture', [
|
||||
'adjust_brightness',
|
||||
'adjust_contrast',
|
||||
'invert_colors',
|
||||
'adjust_curves',
|
||||
'_',
|
||||
'flip_texture_x',
|
||||
'flip_texture_y'
|
||||
/*
|
||||
Hue, Saturation, Contrast, Brightness, Temperature
|
||||
Color Curve
|
||||
Flip, Rotate
|
||||
Resize
|
||||
Animation frame manipulation
|
||||
Replace individual colors
|
||||
Text?
|
||||
Merge
|
||||
*/
|
||||
], {
|
||||
condition: {modes: ['paint']}
|
||||
})
|
||||
|
||||
new BarMenu('display', [
|
||||
'copy',
|
||||
|
426
js/texturing/edit_texture.js
Normal file
426
js/texturing/edit_texture.js
Normal file
@ -0,0 +1,426 @@
|
||||
|
||||
BARS.defineActions(function() {
|
||||
|
||||
function getTextures() {
|
||||
if (Texture.selected) {
|
||||
return [Texture.selected];
|
||||
} else {
|
||||
return Texture.all;
|
||||
}
|
||||
}
|
||||
|
||||
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.filter = 'invert(1)';
|
||||
ctx.drawImage(canvas, 0, 0);
|
||||
|
||||
}, {no_undo: true});
|
||||
})
|
||||
Undo.finishEdit('Invert colors')
|
||||
}
|
||||
})
|
||||
new Action('adjust_brightness', {
|
||||
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',
|
||||
name: 'action.adjust_brightness',
|
||||
darken: false,
|
||||
component: {
|
||||
data() {return {
|
||||
show_preview: true,
|
||||
brightness: 100,
|
||||
textures
|
||||
}},
|
||||
methods: {
|
||||
change() {
|
||||
textures.forEach((texture, i) => {
|
||||
texture.edit((canvas) => {
|
||||
let ctx = canvas.getContext('2d');
|
||||
ctx.filter = `brightness(${this.brightness / 100})`;
|
||||
ctx.drawImage(original_imgs[i], 0, 0);
|
||||
}, {no_undo: true, use_cache: true});
|
||||
})
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div>
|
||||
<div class="texture_adjust_previews checkerboard" :class="{folded: !show_preview}">
|
||||
<img v-for="texture in textures" :src="texture.source" />
|
||||
</div>
|
||||
<div class="tool texture_adjust_preview_toggle" @click="show_preview = !show_preview"><i class="material-icons">{{ show_preview ? 'expand_more' : 'expand_less' }}</i></div>
|
||||
<div class="bar slider_input_combo">
|
||||
<input type="range" class="tool" min="0" max="200" step="1" v-model="brightness" @input="change()">
|
||||
<input lang="en" type="number" class="tool" min="0" max="200" step="1" v-model.number="brightness" @input="change()">
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
},
|
||||
onConfirm() {
|
||||
Undo.finishEdit('Invert colors');
|
||||
},
|
||||
onCancel() {
|
||||
Undo.cancelEdit();
|
||||
}
|
||||
}).show();
|
||||
}
|
||||
})
|
||||
new Action('adjust_contrast', {
|
||||
icon: 'brightness_6',
|
||||
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_contrast',
|
||||
name: 'action.adjust_contrast',
|
||||
darken: false,
|
||||
component: {
|
||||
data() {return {
|
||||
show_preview: true,
|
||||
contrast: 100,
|
||||
textures
|
||||
}},
|
||||
methods: {
|
||||
change() {
|
||||
textures.forEach((texture, i) => {
|
||||
texture.edit((canvas) => {
|
||||
let ctx = canvas.getContext('2d');
|
||||
ctx.filter = `contrast(${this.contrast / 100})`;
|
||||
ctx.drawImage(original_imgs[i], 0, 0);
|
||||
}, {no_undo: true, use_cache: true});
|
||||
})
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div>
|
||||
<div class="texture_adjust_previews checkerboard" :class="{folded: !show_preview}">
|
||||
<img v-for="texture in textures" :src="texture.source" />
|
||||
</div>
|
||||
<div class="tool texture_adjust_preview_toggle" @click="show_preview = !show_preview"><i class="material-icons">{{ show_preview ? 'expand_more' : 'expand_less' }}</i></div>
|
||||
<div class="bar slider_input_combo">
|
||||
<input type="range" class="tool" min="0" max="200" step="1" v-model="contrast" @input="change()">
|
||||
<input lang="en" type="number" class="tool" min="0" max="200" step="1" v-model.number="contrast" @input="change()">
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
},
|
||||
onConfirm() {
|
||||
Undo.finishEdit('Invert colors');
|
||||
},
|
||||
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, closing = false) {
|
||||
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 PointList = (closing) ? [...copyFirstPointToLast(points)] : [...points]
|
||||
let floats = PointList.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: ((-p[0].x + tens * p[1].x + p[2].x) / tens), y: ((-p[0].y + tens * p[1].y + p[2].y) / tens)},
|
||||
{ x: ((p[1].x + tens * p[2].x - p[3].x) / tens), 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] }}
|
||||
})
|
||||
}
|
||||
// Duplicate first element to the end
|
||||
function copyFirstPointToLast(arr) {
|
||||
return (arr[0] !== arr[arr.length - 1]) ? [...arr, arr[0]] : arr
|
||||
}
|
||||
}
|
||||
|
||||
new Dialog({
|
||||
id: 'adjust_curves',
|
||||
name: '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() {
|
||||
for (let key in this.graphs) {
|
||||
var vectors = [];
|
||||
this.graphs[key].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];//ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||||
|
||||
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 = (0.2126*R + 0.7152*G + 0.0722*B);
|
||||
|
||||
let rgb = curves.rgb.getPoint(brightness / 255).y;
|
||||
let r = curves.r.getPoint(brightness / 255).y;
|
||||
let g = curves.g.getPoint(brightness / 255).y;
|
||||
let b = curves.b.getPoint(brightness / 255).y;
|
||||
let a = curves.a.getPoint(brightness / 255).y;
|
||||
|
||||
image_data.data[i+0] = R * ((r * 255) / brightness) * ((rgb * 255) / brightness);
|
||||
image_data.data[i+1] = G * ((g * 255) / brightness) * ((rgb * 255) / brightness);
|
||||
image_data.data[i+2] = B * ((b * 255) / brightness) * ((rgb * 255) / brightness);
|
||||
image_data.data[i+3] = A * ((a * 255) / 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 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: `
|
||||
<div>
|
||||
<div class="bar contrast_graph_selector">
|
||||
<div @click="setGraph('rgb')" :class="{selected: graph == 'rgb'}">RGB</div>
|
||||
<div @click="setGraph('r')" :class="{selected: graph == 'r'}">R</div>
|
||||
<div @click="setGraph('g')" :class="{selected: graph == 'g'}">G</div>
|
||||
<div @click="setGraph('b')" :class="{selected: graph == 'b'}">B</div>
|
||||
<div @click="setGraph('a')" :class="{selected: graph == 'a'}">A</div>
|
||||
</div>
|
||||
<div id="contrast_graph" @mousedown="createNewPoint($event)" @touchstart="createNewPoint($event)">
|
||||
<svg>
|
||||
<path :class="{active: graph == 'r'}" :d="graphs.r.data" style="stroke: #ff0000;"></path>
|
||||
<path :class="{active: graph == 'g'}" :d="graphs.g.data" style="stroke: #00ff00;"></path>
|
||||
<path :class="{active: graph == 'b'}" :d="graphs.b.data" style="stroke: #3b3bff;"></path>
|
||||
<path :class="{active: graph == 'a'}" :d="graphs.a.data" style="stroke: var(--color-text);"></path>
|
||||
<path :class="{active: graph == 'rgb'}" :d="graphs.rgb.data"></path>
|
||||
<polygon :points="light_data" />
|
||||
</svg>
|
||||
<div class="contrast_graph_point"
|
||||
v-for="point in graphs[graph].points"
|
||||
:style="{left: point[0] * width + 'px', top: (1-point[1]) * height + 'px'}"
|
||||
@mousedown="dragPoint(point, $event)" @touchstart="dragPoint(point, $event)"
|
||||
@contextmenu="contextMenu(point, $event)"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
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')
|
||||
}
|
||||
})
|
||||
})
|
@ -629,8 +629,8 @@ Object.defineProperty(String.prototype, 'hashCode', {
|
||||
|
||||
//Color
|
||||
tinycolor.prototype.toInt = function() {
|
||||
var rgba = this.toRgb()
|
||||
return Jimp.rgbaToInt(rgba.r, rgba.g, rgba.b, rgba.a)
|
||||
let {r, g, b, a} = this.toRgb();
|
||||
return r * Math.pow(256, 3) + g * Math.pow(256, 2) + b * Math.pow(256, 1) + a * Math.pow(256, 0);
|
||||
}
|
||||
function getAverageRGB(imgEl, blockSize) {
|
||||
|
||||
|
@ -1323,6 +1323,7 @@
|
||||
"menu.file": "File",
|
||||
"menu.edit": "Edit",
|
||||
"menu.transform": "Transform",
|
||||
"menu.texture": "Texture",
|
||||
"menu.tools": "Tools",
|
||||
"menu.display": "Display",
|
||||
"menu.animation": "Animation",
|
||||
|
Loading…
Reference in New Issue
Block a user