ds: 墨水屏风格图片处理工具
买了一个电子墨水屏摆件,可以显示黑、白、红三色图片,使用小程序上传图片,但未处理过的图片显示效果不好,因此用deepseek生成了一个处理工具,先把图片调整成三色图,再用小程序上传,效果就好多了。
ds生成

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>墨水屏风格图片处理工具</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
body {
background: linear-gradient(135deg, #1a2a6c, #b21f1f, #fdbb2d);
color: #fff;
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
background: rgba(0, 0, 0, 0.7);
border-radius: 15px;
padding: 25px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
}
header {
text-align: center;
margin-bottom: 30px;
padding-bottom: 15px;
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
}
h1 {
font-size: 2.5rem;
margin-bottom: 10px;
background: linear-gradient(to right, #ff9966, #ff5e62);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.subtitle {
font-size: 1.1rem;
opacity: 0.8;
}
.app-container {
display: flex;
flex-wrap: wrap;
gap: 30px;
}
.control-panel {
flex: 1;
min-width: 300px;
background: rgba(30, 30, 40, 0.8);
padding: 25px;
border-radius: 12px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
}
.preview-panel {
flex: 2;
min-width: 500px;
display: flex;
flex-direction: column;
gap: 25px;
}
.section {
margin-bottom: 25px;
}
h2 {
font-size: 1.4rem;
margin-bottom: 15px;
color: #ff9966;
display: flex;
align-items: center;
gap: 10px;
}
h2 i {
font-size: 1.2rem;
}
.upload-area {
border: 2px dashed #ff9966;
border-radius: 10px;
padding: 30px;
text-align: center;
cursor: pointer;
transition: all 0.3s;
background: rgba(255, 153, 102, 0.1);
}
.upload-area:hover {
background: rgba(255, 153, 102, 0.2);
}
.upload-icon {
font-size: 3rem;
margin-bottom: 15px;
color: #ff9966;
}
.slider-container {
margin: 20px 0;
}
.slider-label {
display: flex;
justify-content: space-between;
margin-bottom: 8px;
}
.slider {
width: 100%;
height: 8px;
-webkit-appearance: none;
background: rgba(255, 255, 255, 0.1);
border-radius: 5px;
outline: none;
}
.slider::-webkit-slider-thumb {
-webkit-appearance: none;
width: 20px;
height: 20px;
border-radius: 50%;
background: #ff5e62;
cursor: pointer;
transition: all 0.2s;
}
.slider::-webkit-slider-thumb:hover {
background: #ff9966;
transform: scale(1.2);
}
.ratio-presets {
display: flex;
gap: 10px;
flex-wrap: wrap;
margin-top: 15px;
}
.ratio-btn {
padding: 8px 15px;
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 5px;
color: white;
cursor: pointer;
transition: all 0.2s;
}
.ratio-btn:hover {
background: rgba(255, 153, 102, 0.3);
}
.ratio-btn.active {
background: #ff5e62;
border-color: #ff5e62;
}
.preview-container {
background: rgba(30, 30, 40, 0.8);
border-radius: 12px;
padding: 20px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
flex: 1;
display: flex;
flex-direction: column;
}
.preview-title {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.preview-area {
flex: 1;
background: rgba(0, 0, 0, 0.3);
border-radius: 8px;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
min-height: 300px;
}
.preview-image {
max-width: 100%;
max-height: 100%;
display: none;
}
.effects-container {
display: flex;
gap: 20px;
flex-wrap: wrap;
}
.effect-box {
flex: 1;
min-width: 200px;
background: rgba(30, 30, 40, 0.8);
border-radius: 12px;
padding: 20px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
text-align: center;
}
.effect-title {
font-size: 1.2rem;
margin-bottom: 15px;
color: #ff9966;
}
.effect-preview {
width: 100%;
height: 200px;
background: rgba(0, 0, 0, 0.3);
border-radius: 8px;
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 15px;
overflow: hidden;
}
.effect-image {
max-width: 100%;
max-height: 100%;
display: none;
}
.btn {
padding: 12px 25px;
background: linear-gradient(to right, #ff9966, #ff5e62);
border: none;
border-radius: 8px;
color: white;
font-weight: bold;
cursor: pointer;
transition: all 0.3s;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.btn:hover {
transform: translateY(-3px);
box-shadow: 0 5px 15px rgba(255, 94, 98, 0.4);
}
.btn:active {
transform: translateY(0);
}
.btn-full {
width: 100%;
margin-top: 10px;
}
.hidden {
display: none;
}
.placeholder-text {
color: rgba(255, 255, 255, 0.5);
font-style: italic;
}
.info-box {
background: rgba(255, 153, 102, 0.1);
border-left: 4px solid #ff9966;
padding: 15px;
border-radius: 5px;
margin-top: 20px;
}
@media (max-width: 900px) {
.app-container {
flex-direction: column;
}
.control-panel, .preview-panel {
min-width: 100%;
}
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>墨水屏风格图片处理工具</h1>
<p class="subtitle">上传图片,按比例裁切,生成黑白红三色墨水屏效果</p>
</header>
<div class="app-container">
<div class="control-panel">
<div class="section">
<h2><i>📁</i> 图片上传</h2>
<div class="upload-area" id="uploadArea">
<div class="upload-icon">📁</div>
<p>点击或拖拽图片到此处上传</p>
<p class="placeholder-text">支持 JPG, PNG, GIF 格式</p>
</div>
<input type="file" id="fileInput" class="hidden" accept="image/*">
</div>
<div class="section">
<h2><i>✂️</i> 图片裁切</h2>
<div class="slider-container">
<div class="slider-label">
<span>宽度比例:</span>
<span id="widthValue">100%</span>
</div>
<input type="range" min="10" max="100" value="100" class="slider" id="widthSlider">
</div>
<div class="slider-container">
<div class="slider-label">
<span>高度比例:</span>
<span id="heightValue">100%</span>
</div>
<input type="range" min="10" max="100" value="100" class="slider" id="heightSlider">
</div>
<div class="ratio-presets">
<div class="ratio-btn active" data-ratio="1:1">1:1 (正方形)</div>
<div class="ratio-btn" data-ratio="16:9">16:9 (宽屏)</div>
<div class="ratio-btn" data-ratio="4:3">4:3 (标准)</div>
<div class="ratio-btn" data-ratio="3:2">3:2 (照片)</div>
<div class="ratio-btn" data-ratio="9:16">9:16 (手机)</div>
</div>
</div>
<div class="section">
<h2><i>🎨</i> 墨水屏效果</h2>
<div class="slider-container">
<div class="slider-label">
<span>红色阈值:</span>
<span id="redThresholdValue">50%</span>
</div>
<input type="range" min="1" max="100" value="50" class="slider" id="redThreshold">
</div>
<div class="slider-container">
<div class="slider-label">
<span>黑白对比度:</span>
<span id="contrastValue">50%</span>
</div>
<input type="range" min="1" max="100" value="50" class="slider" id="contrast">
</div>
<button class="btn btn-full" id="generateBtn">
<i>🔄</i> 生成墨水屏效果
</button>
<button class="btn btn-full" id="downloadBtn" style="margin-top: 15px;">
<i>💾</i> 下载墨水屏图片
</button>
</div>
<div class="info-box">
<h3>墨水屏效果说明</h3>
<p>此工具模拟只能显示黑色、白色和红色的墨水屏设备。</p>
<p>通过调整红色阈值和对比度,可以控制哪些区域显示为红色,哪些区域显示为黑白。</p>
</div>
</div>
<div class="preview-panel">
<div class="preview-container">
<div class="preview-title">
<h2><i>👁️</i> 原图预览</h2>
<button class="btn" id="resetBtn">
<i>🔄</i> 重置
</button>
</div>
<div class="preview-area">
<p class="placeholder-text" id="previewPlaceholder">上传的图片将显示在这里</p>
<img id="previewImage" class="preview-image" alt="预览图片">
</div>
</div>
<div class="effects-container">
<div class="effect-box">
<h3 class="effect-title">墨水屏效果预览</h3>
<div class="effect-preview">
<p class="placeholder-text">墨水屏效果预览</p>
<img id="einkImage" class="effect-image" alt="墨水屏效果">
</div>
<p style="font-size: 0.9rem; margin-top: 10px;">模拟三色墨水屏显示效果,仅使用黑、白、红三种颜色</p>
</div>
<div class="effect-box">
<h3 class="effect-title">效果说明</h3>
<div class="effect-preview" style="background: #000; display: flex; flex-direction: column; justify-content: center; align-items: center; color: white;">
<div style="display: flex; flex-direction: column; gap: 10px; text-align: center;">
<div style="background: #000; padding: 5px; border-radius: 3px;">黑色区域</div>
<div style="background: #fff; color: #000; padding: 5px; border-radius: 3px;">白色区域</div>
<div style="background: #f00; padding: 5px; border-radius: 3px;">红色区域</div>
</div>
</div>
<p style="font-size: 0.9rem; margin-top: 10px;">调整红色阈值和对比度参数可以优化显示效果</p>
</div>
</div>
</div>
</div>
</div>
<script>
// DOM元素
const fileInput = document.getElementById('fileInput');
const uploadArea = document.getElementById('uploadArea');
const widthSlider = document.getElementById('widthSlider');
const heightSlider = document.getElementById('heightSlider');
const widthValue = document.getElementById('widthValue');
const heightValue = document.getElementById('heightValue');
const redThreshold = document.getElementById('redThreshold');
const redThresholdValue = document.getElementById('redThresholdValue');
const contrast = document.getElementById('contrast');
const contrastValue = document.getElementById('contrastValue');
const ratioBtns = document.querySelectorAll('.ratio-btn');
const generateBtn = document.getElementById('generateBtn');
const resetBtn = document.getElementById('resetBtn');
const downloadBtn = document.getElementById('downloadBtn');
const previewImage = document.getElementById('previewImage');
const einkImage = document.getElementById('einkImage');
const previewPlaceholder = document.getElementById('previewPlaceholder');
// 全局变量
let originalImage = null;
let croppedImage = null;
let einkCanvas = null; // 保存墨水屏效果的Canvas
// 上传区域点击事件
uploadArea.addEventListener('click', () => {
fileInput.click();
});
// 拖放功能
uploadArea.addEventListener('dragover', (e) => {
e.preventDefault();
uploadArea.style.background = 'rgba(255, 153, 102, 0.3)';
});
uploadArea.addEventListener('dragleave', () => {
uploadArea.style.background = 'rgba(255, 153, 102, 0.1)';
});
uploadArea.addEventListener('drop', (e) => {
e.preventDefault();
uploadArea.style.background = 'rgba(255, 153, 102, 0.1)';
if (e.dataTransfer.files.length) {
fileInput.files = e.dataTransfer.files;
handleImageUpload(e.dataTransfer.files[0]);
}
});
// 文件选择事件
fileInput.addEventListener('change', (e) => {
if (e.target.files.length) {
handleImageUpload(e.target.files[0]);
}
});
// 处理图片上传
function handleImageUpload(file) {
if (!file.type.match('image.*')) {
alert('请上传图片文件!');
return;
}
const reader = new FileReader();
reader.onload = (e) => {
originalImage = new Image();
originalImage.onload = () => {
previewPlaceholder.style.display = 'none';
previewImage.style.display = 'block';
previewImage.src = e.target.result;
// 重置裁切比例
widthSlider.value = 100;
heightSlider.value = 100;
widthValue.textContent = '100%';
heightValue.textContent = '100%';
// 应用裁切
applyCrop();
};
originalImage.src = e.target.result;
};
reader.readAsDataURL(file);
}
// 更新滑块值显示
widthSlider.addEventListener('input', () => {
widthValue.textContent = `${widthSlider.value}%`;
applyCrop();
});
heightSlider.addEventListener('input', () => {
heightValue.textContent = `${heightSlider.value}%`;
applyCrop();
});
redThreshold.addEventListener('input', () => {
redThresholdValue.textContent = `${redThreshold.value}%`;
generateEinkEffect();
});
contrast.addEventListener('input', () => {
contrastValue.textContent = `${contrast.value}%`;
generateEinkEffect();
});
// 应用裁切比例
function applyCrop() {
if (!originalImage) return;
const widthRatio = widthSlider.value / 100;
const heightRatio = heightSlider.value / 100;
// 创建Canvas进行裁切
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// 计算裁切后的尺寸
const newWidth = originalImage.width * widthRatio;
const newHeight = originalImage.height * heightRatio;
// 计算裁切起点(居中裁切)
const startX = (originalImage.width - newWidth) / 2;
const startY = (originalImage.height - newHeight) / 2;
canvas.width = newWidth;
canvas.height = newHeight;
ctx.drawImage(
originalImage,
startX, startY, newWidth, newHeight,
0, 0, newWidth, newHeight
);
// 更新预览图
previewImage.src = canvas.toDataURL('image/png');
// 保存裁切后的图片
croppedImage = new Image();
croppedImage.onload = () => {
generateEinkEffect();
};
croppedImage.src = canvas.toDataURL('image/png');
}
// 预设比例按钮
ratioBtns.forEach(btn => {
btn.addEventListener('click', () => {
ratioBtns.forEach(b => b.classList.remove('active'));
btn.classList.add('active');
const ratio = btn.getAttribute('data-ratio').split(':');
const width = parseInt(ratio[0]);
const height = parseInt(ratio[1]);
// 根据原图尺寸和比例计算裁切比例
if (originalImage) {
const imgRatio = originalImage.width / originalImage.height;
const targetRatio = width / height;
if (imgRatio > targetRatio) {
// 图片更宽,调整高度比例
const newHeight = (originalImage.height * targetRatio / imgRatio) / originalImage.height * 100;
heightSlider.value = newHeight;
widthSlider.value = 100;
heightValue.textContent = `${Math.round(newHeight)}%`;
widthValue.textContent = '100%';
} else {
// 图片更高,调整宽度比例
const newWidth = (originalImage.width * imgRatio / targetRatio) / originalImage.width * 100;
widthSlider.value = newWidth;
heightSlider.value = 100;
widthValue.textContent = `${Math.round(newWidth)}%`;
heightValue.textContent = '100%';
}
applyCrop();
}
});
});
// 生成墨水屏效果
function generateEinkEffect() {
if (!croppedImage) return;
// 创建Canvas用于墨水屏效果
einkCanvas = document.createElement('canvas');
const ctx = einkCanvas.getContext('2d');
einkCanvas.width = croppedImage.width;
einkCanvas.height = croppedImage.height;
ctx.drawImage(croppedImage, 0, 0);
const imageData = ctx.getImageData(0, 0, einkCanvas.width, einkCanvas.height);
const data = imageData.data;
// 获取阈值和对比度值
const redThresholdValue = redThreshold.value / 100;
const contrastValue = contrast.value / 100 * 2; // 将0-1映射到0-2
for (let i = 0; i < data.length; i += 4) {
const r = data[i];
const g = data[i+1];
const b = data[i+2];
// 计算亮度
let brightness = (r * 0.299 + g * 0.587 + b * 0.114) / 255;
// 应用对比度
brightness = (brightness - 0.5) * contrastValue + 0.5;
brightness = Math.max(0, Math.min(1, brightness));
// 计算红色强度
const redIntensity = r / 255;
const greenIntensity = g / 255;
const blueIntensity = b / 255;
// 判断是否为红色区域
const isRed = redIntensity > greenIntensity * 1.5 &&
redIntensity > blueIntensity * 1.5 &&
redIntensity > redThresholdValue;
if (isRed) {
// 红色区域
data[i] = 255; // R
data[i+1] = 0; // G
data[i+2] = 0; // B
} else {
// 黑白区域
const bwValue = brightness > 0.5 ? 255 : 0;
data[i] = bwValue; // R
data[i+1] = bwValue; // G
data[i+2] = bwValue; // B
}
}
ctx.putImageData(imageData, 0, 0);
einkImage.style.display = 'block';
einkImage.src = einkCanvas.toDataURL('image/png');
}
// 生成效果按钮
generateBtn.addEventListener('click', generateEinkEffect);
// 重置按钮
resetBtn.addEventListener('click', () => {
if (originalImage) {
widthSlider.value = 100;
heightSlider.value = 100;
widthValue.textContent = '100%';
heightValue.textContent = '100%';
redThreshold.value = 50;
redThresholdValue.textContent = '50%';
contrast.value = 50;
contrastValue.textContent = '50%';
ratioBtns[0].classList.add('active');
for (let i = 1; i < ratioBtns.length; i++) {
ratioBtns[i].classList.remove('active');
}
previewImage.src = originalImage.src;
croppedImage = originalImage;
generateEinkEffect();
}
});
// 下载功能 - 修复版
downloadBtn.addEventListener('click', () => {
if (!einkCanvas) {
alert('请先生成墨水屏效果图!');
return;
}
// 创建下载链接
const link = document.createElement('a');
link.download = '墨水屏效果图.png';
link.href = einkCanvas.toDataURL('image/png');
// 添加到文档并触发点击
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
});
</script>
</body>
</html>
墨水屏图片上传程序
项目:https://tsl0922.github.io/EPD-nRF5/
需要梯子才可访问。文末为国内可用版本。

体验
图片处理效果:

小程序入口:

图片处理工具地址:https://api.qs100371.top/zm.html
图片上传工具地址:https://api.qs100371.top/msp.html(需用edge浏览器)