feat: load background file
This commit is contained in:
@@ -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
@@ -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 };
|
||||
});
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user