开源透明,所有处理逻辑在此,你也可以自己部署
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta property="og:title" content="老照片在线修复 - η空间">
<meta name="description" content="免费在线老照片修复,增强对比度、锐化细节、色彩修复,让老照片焕发新生">
<meta name="keywords" content="老照片修复,在线修复老照片,照片增强,老照片翻新">
<meta property="og:description" content="免费在线老照片修复,增强对比度、锐化细节、色彩修复,让老照片焕发新生">
<meta property="og:type" content="website">
<title>老照片在线修复 - η空间</title>
<link rel="stylesheet" href="/tools/share.css">
<link rel="stylesheet" href="/assets/pet-cat.css">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
background: #0f172a; color: #e2e8f0; min-height: 100vh;
}
header {
background: linear-gradient(135deg, #1a1a2e, #16213e);
padding: 40px 20px; text-align: center;
border-bottom: 1px solid #1e293b;
}
header h1 { font-size: 28px; margin-bottom: 8px; }
header h1 span { color: #f59e0b; }
header p { color: #94a3b8; font-size: 14px; }
.container { max-width: 720px; margin: 0 auto; padding: 30px 16px; }
/* Upload area */
.upload-area {
background: #1e293b; border: 2px dashed #334155;
border-radius: 16px; padding: 48px 24px; text-align: center;
cursor: pointer; transition: all 0.3s; margin-bottom: 24px;
}
.upload-area:hover, .upload-area.dragover {
border-color: #f59e0b; background: #1e293b;
}
.upload-area .icon { font-size: 48px; margin-bottom: 16px; opacity: 0.6; }
.upload-area .text { font-size: 16px; color: #94a3b8; }
.upload-area .sub { font-size: 13px; color: #64748b; margin-top: 8px; }
.upload-area input[type="file"] { display: none; }
/* Preview */
.preview-area { display: none; }
.preview-area.show { display: block; }
.compare-grid {
display: grid; grid-template-columns: 1fr 1fr; gap: 16px;
margin-bottom: 24px;
}
.compare-item {
background: #1e293b; border-radius: 12px; overflow: hidden;
border: 1px solid #334155;
}
.compare-item .label {
padding: 10px; text-align: center; font-size: 13px; font-weight: 600;
border-bottom: 1px solid #334155;
}
.compare-item .label.original { color: #94a3b8; }
.compare-item .label.restored { color: #22c55e; }
.compare-item img {
width: 100%; display: block;
}
.actions {
display: flex; gap: 12px; justify-content: center; flex-wrap: wrap;
}
.actions button, .actions a {
padding: 12px 28px; border: none; border-radius: 10px;
font-size: 15px; font-weight: 600; cursor: pointer;
text-decoration: none; display: inline-flex; align-items: center; gap: 8px;
transition: all 0.2s; font-family: inherit;
}
.btn-primary {
background: linear-gradient(135deg, #f59e0b, #d97706);
color: #0f172a;
}
.btn-primary:hover { transform: translateY(-2px); box-shadow: 0 4px 15px rgba(245,158,11,0.3); }
.btn-primary:disabled { opacity: 0.5; cursor: not-allowed; transform: none; }
.btn-outline {
background: transparent; color: #e2e8f0;
border: 1px solid #334155;
}
.btn-outline:hover { border-color: #f59e0b; }
.btn-download {
background: #22c55e; color: #0f172a;
}
.btn-download:hover { transform: translateY(-2px); box-shadow: 0 4px 15px rgba(34,197,94,0.3); }
.btn-download.hidden { display: none; }
/* Loading */
.loading-overlay {
display: none; position: fixed; inset: 0; z-index: 100;
background: rgba(15,23,42,0.85);
align-items: center; justify-content: center; flex-direction: column; gap: 16px;
}
.loading-overlay.show { display: flex; }
.spinner {
width: 48px; height: 48px; border: 3px solid #334155;
border-top-color: #f59e0b; border-radius: 50%;
animation: spin 0.8s linear infinite;
}
.loading-overlay .text { color: #94a3b8; font-size: 16px; }
@keyframes spin { to { transform: rotate(360deg); } }
/* Footer */
.site-footer {
margin-top: 48px; padding-top: 24px;
border-top: 1px solid #1e293b;
text-align: center;
}
.site-footer .footer-nav { margin-bottom: 12px; }
.site-footer .footer-nav a {
color: #f59e0b; text-decoration: none; font-size: 14px;
}
.site-footer .footer-nav a:hover { text-decoration: underline; }
.site-footer .footer-nav .sep {
color: #334155; margin: 0 10px; font-size: 14px;
}
.site-footer .footer-meta { font-size: 13px; color: #475569; }
.site-footer .footer-meta a {
color: #22c55e; text-decoration: none;
}
.site-footer .footer-meta a:hover { text-decoration: underline; }
.site-footer .footer-meta .dot {
margin: 0 8px; color: #334155;
}
/* Responsive */
@media (max-width: 768px) {
header { padding: 32px 16px; }
header h1 { font-size: 24px; }
.container { padding: 20px 12px; }
.upload-area { padding: 36px 16px; }
.compare-grid { grid-template-columns: 1fr; gap: 12px; }
.actions { flex-direction: column; align-items: stretch; }
.actions button, .actions a { justify-content: center; }
}
@media (max-width: 480px) {
header { padding: 24px 12px; }
header h1 { font-size: 22px; }
.upload-area { padding: 28px 12px; }
.upload-area .icon { font-size: 36px; }
.upload-area .text { font-size: 14px; }
}
</style>
</head>
<body>
<header>
<h1>🖼️ <span>老照片在线修复</span></h1>
<p>上传老照片,自动增强对比度、锐化细节、修复色彩</p>
</header>
<div class="container">
<!-- Upload -->
<div class="upload-area" id="uploadArea">
<div class="icon">🖼️</div>
<div class="text">点击选择照片或拖拽图片到此处</div>
<div class="sub">支持 JPG / PNG / WebP,最大 20MB</div>
<input type="file" id="fileInput" accept="image/jpeg,image/png,image/webp">
</div>
<!-- Loading -->
<div class="loading-overlay" id="loadingOverlay">
<div class="spinner"></div>
<div class="text">正在修复中... 请稍候</div>
</div>
<!-- Preview -->
<div class="preview-area" id="previewArea">
<div class="compare-grid">
<div class="compare-item">
<div class="label original">📄 修复前</div>
<img id="originalImg" src="" alt="原始照片">
</div>
<div class="compare-item">
<div class="label restored">✨ 修复后</div>
<img id="restoredImg" src="" alt="修复后照片">
</div>
</div>
<div class="actions">
<button class="btn-primary" onclick="uploadPhoto()">🔄 重新修复</button>
<button class="btn-outline" onclick="resetAll()">📁 换一张照片</button>
<a id="downloadBtn" class="btn-download hidden" download="restored.jpg">⬇️ 下载修复结果</a>
</div>
</div>
<!-- Footer -->
<div class="site-footer">
<div class="footer-nav">
<a href="/tools/">← 工具列表</a>
<span class="sep">·</span>
<a href="/">← 首页</a>
</div>
<div class="footer-meta">
<a href="photo-restore-source.php">📄 查看源码</a>
<span class="dot">·</span>
<a href="photo-restore-dl.php?file=all">⬇️ 下载源码</a>
<span class="dot">·</span>
<span class="tag">开源·不存图</span>
</div>
</div>
</div>
<script>
let selectedFile = null;
let restoredBlob = null;
let isProcessing = false;
const uploadArea = document.getElementById('uploadArea');
const fileInput = document.getElementById('fileInput');
const previewArea = document.getElementById('previewArea');
const loadingOverlay = document.getElementById('loadingOverlay');
const originalImg = document.getElementById('originalImg');
const restoredImg = document.getElementById('restoredImg');
const downloadBtn = document.getElementById('downloadBtn');
// Click to upload
uploadArea.addEventListener('click', () => fileInput.click());
// File select
fileInput.addEventListener('change', (e) => {
if (e.target.files.length > 0) {
selectedFile = e.target.files[0];
showPreview(selectedFile);
}
});
// Drag & drop
uploadArea.addEventListener('dragover', (e) => {
e.preventDefault();
uploadArea.classList.add('dragover');
});
uploadArea.addEventListener('dragleave', () => {
uploadArea.classList.remove('dragover');
});
uploadArea.addEventListener('drop', (e) => {
e.preventDefault();
uploadArea.classList.remove('dragover');
if (e.dataTransfer.files.length > 0) {
selectedFile = e.dataTransfer.files[0];
showPreview(selectedFile);
}
});
function showPreview(file) {
const reader = new FileReader();
reader.onload = (e) => {
originalImg.src = e.target.result;
previewArea.classList.add('show');
previewArea.scrollIntoView({ behavior: 'smooth', block: 'start' });
};
reader.readAsDataURL(file);
}
function uploadPhoto() {
if (isProcessing || !selectedFile) return;
isProcessing = true;
loadingOverlay.classList.add('show');
const formData = new FormData();
formData.append('image', selectedFile);
// PHP proxy approach:
// Since the Node.js server is on localhost:3001 and not directly accessible from browser,
// we use a PHP proxy
fetch('photo-restore-proxy.php', { method: 'POST', body: formData })
.then(r => {
if (!r.ok) return r.text().then(t => { throw new Error(t); });
return r.blob();
})
.then(blob => {
restoredBlob = blob;
const url = URL.createObjectURL(blob);
restoredImg.src = url;
downloadBtn.href = url;
downloadBtn.classList.remove('hidden');
loadingOverlay.classList.remove('show');
isProcessing = false;
})
.catch(err => {
alert('修复失败: ' + err.message);
loadingOverlay.classList.remove('show');
isProcessing = false;
});
}
function resetAll() {
previewArea.classList.remove('show');
restoredBlob = null;
downloadBtn.classList.add('hidden');
selectedFile = null;
fileInput.value = '';
originalImg.src = '';
restoredImg.src = '';
window.scrollTo({ top: 0, behavior: 'smooth' });
}
</script>
<script src="/tools/share.js"></script>
<script src="/assets/pet-cat.js"></script>
</body>
</html>