📄 老照片修复 源码

开源透明,所有处理逻辑在此,你也可以自己部署

📋 全部 photo-restore.php photo-restore-proxy.php server.js
前端页面 (HTML + CSS + JS) 305 行 9.8 KB ⬇️ 下载此文件
photo-restore.php ⬇️ 下载
<!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>