From fb4327471ad1cbcb6a210a4fe183ac3aa9511688 Mon Sep 17 00:00:00 2001 From: derpygamer2142 <93667155+derpygamer2142@users.noreply.github.com> Date: Sun, 8 Feb 2026 15:17:45 -0800 Subject: [PATCH 1/3] Load or create on startup --- dist/artimus.js | 15 ++++++++++----- index.html | 5 +++++ lang/english.json | 15 +++++++++++++-- site/src/menus/loadFile.js | 31 +++++++++++++++++++++++++++++++ site/src/menus/newFIle.js | 9 +++++++++ 5 files changed, 68 insertions(+), 7 deletions(-) create mode 100644 site/src/menus/loadFile.js diff --git a/dist/artimus.js b/dist/artimus.js index 8a556d9..e3f0f55 100644 --- a/dist/artimus.js +++ b/dist/artimus.js @@ -2482,7 +2482,7 @@ window.artimus = { importFromPC() { // Not yet widely available, so we will need to check we can use the file system access API - if (window.showSaveFilePicker) window.showOpenFilePicker({ + if (window.showSaveFilePicker) return window.showOpenFilePicker({ id: "artimus_file_location", multiple: false, startIn: "documents", @@ -2501,11 +2501,16 @@ window.artimus = { fileInput.type = "file"; fileInput.accept = "image/*, .artimus"; - fileInput.onchange = () => { - artimus.activeWorkspaces[0].importFromImage(fileInput.files[0]); - }; + const filePromise = new Promise((resolve) => { + fileInput.onchange = () => { + artimus.activeWorkspaces[0].importFromImage(fileInput.files[0]); + resolve(); + }; + fileInput.onError = () => { console.log('file load error wow'); } + }); - fileInput.click(); + fileInput.click(); + return filePromise; } } diff --git a/index.html b/index.html index dd87e70..f055466 100644 --- a/index.html +++ b/index.html @@ -78,5 +78,10 @@ + + \ No newline at end of file diff --git a/lang/english.json b/lang/english.json index 9096f83..c635e83 100644 --- a/lang/english.json +++ b/lang/english.json @@ -79,7 +79,7 @@ "artimus.modal.welcome.info": [ "", - "

A web based art editor and library created by ObviousStudios and open sourced under the MIT liscense

", + "

A web based art editor and library created by ObviousStudios and open sourced under the MIT license

", "

Originally created for Coffee Engine

", "

Disclaimer

", @@ -88,7 +88,13 @@ "

Things may break or be unstable.

", "

If you are fine with this and want to try this web demo click the \"I'm ready!\" button below!

", - "" + "" + ], + "artimus.modal.ready.title": "Load or create", + "artimus.modal.ready.info": [ + "

Do you have an existing file you'd like to open?

", + "", + "" ], "artimus.modal.layerProperty.title": "Editing \"[LAYER]\"", @@ -98,6 +104,11 @@ "artimus.modal.newFile.title": "New File", "artimus.modal.newFile.create": "Create!", + "artimus.modal.newFile.loadInstead": "Load a file instead", + + "artimus.modal.loadFile.title": "Load file", + "artimus.modal.loadFile.load": "Load", + "artimus.modal.loadFile.createInstead": "Create a file instead", "artimus.modal.resizeFile.title": "Resize", "artimus.modal.resizeFile.resize": "Resize!", diff --git a/site/src/menus/loadFile.js b/site/src/menus/loadFile.js new file mode 100644 index 0000000..3578fc3 --- /dev/null +++ b/site/src/menus/loadFile.js @@ -0,0 +1,31 @@ +editor.loadFile = (forced) => { + //Simple, easy. + new editor.modal(artimus.translate("title", "modal.loadFile"), (contents, modal) => { + + const loadButton = document.createElement("button"); + const createButon = document.createElement("button"); + + loadButton.className = "artimus-button"; + createButon.className = "artimus-button"; + + + createButon.innerText = artimus.translate("createInstead", "modal.loadFile"); + loadButton.innerText = artimus.translate("load", "modal.loadFile"); + + createButon.onclick = () => { + modal.close(); + editor.newFile(true); + } + loadButton.onclick = () => { + artimus.activeWorkspaces[0].importFromPC().then(() => { + modal.close(); + }); + }; + + contents.appendChild(loadButton); + contents.appendChild(createButon); + + loadButton.click(); + + }, { hasClose: !forced, translationContext: "loadFile", width: 25, height: 20 }); +} \ No newline at end of file diff --git a/site/src/menus/newFIle.js b/site/src/menus/newFIle.js index 8483c55..70e6c51 100644 --- a/site/src/menus/newFIle.js +++ b/site/src/menus/newFIle.js @@ -16,6 +16,7 @@ editor.newFile = (forced) => { const finalDiv = document.createElement("div"); const createButton = document.createElement("button"); + const loadButton = document.createElement("button"); flipButton.className = "newFile-flip"; sizingDiv.className = "newFile-sizingDiv"; @@ -26,6 +27,7 @@ editor.newFile = (forced) => { currentPreviewHolder.className = "newFile-tuning-previewHolder"; currentPreview.className = "newFile-tuning-preview"; createButton.className = "artimus-button"; + loadButton.className = "artimus-button"; resolutionDiv.appendChild(widthInput); resolutionDiv.appendChild(heightInput); @@ -33,6 +35,7 @@ editor.newFile = (forced) => { sizingDiv.appendChild(flipButton); currentPreviewHolder.appendChild(currentPreview); finalDiv.appendChild(createButton); + finalDiv.appendChild(loadButton); tuning.appendChild(currentPreviewHolder); tuning.appendChild(sizingDiv); tuning.appendChild(finalDiv); @@ -40,6 +43,7 @@ editor.newFile = (forced) => { contents.appendChild(tuning); createButton.innerText = artimus.translate("create", "modal.newFile"); + loadButton.innerText = artimus.translate("loadInstead", "modal.newFile"); fetch("site/images/flipAspect.svg").then(res => res.text()).then(text => { if (flipButton) { @@ -91,6 +95,11 @@ editor.newFile = (forced) => { modal.close(); } + loadButton.onclick = () => { + modal.close(); + editor.loadFile(true); + } + //Append resolution presets for (let presetID in editor.resolutionPresets) { const preset = editor.resolutionPresets[presetID]; From d2c20f2aa02ac4c5e6b470c77e4728faaf7d8c17 Mon Sep 17 00:00:00 2001 From: derpygamer2142 <93667155+derpygamer2142@users.noreply.github.com> Date: Sun, 8 Feb 2026 15:45:44 -0800 Subject: [PATCH 2/3] Layer duplication --- dist/artimus.js | 13 ++++++++++++- lang/english.json | 1 + 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/dist/artimus.js b/dist/artimus.js index e3f0f55..c978921 100644 --- a/dist/artimus.js +++ b/dist/artimus.js @@ -1541,6 +1541,16 @@ window.artimus = { return layer; } + duplicateLayer(name, noSwitch) { + const oldLayer = this.layers.find(layer => layer.name === name); + const newLayer = new artimus.layer(this.canvas.width, this.canvas.height, oldLayer.name + " copy", this, noSwitch); + + newLayer.dataRaw.data.set(oldLayer.dataRaw.data); // Copy the image data between the layers + newLayer.alpha = oldLayer.alpha; + newLayer.visibility = oldLayer.visibility; + newLayer.blendMode = oldLayer.blendMode; + } + _createLayerElement(layerData) { const element = document.createElement("div"); element.className = "artimus-layerWrapper"; @@ -1557,7 +1567,8 @@ window.artimus = { label.CUGI_CONTEXT = () => { return [ { type: "button", text: "delete", onclick: () => this.removeLayer(element.targetLayer) }, - { type: "button", text: "properties", onclick: () => (artimus.layerPropertyMenu)(this, this.getLayer(element.targetLayer)) } + { type: "button", text: "properties", onclick: () => (artimus.layerPropertyMenu)(this, this.getLayer(element.targetLayer)) }, + { type: "button", text: "duplicate", onclick: () => this.duplicateLayer(element.targetLayer, false)} ] } diff --git a/lang/english.json b/lang/english.json index c635e83..9664b39 100644 --- a/lang/english.json +++ b/lang/english.json @@ -47,6 +47,7 @@ "artimus.layerDropdown.delete": "Delete", "artimus.layerDropdown.properties": "Properties", + "artimus.layerDropdown.duplicate": "Duplicate", "artimus.blendModes.source-over": "Source over", "artimus.blendModes.source-in": "Source in", From d45797c60e514095feb3a05802a247e79cb2a33b Mon Sep 17 00:00:00 2001 From: derpygamer2142 <93667155+derpygamer2142@users.noreply.github.com> Date: Tue, 17 Feb 2026 20:52:12 -0800 Subject: [PATCH 3/3] Load file as layer --- dist/artimus.js | 74 ++++++++++++++++++++++++++------------ index.html | 2 +- lang/english.json | 3 +- site/src/menus/loadFile.js | 24 ++++++++----- 4 files changed, 70 insertions(+), 33 deletions(-) diff --git a/dist/artimus.js b/dist/artimus.js index a139368..d0b4b4f 100644 --- a/dist/artimus.js +++ b/dist/artimus.js @@ -371,7 +371,7 @@ window.artimus = { this.workspace = workspace; this.name = name || `${artimus.translate("layer#", "layer").replace("#", this.layers.length + 1)}`; - //Do a thing, guarenteed to not exist (hopefully) + //Do a thing, guaranteed to not exist (hopefully) if (this.workspace.layerExists(name)) { let num = 1; name = (`${artimus.translate("layer#", "layer").replace("#", num)}`); @@ -381,6 +381,7 @@ window.artimus = { num++; name = (`${artimus.translate("layer#", "layer").replace("#", num)}`); } + this.name = name } @@ -2533,7 +2534,9 @@ window.artimus = { this.createLayer(name, true); //Set layer data - this.layers[layer + 1].dataRaw = new ImageData(imageData, this.width, this.height); + this.layers[layer + 1].dataRaw = new ImageData(imageData, + (data[5] << 16) + (data[6] << 8) + (data[7]), + (data[8] << 16) + (data[9] << 8) + (data[10])); this.layers[layer + 1].blendMode = blendMode; this.updateLayer(layer + 1); @@ -2565,7 +2568,9 @@ window.artimus = { this.createLayer(name, true); //Set layer data - this.layers[layer + 1].dataRaw = new ImageData(imageData, this.width, this.height); + this.layers[layer + 1].dataRaw = new ImageData(imageData + (data[5] << 16) + (data[6] << 8) + (data[7]), + (data[8] << 16) + (data[9] << 8) + (data[10])); this.layers[layer + 1].blendMode = blendMode; this.updateLayer(layer + 1); @@ -2598,7 +2603,9 @@ window.artimus = { this.createLayer(name, true); //Set layer data - this.layers[layer + 1].dataRaw = new ImageData(imageData, this.width, this.height); + this.layers[layer + 1].dataRaw = new ImageData(imageData + (data[5] << 16) + (data[6] << 8) + (data[7]), + (data[8] << 16) + (data[9] << 8) + (data[10])); this.layers[layer + 1].blendMode = blendMode; this.updateLayer(layer + 1); @@ -2634,7 +2641,9 @@ window.artimus = { this.createLayer(name, true); //Set layer data - this.layers[layer + 1].dataRaw = new ImageData(imageData, this.width, this.height); + this.layers[layer + 1].dataRaw = new ImageData(imageData, + (data[5] << 16) + (data[6] << 8) + (data[7]), + (data[8] << 16) + (data[9] << 8) + (data[10])); this.layers[layer + 1].blendMode = blendMode; this.layers[layer + 1].alpha = alpha; @@ -2645,7 +2654,7 @@ window.artimus = { }, ]; - importArtimus(input) { + importArtimus(input, replaceFile) { const data = new Uint8Array(input); //Make sure it is an artimus image @@ -2655,12 +2664,14 @@ window.artimus = { data[2] == this.magic[2] && data[3] == this.magic[3] ) { - this.new( - (data[5] << 16) + (data[6] << 8) + (data[7]), - (data[8] << 16) + (data[9] << 8) + (data[10]), - () => { + const handleImport = () => { //Count bytes needed - const bytesPerLayer = this.width * this.height * 4; + if (replaceFile) { + this.width = (data[5] << 16) + (data[6] << 8) + (data[7]) + this.height = (data[8] << 16) + (data[9] << 8) + (data[10]) + } + + const bytesPerLayer = ((data[5] << 16) + (data[6] << 8) + (data[7])) * ((data[8] << 16) + (data[9] << 8) + (data[10])) * 4; const layerCount = (data[11] << 8) + data[12]; const format = (data[4]); @@ -2669,7 +2680,7 @@ window.artimus = { let idx = 12; //layer 1 is set to NaN as to not confuse it with an actual layer - this.layers[0].name = NaN; + if (replaceFile) this.layers[0].name = NaN; //Loop through layers, and read them with whatever format of reader is needed; let layerReader = this.layerReaders[format]; @@ -2679,11 +2690,12 @@ window.artimus = { return; } - for (let layer = 0; layer < layerCount; layer++) { + const fileLayers = this.layers.length; + for (let layer = (replaceFile ? 0 : this.layers.length-1); layer < (replaceFile ? layerCount : layerCount+fileLayers-1); layer++) { idx = layerReader(data, layer, bytesPerLayer, idx); } - this.setLayer(1).then(() => { + if (replaceFile) this.setLayer(1).then(() => { this.removeLayer(0) this.setLayer(0); }); @@ -2703,7 +2715,16 @@ window.artimus = { console.error("Json header could possibly be corrupted :("); } } - }); + + + } + + if (replaceFile) this.new( + (data[5] << 16) + (data[6] << 8) + (data[7]), + (data[8] << 16) + (data[9] << 8) + (data[10]), + handleImport + ); + else handleImport(); } else console.error("Artimus File invalid!"); } @@ -2806,15 +2827,15 @@ window.artimus = { // Will be set upon file save/load fileSystemHandle = null; - importFromImage(image) { + importFromImage(image, replaceFile) { let extension = image.name.split("."); extension = extension[extension.length - 1]; - this.fileReader.onload = () => { this.onImageLoad(this.fileReader.result, extension); }; + this.fileReader.onload = () => { this.onImageLoad(this.fileReader.result, extension, replaceFile); }; this.fileReader[this.importTypes[extension] || "readAsDataURL"](image); } - importFromPC() { + importFromPC(replaceFile) { // Not yet widely available, so we will need to check we can use the file system access API if (window.showSaveFilePicker) return window.showOpenFilePicker({ id: "artimus_file_location", @@ -2827,7 +2848,7 @@ window.artimus = { ] }).then(fsHandle => { artimus.activeWorkspaces[0].fileSystemHandle = fsHandle[0]; - fsHandle[0].getFile().then(file => artimus.activeWorkspaces[0].importFromImage(file)); + fsHandle[0].getFile().then(file => artimus.activeWorkspaces[0].importFromImage(file, replaceFile)); }); else { @@ -2837,7 +2858,7 @@ window.artimus = { const filePromise = new Promise((resolve) => { fileInput.onchange = () => { - artimus.activeWorkspaces[0].importFromImage(fileInput.files[0]); + artimus.activeWorkspaces[0].importFromImage(fileInput.files[0], replaceFile); resolve(); }; fileInput.onError = () => { console.log('file load error wow'); } @@ -2848,21 +2869,28 @@ window.artimus = { } } - onImageLoad(data, extension) { + onImageLoad(data, extension, replaceFile) { switch (extension) { case "art": case "artimus": - this.importArtimus(data); + this.importArtimus(data, replaceFile); break; default: const image = new Image(); image.onload = () => { - this.new(image.width, image.height, () => { + if (replaceFile) this.new(image.width, image.height, () => { this.setLayer(0, () => { this.editGL.drawImage(image, 0, 0); }); }); + + else { + this.createLayer(`Layer ${this.layers.length+1}`, false); + this.setLayer(this.layers.length-1, () => { + this.editGL.drawImage(image, 0, 0, image.width, image.height); // Todo: Maybe prompt about resizing the canvas? + }); + } } image.src = data; diff --git a/index.html b/index.html index 91b995d..991ab24 100644 --- a/index.html +++ b/index.html @@ -52,7 +52,7 @@ editor.exportMenu(); }} { type: "button", text: "Load", onclick: () => { - artimus.activeWorkspaces[0].importFromPC(); + editor.loadFile(false); }} Edit diff --git a/lang/english.json b/lang/english.json index 4038911..22fcc95 100644 --- a/lang/english.json +++ b/lang/english.json @@ -108,7 +108,8 @@ "artimus.modal.newFile.loadInstead": "Load a file instead", "artimus.modal.loadFile.title": "Load file", - "artimus.modal.loadFile.load": "Load", + "artimus.modal.loadFile.loadAndReplace": "Load and replace", + "artimus.modal.loadFile.loadAndAdd": "Load and add as layer", "artimus.modal.loadFile.createInstead": "Create a file instead", "artimus.modal.resizeFile.title": "Resize", diff --git a/site/src/menus/loadFile.js b/site/src/menus/loadFile.js index 3578fc3..857da9e 100644 --- a/site/src/menus/loadFile.js +++ b/site/src/menus/loadFile.js @@ -2,30 +2,38 @@ editor.loadFile = (forced) => { //Simple, easy. new editor.modal(artimus.translate("title", "modal.loadFile"), (contents, modal) => { - const loadButton = document.createElement("button"); + const loadReplaceButton = document.createElement("button"); + const loadAddButton = document.createElement("button"); const createButon = document.createElement("button"); - loadButton.className = "artimus-button"; + loadReplaceButton.className = "artimus-button"; + loadAddButton.className = "artimus-button"; createButon.className = "artimus-button"; createButon.innerText = artimus.translate("createInstead", "modal.loadFile"); - loadButton.innerText = artimus.translate("load", "modal.loadFile"); + loadReplaceButton.innerText = artimus.translate("loadAndReplace", "modal.loadFile"); + loadAddButton.innerText = artimus.translate("loadAndAdd", "modal.loadFile"); createButon.onclick = () => { modal.close(); editor.newFile(true); } - loadButton.onclick = () => { - artimus.activeWorkspaces[0].importFromPC().then(() => { + loadReplaceButton.onclick = () => { + artimus.activeWorkspaces[0].importFromPC(true).then(() => { + modal.close(); + }); + }; + loadAddButton.onclick = () => { + artimus.activeWorkspaces[0].importFromPC(false).then(() => { modal.close(); }); }; - contents.appendChild(loadButton); + contents.appendChild(loadReplaceButton); // don't give the option to add as a layer if this is probably an empty file + if (!forced) contents.appendChild(loadAddButton); + contents.appendChild(document.createElement("br")); // seperate load buttons and create button contents.appendChild(createButon); - - loadButton.click(); }, { hasClose: !forced, translationContext: "loadFile", width: 25, height: 20 }); } \ No newline at end of file