First commit
This commit is contained in:
parent
1e8725b15c
commit
d4ad7468f2
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
dprint.json
|
||||
test/
|
||||
15
README.md
15
README.md
@ -1,3 +1,16 @@
|
||||
# image_overlay
|
||||
|
||||
Tiny web app to combine a background image and a foreground image
|
||||
Tiny web app to combine a background image and a foreground image.
|
||||
|
||||
## Usage
|
||||
|
||||
1. Open [index.html](index.html) in your browser.
|
||||
2. Upload a background image and a foreground image.
|
||||
3. Set the relative size, X offset, and Y offset of the foreground image.
|
||||
4. Click **Download**.
|
||||
|
||||
## Dependencies
|
||||
|
||||
* A browser
|
||||
* Two images
|
||||
* Nothing else
|
||||
|
||||
85
app.js
Normal file
85
app.js
Normal file
@ -0,0 +1,85 @@
|
||||
// Minimal canvas compositor
|
||||
|
||||
const canvas = document.getElementById("canvas");
|
||||
const ctx = canvas.getContext("2d");
|
||||
|
||||
let bgImg = null;
|
||||
let fgImg = null;
|
||||
|
||||
// --- image loader ---
|
||||
function loadImage(file, callback) {
|
||||
if (!file) return;
|
||||
|
||||
const img = new Image();
|
||||
img.onload = () => callback(img);
|
||||
img.src = URL.createObjectURL(file);
|
||||
}
|
||||
|
||||
// --- inputs ---
|
||||
const bgInput = document.getElementById("bgInput");
|
||||
const fgInput = document.getElementById("fgInput");
|
||||
|
||||
const scaleInput = document.getElementById("scale");
|
||||
const offsetXInput = document.getElementById("offsetX");
|
||||
const offsetYInput = document.getElementById("offsetY");
|
||||
|
||||
// --- event bindings ---
|
||||
|
||||
bgInput.onchange = (e) => {
|
||||
loadImage(e.target.files[0], (img) => {
|
||||
bgImg = img;
|
||||
|
||||
// canvas follows background resolution exactly
|
||||
canvas.width = img.width;
|
||||
canvas.height = img.height;
|
||||
|
||||
draw();
|
||||
});
|
||||
};
|
||||
|
||||
fgInput.onchange = (e) => {
|
||||
loadImage(e.target.files[0], (img) => {
|
||||
fgImg = img;
|
||||
draw();
|
||||
});
|
||||
};
|
||||
|
||||
[scaleInput, offsetXInput, offsetYInput].forEach(el => {
|
||||
el.oninput = draw;
|
||||
});
|
||||
|
||||
// --- draw pipeline ---
|
||||
function draw() {
|
||||
if (!bgImg) return;
|
||||
|
||||
// clear + draw background
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.drawImage(bgImg, 0, 0);
|
||||
|
||||
if (!fgImg) return;
|
||||
|
||||
const scale = parseFloat(scaleInput.value) / 100 || 0.2;
|
||||
const offsetX = parseFloat(offsetXInput.value) || 0;
|
||||
const offsetY = parseFloat(offsetYInput.value) || 0;
|
||||
|
||||
// scale based on background width
|
||||
const targetWidth = bgImg.width * scale;
|
||||
const aspect = fgImg.height / fgImg.width;
|
||||
const targetHeight = targetWidth * aspect;
|
||||
|
||||
// centered + offsets
|
||||
const x = (bgImg.width - targetWidth) / 2 + offsetX;
|
||||
const y = (bgImg.height - targetHeight) / 2 + offsetY;
|
||||
|
||||
ctx.drawImage(fgImg, x, y, targetWidth, targetHeight);
|
||||
}
|
||||
|
||||
// --- download ---
|
||||
document.getElementById("downloadBtn").onclick = () => {
|
||||
if (!bgImg) return;
|
||||
|
||||
const link = document.createElement("a");
|
||||
link.download = "wallpaper.png";
|
||||
link.href = canvas.toDataURL("image/png");
|
||||
link.click();
|
||||
};
|
||||
32
index.html
Normal file
32
index.html
Normal file
@ -0,0 +1,32 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Wallpaper Composer</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="canvas"></canvas>
|
||||
|
||||
<div class="controls">
|
||||
<label>Background Image</label>
|
||||
<input type="file" id="bgInput" accept="image/*">
|
||||
|
||||
<label>Foreground Image</label>
|
||||
<input type="file" id="fgInput" accept="image/*">
|
||||
|
||||
<label>Scale (%)</label>
|
||||
<input type="number" id="scale" value="20">
|
||||
|
||||
<label>Offset X (px)</label>
|
||||
<input type="number" id="offsetX" value="0">
|
||||
|
||||
<label>Offset Y (px)</label>
|
||||
<input type="number" id="offsetY" value="0">
|
||||
|
||||
<button id="downloadBtn">Download</button>
|
||||
</div>
|
||||
|
||||
<script src="app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
81
style.css
Normal file
81
style.css
Normal file
@ -0,0 +1,81 @@
|
||||
/* reset */
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: sans-serif;
|
||||
overflow: hidden; /* no scroll */
|
||||
background: #303030;
|
||||
}
|
||||
|
||||
/* canvas = full screen */
|
||||
canvas {
|
||||
position: fixed;
|
||||
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
|
||||
object-fit: contain; /* keep aspect ratio */
|
||||
}
|
||||
|
||||
/* bottom control bar */
|
||||
.controls {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
|
||||
padding: 10px;
|
||||
align-items: center; /* vertical centering */
|
||||
|
||||
background: rgba(209, 209, 209, 0.07);
|
||||
backdrop-filter: blur(6px);
|
||||
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.controls label {
|
||||
font-size: 12px;
|
||||
opacity: 0.8;
|
||||
color: #ccc;
|
||||
|
||||
display: flex;
|
||||
align-items: center; /* vertical center inside its own box */
|
||||
}
|
||||
|
||||
.controls input,
|
||||
.controls button {
|
||||
font-size: 12px;
|
||||
padding: 4px;
|
||||
background: #70707023;
|
||||
color: #eee;
|
||||
border: 1px solid #444;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.controls input::-webkit-outer-spin-button,
|
||||
.controls input::-webkit-inner-spin-button {
|
||||
/* display: none; <- Crashes Chrome on hover */
|
||||
-webkit-appearance: none;
|
||||
margin: 0; /* <-- Apparently some margin are still there even though it's hidden */
|
||||
}
|
||||
|
||||
.controls input[type="number"] {
|
||||
-moz-appearance: textfield; /* Firefox */
|
||||
}
|
||||
|
||||
.controls input:hover,
|
||||
.controls button:hover {
|
||||
background: #99999934;
|
||||
}
|
||||
|
||||
.controls > * {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user