Layer copying, pasting, and moving

This commit is contained in:
JannisX11 2023-10-27 13:02:28 +02:00
parent 48043b7291
commit da62906c94
10 changed files with 396 additions and 77 deletions

View File

@ -127,6 +127,9 @@ const Clipbench = {
}
},
copy(event, cut) {
let match = SharedActions.run('copy', event, cut);
if (match) return;
let copy_type = Clipbench.getCopyType(1);
Clipbench.last_copied = copy_type;
switch (copy_type) {
@ -175,6 +178,9 @@ const Clipbench = {
}
},
async paste(event) {
let match = SharedActions.run('paste', event);
if (match) return;
switch (await Clipbench.getPasteType()) {
case 'text':
Clipbench.setText(window.getSelection()+'');
@ -417,7 +423,7 @@ BARS.defineActions(function() {
icon: 'fa-copy',
category: 'edit',
work_in_dialog: true,
condition: () => Clipbench.getCopyType(1, true),
condition: () => Clipbench.getCopyType(1, true) || SharedActions.condition('copy'),
keybind: new Keybind({key: 'c', ctrl: true, shift: null}),
click(event) {
Clipbench.copy(event)
@ -427,7 +433,7 @@ BARS.defineActions(function() {
icon: 'fa-cut',
category: 'edit',
work_in_dialog: true,
condition: () => Clipbench.getCopyType(1, true),
condition: () => Clipbench.getCopyType(1, true) || SharedActions.condition('copy'),
keybind: new Keybind({key: 'x', ctrl: true, shift: null}),
click(event) {
Clipbench.copy(event, true)
@ -437,7 +443,7 @@ BARS.defineActions(function() {
icon: 'fa-clipboard',
category: 'edit',
work_in_dialog: true,
condition: () => Clipbench.getCopyType(2, true),
condition: () => Clipbench.getCopyType(2, true) || SharedActions.condition('paste'),
keybind: new Keybind({key: 'v', ctrl: true, shift: null}),
click(event) {
Clipbench.paste(event)

View File

@ -434,6 +434,7 @@ class Tool extends Action {
}
}
this.onCanvasClick = data.onCanvasClick;
this.onTextureEditorClick = data.onTextureEditorClick;
this.onSelect = data.onSelect;
this.onUnselect = data.onUnselect;
this.node.onclick = () => {
@ -2044,6 +2045,7 @@ const BARS = {
'draw_shape_tool',
'gradient_tool',
'selection_tool',
'move_layer_tool',
],
vertical: Blockbench.isMobile == true,
default_place: true

View File

@ -4,7 +4,10 @@ const SharedActions = {
* @param {('delete'|'rename'|'duplicate'|'select_all'|'unselect_all')} action_id
* @param {Object} handler Case handler
* @param {*} handler.condition Condition
* @param {number} handler.priority Condition
* @param {number} handler.priority Handler priority.
* Unset or 0 is typically used for relatively specific handlers, like those that check for a specific active panel.
* Higher priorities are used for even more specific conditions, like when multiple handlers work in a panel.
* Lower priorities are used for more fallback-like handlers, like deleting elements in edit mode, regardless of active panel.
* @param {function} handler.run
* @returns
*/
@ -33,6 +36,17 @@ const SharedActions = {
}
return false;
},
runSpecific(action_id, subject, event, context) {
let list = this.actions[action_id];
if (!list) return;
for (let handler of list) {
if (handler.subject == subject && Condition(handler.condition)) {
handler.run(event, context);
return true;
}
}
return false;
},
condition(action_id) {
let list = this.actions[action_id];
if (!list) return;

View File

@ -4,6 +4,7 @@ class TextureLayer {
this.texture = texture;
this.canvas = document.createElement('canvas');
this.ctx = this.canvas.getContext('2d');
this.in_limbo = false;
this.img = new Image();
this.img.onload = () => {
@ -87,6 +88,9 @@ class TextureLayer {
copy.data_url = this.canvas.toDataURL();
return copy;
}
setLimbo() {
this.in_limbo = true;
}
setSize(width, height) {
this.canvas.width = width;
this.canvas.height = height;
@ -210,20 +214,7 @@ BARS.defineActions(() => {
Modes.options.paint.select();
}
let texture = Texture.selected;
Undo.initEdit({textures: [texture], bitmap: true});
texture.layers_enabled = true;
if (!texture.layers.length) {
let layer = new TextureLayer({
}, texture);
let image_data = texture.ctx.getImageData(0, 0, texture.width, texture.height);
layer.setSize(texture.width, texture.height);
layer.ctx.putImageData(image_data, 0, 0);
texture.layers.push(layer);
layer.select();
}
Undo.finishEdit('Enable layers on texture');
updateInterfacePanels();
BARS.updateConditions();
texture.activateLayers(true);
}
})
new NumSlider('layer_opacity', {

View File

@ -263,12 +263,14 @@ const Painter = {
delete Painter.paint_stroke_canceled;
return;
}
let texture = Texture.selected;
if (Toolbox.selected.brush && Toolbox.selected.brush.onStrokeEnd) {
let result = Toolbox.selected.brush.onStrokeEnd({texture, x, y, uv, event, raycast_data: data});
if (result == false) return;
}
if (Painter.brushChanges) {
texture.updateLayerChanges(true);
Undo.finishEdit('Paint texture');
Painter.brushChanges = false;
}
@ -1643,6 +1645,9 @@ class IntMatrix {
this.array = null;
this.override = false;
}
get is_custom() {
return this.override === null;
}
/**
* The array does not exist by default to save memory, this activates it.
*/
@ -1685,6 +1690,27 @@ class IntMatrix {
getDirect(x, y) {
return this.array[y * this.width + x];
}
getBoundingRect() {
let rect = new Rectangle();
if (this.override == true) {
rect.width = this.width;
rect.height = this.height;
} else if (this.override == null) {
let min_x = Infinity;
let min_y = Infinity;
let max_x = -Infinity;
let max_y = -Infinity;
this.forEachPixel((x, y, value) => {
if (!value) return;
min_x = Math.min(min_x, x);
min_y = Math.min(min_y, y);
max_x = Math.max(max_x, x+1);
max_y = Math.max(max_y, y+1);
})
rect.fromCoords(min_x, min_y, max_x, max_y);
}
return rect;
}
/**
* Set the value at a specified pixel
* @param {number} x
@ -1751,9 +1777,118 @@ class IntMatrix {
}
}
SharedActions.add('delete', {
SharedActions.add('copy', {
subject: 'image_content',
condition: () => Prop.active_panel == 'uv' && Modes.paint && Texture.getDefault(),
run() {
run(event, cut) {
let texture = Texture.getDefault();
let selection = texture.selection;
let {canvas, ctx} = texture.getActiveCanvas();
let layer = texture.selected_layer;
let offset = layer ? layer.offset : [0, 0];
if (selection.override != null) {
Clipbench.image = {
x: offset[0], y: offset[1],
data: canvas.toDataURL(),
}
} else {
let rect = selection.getBoundingRect();
let copy_canvas = document.createElement('canvas');
let copy_ctx = copy_canvas.getContext('2d');
copy_canvas.width = rect.width;
copy_canvas.height = rect.height;
copy_ctx.beginPath()
selection.forEachPixel((x, y, val) => {
if (!val) return;
copy_ctx.rect(
x - rect.start_x - offset[0],
y - rect.start_y - offset[1],
1, 1
);
})
copy_ctx.closePath();
copy_ctx.clip();
copy_ctx.drawImage(canvas, -rect.start_x, -rect.start_y);
Clipbench.image = {
x: rect.start_x,
y: rect.start_y,
data: copy_canvas.toDataURL()
}
canvas = copy_canvas;
}
if (isApp) {
let img = nativeImage.createFromDataURL(Clipbench.image.data);
clipboard.writeImage(img);
} else {
canvas.toBlob(blob => {
navigator.clipboard.write([
new ClipboardItem({
[blob.type]: blob,
}),
]);
});
}
if (cut) {
SharedActions.runSpecific('delete', 'image_content', {message: 'Cut texture selection'});
}
}
})
SharedActions.add('paste', {
subject: 'image_content',
condition: () => Prop.active_panel == 'uv' && Modes.paint && Texture.getDefault(),
run(event) {
let texture = Texture.getDefault();
async function loadFromDataUrl(data_url) {
let frame = new CanvasFrame();
await frame.loadFromURL(data_url);
Undo.initEdit({textures: [texture], bitmap: true});
if (!texture.layers_enabled) {
texture.activateLayers(false);
}
let layer = new TextureLayer({name: 'pasted', offset: [0, 0]}, texture);
let image_data = frame.ctx.getImageData(0, 0, frame.width, frame.height);
layer.setSize(frame.width, frame.height);
layer.ctx.putImageData(image_data, 0, 0);
texture.layers.push(layer);
layer.select();
layer.setLimbo();
texture.updateLayerChanges(true);
Undo.finishEdit('Paste into texture');
updateInterfacePanels();
BARS.updateConditions();
}
if (isApp) {
var image = clipboard.readImage().toDataURL();
loadFromDataUrl(image);
} else {
navigator.clipboard.read().then(content => {
if (content && content[0] && content[0].types.includes('image/png')) {
content[0].getType('image/png').then(blob => {
let url = URL.createObjectURL(blob);
loadFromDataUrl(url);
})
}
}).catch(() => {})
}
}
})
SharedActions.add('delete', {
subject: 'image_content',
condition: () => Prop.active_panel == 'uv' && Modes.paint && Texture.getDefault(),
run(event, context = 0) {
let texture = Texture.getDefault();
if (texture.selection.override == false) return;
@ -1765,7 +1900,7 @@ SharedActions.add('delete', {
ctx.clearRect(x, y, 1, 1);
}
})
}, {edit_name: 'Delete texture section'});
}, {edit_name: context.message || 'Delete texture section'});
}
})
@ -2074,6 +2209,12 @@ BARS.defineActions(function() {
onCanvasClick: function(data) {
Painter.startPaintToolCanvas(data, data.event)
},
onTextureEditorClick(texture, x, y, event) {
if (texture) {
Painter.startPaintTool(texture, x, y, undefined, event);
}
return false;
},
onSelect: function() {
Painter.updateNslideValues()
}
@ -2191,12 +2332,47 @@ BARS.defineActions(function() {
Blockbench.showQuickMessage('message.copy_paste_tool_viewport')
}
},
onTextureEditorClick(texture, x, y, event) {
if (texture) {
UVEditor.vue.startTextureSelection(x, y, event);
}
return false;
},
onSelect() {
}
})
selection_tool.mode = 'rectangle';
new Tool('move_layer_tool', {
icon: 'drag_pan',
category: 'tools',
toolbar: 'brush',
cursor: 'move',
selectFace: true,
transformerMode: 'hidden',
paintTool: true,
allowed_view_modes: ['textured'],
modes: ['paint'],
condition: {modes: ['paint']},
onCanvasClick(data) {
if (data && data.element) {
Blockbench.showQuickMessage('message.copy_paste_tool_viewport')
}
},
onTextureEditorClick(texture, x, y, event) {
if (texture) {
UVEditor.vue.startTextureSelection(x, y, event);
}
return false;
},
onSelect() {
if (Texture.selected && Texture.selected.selection.is_custom) {
Texture.selected.selectionToLayer();
}
}
})
new BarSelect('brush_shape', {
category: 'paint',
condition: () => Toolbox && Toolbox.selected.brush && Toolbox.selected.brush.shapes,

View File

@ -1307,6 +1307,83 @@ class Texture {
if (this.layers_enabled) {
return this.selected_layer || this.layers[0];
}
}
activateLayers(undo) {
if (undo) Undo.initEdit({textures: [this], bitmap: true});
this.layers_enabled = true;
if (!this.layers.length) {
let layer = new TextureLayer({
}, this);
let image_data = this.ctx.getImageData(0, 0, this.width, this.height);
layer.setSize(this.width, this.height);
layer.ctx.putImageData(image_data, 0, 0);
this.layers.push(layer);
layer.select();
}
if (undo) Undo.finishEdit('Enable layers on texture');
updateInterfacePanels();
BARS.updateConditions();
}
selectionToLayer() {
let texture = this;
let selection = texture.selection;
let {canvas, ctx} = texture.getActiveCanvas();
let layer = texture.selected_layer;
let offset = layer ? layer.offset.slice() : [0, 0];
let copy_canvas = canvas;
if (selection.is_custom) {
let rect = selection.getBoundingRect();
copy_canvas = document.createElement('canvas');
let copy_ctx = copy_canvas.getContext('2d');
copy_canvas.width = rect.width;
copy_canvas.height = rect.height;
copy_ctx.beginPath()
selection.forEachPixel((x, y, val) => {
if (!val) return;
copy_ctx.rect(
x - rect.start_x - offset[0],
y - rect.start_y - offset[1],
1, 1
);
})
copy_ctx.closePath();
copy_ctx.clip();
copy_ctx.drawImage(canvas, -rect.start_x, -rect.start_y);
offset.V2_add(rect.start_x, rect.start_y);
}
texture.edit(canvas => {
let ctx = canvas.getContext('2d');
let selection = texture.selection;
selection.forEachPixel((x, y, val) => {
if (val) {
ctx.clearRect(x, y, 1, 1);
}
})
}, {no_undo: true});
//Undo.initEdit({textures: [texture], bitmap: true});
if (!texture.layers_enabled) {
texture.activateLayers(false);
}
let new_layer = new TextureLayer({name: 'selection', offset}, texture);
new_layer.ctx.drawImage(copy_canvas, 0, 0);
texture.layers.splice(texture.layers.indexOf(texture.selected_layer)+1, 0, new_layer);
new_layer.select();
new_layer.setLimbo();
texture.updateLayerChanges(true);
//Undo.finishEdit('Paste into texture');
updateInterfacePanels();
BARS.updateConditions();
}
//Export
javaTextureLink() {

View File

@ -23,7 +23,7 @@ const UVEditor = {
//Brush
getBrushCoordinates(event, tex) {
convertTouchEvent(event);
let pixel_size = this.inner_width / tex.width
let pixel_size = this.inner_width / (tex ? tex.width : Project.texture_width);
let result = {};
let mouse_coords;
if (event.target.id == 'uv_frame') {
@ -44,6 +44,9 @@ const UVEditor = {
result.x = Math.floor(mouse_coords[0]/pixel_size*1);
result.y = Math.floor(mouse_coords[1]/pixel_size*1);
}
} else if (Toolbox.selected.id === 'move_layer_tool') {
result.x = Math.round(mouse_coords[0]/pixel_size*1);
result.y = Math.round(mouse_coords[1]/pixel_size*1);
} else {
let offset = BarItems.slider_brush_size.get()%2 == 0 && Toolbox.selected.brush?.offset_even_radius ? 0.5 : 0;
result.x = mouse_coords[0]/pixel_size*1 + offset;
@ -53,8 +56,10 @@ const UVEditor = {
result.y = Math.floor(result.y);
}
}
if (tex.frameCount) result.y += (tex.height / tex.frameCount) * tex.currentFrame;
if (!tex.frameCount && tex.ratio != tex.getUVWidth() / tex.getUVHeight()) result.y /= tex.ratio;
if (tex) {
if (tex.frameCount) result.y += (tex.height / tex.frameCount) * tex.currentFrame;
if (!tex.frameCount && tex.ratio != tex.getUVWidth() / tex.getUVHeight()) result.y /= tex.ratio;
}
return result;
},
startPaintTool(event) {
@ -62,16 +67,14 @@ const UVEditor = {
delete Painter.current.element;
var texture = this.getTexture()
if (texture) {
var coords = this.getBrushCoordinates(event, texture)
if (Toolbox.selected.id == 'selection_tool') {
this.vue.startTextureSelection(coords.x, coords.y, event);
} else {
Painter.startPaintTool(texture, coords.x, coords.y, undefined, event);
}
var coords = this.getBrushCoordinates(event, texture);
let tool_result;
if (Toolbox.selected.onTextureEditorClick) {
tool_result = Toolbox.selected.onTextureEditorClick(texture, coords.x, coords.y, event);
}
if (Toolbox.selected.id !== 'color_picker' && Toolbox.selected.id !== 'selection_tool' && texture) {
if (tool_result !== false && texture) {
Painter.startPaintTool(texture, coords.x, coords.y, undefined, event);
addEventListeners(this.vue.$refs.viewport, 'mousemove touchmove', UVEditor.movePaintTool, false );
addEventListeners(document, 'mouseup touchend', UVEditor.stopBrush, false );
}
@ -2246,7 +2249,8 @@ Interface.definePanels(function() {
this.mouse_coords.active = true;
this.mouse_coords.x = x;
this.mouse_coords.y = y;
let grab = Toolbox.selected.id == 'selection_tool' && this.texture && this.texture.selection.get(this.mouse_coords.x, this.mouse_coords.y) && BarItems.selection_tool_operation_mode.value == 'create';
let grab = Toolbox.selected.id == 'move_layer_tool' ||
(Toolbox.selected.id == 'selection_tool' && this.texture && this.texture.selection.get(this.mouse_coords.x, this.mouse_coords.y) && BarItems.selection_tool_operation_mode.value == 'create');
this.$refs.frame.style.cursor = grab ? 'move' : '';
},
onMouseWheel(event) {
@ -3268,7 +3272,9 @@ Interface.definePanels(function() {
let op_mode = BarItems.selection_tool_operation_mode.value;
let selection_rect = this.texture_selection_rect;
let start_x, start_y, calcrect;
let create_selection = !(op_mode == 'create' && clicked_val);
let create_selection = !(op_mode == 'create' && clicked_val) && Toolbox.selected.id == 'selection_tool';
let layer = texture.selected_layer;
let initial_offset = layer ? layer.offset.slice() : [0, 0];
/*if (op_mode == 'create' && clicked_val) {
if (open_interface) {
@ -3277,14 +3283,15 @@ Interface.definePanels(function() {
UVEditor.removePastingOverlay()
}
}*/
start_x = Math.clamp(x, 0, UVEditor.texture ? UVEditor.texture.width : Project.texture_width);
start_y = Math.clamp(y, 0, UVEditor.texture ? UVEditor.texture.height : Project.texture_height);
if (create_selection) {
//$(this.$refs.frame).find('#texture_selection_rect').detach();
//let rect = document.createElement('div');
//rect.style.visibility = 'hidden';
//rect.id = 'texture_selection_rect';
//this.$refs.frame.append(rect)
start_x = Math.clamp(x, 0, UVEditor.texture ? UVEditor.texture.width : Project.texture_width);
start_y = Math.clamp(y, 0, UVEditor.texture ? UVEditor.texture.height : Project.texture_height);
if (op_mode == 'create') {
texture.selection.clear();
@ -3339,11 +3346,8 @@ Interface.definePanels(function() {
}
} else {
//Painter.selection.start_x = Painter.selection.x;
//Painter.selection.start_y = Painter.selection.y;
//Painter.selection.start_scroll_x = viewport.scrollLeft;
//Painter.selection.start_scroll_y = viewport.scrollTop;
//Painter.selection.start_event = event;
texture.display_canvas = true;
UVEditor.vue.updateTextureCanvas();
}
let last_x, last_y;
@ -3352,18 +3356,6 @@ Interface.definePanels(function() {
var {x, y} = UVEditor.getBrushCoordinates(e1, texture);
if (last_x == x && last_y == y) return;
last_x = x, last_y = y;
/*let rect = getRectangle(
event.offsetX / scope.inner_width * scope.uv_resolution[0],
event.offsetY / scope.inner_height * scope.uv_resolution[1],
(event.offsetX - event.clientX + e1.clientX) / scope.inner_width * scope.uv_resolution[0],
(event.offsetY - event.clientY + e1.clientY) / scope.inner_height * scope.uv_resolution[1],
)
selection_rect.pos_x = rect.ax;
selection_rect.pos_y = rect.ay;
selection_rect.width = rect.x;
selection_rect.height = rect.y;*/
if (create_selection) {
let start_x_here = start_x;
@ -3377,15 +3369,11 @@ Interface.definePanels(function() {
if (x === Painter.current.x && y === Painter.current.y) return;
Painter.current.x = x = Math.clamp(x, 0, UVEditor.texture.img.naturalWidth);
Painter.current.y = y = Math.clamp(y, 0, UVEditor.texture.img.naturalHeight);
start_x_here = Math.clamp(start_x_here, 0, UVEditor.texture.img.naturalWidth);
start_y_here = Math.clamp(start_y_here, 0, UVEditor.texture.img.naturalHeight);
calcrect = getRectangle(start_x_here, start_y_here, x, y);
if (!calcrect.x && !calcrect.y) return;
//UVEditor.vue.copy_overlay.state = 'select';
//Painter.selection.calcrect = calcrect;
//Painter.selection.x = calcrect.ax;
//Painter.selection.y = calcrect.ay;
//UVEditor.vue.copy_overlay.width = calcrect.x;
//UVEditor.vue.copy_overlay.height = calcrect.y;
selection_rect.active = true;
selection_rect.ellipse = selection_mode == 'ellipse';
@ -3395,14 +3383,19 @@ Interface.definePanels(function() {
selection_rect.height = calcrect.y;
} else {
//let viewport = UVEditor.vue.$refs.viewport;
//let move_offset_x = e1.clientX - Painter.selection.start_event.clientX - Painter.selection.start_scroll_x + viewport.scrollLeft;
//let move_offset_y = e1.clientY - Painter.selection.start_event.clientY - Painter.selection.start_scroll_y + viewport.scrollTop;
//Painter.selection.x = Painter.selection.start_x + Math.round((move_offset_x) / m);
//Painter.selection.y = Painter.selection.start_y + Math.round((move_offset_y) / m);
//Painter.selection.x = Math.clamp(Painter.selection.x, 1-Painter.selection.canvas.width, UVEditor.texture.width -1)
//Painter.selection.y = Math.clamp(Painter.selection.y, 1-Painter.selection.canvas.height, UVEditor.texture.height-1)
//UVEditor.updatePastingOverlay()
if (!layer) {
texture.activateLayers();
layer = texture.selected_layer;
}
if (!layer.in_limbo && texture.selection.is_custom) {
texture.selectionToLayer();
layer = texture.selected_layer;
initial_offset = layer.offset.slice();
}
layer.offset[0] = initial_offset[0] + x - start_x;
layer.offset[1] = initial_offset[1] + y - start_y;
texture.updateLayerChanges();
}
}
function stop() {
@ -3482,14 +3475,10 @@ Interface.definePanels(function() {
}
}
UVEditor.updateSelectionOutline();
} else {
texture.updateLayerChanges(true);
}
/*let calcrect = Painter.selection.calcrect;
var canvas = document.createElement('canvas')
var ctx = canvas.getContext('2d');
canvas.width = calcrect.x;
canvas.height = calcrect.y;
ctx.drawImage(UVEditor.vue.texture.img, -calcrect.ax, -calcrect.ay)
/*if (isApp) {
let image = nativeImage.createFromDataURL(canvas.toDataURL())

View File

@ -183,6 +183,68 @@ function highestInObject(obj, inverse) {
}
return result;
}
class Rectangle {
constructor(start_x = 0, start_y = 0, width = 0, height = 0) {
this.start_x = start_x;
this.start_y = start_y;
this.width = width;
this.height = height;
}
get start() {
return [this.x, this.y];
}
get w() {
return this.width;
}
get h() {
return this.width;
}
get end_x() {
return this.start_x + this.width;
}
get end_y() {
return this.start_y + this.height;
}
set end_x(val) {
return this.width = val - this.start_x;
}
set end_y(val) {
return this.height = val - this.start_y;
}
get area() {
return this.width * this.height;
}
fromCoords(x1, y1, x2, y2) {
this.start_x = x1;
this.width = x2 - x1;
this.start_y = y1;
this.height = y2 - y1;
}
fromUnorderedCoords(x1, y1, x2, y2) {
if (x1 < x2) {
this.start_x = x1;
this.width = x2 - x1;
} else {
this.start_x = x2;
this.width = x1 - x2;
}
if (y1 < y2) {
this.start_y = y1;
this.height = y2 - y1;
} else {
this.start_y = y2;
this.height = y1 - y2;
}
}
expandTo(x, y) {
if (x < this.start_x) this.start_x = x;
else if (x > this.end_x) this.end_x = x;
if (y < this.start_y) this.start_y = y;
else if (y > this.end_y) this.end_y = y;
}
}
function getRectangle(a, b, c, d) {
var rect = {};
if (!b && typeof a === 'object') {

View File

@ -1145,6 +1145,8 @@
"action.selection_tool.lasso": "Lasso Select",
"action.selection_tool.wand": "Magic Wand",
"action.selection_tool.color": "Same Color",
"action.move_layer_tool": "Move Layer",
"action.move_layer_tool.desc": "Move the current layer or selection",
"action.toggle_skin_layer": "Toggle Skin Layer",
"action.toggle_skin_layer.desc": "Toggle the hat and clothing layer of the skin model",
"action.explode_skin_model": "Explode Skin Model",

View File

@ -20,8 +20,8 @@ class CanvasFrame {
this.createCanvas(a.naturalWidth, a.naturalHeight)
this.loadFromImage(a)
} else if (a && b) {
this.createCanvas(a, b)
} else {
this.createCanvas(a || 16, b || 16)
}
this.ctx = this.canvas.getContext('2d')
}