feat: load background file

This commit is contained in:
2026-06-24 06:48:13 +05:00
parent 680dad30bf
commit 3b49c9f3c2
7 changed files with 242 additions and 3 deletions
+12
View File
@@ -2,6 +2,16 @@ import { computeHomography } from './homography.js';
function $(id) { return document.getElementById(id); }
function applyBgToField(field, state) {
if (state.bgImage) {
field.style.backgroundImage = `url(${state.bgImage})`;
field.style.backgroundSize = state.objectFit === 'fill' ? '100% 100%' : state.objectFit;
} else {
field.style.backgroundImage = 'none';
field.style.backgroundSize = '';
}
}
export function updateLivePreview(state) {
const container = $('fieldContainer');
const cw = container.clientWidth;
@@ -18,6 +28,8 @@ export function updateLivePreview(state) {
field.style.transform = `scale(${scale})`;
container.style.height = (state.fieldH * scale) + 'px';
applyBgToField(field, state);
const widget = $('widget');
widget.style.width = state.widgetW + 'px';
widget.style.height = state.widgetH + 'px';
+8 -2
View File
@@ -6,7 +6,9 @@ export const defaultState = {
{ x: 1620, y: 150 },
{ x: 1580, y: 920 },
{ x: 340, y: 950 }
]
],
bgImage: null,
objectFit: 'contain'
};
export const state = {
@@ -17,7 +19,9 @@ export const state = {
{ x: 1620, y: 150 },
{ x: 1580, y: 920 },
{ x: 340, y: 950 }
]
],
bgImage: null,
objectFit: 'contain'
};
export function resetState() {
@@ -25,6 +29,8 @@ export function resetState() {
state.fieldH = defaultState.fieldH;
state.widgetW = defaultState.widgetW;
state.widgetH = defaultState.widgetH;
state.bgImage = null;
state.objectFit = defaultState.objectFit;
defaultState.corners.forEach((c, i) => {
state.corners[i] = { x: c.x, y: c.y };
});
+19
View File
@@ -1,6 +1,14 @@
const LABELS = ['TL', 'TR', 'BR', 'BL'];
const ns = 'http://www.w3.org/2000/svg';
const OBJECT_FIT_MAP = {
contain: 'xMidYMid meet',
cover: 'xMidYMid slice',
fill: 'none',
none: 'xMinYMin meet',
'scale-down': 'xMidYMid meet'
};
function gridInterval(maxDim) {
if (maxDim <= 500) return 50;
if (maxDim <= 1000) return 100;
@@ -26,6 +34,17 @@ export function renderSVG(svg, state) {
});
svg.appendChild(rect);
if (state.bgImage) {
const img = document.createElementNS(ns, 'image');
setAttrs(img, {
href: state.bgImage,
width: state.fieldW,
height: state.fieldH,
preserveAspectRatio: OBJECT_FIT_MAP[state.objectFit] || 'xMidYMid meet'
});
svg.appendChild(img);
}
const step = gridInterval(Math.max(state.fieldW, state.fieldH));
const gridG = document.createElementNS(ns, 'g');
for (let x = step; x < state.fieldW; x += step) {
+65
View File
@@ -54,12 +54,33 @@ function setMode(mode) {
: 'matrix3d() transform result';
}
let updateAllRef;
function loadBgFile(file, state) {
if (!file || !file.type.startsWith('image/')) return;
const reader = new FileReader();
reader.onload = () => {
state.bgImage = reader.result;
$('btnBgRemove').style.display = '';
updateAllRef();
};
reader.readAsDataURL(file);
}
function clearBg(state) {
state.bgImage = null;
$('btnBgRemove').style.display = 'none';
updateAllRef();
}
export function updateCSSOutput(state) {
const H = computeHomography(state);
$('cssOutput').value = toMatrix3dCSS(H);
}
export function initUI(state, updateAll) {
updateAllRef = updateAll;
$('chainLink').addEventListener('click', () => {
keepAspect = !keepAspect;
updateChainIcon();
@@ -150,6 +171,8 @@ export function initUI(state, updateAll) {
syncFieldInputs(state, state.fieldW, state.fieldH);
syncWidgetInputs(state);
buildCornerInputs(state);
$('btnBgRemove').style.display = 'none';
$('bgObjectFit').value = state.objectFit;
updateAll();
});
@@ -164,4 +187,46 @@ export function initUI(state, updateAll) {
});
window.addEventListener('resize', () => updateLivePreview(state));
$('btnBgLoad').addEventListener('click', () => $('bgFileInput').click());
$('bgFileInput').addEventListener('change', (e) => {
if (e.target.files[0]) loadBgFile(e.target.files[0], state);
e.target.value = '';
});
$('btnBgRemove').addEventListener('click', () => clearBg(state));
$('bgObjectFit').addEventListener('change', (e) => {
state.objectFit = e.target.value;
updateAll();
});
document.addEventListener('paste', (e) => {
const tag = document.activeElement?.tagName;
if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT') return;
const items = e.clipboardData?.items;
if (!items) return;
for (const item of items) {
if (item.type.startsWith('image/')) {
const file = item.getAsFile();
if (file) loadBgFile(file, state);
break;
}
}
});
const lc = document.querySelector('.left-col');
lc.addEventListener('dragover', (e) => {
e.preventDefault();
lc.classList.add('drag-over');
});
lc.addEventListener('dragleave', () => {
lc.classList.remove('drag-over');
});
lc.addEventListener('drop', (e) => {
e.preventDefault();
lc.classList.remove('drag-over');
if (e.dataTransfer.files[0]) loadBgFile(e.dataTransfer.files[0], state);
});
}