不知名提交

This commit is contained in:
2025-12-13 20:53:50 +08:00
parent c147502b4d
commit 1221d6faf1
120 changed files with 11005 additions and 1092 deletions

View File

@@ -0,0 +1,295 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>图片黑白处理</title>
<style>
:root{
--bg-1:#E9F9E7; /* 淡绿色 */
--bg-2:#F5FBE7; /* 淡黄绿色 */
--fg:#0f5132; /* 深一点的绿色文字 */
--muted:#5c7a66;
--card:#ffffffcc;
--accent:#6cc870;
--accent-2:#90d47f;
--shadow: 0 8px 24px rgba(39, 115, 72, .15);
--radius: 18px;
}
html,body{
height:100%;
}
body{
margin:0;
font-family: -apple-system,BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, "Noto Sans", "PingFang SC", "Hiragino Sans GB", "Microsoft Yahei", sans-serif;
color:var(--fg);
background: linear-gradient(160deg,var(--bg-1) 0%, var(--bg-2) 100%);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
display:flex; align-items:stretch; justify-content:center;
}
.wrap{
width:min(100%, 760px);
padding:16px;
}
header{
text-align:center;
margin: 8px 0 14px;
}
h1{
margin:0 0 6px;
font-weight:800;
letter-spacing:.3px;
font-size: clamp(20px, 4.5vw, 28px);
}
.sub{
margin:0 auto;
max-width: 32em;
color:var(--muted);
font-size: clamp(12px, 3.4vw, 14px);
}.card{
background: var(--card);
backdrop-filter: blur(6px) saturate(120%);
border: 1px solid rgba(108,200,112,.18);
border-radius: var(--radius);
padding: 14px;
box-shadow: var(--shadow);
}
.controls{
display:grid;
grid-template-columns: 1fr;
gap:12px;
}
.row{
display:flex; align-items:center; gap:10px; flex-wrap:wrap;
}
label{
font-weight:600; font-size:14px;
}
input[type="file"]{
width:100%;
padding:12px;
border: 1px dashed #a8d7a3;
border-radius: 14px;
background: #ffffffb0;
}
input[type="range"]{
width:100%;
accent-color: var(--accent);
height: 28px;
}
output{ font-variant-numeric: tabular-nums; min-width:3ch; text-align:right; display:inline-block; }
.buttons{
display:grid; grid-template-columns: 1fr 1fr; gap:10px;
}
button{
appearance:none; border:0; cursor:pointer;
padding:12px 14px; border-radius: 14px; font-weight:700;
background: linear-gradient(180deg, var(--accent), var(--accent-2));
color:white; box-shadow: 0 6px 16px rgba(16,123,62,.25);
}
button.secondary{
background:#eaf8ea; color:#245b35; box-shadow:none; border:1px solid #cfe9ce;
}
button:disabled{ opacity:.5; cursor:not-allowed; }
.preview{
margin-top: 12px;
display:grid; gap:10px;
}
.canvas-wrap{
background: repeating-linear-gradient( 45deg, #f3fbf0, #f3fbf0 14px, #eef8ec 14px, #eef8ec 28px);
border:1px solid #dcefd7; border-radius: 14px; overflow:hidden;
display:flex; align-items:center; justify-content:center;
min-height: 240px;
}
canvas{ max-width:100%; height:auto; display:block; }
.placeholder{ color:#7da287; padding:20px; text-align:center; }
footer{
text-align:center; color:var(--muted); font-size:12px; margin:14px auto 6px;
}
/* 更偏向手机竖屏的合理布局 */
@media (min-width: 720px){
.controls{ grid-template-columns: 1.2fr 1fr; align-items:end; }
.buttons{ grid-template-columns: 1fr 1fr; }
}
</style>
</head>
<body>
<div class="wrap">
<header>
<h1>图片黑白处理 · 轻柔绿意版</h1>
<p class="sub">上传一张图片,拖动滑块设置<span style="font-weight:600">黑白程度</span>0% 原图 → 100% 全黑白),一键下载处理结果。完全本地处理,无需联网。</p>
</header><section class="card">
<div class="controls">
<div>
<label for="file">选择图片</label>
<input id="file" type="file" accept="image/*" />
</div>
<div>
<div class="row" style="justify-content:space-between">
<label for="amount">黑白程度</label>
<div><output id="val">100</output>%</div>
</div>
<input id="amount" type="range" min="0" max="100" step="1" value="100" />
<div class="buttons">
<button id="download" disabled>下载处理后的图片</button>
<button id="reset" class="secondary" disabled>重置</button>
</div>
</div>
</div>
<div class="preview">
<div class="canvas-wrap">
<canvas id="canvas" aria-label="预览画布"></canvas>
<div id="ph" class="placeholder">⬆️ 请选择一张图片开始…</div>
</div>
</div>
</section>
<footer>© 黑白处理在您的设备本地完成 · 支持手机竖屏友好显示</footer>
</div><script>
(() => {
const fileInput = document.getElementById('file');
const amount = document.getElementById('amount');
const valOut = document.getElementById('val');
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const downloadBtn = document.getElementById('download');
const resetBtn = document.getElementById('reset');
const placeholder = document.getElementById('ph');
let originalPixels = null; // Uint8ClampedArray当前画布的原始像素用于反复处理
let imgNatural = { w: 0, h: 0 };
let currentFileName = 'processed.png';
let rafId = null;
// 将图像绘制到画布,并根据屏幕宽度进行适度缩放,避免超大图导致内存压力
const MAX_DIM = 4096; // 上限,兼顾清晰度与移动端内存
function drawImageToCanvas(img) {
// 处理超大图片:在不改变比例的前提下收敛到 MAX_DIM 以内
let w = img.naturalWidth;
let h = img.naturalHeight;
const scale = Math.min(1, MAX_DIM / Math.max(w, h));
w = Math.round(w * scale);
h = Math.round(h * scale);
// 为了保证下载清晰度canvas 使用实际像素尺寸;显示层用 CSS 自适应
canvas.width = w;
canvas.height = h;
ctx.clearRect(0,0,w,h);
ctx.drawImage(img, 0, 0, w, h);
// 保存原始像素用于反复应用不同程度
originalPixels = ctx.getImageData(0,0,w,h);
imgNatural = { w, h };
}
function lerp(a,b,t){ return a + (b-a) * t; }
// 将原图按给定强度转为黑白(灰度)
function applyGrayscale(strength01){
if(!originalPixels) return;
const { data: src } = originalPixels;
const copy = new ImageData(new Uint8ClampedArray(src), imgNatural.w, imgNatural.h);
const out = copy.data;
// 加权灰度(符合 sRGB 感知权重)
for(let i=0; i<out.length; i+=4){
const r = src[i], g = src[i+1], b = src[i+2];
const y = 0.2126*r + 0.7152*g + 0.0722*b;
out[i] = lerp(r, y, strength01);
out[i+1] = lerp(g, y, strength01);
out[i+2] = lerp(b, y, strength01);
// 保留 alpha
}
ctx.putImageData(copy, 0, 0);
}
// 防抖 + rAF流畅响应滑杆
function scheduleRender(){
if(rafId) cancelAnimationFrame(rafId);
rafId = requestAnimationFrame(() => {
const t = Number(amount.value) / 100; // 0~1
valOut.textContent = amount.value;
applyGrayscale(t);
});
}
// 处理文件加载
fileInput.addEventListener('change', async (e) => {
const file = e.target.files && e.target.files[0];
if(!file) return;
currentFileName = (file.name ? file.name.replace(/\.[^.]+$/, '') : 'processed') + '.png';
const url = URL.createObjectURL(file);
const img = new Image();
img.onload = () => {
placeholder.style.display = 'none';
drawImageToCanvas(img);
// 初始按当前滑杆值处理
scheduleRender();
downloadBtn.disabled = false;
resetBtn.disabled = false;
URL.revokeObjectURL(url);
};
img.onerror = () => {
alert('无法加载该图片,请更换文件试试。');
URL.revokeObjectURL(url);
};
img.src = url;
});
amount.addEventListener('input', scheduleRender);
resetBtn.addEventListener('click', () => {
if(!originalPixels) return;
amount.value = 100;
scheduleRender();
});
// 下载当前画布
function downloadCanvas(filename){
// toBlob 在少数旧版浏览器可能不可用,做个兼容
if(canvas.toBlob){
canvas.toBlob((blob) => {
if(!blob){ fallback(); return; }
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = filename;
document.body.appendChild(link);
link.click();
link.remove();
setTimeout(() => URL.revokeObjectURL(link.href), 5000);
}, 'image/png');
} else {
fallback();
}
function fallback(){
const dataURL = canvas.toDataURL('image/png');
const link = document.createElement('a');
link.href = dataURL;
link.download = filename;
document.body.appendChild(link);
link.click();
link.remove();
}
}
downloadBtn.addEventListener('click', () => {
if(!originalPixels) return;
downloadCanvas(currentFileName);
});
})();
</script></body>
</html>