<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>🖼️ WASM Image Filter with Download</title>
<style>
body {
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
max-width: 1000px;
margin: 0 auto;
padding: 20px;
background-color: #f5f5f5;
color: #333;
}
h1 {
color: #2c3e50;
text-align: center;
}
.container {
background-color: white;
border-radius: 8px;
padding: 20px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.image-container {
display: flex;
justify-content: space-around;
margin: 20px 0;
flex-wrap: wrap;
}
.image-box {
text-align: center;
margin: 10px;
position: relative;
}
.image-box h3 {
margin-bottom: 10px;
color: #2c3e50;
}
img {
max-width: 300px;
max-height: 300px;
border: 1px solid #ddd;
border-radius: 4px;
padding: 5px;
background-color: #fff;
}
input[type="file"] {
margin: 20px 0;
padding: 10px;
border: 2px dashed #ccc;
border-radius: 4px;
width: 100%;
box-sizing: border-box;
}
.button-container {
text-align: center;
margin: 20px 0;
}
button {
background-color: #4caf50;
color: white;
padding: 12px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
margin: 0 10px;
font-size: 16px;
transition: background-color 0.3s;
}
button:hover {
background-color: #45a049;
}
button:disabled {
background-color: #cccccc;
cursor: not-allowed;
}
.download-btn {
background-color: #2196f3;
}
.download-btn:hover {
background-color: #0b7dda;
}
.status {
padding: 10px;
margin: 10px 0;
border-radius: 4px;
text-align: center;
}
.success {
background-color: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.error {
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
.loading {
display: none;
text-align: center;
margin: 20px 0;
}
.spinner {
border: 4px solid #f3f3f3;
border-top: 4px solid #3498db;
border-radius: 50%;
width: 30px;
height: 30px;
animation: spin 1s linear infinite;
margin: 0 auto;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.debug {
margin-top: 30px;
padding: 15px;
background-color: #f8f9fa;
border-left: 4px solid #6c757d;
border-radius: 4px;
}
.debug h3 {
margin-top: 0;
color: #6c757d;
}
</style>
</head>
<body>
<div class="container">
<h1>🧽 Grayscale Filter (Rust + WASM)</h1>
<div id="status" class="status"></div>
<div class="loading" id="loading">
<div class="spinner"></div>
<p>Processing image...</p>
</div>
<input type="file" id="upload" accept="image/*" />
<div class="button-container">
<button id="downloadBtn" class="download-btn" disabled>
Download Filtered Image
</button>
</div>
<div class="image-container">
<div class="image-box">
<h3>Original Image</h3>
<img
id="original"
src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzAwIiBoZWlnaHQ9IjMwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZGRkZGRkIi8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtZmFtaWx5PSJtb25vc3BhY2UsIHNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMThweCIgZmlsbD0iIzk5OSIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPlVwbG9hZCBhbiBpbWFnZTwvdGV4dD48L3N2Zz4="
alt="Original image preview"
/>
</div>
<div class="image-box">
<h3>Filtered Output</h3>
<img
id="filtered"
src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzAwIiBoZWlnaHQ9IjMwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZGRkZGRkIi8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtZmFtaWx5PSJtb25vc3BhY2UsIHNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMThweCIgZmlsbD0iIzk5OSIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPkZpbHRlcmVkIGltYWdlPC90ZXh0Pjwvc3ZnPg=="
alt="Filtered image preview"
/>
</div>
</div>
<div class="debug">
<h3>Debug Information</h3>
<p id="debugInfo">Waiting for image upload...</p>
<button id="clearDebug">Clear Debug</button>
</div>
</div>
<script type="module">
// Add debug information to the page
function debugLog(message, isError = false) {
const debugElement = document.getElementById("debugInfo");
const newMessage = document.createElement("div");
newMessage.textContent = `${new Date().toLocaleTimeString()}: ${message}`;
if (isError) {
newMessage.style.color = "#e74c3c";
}
debugElement.prepend(newMessage);
}
// Update status message
function updateStatus(message, isError = false) {
const statusElement = document.getElementById("status");
statusElement.textContent = message;
statusElement.className = isError ? "status error" : "status success";
statusElement.style.display = "block";
}
// Show/hide loading indicator
function setLoading(show) {
document.getElementById("loading").style.display = show
? "block"
: "none";
}
// Clear debug info
document.getElementById("clearDebug").addEventListener("click", () => {
document.getElementById("debugInfo").innerHTML = "";
});
// Download filtered image
function setupDownload(blob) {
const downloadBtn = document.getElementById("downloadBtn");
// Enable the download button
downloadBtn.disabled = false;
// Set up download functionality
downloadBtn.addEventListener("click", () => {
// Create a download link
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "grayscale-image.png";
document.body.appendChild(a);
a.click();
// Clean up
setTimeout(() => {
document.body.removeChild(a);
URL.revokeObjectURL(url);
}, 100);
debugLog("Download initiated for filtered image");
updateStatus("Download started!");
});
}
// Main application logic
async function run() {
debugLog("Initializing application...");
try {
// Import the WASM module
debugLog("Importing WASM module...");
const wasmModule = await import("./pkg/wasm_filter.js");
debugLog("Initializing WASM module...");
await wasmModule.default();
debugLog("WASM module initialized successfully");
updateStatus("WASM module loaded successfully!");
// Set up file upload handler
const upload = document.getElementById("upload");
const original = document.getElementById("original");
const filtered = document.getElementById("filtered");
upload.addEventListener("change", async (e) => {
const file = e.target.files[0];
if (!file) return;
debugLog(
`File selected: ${file.name}, type: ${file.type}, size: ${file.size} bytes`
);
// Validate file type
if (!file.type.startsWith("image/")) {
const errorMsg = "Please select an image file";
debugLog(errorMsg, true);
updateStatus(errorMsg, true);
return;
}
setLoading(true);
updateStatus("Processing image...");
try {
// Read the file as ArrayBuffer
debugLog("Reading file data...");
const buffer = await file.arrayBuffer();
// Display original image
original.src = URL.createObjectURL(file);
debugLog("Original image displayed");
// Apply grayscale filter using WASM
debugLog("Applying grayscale filter via WASM...");
const output = wasmModule.apply_grayscale(new Uint8Array(buffer));
debugLog(`Filter applied, output size: ${output.length} bytes`);
// Create and display the filtered image
const blob = new Blob([output], { type: "image/png" });
filtered.src = URL.createObjectURL(blob);
debugLog("Filtered image displayed");
// Set up download functionality
setupDownload(blob);
updateStatus(
"Image processed successfully! Click Download to save."
);
} catch (err) {
const errorMsg = `Error processing image: ${err.message}`;
debugLog(errorMsg, true);
updateStatus(errorMsg, true);
console.error(err);
} finally {
setLoading(false);
}
});
} catch (err) {
const errorMsg = `Failed to initialize WASM module: ${err.message}`;
debugLog(errorMsg, true);
updateStatus(errorMsg, true);
console.error(err);
// Provide troubleshooting tips
debugLog("Troubleshooting tips:", true);
debugLog(
"1. Make sure you've built the WASM module with: wasm-pack build --target web",
true
);
debugLog(
"2. Check that the pkg/ directory exists and contains the compiled WASM",
true
);
debugLog(
"3. If serving with a file:// URL, try using a local server instead",
true
);
debugLog(
"4. Check browser console for detailed error messages",
true
);
}
}
// Start the application
run();
</script>
</body>
</html>