style: formatting with prettier

This commit is contained in:
Orangerot 2025-01-13 09:48:05 +01:00
parent 13944b526f
commit 05984aa043
3 changed files with 353 additions and 235 deletions

View file

@ -1,15 +1,15 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" type="image/x-icon" href="assets/favicon.ico"> <link rel="icon" type="image/x-icon" href="assets/favicon.ico" />
<script src="main.js"></script> <script src="main.js"></script>
<link rel="stylesheet" href="lib/bulma.min.css"> <link rel="stylesheet" href="lib/bulma.min.css" />
<link rel="stylesheet" href="lib/bulma-slider.css"> <link rel="stylesheet" href="lib/bulma-slider.css" />
<link rel="stylesheet" href="lib/fontawesome/css/fontawesome.min.css"> <link rel="stylesheet" href="lib/fontawesome/css/fontawesome.min.css" />
<link rel="stylesheet" href="lib/fontawesome/css/solid.min.css"> <link rel="stylesheet" href="lib/fontawesome/css/solid.min.css" />
<link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="style.css" />
<title>Imagine - Image Editor</title> <title>Imagine - Image Editor</title>
</head> </head>
<body class="import-active"> <body class="import-active">
@ -21,28 +21,48 @@
<div class="column" id="viewport"> <div class="column" id="viewport">
<div class="hero is-fullheight"> <div class="hero is-fullheight">
<div class="hero-body"> <div class="hero-body">
<div class="container" > <div class="container">
<!-- viewport that contains canvas/video/import buttons --> <!-- viewport that contains canvas/video/import buttons -->
<canvas id="myCanvas" class="is-visible-editor" width="300" height="300"></canvas> <canvas
<video id="video" class="is-visible-camera" width="300" height="300"></video> id="myCanvas"
class="is-visible-editor"
width="300"
height="300"
></canvas>
<video
id="video"
class="is-visible-camera"
width="300"
height="300"
></video>
<div class="container has-text-centered mb-6 is-visible-import"> <div class="container has-text-centered mb-6 is-visible-import">
<h1 class="title is-1">Imagine</h1> <h1 class="title is-1">Imagine</h1>
<h2 class="subtitle is-3">A simple Image Editor</h2> <h2 class="subtitle is-3">A simple Image Editor</h2>
</div> </div>
<div class="columns is-mobile is-visible-import"> <div class="columns is-mobile is-visible-import">
<div class="column"> <div class="column">
<button id="take-picture" class="button is-large is-responsive is-fullwidth py-6" > <button
id="take-picture"
class="button is-large is-responsive is-fullwidth py-6"
>
<div class="container"> <div class="container">
<i class="fa-solid fa-6x fa-video mb-4"></i><br> <i class="fa-solid fa-6x fa-video mb-4"></i><br />
Use Camera Use Camera
</div> </div>
</button> </button>
</div> </div>
<div class="column"> <div class="column">
<label class="button is-large is-responsive is-fullwidth py-6" > <label
<input class="file-input" type="file" id="upload-image" accept="image/*"> class="button is-large is-responsive is-fullwidth py-6"
>
<input
class="file-input"
type="file"
id="upload-image"
accept="image/*"
/>
<div class="container"> <div class="container">
<i class="fa-solid fa-6x fa-upload mb-4"></i><br> <i class="fa-solid fa-6x fa-upload mb-4"></i><br />
Choose a file… Choose a file…
</div> </div>
</label> </label>
@ -50,20 +70,26 @@
</div> </div>
</div> </div>
<!-- buttons inside of the viewport --> <!-- buttons inside of the viewport -->
<a id="settings-button" href="#settings" class="button is-hidden-desktop is-visible-editor"> <a
<i class="fa-solid fa-sliders mr-2"></i> Settings id="settings-button"
href="#settings"
class="button is-hidden-desktop is-visible-editor"
>
<i class="fa-solid fa-sliders mr-2"></i> Settings
</a> </a>
<button id="back" class="button is-hidden-import"> <button id="back" class="button is-hidden-import">
<i class="fa-solid fa-arrow-left mr-2"></i> Back <i class="fa-solid fa-arrow-left mr-2"></i> Back
</button> </button>
<button id="cheese" class="button is-large is-visible-camera" > <button id="cheese" class="button is-large is-visible-camera">
<i class="fa-solid fa-camera mr-2"></i> Take a Picture <i class="fa-solid fa-camera mr-2"></i> Take a Picture
</button> </button>
</div> </div>
</div> </div>
</div> </div>
<!-- settings menu for desktop use --> <!-- settings menu for desktop use -->
<div class="column is-narrow has-background-black-ter is-hidden-mobile is-hidden-tablet-only is-visible-editor"> <div
class="column is-narrow has-background-black-ter is-hidden-mobile is-hidden-tablet-only is-visible-editor"
>
<aside class="menu mr-3"> <aside class="menu mr-3">
<h1 class="title">Imagine</h1> <h1 class="title">Imagine</h1>
<h2 class="subtitle">Image Editor</h2> <h2 class="subtitle">Image Editor</h2>
@ -71,9 +97,7 @@
<!-- autocomplete off prevents browsers from remebering the value on page reloads --> <!-- autocomplete off prevents browsers from remebering the value on page reloads -->
<form autocomplete="off"> <form autocomplete="off">
<div class="buttons"> <div class="buttons">
<button class="button is-primary save-image"> <button class="button is-primary save-image">Save Image</button>
Save Image
</button>
<button class="button is-secondary share-image"> <button class="button is-secondary share-image">
<i class="fa-solid fa-share-from-square fa-fw mr-1"></i> <i class="fa-solid fa-share-from-square fa-fw mr-1"></i>
Share Share
@ -86,9 +110,7 @@
</button> </button>
</div> </div>
<p class="menu-label"> <p class="menu-label">Color Correction</p>
Color Correction
</p>
<div class="field"> <div class="field">
<label class="label"> <label class="label">
@ -99,10 +121,15 @@
</span> </span>
</label> </label>
<div class="control"> <div class="control">
<input type="range" <input
id="brightness" type="range"
class="brightness slider is-fullwidth is-primary" id="brightness"
min="0" max="2" value="1" step="0.01"> class="brightness slider is-fullwidth is-primary"
min="0"
max="2"
value="1"
step="0.01"
/>
</div> </div>
</div> </div>
<div class="field"> <div class="field">
@ -114,10 +141,15 @@
</span> </span>
</label> </label>
<div class="control"> <div class="control">
<input type="range" <input
id="saturate" type="range"
class="saturate slider is-fullwidth is-primary" id="saturate"
min="0" max="2" value="1" step="0.01"> class="saturate slider is-fullwidth is-primary"
min="0"
max="2"
value="1"
step="0.01"
/>
</div> </div>
</div> </div>
<div class="field"> <div class="field">
@ -129,10 +161,15 @@
</span> </span>
</label> </label>
<div class="control"> <div class="control">
<input type="range" <input
id="contrast" type="range"
class="contrast slider is-fullwidth is-primary" id="contrast"
min="0" max="2" value="1" step="0.01"> class="contrast slider is-fullwidth is-primary"
min="0"
max="2"
value="1"
step="0.01"
/>
</div> </div>
</div> </div>
<div class="field"> <div class="field">
@ -144,16 +181,19 @@
</span> </span>
</label> </label>
<div class="control"> <div class="control">
<input type="range" <input
id="hue-rotate" type="range"
class="hue-rotate slider is-fullwidth is-primary" id="hue-rotate"
min="-180" max="180" value="0" step="1"> class="hue-rotate slider is-fullwidth is-primary"
min="-180"
max="180"
value="0"
step="1"
/>
</div> </div>
</div> </div>
<p class="menu-label"> <p class="menu-label">Filters</p>
Filters
</p>
<div class="field"> <div class="field">
<label class="label"> <label class="label">
<i class="fa-solid fa-radio fa-fw"></i> <i class="fa-solid fa-radio fa-fw"></i>
@ -163,10 +203,15 @@
</span> </span>
</label> </label>
<div class="control"> <div class="control">
<input type="range" <input
id="grayscale" type="range"
class="grayscale slider is-fullwidth is-primary" id="grayscale"
min="0" max="1" value="0" step="0.01"> class="grayscale slider is-fullwidth is-primary"
min="0"
max="1"
value="0"
step="0.01"
/>
</div> </div>
</div> </div>
<div class="field"> <div class="field">
@ -178,10 +223,15 @@
</span> </span>
</label> </label>
<div class="control"> <div class="control">
<input type="range" <input
id="sepia" type="range"
class="sepia slider is-fullwidth is-primary" id="sepia"
min="0" max="1" value="0" step="0.01"> class="sepia slider is-fullwidth is-primary"
min="0"
max="1"
value="0"
step="0.01"
/>
</div> </div>
</div> </div>
<div class="field"> <div class="field">
@ -193,15 +243,18 @@
</span> </span>
</label> </label>
<div class="control"> <div class="control">
<input type="range" <input
id="invert" type="range"
class="invert slider is-fullwidth is-primary" id="invert"
min="0" max="1" value="0" step="0.01"> class="invert slider is-fullwidth is-primary"
min="0"
max="1"
value="0"
step="0.01"
/>
</div> </div>
</div> </div>
<p class="menu-label"> <p class="menu-label">Blur and Sharpen</p>
Blur and Sharpen
</p>
<div class="field"> <div class="field">
<label class="label"> <label class="label">
<i class="fa-solid fa-water fa-fw"></i> <i class="fa-solid fa-water fa-fw"></i>
@ -211,10 +264,15 @@
</span> </span>
</label> </label>
<div class="control"> <div class="control">
<input type="range" <input
id="blur" type="range"
class="blur slider is-fullwidth is-primary" id="blur"
min="0" max="1" value="0" step="0.05"> class="blur slider is-fullwidth is-primary"
min="0"
max="1"
value="0"
step="0.05"
/>
</div> </div>
</div> </div>
</form> </form>
@ -222,7 +280,10 @@
</div> </div>
</div> </div>
<!-- settings menu for mobile use --> <!-- settings menu for mobile use -->
<div id="settings" class="notification is-fullwidth is-hidden-desktop is-visible-editor"> <div
id="settings"
class="notification is-fullwidth is-hidden-desktop is-visible-editor"
>
<!-- #settings is an anhor to scroll to when clicked on the settings-button --> <!-- #settings is an anhor to scroll to when clicked on the settings-button -->
<!-- #top is an anchor provided by the browser to go to the top of the page --> <!-- #top is an anchor provided by the browser to go to the top of the page -->
<a href="#top" class="delete"></a> <a href="#top" class="delete"></a>
@ -230,9 +291,7 @@
<!-- autocomplete off prevents browsers from remebering the value on page reloads --> <!-- autocomplete off prevents browsers from remebering the value on page reloads -->
<form autocomplete="off"> <form autocomplete="off">
<div class="buttons"> <div class="buttons">
<button class="button is-primary save-image"> <button class="button is-primary save-image">Save Image</button>
Save Image
</button>
<button class="button is-secondary share-image"> <button class="button is-secondary share-image">
<i class="fa-solid fa-share-from-square fa-fw mr-1"></i> <i class="fa-solid fa-share-from-square fa-fw mr-1"></i>
Share Share
@ -245,9 +304,7 @@
</button> </button>
</div> </div>
<p class="menu-label"> <p class="menu-label">Color Correction</p>
Color Correction
</p>
<div class="field"> <div class="field">
<label class="label"> <label class="label">
@ -258,10 +315,15 @@
</span> </span>
</label> </label>
<div class="control"> <div class="control">
<input type="range" <input
id="brightness" type="range"
class="brightness slider is-fullwidth is-primary" id="brightness"
min="0" max="2" value="1" step="0.01"> class="brightness slider is-fullwidth is-primary"
min="0"
max="2"
value="1"
step="0.01"
/>
</div> </div>
</div> </div>
<div class="field"> <div class="field">
@ -273,10 +335,15 @@
</span> </span>
</label> </label>
<div class="control"> <div class="control">
<input type="range" <input
id="saturate" type="range"
class="saturate slider is-fullwidth is-primary" id="saturate"
min="0" max="2" value="1" step="0.01"> class="saturate slider is-fullwidth is-primary"
min="0"
max="2"
value="1"
step="0.01"
/>
</div> </div>
</div> </div>
<div class="field"> <div class="field">
@ -288,10 +355,15 @@
</span> </span>
</label> </label>
<div class="control"> <div class="control">
<input type="range" <input
id="contrast" type="range"
class="contrast slider is-fullwidth is-primary" id="contrast"
min="0" max="2" value="1" step="0.01"> class="contrast slider is-fullwidth is-primary"
min="0"
max="2"
value="1"
step="0.01"
/>
</div> </div>
</div> </div>
<div class="field"> <div class="field">
@ -303,16 +375,19 @@
</span> </span>
</label> </label>
<div class="control"> <div class="control">
<input type="range" <input
id="hue-rotate" type="range"
class="hue-rotate slider is-fullwidth is-primary" id="hue-rotate"
min="-180" max="180" value="0" step="1"> class="hue-rotate slider is-fullwidth is-primary"
min="-180"
max="180"
value="0"
step="1"
/>
</div> </div>
</div> </div>
<p class="menu-label"> <p class="menu-label">Filters</p>
Filters
</p>
<div class="field"> <div class="field">
<label class="label"> <label class="label">
<i class="fa-solid fa-radio fa-fw"></i> <i class="fa-solid fa-radio fa-fw"></i>
@ -322,10 +397,15 @@
</span> </span>
</label> </label>
<div class="control"> <div class="control">
<input type="range" <input
id="grayscale" type="range"
class="grayscale slider is-fullwidth is-primary" id="grayscale"
min="0" max="1" value="0" step="0.01"> class="grayscale slider is-fullwidth is-primary"
min="0"
max="1"
value="0"
step="0.01"
/>
</div> </div>
</div> </div>
<div class="field"> <div class="field">
@ -337,10 +417,15 @@
</span> </span>
</label> </label>
<div class="control"> <div class="control">
<input type="range" <input
id="sepia" type="range"
class="sepia slider is-fullwidth is-primary" id="sepia"
min="0" max="1" value="0" step="0.01"> class="sepia slider is-fullwidth is-primary"
min="0"
max="1"
value="0"
step="0.01"
/>
</div> </div>
</div> </div>
<div class="field"> <div class="field">
@ -352,15 +437,18 @@
</span> </span>
</label> </label>
<div class="control"> <div class="control">
<input type="range" <input
id="invert" type="range"
class="invert slider is-fullwidth is-primary" id="invert"
min="0" max="1" value="0" step="0.01"> class="invert slider is-fullwidth is-primary"
min="0"
max="1"
value="0"
step="0.01"
/>
</div> </div>
</div> </div>
<p class="menu-label"> <p class="menu-label">Blur and Sharpen</p>
Blur and Sharpen
</p>
<div class="field"> <div class="field">
<label class="label"> <label class="label">
<i class="fa-solid fa-water fa-fw"></i> <i class="fa-solid fa-water fa-fw"></i>
@ -370,10 +458,15 @@
</span> </span>
</label> </label>
<div class="control"> <div class="control">
<input type="range" <input
id="blur" type="range"
class="blur slider is-fullwidth is-primary" id="blur"
min="0" max="1" value="0" step="0.05"> class="blur slider is-fullwidth is-primary"
min="0"
max="1"
value="0"
step="0.05"
/>
</div> </div>
</div> </div>
</form> </form>

