First commit

This commit is contained in:
Candifloss 2026-08-08 14:11:07 +05:30
parent 1e8725b15c
commit d4ad7468f2
5 changed files with 215 additions and 2 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
dprint.json
test/

View File

@ -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
View 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
View 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
View 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;
}