Arpit16112's picture
Updated app.py & index.html
3c8aad9
Raw
History Blame Contribute Delete
31 kB
<!-- Main Flask/Jinja template for the StyleXfer web app. This page contains the upload form, preview areas, result display, examples section, footer, styling, and small client-side interactions. -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>StyleXfer : Adaptive Neural Style Transfer</title>
<!-- Bootstrap provides the responsive grid, navbar, cards, buttons, and utility classes used across the page. -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Font Awesome supplies the brand, navigation, contact, and social media icons. -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<!-- Google Fonts: Fugaz One for display headings and Outfit for body/UI text. -->
<link href="https://fonts.googleapis.com/css2?family=Fugaz+One&family=Outfit:wght@100..900&display=swap" rel="stylesheet">
<style>
/* Base page theme and typography. */
html {
scroll-behavior: smooth;
}
body {
font-family: 'Outfit', sans-serif;
background-color: #172033;
color: #e2e8f0;
min-height: 100vh;
}
/* Hero/header area shown below the fixed navigation bar. */
.header-section {
padding: 5rem 0 3rem;
text-align: center;
position: relative;
}
.header-section h1 {
font-family: 'Fugaz One', sans-serif;
font-weight: 700;
font-size: 3.5rem;
background: linear-gradient(to right, #818cf8, #c084fc, #f472b6);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
text-shadow: 0 0 30px rgba(192, 132, 252, 0.3);
margin-bottom: 1rem;
}
/* Shared card styling for upload panels, result preview, and examples. */
.card {
background: rgba(30, 41, 59, 0.6);
backdrop-filter: blur(12px);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 20px;
overflow: hidden;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.card:hover {
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
border-color: rgba(129, 140, 248, 0.3);
}
.card-header {
background: rgba(255, 255, 255, 0.05);
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
color: #f8fafc;
font-family: 'Fugaz One', sans-serif;
letter-spacing: 1px;
text-transform: uppercase;
font-size: 0.9rem;
border-radius: 19px 19px 0 0;
}
/* Image preview boxes keep uploaded images visible without stretching them. */
.preview-image {
width: 100%;
height: 300px;
object-fit: contain;
background-color: #0f172a;
border: 2px dashed #334155;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
color: #64748b;
font-size: 1.2rem;
overflow: hidden;
}
.preview-image img {
width: 100%;
height: 100%;
object-fit: contain;
}
/* Primary action button used for starting the style transfer. */
.btn-primary {
background: linear-gradient(135deg, #6366f1 0%, #a855f7 100%);
border: none;
padding: 15px 50px;
font-size: 1.2rem;
font-family: 'Fugaz One', sans-serif;
border-radius: 50px;
transition: all 0.3s;
text-transform: uppercase;
letter-spacing: 2px;
box-shadow: 0 10px 20px -10px rgba(168, 85, 247, 0.5);
}
.btn-primary:hover {
background: linear-gradient(135deg, #4f46e5 0%, #9333ea 100%);
box-shadow: 0 20px 30px -10px rgba(168, 85, 247, 0.6);
}
/* Glow animation draws attention to the submit button. */
.btn-glow {
animation: glowing 2s infinite;
}
@keyframes glowing {
0% { box-shadow: 0 0 5px #a855f7; }
50% { box-shadow: 0 0 20px #a855f7, 0 0 10px #6366f1; }
100% { box-shadow: 0 0 5px #a855f7; }
}
/* Custom range slider for controlling AdaIN style strength (alpha). */
.range-wrap {
position: relative;
margin: 2rem auto;
width: 100%;
max-width: 90%;
}
#alphaRange {
-webkit-appearance: none;
width: 100%;
height: 12px;
background: rgba(30, 41, 59, 0.8);
border-radius: 15px;
outline: none;
border: 1px solid rgba(255, 255, 255, 0.1);
box-shadow: inset 0 2px 5px rgba(0,0,0,0.5);
transition: background 0.1s;
}
#alphaRange::-webkit-slider-runnable-track {
width: 100%;
height: 100%;
cursor: pointer;
background: transparent;
}
#alphaRange::-webkit-slider-thumb {
-webkit-appearance: none;
height: 24px;
width: 24px;
border-radius: 50%;
background: #fff;
cursor: pointer;
margin-top: -6px;
box-shadow: 0 0 10px #c084fc, 0 0 20px #818cf8;
border: 3px solid #1e293b;
transition: transform 0.2s, box-shadow 0.2s;
}
#alphaRange::-webkit-slider-thumb:hover {
box-shadow: 0 0 15px #c084fc, 0 0 30px #818cf8;
}
.range-value {
position: absolute;
top: -40px;
left: 50%;
transform: translateX(-50%);
background: linear-gradient(135deg, #6366f1 0%, #a855f7 100%);
color: white;
padding: 4px 12px;
border-radius: 15px;
font-family: 'Fugaz One', sans-serif;
font-size: 0.85rem;
box-shadow: 0 5px 15px rgba(168, 85, 247, 0.4);
opacity: 0;
transition: opacity 0.3s;
pointer-events: none;
}
.range-value::after {
content: '';
position: absolute;
bottom: -5px;
left: 50%;
margin-left: -5px;
border-width: 5px 5px 0;
border-style: solid;
border-color: #a855f7 transparent transparent transparent;
}
.range-wrap:hover .range-value {
opacity: 1;
}
/* File input styling for the content and style image upload controls. */
.form-control {
background-color: #1e293b;
border: 1px solid #334155;
color: #f8fafc;
}
.form-control:focus {
background-color: #1e293b;
border-color: #818cf8;
color: #f8fafc;
box-shadow: 0 0 0 0.25rem rgba(129, 140, 248, 0.25);
}
.form-control::file-selector-button {
background-color: #334155;
color: #f8fafc;
border: none;
padding: 0.375rem 0.75rem;
margin-right: 0.75rem;
border-radius: 0.25rem;
transition: background-color 0.2s;
}
.form-control::file-selector-button:hover {
background-color: #475569;
}
/* Custom scrollbar to match the dark visual theme. */
::-webkit-scrollbar {
width: 10px;
}
::-webkit-scrollbar-track {
background: #0f172a;
}
::-webkit-scrollbar-thumb {
background: #334155;
border-radius: 5px;
}
::-webkit-scrollbar-thumb:hover {
background: #818cf8;
}
/* Reusable fade-in animation for major content sections. */
.fade-in-up {
animation: fadeInUp 0.8s ease-out forwards;
opacity: 0;
transform: translateY(30px);
}
@keyframes fadeInUp {
to {
opacity: 1;
transform: translateY(0);
}
}
/* Full-page loading overlay shown while the server processes the submitted images. */
.loader-overlay {
position: fixed;
inset: 0;
z-index: 9999;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
gap: 1rem;
background: rgba(15, 23, 42, 0.92);
opacity: 0;
pointer-events: none;
transition: opacity 0.25s ease;
}
.loader-overlay.active {
opacity: 1;
pointer-events: all;
}
.style-spinner {
width: 72px;
height: 72px;
border: 4px solid rgba(129, 140, 248, 0.18);
border-top-color: #818cf8;
border-right-color: #c084fc;
border-radius: 50%;
animation: spin 0.9s linear infinite;
box-shadow: 0 0 18px rgba(129, 140, 248, 0.35);
}
.loader-overlay h3 {
color: #f8fafc;
font-family: 'Fugaz One', sans-serif;
letter-spacing: 2px;
margin: 0;
}
.loader-overlay p {
color: #cbd5e1;
margin: 0;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
/* Fixed top navigation and brand mark. */
.navbar {
background: rgba(15, 23, 42, 0.8);
backdrop-filter: blur(10px);
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}
.navbar-brand {
font-family: 'Fugaz One', sans-serif;
font-weight: 700;
font-size: 1.5rem;
background: linear-gradient(to right, #818cf8, #c084fc);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
display: inline-flex;
align-items: center;
gap: 0.55rem;
}
.brand-logo {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
width: 2rem;
height: 2rem;
border-radius: 50%;
background: rgba(129, 140, 248, 0.14);
border: 1px solid rgba(165, 180, 252, 0.45);
color: #c4b5fd;
-webkit-text-fill-color: #c4b5fd;
box-shadow: 0 0 14px rgba(129, 140, 248, 0.18);
flex: 0 0 auto;
}
.brand-logo .fa-wave-square {
font-size: 0.95rem;
}
.brand-logo .fa-circle-nodes {
position: absolute;
right: -0.22rem;
bottom: -0.12rem;
font-size: 0.62rem;
color: #93c5fd;
-webkit-text-fill-color: #93c5fd;
}
.nav-link {
color: #e2e8f0 !important;
font-weight: 500;
margin-left: 1rem;
transition: color 0.3s;
position: relative;
}
.nav-link:hover {
color: #818cf8 !important;
}
.nav-link::after {
content: '';
position: absolute;
width: 0;
height: 2px;
bottom: 0;
left: 0;
background-color: #818cf8;
transition: width 0.3s;
}
.nav-link:hover::after {
width: 100%;
}
/* Footer, contact details, and social links. */
.footer-section {
background: rgba(15, 23, 42, 0.95);
border-top: 1px solid rgba(255, 255, 255, 0.05);
padding: 4rem 0 2rem;
margin-top: 5rem;
color: #94a3b8;
scroll-margin-top: 90px;
}
.footer-section h5 {
color: #f8fafc;
font-family: 'Fugaz One', sans-serif;
margin-bottom: 1.5rem;
font-size: 1.1rem;
}
.footer-brand {
display: inline-flex;
align-items: center;
gap: 0.55rem;
}
.footer-link {
color: #94a3b8;
text-decoration: none;
transition: color 0.3s;
display: block;
margin-bottom: 0.8rem;
}
.footer-link:hover {
color: #818cf8;
}
.social-icon {
width: 40px;
height: 40px;
display: inline-flex;
align-items: center;
justify-content: center;
background: rgba(255,255,255,0.05);
border-radius: 50%;
color: #f8fafc;
margin-right: 10px;
transition: all 0.3s;
text-decoration: none;
}
.social-icon:hover {
background: #818cf8;
color: white;
}
/* Example gallery showing content + style image pairs and their generated output. */
.examples-section {
margin-top: 4rem;
scroll-margin-top: 90px;
}
.examples-section .examples-note {
color: #cbd5e1;
}
.examples-section .example-image {
width: 100%;
height: 220px;
object-fit: contain;
border-radius: 10px;
border: 1px solid rgba(255, 255, 255, 0.12);
background: rgba(15, 23, 42, 0.8);
}
.examples-section .example-role {
font-size: 0.82rem;
font-weight: 700;
letter-spacing: 0.08em;
color: #cbd5e1;
margin-bottom: 0.45rem;
text-transform: uppercase;
}
.examples-section .example-flow {
display: flex;
align-items: center;
gap: 1.5rem;
padding: 1.25rem;
border-radius: 18px;
background: rgba(15, 23, 42, 0.45);
border: 1px solid rgba(255, 255, 255, 0.08);
}
.examples-section .example-inputs {
flex: 1;
display: grid;
gap: 1rem;
}
.examples-section .example-output {
flex: 1;
}
.examples-section .example-arrow {
color: #a5b4fc;
font-size: 1.75rem;
font-weight: 700;
line-height: 1;
}
@media (max-width: 767.98px) {
.examples-section .example-flow {
flex-direction: column;
}
.examples-section .example-arrow {
transform: rotate(90deg);
}
}
</style>
</head>
<body>
<!-- Fixed navigation with links to major page sections. -->
<nav class="navbar navbar-expand-lg fixed-top">
<div class="container">
<a class="navbar-brand" href="#">
<span class="brand-logo" aria-hidden="true">
<i class="fas fa-wave-square"></i>
<i class="fas fa-circle-nodes"></i>
</span>
<span>StyleXfer</span>
</a>
<button class="navbar-toggler navbar-dark" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto">
<li class="nav-item"><a class="nav-link" href="#">Home</a></li>
<li class="nav-item"><a class="nav-link" href="#examples">Examples</a></li>
<li class="nav-item"><a class="nav-link" href="#contact">Contact</a></li>
</ul>
<a href="#nst_section" class="btn btn-sm btn-outline-light ms-3 rounded-pill px-4">Try It</a>
</div>
</div>
</nav>
<!-- App title and short description. -->
<div class="header-section">
<div class="container">
<h1 class="display-4 fw-bold">StyleXfer</h1>
<h3 style="color: white; margin: 0 auto; text-align: center;">Adaptive Neural Style Transfer Using AdaIN</h3>
</div>
</div>
<!-- Upload form: Flask-WTF renders CSRF protection and file/range/submit fields. -->
<div class="container mb-5 fade-in-up" id="nst_section">
<!-- # method="post" → the form will send data to the server via POST (not GET). -->
<!-- enctype="multipart/form-data" → required when uploading files (images, etc.). Without this, file uploads won’t work. -->
<form method="post" enctype="multipart/form-data" id="styleTransferForm">
<!-- This is a Flask‑WTF helper. It automatically renders hidden fields needed for the form, most importantly the CSRF token. -->
<!-- CSRF (Cross‑Site Request Forgery) protection ensures that only requests generated from your app are accepted, preventing malicious submissions. -->
<!-- So, when you use form.hidden_tag(), Flask‑WTF inserts something like: -->
<!-- <input id="csrf_token" name="csrf_token" type="hidden" value="random_secure_token"> -->
<!-- This hidden token is checked on the server side when the form is submitted. -->
{{ form.hidden_tag() }}
<div class="row g-4">
<!-- Content image upload and live preview. -->
<div class="col-md-6">
<div class="card h-100">
<div class="card-header text-center py-3">Content Source</div>
<div class="card-body text-center">
<div class="preview-image mb-3" id="contentPreview">
{% if content_image %}
<!-- {{ url_for('send_image', filename=content_image) }} :- This is Jinja2 template syntax (used in Flask). -->
<!-- url_for('send_image', filename=content_image) dynamically generates the URL for the Flask route named send_image. -->
<!-- The filename=content_image part passes the image filename as a parameter to that route. -->
<img src="{{ url_for('send_image', filename=content_image) }}" alt="Content Image">
{% else %}
<span>Select Content Image</span>
{% endif %}
</div>
{{ form.content(class="form-control", onchange="previewFile(this, 'contentPreview')") }}
</div>
</div>
</div>
<!-- Style reference upload and live preview. -->
<div class="col-md-6">
<div class="card h-100">
<div class="card-header text-center py-3">Style Reference</div>
<div class="card-body text-center">
<div class="preview-image mb-3" id="stylePreview">
{% if style_image %}
<img src="{{ url_for('send_image', filename=style_image) }}" alt="Style Image">
{% else %}
<span>Select Style Image</span>
{% endif %}
</div>
{{ form.style(class="form-control", onchange="previewFile(this, 'stylePreview')") }}
</div>
</div>
</div>
</div>
<!-- Style-strength control and submit button. -->
<div class="row justify-content-center mt-4">
<div class="col-md-8">
<div class="range-wrap">
<label for="alpha" class="form-label fw-bold mb-3 d-block text-center" style="font-family: 'Fugaz One', sans-serif; letter-spacing: 1px; color: #a855f7;">STYLE STRENGTH</label>
<div class="position-relative">
<div class="range-value" id="rangeValue">1.0</div>
{{ form.alpha(class="form-control-range", id="alphaRange", type="range", min="0", max="1", step="0.1") }}
</div>
</div>
<div class="text-center mt-5">
{{ form.submit(class="btn btn-primary shadow-lg btn-glow") }}
</div>
</div>
</div>
</form>
<!-- Render the processed output only after the backend returns a result image filename. -->
{% if result_image %}
<div class="row justify-content-center mt-5" id="resultSection">
<div class="col-md-8">
<div class="card border-success">
<div class="card-header bg-success text-white fw-bold text-center py-3" style="background: linear-gradient(135deg, #10b981 0%, #059669 100%);">Stylized Result</div>
<div class="card-body p-0">
<img src="{{ url_for('send_image', filename=result_image) }}" class="img-fluid w-100" alt="Stylized Image" style="border-radius: 0 0 20px 20px;">
</div>
<div class="card-footer text-center" style="background: transparent; border-top: none; padding-bottom: 20px;">
<a href="{{ url_for('send_image', filename=result_image) }}" download class="btn btn-outline-light mt-2">Download Result</a>
</div>
</div>
</div>
</div>
{% endif %}
<!-- Show backend validation or processing errors near the form. -->
{% if error %}
<div class="alert alert-danger mt-4 text-center" role="alert" style="background: rgba(220, 38, 38, 0.2); border-color: #dc2626; color: #fecaca;">
<strong>Error:</strong> {{ error }}
</div>
{% endif %}
</div>
<!-- Static examples served from the configured examples folder. -->
<section class="container examples-section fade-in-up" id="examples">
<div class="card">
<div class="card-header text-center py-3">Examples</div>
<div class="card-body">
<p class="examples-note text-center mb-4">Each example shows the content image, the style below it, and the final output beside them.</p>
<div class="row g-4">
<div class="col-12">
<div class="example-flow">
<div class="example-inputs">
<div>
<div class="example-role text-center">Content Image</div>
<img src="{{ url_for('send_example', filename='brad_pitt.jpg') }}" alt="Brad Pitt Content" class="example-image">
</div>
<div>
<div class="example-role text-center">Style Image</div>
<img src="{{ url_for('send_example', filename='sketch.png') }}" alt="Sketch Style" class="example-image">
</div>
</div>
<div class="example-arrow">-></div>
<div class="example-output">
<div class="example-role text-center">Output</div>
<img src="{{ url_for('send_example', filename='stylized_brad_pitt.jpg') }}" alt="Brad Pitt Sketch Result" class="example-image">
</div>
</div>
</div>
<div class="col-12">
<div class="example-flow">
<div class="example-inputs">
<div>
<div class="example-role text-center">Content Image</div>
<img src="{{ url_for('send_example', filename='brad_pitt.jpg') }}" alt="Brad Pitt Content" class="example-image">
</div>
<div>
<div class="example-role text-center">Style Image</div>
<img src="{{ url_for('send_example', filename='picasso_seated_nude_hr.jpg') }}" alt="Picasso Seated Nude Style" class="example-image">
</div>
</div>
<div class="example-arrow">-></div>
<div class="example-output">
<div class="example-role text-center">Output</div>
<img src="{{ url_for('send_example', filename='stylized_brad_pitt_1.jpg') }}" alt="Brad Pitt Picasso Result" class="example-image">
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- Footer with project summary, quick links, contact email, and social profiles. -->
<footer class="footer-section" id="contact">
<div class="container">
<div class="row g-4">
<div class="col-lg-4 col-md-6">
<h5 class="footer-brand">
<span class="brand-logo" aria-hidden="true">
<i class="fas fa-wave-square"></i>
<i class="fas fa-circle-nodes"></i>
</span>
<span>StyleXfer</span>
</h5>
<p class="mb-4">"Transform reality into art. Apply any artistic style to your images with AI-powered neural style transfer. Blend inspiration with imagination—instantly reimagine your photos through advanced neural technology."</p>
</div>
<div class="col-lg-2 col-md-6">
<h5>Quick Links</h5>
<a href="#" class="footer-link">Home</a>
<a href="#examples" class="footer-link">Examples</a>
</div>
<div class="col-lg-3 col-md-6">
<h5>Contact</h5>
<p class="mb-2"><i class="fas fa-envelope me-2"></i> arpitpal16112@gmail.com</p>
</div>
<div class="col-lg-3 col-md-6">
<h5>Follow</h5>
<div class="d-flex mb-3">
<a href="https://www.linkedin.com/in/arpitpal0711" class="social-icon"><i class="fab fa-linkedin-in"></i></a>
<a href="https://www.instagram.com/arpit_p_07/" class="social-icon"><i class="fab fa-instagram"></i></a>
<a href="https://github.com/AP16112" class="social-icon"><i class="fab fa-github"></i></a>
</div>
</div>
</div>
<hr class="my-4" style="border-color: rgba(255,255,255,0.1);">
<div class="text-center text-muted small">
<p style='font-weight: bold; color: white;'> Created with ❤️ by <span style="color: #facc15;">Arpit Pal</span> </p>
</div>
</div>
</footer>
<!-- Processing indicator displayed while the form submission is running. -->
<div class="loader-overlay" id="loader" aria-live="polite" aria-hidden="true">
<div class="style-spinner"></div>
<h3>PROCESSING</h3>
<p>Creating your stylized image...</p>
</div>
<!-- Bootstrap JavaScript enables the responsive navbar collapse behavior. -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
// Display a selected local image immediately before the form is submitted.
function previewFile(input, previewId) {
const preview = document.getElementById(previewId);
const file = input.files[0];
const reader = new FileReader();
reader.addEventListener("load", function () {
// Convert the selected file to a temporary base64 URL for previewing.
preview.innerHTML = `<img src="${reader.result}" alt="Image Preview">`;
}, false);
if (file) {
reader.readAsDataURL(file);
}
}
// Initialize result scrolling, slider UI, and loading overlay behavior after the page loads.
window.onload = function() {
if(document.getElementById('resultSection')) {
document.getElementById('resultSection').scrollIntoView({behavior: "smooth"});
}
// Range Slider Logic
const form = document.getElementById('styleTransferForm');
const loader = document.getElementById('loader');
const range = document.getElementById('alphaRange');
const rangeValue = document.getElementById('rangeValue');
function updateRange() {
const val = range.value;
const min = range.min || 0;
const max = range.max || 1;
const percentage = ((val - min) * 100) / (max - min);
rangeValue.textContent = val;
// Adjust position of the bubble to follow the thumb
const thumbWidth = 24;
const offset = thumbWidth/2 - (percentage * thumbWidth / 100);
rangeValue.style.left = `calc(${percentage}% + ${offset}px)`;
// Fill the slider track up to the current value.
range.style.background = `linear-gradient(to right, #818cf8 0%, #c084fc ${percentage}%, rgba(30, 41, 59, 0.8) ${percentage}%, rgba(30, 41, 59, 0.8) 100%)`;
}
if(range) {
range.addEventListener('input', updateRange);
updateRange();
}
if(form && loader) {
form.addEventListener('submit', function() {
loader.classList.add('active');
loader.setAttribute('aria-hidden', 'false');
});
}
}
</script>
</body>
</html>