cabbagehao
Explain face swap reference preparation workflow
aaab576
Raw History Blame Contribute Delete
18.2 kB
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="Prepare a clear portrait reference for LiveFaceSwap AI real-time face swap. Fix orientation, position a square crop, and export a clean RGB PNG locally in your browser." />
<meta name="robots" content="index,follow" />
<link rel="canonical" href="https://huggingface.co/spaces/chris-fox/livefaceswap-reference-prep" />
<title>LiveFaceSwap AI Face Swap Reference Image Prep</title>
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "SoftwareApplication",
"name": "LiveFaceSwap AI Face Swap Reference Image Prep",
"applicationCategory": "MultimediaApplication",
"operatingSystem": "Any modern web browser",
"description": "A browser-based utility that prepares authorized portrait reference images for LiveFaceSwap AI real-time face swap workflows.",
"url": "https://huggingface.co/spaces/chris-fox/livefaceswap-reference-prep",
"isAccessibleForFree": true,
"license": "https://opensource.org/license/mit",
"publisher": { "@type": "Organization", "name": "LiveFaceSwap AI", "url": "https://livefaceswap.ai/" },
"codeRepository": "https://github.com/LiveFaceSwapAI/livefaceswap-reference-prep"
}
</script>
<style>
:root { color-scheme: dark; font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background: #080d1c; color: #f4f7ff; }
* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body { margin: 0; min-height: 100vh; background: radial-gradient(circle at 50% -10%, #273c82 0, #111936 34%, #080d1c 68%); }
main { width: min(1080px, calc(100% - 32px)); margin: 0 auto; padding: 42px 0 72px; }
a { color: #a9c3ff; text-underline-offset: 3px; }
p, li { color: #cbd5f2; line-height: 1.68; }
h1, h2, h3 { letter-spacing: -0.025em; }
h1 { margin: 12px 0 16px; max-width: 880px; font-size: clamp(2.35rem, 6vw, 4.6rem); line-height: 0.98; }
h2 { margin: 0 0 12px; font-size: clamp(1.55rem, 3vw, 2.15rem); }
h3 { margin: 0 0 6px; font-size: 1.05rem; }
.eyebrow { display: inline-flex; padding: 7px 11px; border: 1px solid #5369a4; border-radius: 999px; color: #cbd7ff; background: rgba(64, 87, 157, 0.22); font-size: 0.82rem; font-weight: 750; letter-spacing: 0.04em; text-transform: uppercase; }
.lede { max-width: 790px; margin: 0; font-size: clamp(1rem, 2vw, 1.2rem); }
.hero-links, .project-links { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 22px; }
.button-link { display: inline-flex; align-items: center; justify-content: center; min-height: 42px; padding: 10px 15px; border: 1px solid #6881c3; border-radius: 11px; color: #eef3ff; background: #16244d; font-weight: 750; text-decoration: none; }
.button-link.primary { border-color: #88a8ff; background: #85a7ff; color: #07122d; }
.tool { display: grid; grid-template-columns: minmax(250px, 0.8fr) minmax(300px, 1.2fr); gap: 24px; margin-top: 36px; padding: 24px; border: 1px solid #3d518b; border-radius: 22px; background: rgba(12, 20, 48, 0.91); box-shadow: 0 26px 90px rgba(0, 0, 0, 0.32); }
.controls { display: grid; align-content: start; gap: 17px; }
label { display: grid; gap: 8px; color: #f4f7ff; font-weight: 680; }
input, select, button { width: 100%; font: inherit; }
input[type="file"], select { border: 1px solid #536aa8; border-radius: 11px; padding: 11px; background: #101936; color: inherit; }
input[type="range"] { accent-color: #89a9ff; }
.value { color: #aebfe9; font-weight: 520; }
button { min-height: 44px; border: 0; border-radius: 11px; padding: 11px 15px; background: #89a9ff; color: #07112b; font-weight: 820; cursor: pointer; }
button.secondary { border: 1px solid #536aa8; background: #18264e; color: #eef3ff; }
button:disabled { cursor: not-allowed; opacity: 0.45; }
.preview { min-height: 430px; display: grid; place-items: center; border: 1px dashed #5268a3; border-radius: 16px; overflow: hidden; background: #060a15; }
canvas { display: block; width: 100%; height: auto; max-height: 620px; object-fit: contain; }
.placeholder { max-width: 34ch; padding: 32px; text-align: center; color: #93a4d0; line-height: 1.55; }
.privacy { margin: 16px 2px 0; font-size: 0.91rem; }
.section { margin-top: 64px; }
.section-intro { max-width: 760px; margin: 0; }
.workflow { display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px; margin-top: 22px; }
.card { padding: 19px; border: 1px solid #34477b; border-radius: 16px; background: rgba(18, 28, 61, 0.76); }
.step { width: 32px; height: 32px; display: grid; place-items: center; margin-bottom: 16px; border-radius: 9px; background: #87a7ff; color: #07122c; font-weight: 850; }
.card p { margin: 0; }
.benefits { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; margin-top: 22px; }
.details { display: grid; grid-template-columns: 0.8fr 1.2fr; gap: 24px; align-items: start; }
.details ul { margin: 12px 0 0; padding-left: 21px; }
.specs { margin: 0; overflow: hidden; border: 1px solid #34477b; border-radius: 16px; background: rgba(18, 28, 61, 0.76); }
.specs div { display: grid; grid-template-columns: 110px 1fr; gap: 12px; padding: 14px 16px; border-bottom: 1px solid #304272; }
.specs div:last-child { border-bottom: 0; }
dt { color: #f4f7ff; font-weight: 750; }
dd { margin: 0; color: #cbd5f2; line-height: 1.55; }
details { padding: 15px 17px; border: 1px solid #34477b; border-radius: 13px; background: rgba(18, 28, 61, 0.68); }
details + details { margin-top: 10px; }
summary { cursor: pointer; color: #f4f7ff; font-weight: 730; }
details p { margin: 10px 0 0; }
footer { margin-top: 64px; padding-top: 24px; border-top: 1px solid #2e3e6b; }
footer p { max-width: 850px; font-size: 0.92rem; }
@media (max-width: 820px) { .workflow, .benefits { grid-template-columns: 1fr 1fr; } .details { grid-template-columns: 1fr; } }
@media (max-width: 720px) { main { padding-top: 28px; } .tool { grid-template-columns: 1fr; padding: 18px; } .preview { min-height: 310px; } .workflow, .benefits { grid-template-columns: 1fr; } .specs div { grid-template-columns: 88px 1fr; } }
</style>
</head>
<body>
<main>
<header>
<span class="eyebrow">LiveFaceSwap AI · Browser utility</span>
<h1>Prepare a better reference image for real-time face swap</h1>
<p class="lede">Fix portrait orientation, position a square crop around the face, and export a consistent RGB PNG before uploading it as a reference in LiveFaceSwap AI. Processing stays in your browser.</p>
<nav class="hero-links" aria-label="Primary links">
<a class="button-link primary" href="#tool">Prepare a portrait</a>
<a class="button-link" href="https://livefaceswap.ai/?utm_source=huggingface&amp;utm_medium=referral&amp;utm_campaign=reference_prep" target="_blank" rel="noopener">Open LiveFaceSwap AI</a>
</nav>
</header>
<section class="tool" id="tool" aria-labelledby="tool-title">
<div class="controls">
<div><h2 id="tool-title">Reference image prep</h2><p>Upload an authorized portrait or use the synthetic example to test the complete crop-and-export path.</p></div>
<label>Portrait image<input id="file" type="file" accept="image/jpeg,image/png,image/webp" /></label>
<button class="secondary" id="example" type="button">Try synthetic example</button>
<label>Output size<select id="size"><option value="512">512 × 512</option><option value="768">768 × 768</option><option value="1024" selected>1024 × 1024</option></select></label>
<label>Horizontal crop focus <span class="value" id="focus-x-value">0.50</span><input id="focus-x" type="range" min="0" max="1" value="0.5" step="0.01" /></label>
<label>Vertical crop focus <span class="value" id="focus-y-value">0.42</span><input id="focus-y" type="range" min="0" max="1" value="0.42" step="0.01" /></label>
<button id="download" type="button" disabled>Download prepared RGB PNG</button>
</div>
<div class="preview" id="preview" aria-live="polite"><div class="placeholder">Choose a JPG, PNG, or WebP portrait to generate a real square preview. Maximum file size: 20 MB.</div><canvas id="canvas" hidden aria-label="Prepared square portrait preview"></canvas></div>
</section>
<p class="privacy">Your selected image is decoded and processed on this page. It is not sent to an application server.</p>
<section class="section" aria-labelledby="workflow-title">
<h2 id="workflow-title">Where this fits in the face swap workflow</h2>
<p class="section-intro">This page prepares the portrait file; it does not perform identity transformation. The actual real-time face swap happens after you upload the prepared PNG to LiveFaceSwap AI.</p>
<div class="workflow">
<article class="card"><div class="step">1</div><h3>Choose an authorized portrait</h3><p>Use one clear, front-facing face that you own or have permission to process.</p></article>
<article class="card"><div class="step">2</div><h3>Frame the face</h3><p>Correct orientation, choose a size, and adjust the crop focus until the face is centered.</p></article>
<article class="card"><div class="step">3</div><h3>Export a clean PNG</h3><p>Download a square 512, 768, or 1024 pixel RGB PNG with image metadata removed.</p></article>
<article class="card"><div class="step">4</div><h3>Use it in LiveFaceSwap</h3><p>Choose Face Swap, upload the PNG under Reference, turn on your camera, and review the live output.</p></article>
</div>
</section>
<section class="section" aria-labelledby="value-title">
<h2 id="value-title">Why prepare the reference first?</h2>
<div class="benefits">
<article class="card"><h3>Predictable framing</h3><p>Move the crop across a landscape or portrait image instead of losing the intended face to an automatic center crop.</p></article>
<article class="card"><h3>Consistent image format</h3><p>Normalize orientation and produce a square RGB PNG at a known size for the downstream workflow.</p></article>
<article class="card"><h3>Private preprocessing</h3><p>Preview and export locally in your browser before deciding whether to use the image elsewhere.</p></article>
</div>
</section>
<section class="section details" aria-labelledby="input-title">
<div><h2 id="input-title">Choose a useful portrait</h2><ul><li>Prefer one visible, front-facing face with open eyes and a clear facial outline.</li><li>Use even lighting and enough resolution to preserve detail after cropping.</li><li>Avoid group photos, heavy blur, extreme angles, strong shadows, and facial obstructions.</li><li>Keep the full face inside the preview; preparation reduces input issues but cannot guarantee a particular swap result.</li></ul></div>
<dl class="specs"><div><dt>Input</dt><dd>One JPG, PNG, or WebP image up to 20 MB</dd></div><div><dt>Controls</dt><dd>Output size, horizontal crop focus, and vertical crop focus</dd></div><div><dt>Output</dt><dd>Square 512×512, 768×768, or 1024×1024 RGB PNG</dd></div><div><dt>Boundary</dt><dd>Reference preprocessing only; no face swap model or identity transformation runs here</dd></div></dl>
</section>
<section class="section" aria-labelledby="faq-title">
<h2 id="faq-title">Frequently asked questions</h2>
<details><summary>Does this page perform face swapping?</summary><p>No. It prepares a portrait reference. Face swapping happens later in the LiveFaceSwap AI browser or desktop workflow.</p></details>
<details><summary>Is my portrait uploaded to a server?</summary><p>No. This is a static browser utility. The selected image is decoded, cropped, resized, and exported locally on this page.</p></details>
<details><summary>Which output size should I use?</summary><p>1024×1024 is a practical default when the source contains enough detail. Use 512 or 768 when you need a smaller file.</p></details>
<details><summary>What do the focus sliders change?</summary><p>They move the square crop across a non-square source so you can keep the intended face centered in the exported image.</p></details>
<details><summary>Can I use the output elsewhere?</summary><p>Yes. It is a standard RGB PNG and can be used in other authorized portrait workflows that accept square references.</p></details>
</section>
<footer>
<h2>Continue with LiveFaceSwap AI</h2>
<p>Open the real-time face swap workspace, read the step-by-step guide, or inspect the MIT-licensed preprocessing source.</p>
<nav class="project-links" aria-label="Project links"><a class="button-link primary" href="https://livefaceswap.ai/?utm_source=huggingface&amp;utm_medium=referral&amp;utm_campaign=reference_prep" target="_blank" rel="noopener">LiveFaceSwap AI official website</a><a class="button-link" href="https://livefaceswap.ai/guides/live-face-swap-online?utm_source=huggingface&amp;utm_medium=referral&amp;utm_campaign=reference_prep" target="_blank" rel="noopener">Online face swap guide</a><a class="button-link" href="https://github.com/LiveFaceSwapAI/livefaceswap-reference-prep" target="_blank" rel="noopener">Canonical source</a></nav>
<p>Only process images you own or have permission to use. Do not use portrait references to impersonate, deceive, defraud, harass, or violate another person's rights. Disclose AI-altered media when context requires it.</p>
</footer>
</main>
<script>
const fileInput = document.querySelector("#file");
const exampleButton = document.querySelector("#example");
const sizeInput = document.querySelector("#size");
const focusXInput = document.querySelector("#focus-x");
const focusYInput = document.querySelector("#focus-y");
const focusXValue = document.querySelector("#focus-x-value");
const focusYValue = document.querySelector("#focus-y-value");
const canvas = document.querySelector("#canvas");
const placeholder = document.querySelector(".placeholder");
const downloadButton = document.querySelector("#download");
const context = canvas.getContext("2d", { alpha: false });
let source = null;
async function decodeImage(file) {
if ("createImageBitmap" in window) return createImageBitmap(file, { imageOrientation: "from-image" });
const url = URL.createObjectURL(file);
try { const image = new Image(); image.src = url; await image.decode(); return image; }
finally { URL.revokeObjectURL(url); }
}
function render() {
focusXValue.textContent = Number(focusXInput.value).toFixed(2);
focusYValue.textContent = Number(focusYInput.value).toFixed(2);
if (!source) return;
const outputSize = Number(sizeInput.value);
const width = source.width || source.naturalWidth;
const height = source.height || source.naturalHeight;
const side = Math.min(width, height);
const left = Math.round((width - side) * Number(focusXInput.value));
const top = Math.round((height - side) * Number(focusYInput.value));
canvas.width = outputSize;
canvas.height = outputSize;
context.fillStyle = "#ffffff";
context.fillRect(0, 0, outputSize, outputSize);
context.drawImage(source, left, top, side, side, 0, 0, outputSize, outputSize);
canvas.hidden = false;
placeholder.hidden = true;
downloadButton.disabled = false;
}
fileInput.addEventListener("change", async () => {
const file = fileInput.files && fileInput.files[0];
if (!file) return;
if (file.size > 20 * 1024 * 1024) { alert("Please choose an image smaller than 20 MB."); fileInput.value = ""; return; }
if (source && typeof source.close === "function") source.close();
source = await decodeImage(file);
render();
});
exampleButton.addEventListener("click", async () => {
const syntheticPortrait = `<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="800" viewBox="0 0 1200 800"><defs><linearGradient id="b" x1="0" y1="0" x2="1" y2="1"><stop stop-color="#263d7a"/><stop offset="1" stop-color="#8aa9ef"/></linearGradient></defs><rect width="1200" height="800" fill="url(#b)"/><circle cx="700" cy="310" r="170" fill="#f1c6a8"/><path d="M530 310c10-150 90-210 185-210 115 0 190 86 170 225-54-58-96-94-178-98-68-3-117 24-177 83Z" fill="#29283a"/><ellipse cx="645" cy="315" rx="18" ry="12" fill="#242334"/><ellipse cx="760" cy="315" rx="18" ry="12" fill="#242334"/><path d="M665 390c31 24 69 24 100 0" fill="none" stroke="#a85e68" stroke-width="12" stroke-linecap="round"/><path d="M430 800c28-205 125-310 270-310s242 105 270 310" fill="#18274f"/><circle cx="1010" cy="150" r="70" fill="#f4d77b" opacity=".72"/></svg>`;
if (source && typeof source.close === "function") source.close();
const image = new Image();
image.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(syntheticPortrait)}`;
await image.decode();
source = image;
focusXInput.value = "0.58";
focusYInput.value = "0.45";
render();
});
[sizeInput, focusXInput, focusYInput].forEach((input) => input.addEventListener("input", render));
downloadButton.addEventListener("click", () => {
canvas.toBlob((blob) => { if (!blob) return; const link = document.createElement("a"); link.href = URL.createObjectURL(blob); link.download = "livefaceswap-reference.png"; link.click(); setTimeout(() => URL.revokeObjectURL(link.href), 1000); }, "image/png");
});
</script>
</body>
</html>