248
main.js
View file

@ -2,49 +2,64 @@ let canvas;
let video; let video;
let ctx; let ctx;
/* /*
* theses settings correspond to a css-filter. you can specify an optional * theses settings correspond to a css-filter. you can specify an optional
* filter attribute to modify the value from the input element. * filter attribute to modify the value from the input element.
*/ */
let settings = { let settings = {
"brightness": {}, "brightness": {},
"saturate": {}, "saturate": {},
"contrast": {}, "contrast": {},
"hue-rotate": { filter: value => value + "deg" }, "hue-rotate": { filter: (value) => value + "deg" },
"grayscale": {}, "grayscale": {},
"sepia": {}, "sepia": {},
"invert": {}, "invert": {},
"blur": { filter: value => value * canvas.width / 100 + 'px' } "blur": { filter: (value) => (value * canvas.width) / 100 + "px" },
}; };
const img = new Image(); const img = new Image();
let lensflare_active = false; let lensflare_active = false;
let cursor = { let cursor = {
x: 0, x: 0,
y: 0 y: 0,
}; };
// wait for site to be parsed so element can be found // wait for site to be parsed so element can be found
document.addEventListener("DOMContentLoaded", function() { document.addEventListener("DOMContentLoaded", function () {
canvas = document.getElementById("myCanvas"); canvas = document.getElementById("myCanvas");
ctx = canvas.getContext("2d"); ctx = canvas.getContext("2d");
video = document.getElementById("video"); video = document.getElementById("video");
document.getElementById("back").addEventListener("click", () => document.body.className = "import-active"); document
.getElementById("back")
.addEventListener(
"click",
() => (document.body.className = "import-active"),
);
// bind listeners // bind listeners
document.getElementById("viewport").addEventListener("drop", drop_handler); document
document.getElementById("viewport").addEventListener("dragover", event => event.preventDefault()); .getElementById("viewport")
document.getElementById("take-picture").addEventListener("click", use_camera); .addEventListener("drop", drop_handler);
document.getElementById("cheese").addEventListener("click", take_picture); document
document.getElementById("upload-image").addEventListener("change", upload_image) .getElementById("viewport")
.addEventListener("dragover", (event) => event.preventDefault());
document
.getElementById("take-picture")
.addEventListener("click", use_camera);
document
.getElementById("cheese")
.addEventListener("click", take_picture);
document
.getElementById("upload-image")
.addEventListener("change", upload_image);
for (let element of document.getElementsByClassName("save-image")) { for (let element of document.getElementsByClassName("save-image")) {
element.addEventListener("click", save_image) element.addEventListener("click", save_image);
} }
for (let element of document.getElementsByClassName("share-image")) { for (let element of document.getElementsByClassName("share-image")) {
element.addEventListener("click", share_image) element.addEventListener("click", share_image);
} }
for (let element of document.getElementsByClassName("lensflare")) { for (let element of document.getElementsByClassName("lensflare")) {
element.addEventListener("click", function(event) { element.addEventListener("click", function (event) {
event.preventDefault(); event.preventDefault();
lensflare_active = !lensflare_active; lensflare_active = !lensflare_active;
if (lensflare_active) { if (lensflare_active) {
@ -56,19 +71,23 @@ document.addEventListener("DOMContentLoaded", function() {
}); });
} }
document.getElementById("viewport").addEventListener("mousemove", function(event) { document
if (event.buttons === 1 && lensflare_active) { .getElementById("viewport")
cursor.x = (event.clientX - canvas.offsetLeft) / canvas.clientWidth - 0.5; .addEventListener("mousemove", function (event) {
cursor.y = (event.clientY - canvas.offsetTop) / canvas.clientWidth - 0.5; if (event.buttons === 1 && lensflare_active) {
console.log(cursor); cursor.x =
draw(true); (event.clientX - canvas.offsetLeft) / canvas.clientWidth - 0.5;
} cursor.y =
}); (event.clientY - canvas.offsetTop) / canvas.clientWidth - 0.5;
console.log(cursor);
draw(true);
}
});
window.addEventListener("deviceorientation", function(event) { window.addEventListener("deviceorientation", function (event) {
if (lensflare_active) { if (lensflare_active) {
cursor.x = event.gamma / 360 * 4; cursor.x = (event.gamma / 360) * 4;
cursor.y = (event.beta - 90) / 360 * 4; cursor.y = ((event.beta - 90) / 360) * 4;
console.log(cursor); console.log(cursor);
// draw(true); // draw(true);
} }
@ -78,22 +97,22 @@ document.addEventListener("DOMContentLoaded", function() {
if (lensflare_active) { if (lensflare_active) {
draw(true); draw(true);
} }
requestAnimationFrame(animation) requestAnimationFrame(animation);
}; }
animation(); animation();
window.addEventListener("resize", () => draw(true)) window.addEventListener("resize", () => draw(true));
video.addEventListener("canplay", function() { video.addEventListener("canplay", function () {
const width = 320; const width = 320;
let height = video.videoHeight / (video.videoWidth / width); let height = video.videoHeight / (video.videoWidth / width);
// Firefox currently has a bug where the height can't be read from // Firefox currently has a bug where the height can't be read from
// the video, so we will make assumptions if this happens. // the video, so we will make assumptions if this happens.
if (isNaN(height)) { if (isNaN(height)) {
height = width / (4 / 3); height = width / (4 / 3);
} }
video.width = width; video.width = width;
video.height = height; video.height = height;
}); });
@ -103,7 +122,7 @@ document.addEventListener("DOMContentLoaded", function() {
const elements = [...document.getElementsByClassName(setting)]; const elements = [...document.getElementsByClassName(setting)];
settings[setting].elements = elements; settings[setting].elements = elements;
// if filter is not definded, use identity function // if filter is not definded, use identity function
settings[setting].filter ||= value => value; settings[setting].filter ||= (value) => value;
for (let element of elements) { for (let element of elements) {
element.addEventListener("input", settings_apply); element.addEventListener("input", settings_apply);
} }
@ -116,10 +135,10 @@ document.addEventListener("DOMContentLoaded", function() {
}); });
/** /**
* Reset all inputs of a setting back to the default value * Reset all inputs of a setting back to the default value
* *
* @param {string} setting - key of the settings object to reset * @param {string} setting - key of the settings object to reset
*/ */
function reset_all(setting) { function reset_all(setting) {
console.log("reseting " + setting); console.log("reseting " + setting);
for (let element of settings[setting].elements) { for (let element of settings[setting].elements) {
@ -129,9 +148,9 @@ function reset_all(setting) {
} }
/** /**
* Request camera access and on succhess, show camera feed on video-element and * Request camera access and on succhess, show camera feed on video-element and
* switch to camera-active view * switch to camera-active view
*/ */
function use_camera() { function use_camera() {
navigator.mediaDevices navigator.mediaDevices
.getUserMedia({ video: true, audio: false }) .getUserMedia({ video: true, audio: false })
@ -146,9 +165,9 @@ function use_camera() {
} }
/** /**
* Take a still frame of the video-element showing the camera-feed and load it * Take a still frame of the video-element showing the camera-feed and load it
* to the img to be rendered by canvas and switch to editor-active view * to the img to be rendered by canvas and switch to editor-active view
*/ */
function take_picture() { function take_picture() {
canvas.width = video.width; canvas.width = video.width;
canvas.height = video.height; canvas.height = video.height;
@ -160,9 +179,9 @@ function take_picture() {
} }
/** /**
* Load selected file by input element to img to be rendered by canvas and * Load selected file by input element to img to be rendered by canvas and
* switch to editor-active view * switch to editor-active view
*/ */
function upload_image() { function upload_image() {
document.body.className = "editor-active"; document.body.className = "editor-active";
@ -173,13 +192,13 @@ function upload_image() {
} }
/** /**
* Get file that is dropped into the website and load it to img to be rendered * Get file that is dropped into the website and load it to img to be rendered
* by canvas and switch to editor-active view. * by canvas and switch to editor-active view.
* *
* @param {DragEvent} ev -supplier by event listener * @param {DragEvent} ev -supplier by event listener
* *
* https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API/File_drag_and_drop * https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API/File_drag_and_drop
*/ */
function drop_handler(ev) { function drop_handler(ev) {
ev.preventDefault(); ev.preventDefault();
let file; let file;
@ -199,28 +218,28 @@ function drop_handler(ev) {
} }
/** /**
* Creates a download of the edited image in full resolution by creating a link * Creates a download of the edited image in full resolution by creating a link
* and virtually clicking it. * and virtually clicking it.
* *
* @param {PointerEvent} event - supplied by event listener * @param {PointerEvent} event - supplied by event listener
*/ */
function save_image(event) { function save_image(event) {
event.preventDefault(); event.preventDefault();
draw(false); draw(false);
const dataUrl = canvas.toDataURL("image/png"); const dataUrl = canvas.toDataURL("image/png");
// downloading only works with links but not window.open // downloading only works with links but not window.open
const link = document.createElement('a'); const link = document.createElement("a");
link.href = dataUrl; link.href = dataUrl;
link.download = 'imagine.png'; link.download = "imagine.png";
link.click(); link.click();
} }
/** /**
* Uses the navigator.share API to share the edited image in full reslution. * Uses the navigator.share API to share the edited image in full reslution.
* *
* @param {PointerEvent} event - supplied by event listener * @param {PointerEvent} event - supplied by event listener
*/ */
function share_image(event) { function share_image(event) {
event.preventDefault(); event.preventDefault();
if (!navigator.share) { if (!navigator.share) {
@ -230,21 +249,21 @@ function share_image(event) {
canvas.toBlob(async (blob) => { canvas.toBlob(async (blob) => {
if (!blob) return; if (!blob) return;
const file = new File([blob], 'imagine.png', {type: 'image/png'}); const file = new File([blob], "imagine.png", { type: "image/png" });
try { try {
await navigator.share({files: [file]}); await navigator.share({ files: [file] });
} catch (error) { } catch (error) {
console.log('Error sharing:', error); console.log("Error sharing:", error);
} }
}, 'image/png'); }, "image/png");
} }
/** /**
* Set all inputs of a setting to the value of the input that changed it. * Set all inputs of a setting to the value of the input that changed it.
* *
* @param {Event} event - supplied by event listener * @param {Event} event - supplied by event listener
*/ */
function settings_apply(event) { function settings_apply(event) {
const changed_setting = event.target.id; const changed_setting = event.target.id;
const new_value = event.target.value; const new_value = event.target.value;
@ -259,12 +278,12 @@ function settings_apply(event) {
} }
/** /**
* Render a lensflare shader for every pixel on the canvas. * Render a lensflare shader for every pixel on the canvas.
* *
* @param {number} pos_x - x position of the lensflare in the range [-0.5,0.5] * @param {number} pos_x - x position of the lensflare in the range [-0.5,0.5]
* @param {number} pos_y - y position of the lensflare in the range [-0.5,0.5] * @param {number} pos_y - y position of the lensflare in the range [-0.5,0.5]
* https://www.shadertoy.com/view/ldSXWK * https://www.shadertoy.com/view/ldSXWK
*/ */
function lensflare(pos_x, pos_y) { function lensflare(pos_x, pos_y) {
const imgdata = ctx.getImageData(0, 0, canvas.width, canvas.height); const imgdata = ctx.getImageData(0, 0, canvas.width, canvas.height);
const pixel_count = imgdata.data.length / 4; const pixel_count = imgdata.data.length / 4;
@ -272,8 +291,8 @@ function lensflare(pos_x, pos_y) {
for (let i = 0; i < pixel_count; i++) { for (let i = 0; i < pixel_count; i++) {
const x = i % canvas.width; const x = i % canvas.width;
const y = i / canvas.width; const y = i / canvas.width;
const u = (x / canvas.width - .5) * aspect_ratio; const u = (x / canvas.width - 0.5) * aspect_ratio;
const v = y / canvas.height - .5; const v = y / canvas.height - 0.5;
const intensity = 1.5 * 255; const intensity = 1.5 * 255;
const uv_len = u * u + v * v; const uv_len = u * u + v * v;
@ -283,60 +302,66 @@ function lensflare(pos_x, pos_y) {
const uvd_pos_x = uvd_x + pos_x; const uvd_pos_x = uvd_x + pos_x;
const uvd_pos_y = uvd_y + pos_y; const uvd_pos_y = uvd_y + pos_y;
let temp = uvd_pos_x * uvd_pos_x + uvd_pos_y * uvd_pos_y let temp = uvd_pos_x * uvd_pos_x + uvd_pos_y * uvd_pos_y;
const f2 = Math.max(1.0 / (1.0 + 32.0 * temp * .64), .0) * 0.1; const f2 = Math.max(1.0 / (1.0 + 32.0 * temp * 0.64), 0.0) * 0.1;
const f22 = Math.max(1.0 / (1.0 + 32.0 * temp * .72), .0) * 0.08; const f22 = Math.max(1.0 / (1.0 + 32.0 * temp * 0.72), 0.0) * 0.08;
const f23 = Math.max(1.0 / (1.0 + 32.0 * temp * .81), .0) * 0.06; const f23 = Math.max(1.0 / (1.0 + 32.0 * temp * 0.81), 0.0) * 0.06;
let uvx_x = u * (1 + 0.5) + uvd_x * 0.5; let uvx_x = u * (1 + 0.5) + uvd_x * 0.5;
let uvx_y = v * (1 + 0.5) + uvd_y * 0.5; let uvx_y = v * (1 + 0.5) + uvd_y * 0.5;
let uvx_pos_x = uvx_x + pos_x; let uvx_pos_x = uvx_x + pos_x;
let uvx_pos_y = uvx_y + pos_y; let uvx_pos_y = uvx_y + pos_y;
temp = uvx_pos_x * uvx_pos_x + uvx_pos_y * uvx_pos_y temp = uvx_pos_x * uvx_pos_x + uvx_pos_y * uvx_pos_y;
const f4 = Math.max(0.01 - temp * .16, .0) * 6.0; const f4 = Math.max(0.01 - temp * 0.16, 0.0) * 6.0;
const f42 = Math.max(0.01 - temp * .20, .0) * 5.0; const f42 = Math.max(0.01 - temp * 0.2, 0.0) * 5.0;
const f43 = Math.max(0.01 - temp * .25, .0) * 3.0; const f43 = Math.max(0.01 - temp * 0.25, 0.0) * 3.0;
uvx_x = u * (1 + 0.4) + uvd_x * 0.4; uvx_x = u * (1 + 0.4) + uvd_x * 0.4;
uvx_y = v * (1 + 0.4) + uvd_y * 0.4; uvx_y = v * (1 + 0.4) + uvd_y * 0.4;
uvx_pos_x = uvx_x + pos_x; uvx_pos_x = uvx_x + pos_x;
uvx_pos_y = uvx_y + pos_y; uvx_pos_y = uvx_y + pos_y;
temp = uvx_pos_x * uvx_pos_x + uvx_pos_y * uvx_pos_y temp = uvx_pos_x * uvx_pos_x + uvx_pos_y * uvx_pos_y;
const f5 = Math.max(0.01 - temp * .04, .0) * 2.0; const f5 = Math.max(0.01 - temp * 0.04, 0.0) * 2.0;
const f52 = Math.max(0.01 - temp * .16, .0) * 2.0; const f52 = Math.max(0.01 - temp * 0.16, 0.0) * 2.0;
const f53 = Math.max(0.01 - temp * .36, .0) * 2.0; const f53 = Math.max(0.01 - temp * 0.36, 0.0) * 2.0;
uvx_x = u * (1 + 0.5) + uvd_x * 0.5; uvx_x = u * (1 + 0.5) + uvd_x * 0.5;
uvx_y = v * (1 + 0.5) + uvd_y * 0.5; uvx_y = v * (1 + 0.5) + uvd_y * 0.5;
uvx_pos_x = uvx_x - pos_x; uvx_pos_x = uvx_x - pos_x;
uvx_pos_y = uvx_y - pos_y; uvx_pos_y = uvx_y - pos_y;
temp = uvx_pos_x * uvx_pos_x + uvx_pos_y * uvx_pos_y temp = uvx_pos_x * uvx_pos_x + uvx_pos_y * uvx_pos_y;
const f6 = Math.max(0.01 - temp * .9, .0) * 6.0; const f6 = Math.max(0.01 - temp * 0.9, 0.0) * 6.0;
const f62 = Math.max(0.01 - temp * .1, .0) * 3.0; const f62 = Math.max(0.01 - temp * 0.1, 0.0) * 3.0;
const f63 = Math.max(0.01 - temp * .12, .0) * 5.0; const f63 = Math.max(0.01 - temp * 0.12, 0.0) * 5.0;
imgdata.data[4 * i + 0] += 1.2 * ((f2 + f4 + f5 + f6) * 1.3 - uvd_len * .05) * intensity; imgdata.data[4 * i + 0] +=
imgdata.data[4 * i + 1] += 1.5 * ((f22 + f42 + f52 + f62) * 1.3 - uvd_len * .05) * intensity; 1.2 * ((f2 + f4 + f5 + f6) * 1.3 - uvd_len * 0.05) * intensity;
imgdata.data[4 * i + 2] += 1.3 * ((f23 + f43 + f53 + f63) * 1.3 - uvd_len * .05) * intensity; imgdata.data[4 * i + 1] +=
1.5 * ((f22 + f42 + f52 + f62) * 1.3 - uvd_len * 0.05) * intensity;
imgdata.data[4 * i + 2] +=
1.3 * ((f23 + f43 + f53 + f63) * 1.3 - uvd_len * 0.05) * intensity;
imgdata.data[4 * i + 3] = 255; imgdata.data[4 * i + 3] = 255;
} }
ctx.putImageData(imgdata, 0, 0); ctx.putImageData(imgdata, 0, 0);
} }
/** /**
* Amply filters and lensflare to the optionally scaled image, to only * Amply filters and lensflare to the optionally scaled image, to only
* calculate on pixels the user can see. * calculate on pixels the user can see.
* *
* @param {bool} viewport_scale - render image scaled to viewport or in full * @param {bool} viewport_scale - render image scaled to viewport or in full
* resolution * resolution
*/ */
function draw(viewport_scale) { function draw(viewport_scale) {
const filter = Object.entries(settings) const filter = Object.entries(settings)
.map(([setting, { elements, filter }]) => `${setting}(${filter(elements[0].value)})`) .map(
.join(" ") ([setting, { elements, filter }]) =>
`${setting}(${filter(elements[0].value)})`,
)
.join(" ");
// set the resolution to the original and then scale down to the viewport to // set the resolution to the original and then scale down to the viewport to
// only calculate the filter on pixels the user can see. // only calculate the filter on pixels the user can see.
@ -346,13 +371,12 @@ function draw(viewport_scale) {
canvas.height = canvas.clientHeight; canvas.height = canvas.clientHeight;
canvas.width = canvas.clientWidth; canvas.width = canvas.clientWidth;
} }
ctx.filter = filter ctx.filter = filter;
console.log(filter); console.log(filter);
ctx.drawImage(img, 0, 0, canvas.width, canvas.height); ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
ctx.filter = ''; ctx.filter = "";
if (lensflare_active) { if (lensflare_active) {
lensflare(cursor.x, cursor.y); lensflare(cursor.x, cursor.y);
} }
} }

View file

@ -4,7 +4,8 @@ body {
padding-top: 100dvh; padding-top: 100dvh;
} }
canvas, video { canvas,
video {
max-width: 100%; max-width: 100%;
max-height: 100%; max-height: 100%;
} }
@ -32,13 +33,13 @@ body.import-active .is-hidden-import {
#settings-button { #settings-button {
position: absolute; position: absolute;
right: 30px; right: 30px;
bottom: 30px bottom: 30px;
} }
#back { #back {
position: absolute; position: absolute;
left: 30px; left: 30px;
top: 30px top: 30px;
} }
#cheese { #cheese {