WWW.YOUINFO.SITE
标签聚合 cn

/tag/cn

LinuxDo 最新话题 · 2026-06-11 22:18:31+08:00 · tech

ChatGPT Plus 每月全球价格 排名 地区 原价 CNY 价格 状态 1 菲律宾 PHP 999 ¥110.15 最低 2 巴基斯坦 PKR 4,900 ¥119.49 - 3 加拿大 CAD 24.99 ¥121.61 - 4 日本 JPY 3,000 ¥127.00 - 5 韩国 KRW 29,000 ¥129.14 - 6 越南 VND 499,000 ¥129.15 - 7 巴西 BRL 99.9 ¥131.02 - 8 埃及 EGP 999.99 ¥131.22 - 9 印度尼西亚 IDR 349,000 ¥131.58 - 10 阿根廷 USD 19.99 ¥135.64 - 11 美国 USD 19.99 ¥135.64 - 12 哈萨克斯坦 KZT 9,990 ¥138.99 - 13 印度 INR 1,999 ¥142.17 - 14 澳大利亚 AUD 29.99 ¥143.09 - 15 泰国 THB 699 ¥144.25 - 16 智利 CLP 19,990 ¥146.99 - 17 土耳其 TRY 999.99 ¥147.10 - 18 阿联酋 AED 79.99 ¥147.79 - 19 台湾 TWD 690 ¥148.25 - 5 个帖子 - 5 位参与者 阅读完整话题

v2ex · 2026-06-11 22:14:02+08:00 · tech

关于林社中转站 这是一个技术宅创立的站点 https://www.lamclod.cn https://auth.lamclod.cn 站长 QQ:2070346656 交流 Q 群:1103238053 其创建中转站的本意是为了炫技和科研学生一起聊天八卦 所以这个站点与众不同的特点: 1.整个系统架构全面融合 OIDC 身份机制,多个合作中转站身份统一验证,无需管理多站点账户。 2.借助 OIDC 密钥交换建立协同调度机制,为软件开发者提供便捷且精准的 LLMAPI 接入。 3.站长是技术宅,技术自然不在话下,平时可以聊聊天,讨论讨论技术。站长为人和善,包容大度。 4.Q 群中有不少热心网友和科研认识,值得一番认识和交流。 5.本站极其欢迎科研学生。

v2ex · 2026-06-11 22:14:02+08:00 · tech

关于林社中转站 这是一个技术宅创立的站点 https://www.lamclod.cn https://auth.lamclod.cn 站长 QQ:2070346656 交流 Q 群:1103238053 其创建中转站的本意是为了炫技和科研学生一起聊天八卦 所以这个站点与众不同的特点: 1.整个系统架构全面融合 OIDC 身份机制,多个合作中转站身份统一验证,无需管理多站点账户。 2.借助 OIDC 密钥交换建立协同调度机制,为软件开发者提供便捷且精准的 LLMAPI 接入。 3.站长是技术宅,技术自然不在话下,平时可以聊聊天,讨论讨论技术。站长为人和善,包容大度。 4.Q 群中有不少热心网友和科研认识,值得一番认识和交流。 5.本站极其欢迎科研学生。

v2ex · 2026-06-11 20:56:26+08:00 · tech

关于林社中转站 这是一个技术宅创立的站点 https://www.lamclod.cn https://auth.lamclod.cn 站长 QQ:2070346656 交流 Q 群:1103238053 其创建中转站的本意是为了炫技和科研学生一起聊天八卦 所以这个站点与众不同的特点: 1.整个系统架构全面融合 OIDC 身份机制,多个合作中转站身份统一验证,无需管理多站点账户。 2.借助 OIDC 密钥交换建立协同调度机制,为软件开发者提供便捷且精准的 LLMAPI 接入。 3.站长是技术宅,技术自然不在话下,平时可以聊聊天,讨论讨论技术。站长为人和善,包容大度。 4.Q 群中有不少热心网友和科研认识,值得一番认识和交流。 5.本站极其欢迎科研学生。

LinuxDo 最新话题 · 2026-06-11 18:27:55+08:00 · tech

base64编码: dHAtY3diZTdsdTk3dWE2ZHZ1eHRjanVhdW1ybHJsNXhvbWYyZXo0bzdjaHJ0czFubW1w 兼容 OpenAI 接口协议: https://token-plan-cn.xiaomimimo.com/v1 兼容 Anthropic 接口协议: https://token-plan-cn.xiaomimimo.com/anthropic 模型 mimo-v2.5-pro、mimo-v2.5、mimo-v2.5-asr、mimo-v2.5-tts-voiceclone、mimo-v2.5-tts-voicedesign、mimo-v2.5-tts、mimo-v2-pro、mimo-v2-omni、mimo-v2-tts 8 个帖子 - 3 位参与者 阅读完整话题

LinuxDo 最新话题 · 2026-06-11 17:19:15+08:00 · tech

我是想做一个类似于千问高考agent的纯聊天小程序,需要一些简单的数据支撑,现在网上能搜索到正式一点的高考历年录取分数线的数据好像只有cnopendata上有,但是本人不是学术工作者所以申请不了,就想问问论坛的大佬们看看有没有可以帮忙下载的,当然也可以走ldc。 原数据在: 中国高考录取分数线数据 当然这块如果有违规或者风险也可以告知,只是暂时有这么一个想法 1 个帖子 - 1 位参与者 阅读完整话题

LinuxDo 最新话题 · 2026-06-11 09:30:30+08:00 · tech

CNN – 10 Jun 26 1 in 3 adults don’t know how to dose medications. Are you one of them? | CNN Medical visits are often quick, leaving patients to rely on written instructions. But many aren’t taught to interpret the information they are given, a study says. 根据周三发表在《内科医学杂志》上 的一项研究, 近三分之一的美国中年人在处理个人健康事务方面存在困难,包括回忆就医信息、阅读标准健康资料和正确服用药物。 参与这项研究的许多患者并非医疗保健系统的新人——有些人患有多种慢性疾病。 美国内科医师学会主席、佛蒙特大学罗伯特·拉纳医学院医学教授简·卡尼博士表示,服用处方药时,是否与食物同服、服用频率甚至药物用途等信息都可能在沟通中丢失,尤其是在患者不愿向医疗保健提供者提问的情况下。 Vogeley表示,对用药说明的误解可能导致患者服用过少或过量的药物,或者与不应该同时服用的药物一起服用,或者以可能更容易产生副作用的方式服用。 北卡罗来纳大学教堂山分校埃舍尔曼药学院药学实践教授斯蒂芬妮·费雷里说,例如,有些人可能不知道应该在早上服用利尿剂,所以他们会在晚餐时服用,结果半夜醒来上厕所。 SpringerLink Prevalence of Limited Health Literacy During Middle Adulthood and Its... Background Although the relationship between health literacy and health self-management has been studied in older adults, it is understudied in middle-aged, primary care patients. Objective To determine the prevalence of limited health literacy and... 1 个帖子 - 1 位参与者 阅读完整话题

LinuxDo 最新话题 · 2026-06-10 15:40:53+08:00 · tech

本帖使用社区开源推广,符合推广要求。我申明并遵循社区要求的以下内容: 我的帖子已经打上 开源推广 标签: 是 我的开源项目完整开源,无未开源部分: 是 我的开源项目已链接认可 LINUX DO 社区: 是 我帖子内的项目介绍,AI生成、润色内容部分已截图发出: 是 以上选择我承诺是永久有效的,接受社区和佬友监督: 是 以下为项目介绍正文内容,AI生成、润色内容已使用截图方式发出 How 2 use? 修改配置文件 打开config.yaml 修改 panel-github-repository 键的值为 https://github.com/CuzTeam/CPAPro 重启,完成!(国内服务器可能连不上Github会。。。) 人工下载(bin运行、非Docker) 在CPA目录执行: curl -s https://api.github.com/repos/CuzTeam/CPAPro/releases/latest | grep -o '"browser_download_url": *"[^"]*management\.html[^"]*"' | grep -o 'https://[^"]*' | xargs -I{} curl -L -o static/management.html --create-dirs {} Repo: github.com GitHub - CuzTeam/CPAPro: CPAPro, a modern CLIProxyAPI panel. CPAPro, a modern CLIProxyAPI panel. 求PR、Star、Issue!!QAQ 5 个帖子 - 3 位参与者 阅读完整话题

LinuxDo 最新话题 · 2026-06-10 15:09:59+08:00 · tech

How 2 use? 修改配置文件 打开config.yaml 修改 panel-github-repository 键的值为 https://github.com/CuzTeam/CPAPro 重启,完成!(国内服务器可能连不上Github会。。。) 人工下载(bin运行、非Docker) 在CPA目录执行: curl -s https://api.github.com/repos/CuzTeam/CPAPro/releases/latest | grep -o '"browser_download_url": *"[^"]*management\.html[^"]*"' | grep -o 'https://[^"]*' | xargs -I{} curl -L -o static/management.html --create-dirs {} Repo: github.com GitHub - CuzTeam/CPAPro: CPAPro, a modern CLIProxyAPI panel. CPAPro, a modern CLIProxyAPI panel. 求PR、Star、Issue!!QAQ 1 个帖子 - 1 位参与者 阅读完整话题

LinuxDo 最新话题 · 2026-06-10 11:36:24+08:00 · tech

证件照换底(支持自定义颜色代码) <!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;} body{ font-family: -apple-system,BlinkMacSystemFont,'Segoe UI','PingFang SC','Microsoft YaHei',sans-serif; background:#f0f2f5;min-height:100vh;color:#1a1a2e;line-height:1.6; } .header{ background:#fff;border-bottom:1px solid #e0e0e0;padding:14px 0; position:sticky;top:0;z-index:100;box-shadow:0 1px 3px rgba(0,0,0,0.06); } .header-inner{max-width:1200px;margin:0 auto;padding:0 24px;display:flex;align-items:center;justify-content:space-between;} .logo{display:flex;align-items:center;gap:10px;font-size:1.25rem;font-weight:700;} .logo-icon{width:38px;height:38px;border-radius:11px;background:linear-gradient(135deg,#4f6ef7,#7b8ff7);display:flex;align-items:center;justify-content:center;font-size:1.3rem;color:#fff;} .privacy-badge{display:flex;align-items:center;gap:6px;font-size:0.8rem;color:#27ae60;background:#eafaf1;padding:5px 12px;border-radius:20px;font-weight:600;} .privacy-dot{width:7px;height:7px;border-radius:50%;background:#27ae60;animation:pulse 2s infinite;} @keyframes pulse{0%,100%{opacity:1}50%{opacity:0.4}} .main-container{max-width:1200px;margin:0 auto;padding:24px;display:grid;grid-template-columns:1fr 1fr;gap:24px;} @media(max-width:900px){.main-container{grid-template-columns:1fr;max-width:520px;}} .card{background:#fff;border-radius:16px;padding:22px;box-shadow:0 4px 24px rgba(0,0,0,0.08);border:1px solid #e0e0e0;} .card-title{font-size:1rem;font-weight:700;margin-bottom:16px;} .upload-zone{border:2px dashed #d0d5e0;border-radius:10px;padding:36px 20px;text-align:center;cursor:pointer;background:#fafbfc;position:relative;min-height:180px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;transition:0.25s;} .upload-zone:hover,.upload-zone.drag-over{border-color:#4f6ef7;background:#eef1ff;} .upload-zone.has-image{border-style:solid;border-color:#e0e0e0;padding:6px;min-height:auto;} .upload-icon{font-size:2.8rem;opacity:0.5;} .upload-text{font-size:0.9rem;color:#555;} .upload-hint{font-size:0.75rem;color:#999;} .upload-zone input[type="file"]{position:absolute;inset:0;opacity:0;cursor:pointer;} .preview-image{width:100%;max-height:350px;object-fit:contain;display:block;border-radius:5px;} .image-container{position:relative;display:block;width:100%;} .image-actions-overlay{position:absolute;top:8px;right:8px;z-index:5;} .btn-icon-sm{width:32px;height:32px;border-radius:50%;border:none;background:rgba(0,0,0,0.5);color:#fff;cursor:pointer;font-size:0.85rem;display:flex;align-items:center;justify-content:center;} .btn{display:inline-flex;align-items:center;justify-content:center;gap:5px;padding:9px 18px;border-radius:25px;font-size:0.88rem;font-weight:600;cursor:pointer;border:none;transition:0.25s;white-space:nowrap;} .btn:active{transform:scale(0.96);} .btn-primary{background:#4f6ef7;color:#fff;} .btn-primary:hover{background:#3b54db;} .btn-outline{background:#fff;color:#4f6ef7;border:2px solid #4f6ef7;} .btn-outline:hover{background:#eef1ff;} .btn-success{background:#27ae60;color:#fff;} .btn-success:hover{opacity:0.9;} .btn-lg{padding:11px 26px;font-size:0.95rem;border-radius:28px;} .btn:disabled{opacity:0.5;cursor:not-allowed;pointer-events:none;} .btn-block{width:100%;} .section-label{font-size:0.82rem;font-weight:600;margin:14px 0 8px;} .color-presets{display:flex;gap:10px;flex-wrap:wrap;margin-bottom:8px;} .color-preset{width:40px;height:40px;border-radius:50%;cursor:pointer;border:3px solid transparent;transition:0.25s;box-shadow:0 1px 3px rgba(0,0,0,0.1);position:relative;} .color-preset:hover{transform:scale(1.12);} .color-preset.active{border-color:#1a1a2e;box-shadow:0 0 0 4px rgba(0,0,0,0.08);} .color-preset.active::after{content:'✓';position:absolute;inset:0;display:flex;align-items:center;justify-content:center;color:#fff;font-weight:bold;font-size:0.9rem;text-shadow:0 1px 2px rgba(0,0,0,0.5);} .custom-color-section{background:#f8f9fb;border-radius:12px;padding:12px 14px;margin-bottom:4px;border:1px solid #e8eaef;} .custom-color-row{display:flex;gap:10px;align-items:center;flex-wrap:wrap;} .custom-color-row input[type="color"]{width:40px;height:40px;border-radius:50%;border:2px solid #e0e0e0;cursor:pointer;padding:2px;flex-shrink:0;} .custom-color-row input[type="color"]::-webkit-color-swatch-wrapper{padding:0;} .custom-color-row input[type="color"]::-webkit-color-swatch{border-radius:50%;border:none;} .hex-input-wrapper{position:relative;flex:1;min-width:110px;} .hex-input-wrapper .hash{position:absolute;left:10px;top:50%;transform:translateY(-50%);color:#999;font-weight:600;font-size:0.85rem;pointer-events:none;} .hex-input{width:100%;padding:8px 10px 8px 24px;border:2px solid #e0e5ec;border-radius:10px;font-size:0.85rem;font-family:monospace;font-weight:600;color:#1a1a2e;background:#fff;outline:none;transition:0.25s;} .hex-input:focus{border-color:#4f6ef7;box-shadow:0 0 0 3px rgba(79,110,247,0.1);} .hex-input.valid{border-color:#27ae60;background:#f8fdf8;} .hex-input.invalid{border-color:#e74c3c;background:#fef8f8;} .color-preview-dot{width:30px;height:30px;border-radius:50%;border:2px solid #ddd;flex-shrink:0;box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);} .control-group{margin-bottom:12px;} .control-label{display:flex;justify-content:space-between;align-items:center;font-size:0.82rem;font-weight:600;margin-bottom:5px;} .control-value{font-size:0.75rem;color:#555;background:#f5f6f8;padding:2px 10px;border-radius:12px;} input[type="range"]{-webkit-appearance:none;width:100%;height:5px;border-radius:3px;background:#e0e5ec;outline:none;cursor:pointer;} input[type="range"]::-webkit-slider-thumb{-webkit-appearance:none;width:20px;height:20px;border-radius:50%;background:#4f6ef7;cursor:pointer;} .size-presets{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:8px;} .size-preset{padding:10px 12px;border-radius:8px;border:2px solid #e0e0e0;cursor:pointer;text-align:center;font-size:0.82rem;font-weight:600;transition:0.25s;background:#fafbfc;} .size-preset:hover{border-color:#4f6ef7;background:#eef1ff;} .size-preset.active{border-color:#4f6ef7;background:#eef1ff;color:#4f6ef7;} .size-preset .size-dims{font-size:0.7rem;color:#555;font-weight:400;} .result-container{text-align:center;} .result-image{max-width:100%;max-height:350px;border-radius:10px;box-shadow:0 4px 24px rgba(0,0,0,0.08);border:1px solid #e0e0e0;} .result-placeholder{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:220px;color:#bbb;gap:10px;} .result-placeholder .icon{font-size:3.5rem;} .action-row{display:flex;gap:10px;flex-wrap:wrap;margin-top:14px;} .toast{position:fixed;bottom:30px;left:50%;transform:translateX(-50%) translateY(100px);background:#1a1a2e;color:#fff;padding:10px 22px;border-radius:25px;font-size:0.85rem;font-weight:600;z-index:999;opacity:0;transition:all 0.35s;pointer-events:none;box-shadow:0 8px 30px rgba(0,0,0,0.25);} .toast.show{opacity:1;transform:translateX(-50%) translateY(0);} .toast.success{background:#27ae60;} .toast.error{background:#e74c3c;} .spinner{display:inline-block;width:16px;height:16px;border:2px solid rgba(255,255,255,0.3);border-top-color:#fff;border-radius:50%;animation:spin 0.7s linear infinite;} @keyframes spin{to{transform:rotate(360deg);}} .footer{text-align:center;padding:18px;font-size:0.75rem;color:#aaa;} </style> </head> <body> <header class="header"> <div class="header-inner"> <div class="logo"><div class="logo-icon">📸</div><span>证件照换底</span></div> <div class="privacy-badge"><span class="privacy-dot"></span>离线处理 · 隐私安全</div> </div> </header> <div class="main-container"> <!-- ========== 左侧面板 ========== --> <div class="card"> <div class="card-title">⚙️ 上传与设置</div> <div class="upload-zone" id="uploadZone"> <span class="upload-icon">📷</span> <span class="upload-text">点击或拖拽上传证件照</span> <span class="upload-hint">支持 JPG / PNG / WebP</span> <input type="file" id="fileInput" accept="image/*"> </div> <div class="section-label">🎨 背景颜色(实时预览)</div> <div class="color-presets" id="colorPresets"> <div class="color-preset active" data-color="#FF0000" style="background:#FF0000;" title="红色"></div> <div class="color-preset" data-color="#FFFFFF" style="background:#FFFFFF;" title="白色"></div> <div class="color-preset" data-color="#438EDB" style="background:#438EDB;" title="蓝色"></div> <div class="color-preset" data-color="#1AAD19" style="background:#1AAD19;" title="绿色"></div> <div class="color-preset" data-color="#808080" style="background:#808080;" title="灰色"></div> <div class="color-preset" data-color="#003399" style="background:#003399;" title="深蓝"></div> </div> <div class="custom-color-section"> <div style="font-size:0.78rem;font-weight:600;margin-bottom:8px;color:#555;">🎯 自定义颜色</div> <div class="custom-color-row"> <input type="color" id="customColorPicker" value="#FF0000" title="取色器"> <div class="hex-input-wrapper"> <span class="hash">#</span> <input type="text" class="hex-input valid" id="hexInput" value="FF0000" maxlength="6" placeholder="输入6位HEX色值"> </div> <div class="color-preview-dot" id="colorPreviewDot" style="background:#FF0000;" title="当前颜色"></div> </div> </div> <div class="control-group" style="margin-top:14px;"> <div class="control-label"><span>🔍 容差范围</span><span class="control-value" id="toleranceVal">50</span></div> <input type="range" id="toleranceSlider" min="10" max="120" value="50" step="1"> </div> <div class="control-group"> <div class="control-label"><span>✨ 边缘羽化</span><span class="control-value" id="featherVal">1</span></div> <input type="range" id="featherSlider" min="0" max="5" value="1" step="0.5"> </div> <div class="control-group"> <div class="control-label"><span>☀️ 亮度</span><span class="control-value" id="brightnessVal">0</span></div> <input type="range" id="brightnessSlider" min="-30" max="30" value="0" step="1"> </div> <div class="section-label">📐 裁剪尺寸</div> <div class="size-presets" id="sizePresets"> <div class="size-preset active" data-width="295" data-height="413" data-name="一寸"><div>一寸</div><div class="size-dims">25×35mm</div></div> <div class="size-preset" data-width="413" data-height="579" data-name="二寸"><div>二寸</div><div class="size-dims">35×49mm</div></div> <div class="size-preset" data-width="260" data-height="378" data-name="小一寸"><div>小一寸</div><div class="size-dims">22×32mm</div></div> <div class="size-preset" data-width="390" data-height="567" data-name="小二寸"><div>小二寸</div><div class="size-dims">33×48mm</div></div> </div> <div class="action-row"> <button class="btn btn-primary btn-lg" id="processBtn" disabled>🎯 开始换底</button> <button class="btn btn-outline" id="resetBtn">🔄 重置</button> </div> </div> <!-- ========== 右侧面板 ========== --> <div class="card"> <div class="card-title">🖼️ 结果预览</div> <div class="result-container"> <div class="result-placeholder" id="resultPlaceholder"> <span class="icon">🖼️</span> <span>处理后的照片将显示在这里</span> </div> <img class="result-image" id="resultImage" style="display:none;" alt="处理结果"> </div> <div class="action-row"> <button class="btn btn-success btn-block" id="downloadBtn" disabled>💾 下载照片</button> </div> </div> </div> <div class="toast" id="toast"></div> <div class="footer">🔒 所有图片处理均在浏览器本地完成,不会上传到任何服务器</div> <script> (function() { // ==================== DOM引用 ==================== var uploadZone = document.getElementById('uploadZone'); var fileInput = document.getElementById('fileInput'); var processBtn = document.getElementById('processBtn'); var resetBtn = document.getElementById('resetBtn'); var downloadBtn = document.getElementById('downloadBtn'); var resultImage = document.getElementById('resultImage'); var resultPH = document.getElementById('resultPlaceholder'); var toastEl = document.getElementById('toast'); var colorPicker = document.getElementById('customColorPicker'); var hexInput = document.getElementById('hexInput'); var colorDot = document.getElementById('colorPreviewDot'); var tolSlider = document.getElementById('toleranceSlider'); var tolVal = document.getElementById('toleranceVal'); var feaSlider = document.getElementById('featherSlider'); var feaVal = document.getElementById('featherVal'); var briSlider = document.getElementById('brightnessSlider'); var briVal = document.getElementById('brightnessVal'); // ==================== 状态 ==================== var originalImage = null; var resultDataURL = null; var currentColor = '#FF0000'; var currentSize = { w: 295, h: 413, name: '一寸' }; var toastTimer = null; var autoTimer = null; var cachedMask = null; // ==================== Toast ==================== function toast(msg, type) { if (toastTimer) clearTimeout(toastTimer); toastEl.textContent = msg; toastEl.className = 'toast ' + (type || '') + ' show'; toastTimer = setTimeout(function() { toastEl.classList.remove('show'); }, 2000); } // ==================== 颜色工具 ==================== function hex3to6(s) { s = s.toUpperCase(); if (s.length === 3) { return s[0]+s[0]+s[1]+s[1]+s[2]+s[2]; } return s; } function isValidHex(s) { return /^[0-9a-fA-F]{3}$/.test(s) || /^[0-9a-fA-F]{6}$/.test(s); } function hexToRgb(hex) { hex = hex.replace('#', ''); if (hex.length === 3) hex = hex3to6(hex); return { r: parseInt(hex.substr(0,2), 16), g: parseInt(hex.substr(2,2), 16), b: parseInt(hex.substr(4,2), 16) }; } // 同步所有颜色UI function applyColor(hex6, source) { hex6 = hex6.toUpperCase(); if (!/^[0-9a-fA-F]{6}$/.test(hex6)) return; currentColor = '#' + hex6; // 取色器 if (source !== 'picker') { colorPicker.value = currentColor; } // 输入框 if (source !== 'input') { hexInput.value = hex6; hexInput.className = 'hex-input valid'; } // 预设圆点 if (source !== 'preset') { var all = document.querySelectorAll('.color-preset'); for (var i = 0; i < all.length; i++) { all[i].classList.remove('active'); } var m = document.querySelector('.color-preset[data-color="#' + hex6 + '"]'); if (m) m.classList.add('active'); } // 预览圆点 colorDot.style.background = currentColor; // 实时更新结果 scheduleAuto(); } // ==================== 颜色事件 ==================== document.getElementById('colorPresets').addEventListener('click', function(e) { var p = e.target.closest('.color-preset'); if (!p) return; var hex6 = p.getAttribute('data-color').replace('#', ''); var all = document.querySelectorAll('.color-preset'); for (var i = 0; i < all.length; i++) all[i].classList.remove('active'); p.classList.add('active'); applyColor(hex6, 'preset'); }); colorPicker.addEventListener('input', function() { var hex = colorPicker.value.replace('#', ''); if (isValidHex(hex)) { applyColor(hex3to6(hex), 'picker'); } }); hexInput.addEventListener('input', function() { var raw = hexInput.value.replace(/[^0-9a-fA-F]/g, '').slice(0, 6); hexInput.value = raw; if (raw.length === 6 && /^[0-9a-fA-F]{6}$/.test(raw)) { hexInput.className = 'hex-input valid'; applyColor(raw, 'input'); } else if (raw.length === 3 && /^[0-9a-fA-F]{3}$/.test(raw)) { hexInput.className = 'hex-input valid'; applyColor(hex3to6(raw), 'input'); } else if (raw.length === 0) { hexInput.className = 'hex-input'; } else if (/^[0-9a-fA-F]+$/.test(raw)) { hexInput.className = 'hex-input'; } else { hexInput.className = 'hex-input invalid'; } }); hexInput.addEventListener('blur', function() { var raw = hexInput.value; if (isValidHex(raw)) { var h6 = hex3to6(raw); hexInput.value = h6; hexInput.className = 'hex-input valid'; applyColor(h6, 'input'); } }); hexInput.addEventListener('paste', function(e) { e.preventDefault(); var t = (e.clipboardData || window.clipboardData).getData('text'); t = t.replace('#', '').replace(/[^0-9a-fA-F]/g, '').slice(0, 6); hexInput.value = t; if (isValidHex(t)) { hexInput.className = 'hex-input valid'; applyColor(hex3to6(t), 'input'); } }); // ==================== 尺寸 ==================== document.getElementById('sizePresets').addEventListener('click', function(e) { var p = e.target.closest('.size-preset'); if (!p) return; var all = document.querySelectorAll('.size-preset'); for (var i = 0; i < all.length; i++) all[i].classList.remove('active'); p.classList.add('active'); currentSize = { w: parseInt(p.getAttribute('data-width')), h: parseInt(p.getAttribute('data-height')), name: p.getAttribute('data-name') }; scheduleAuto(); }); // ==================== 滑块 ==================== tolSlider.addEventListener('input', function() { tolVal.textContent = tolSlider.value; cachedMask = null; scheduleAuto(); }); feaSlider.addEventListener('input', function() { feaVal.textContent = feaSlider.value; scheduleAuto(); }); briSlider.addEventListener('input', function() { briVal.textContent = briSlider.value; scheduleAuto(); }); // ==================== 防抖 ==================== function scheduleAuto() { if (!originalImage) return; if (autoTimer) clearTimeout(autoTimer); autoTimer = setTimeout(function() { doProcess(true); }, 200); } // ==================== 上传 ==================== uploadZone.addEventListener('click', function(e) { if (e.target.tagName === 'BUTTON' || e.target.tagName === 'INPUT') return; fileInput.click(); }); fileInput.addEventListener('change', function(e) { if (e.target.files && e.target.files[0]) { loadFile(e.target.files[0]); } }); uploadZone.addEventListener('dragover', function(e) { e.preventDefault(); uploadZone.classList.add('drag-over'); }); uploadZone.addEventListener('dragleave', function() { uploadZone.classList.remove('drag-over'); }); uploadZone.addEventListener('drop', function(e) { e.preventDefault(); uploadZone.classList.remove('drag-over'); if (e.dataTransfer.files && e.dataTransfer.files[0]) { loadFile(e.dataTransfer.files[0]); } }); function loadFile(file) { if (!file.type.match(/image\//)) { toast('请上传图片文件', 'error'); return; } var reader = new FileReader(); reader.onload = function(ev) { var img = new Image(); img.onload = function() { originalImage = img; cachedMask = null; showPreview(img); processBtn.disabled = false; toast('图片加载成功', 'success'); doProcess(false); }; img.onerror = function() { toast('图片加载失败', 'error'); }; img.src = ev.target.result; }; reader.readAsDataURL(file); } function showPreview(img) { // 清除旧预览 var old = uploadZone.querySelector('.image-container'); if (old) old.remove(); var iconEl = uploadZone.querySelector('.upload-icon'); var textEl = uploadZone.querySelector('.upload-text'); var hintEl = uploadZone.querySelector('.upload-hint'); uploadZone.classList.add('has-image'); if (iconEl) iconEl.style.display = 'none'; if (textEl) textEl.style.display = 'none'; if (hintEl) hintEl.style.display = 'none'; var container = document.createElement('div'); container.className = 'image-container'; var pImg = document.createElement('img'); pImg.className = 'preview-image'; pImg.src = img.src; pImg.alt = '原始照片'; var overlay = document.createElement('div'); overlay.className = 'image-actions-overlay'; var delBtn = document.createElement('button'); delBtn.className = 'btn-icon-sm'; delBtn.innerHTML = '✕'; delBtn.title = '移除图片'; delBtn.addEventListener('click', function(ev) { ev.stopPropagation(); resetAll(); }); overlay.appendChild(delBtn); container.appendChild(pImg); container.appendChild(overlay); uploadZone.appendChild(container); } // ==================== 核心算法 ==================== function doProcess(silent) { if (!originalImage) return; var tolerance = parseInt(tolSlider.value); var featherR = parseFloat(feaSlider.value); var brightness = parseInt(briSlider.value); var target = hexToRgb(currentColor); var srcW = originalImage.naturalWidth; var srcH = originalImage.naturalHeight; // 画到canvas var canvas = document.createElement('canvas'); canvas.width = srcW; canvas.height = srcH; var ctx = canvas.getContext('2d'); ctx.drawImage(originalImage, 0, 0); var imgData = ctx.getImageData(0, 0, srcW, srcH); var data = imgData.data; var w = srcW; var h = srcH; // 步骤1:建立遮罩(仅首次或容差改变时) if (cachedMask === null) { // 采样边缘 var samples = []; var step = Math.max(1, Math.floor(Math.min(w, h) / 30)); var x, y; for (x = 0; x < w; x += step) { samples.push(getPixel(data, w, x, 0)); samples.push(getPixel(data, w, x, h - 1)); } for (y = 0; y < h; y += step) { samples.push(getPixel(data, w, 0, y)); samples.push(getPixel(data, w, w - 1, y)); } // 中位数 samples.sort(function(a, b) { return (a.r + a.g + a.b) - (b.r + b.g + b.b); }); var mid = samples[Math.floor(samples.length / 2)]; var bgR = mid.r; var bgG = mid.g; var bgB = mid.b; // 颜色距离 var mask = new Uint8Array(w * h); var tolSq = tolerance * tolerance; var i; for (i = 0; i < w * h; i++) { var pi = i * 4; var dr = data[pi] - bgR; var dg = data[pi + 1] - bgG; var db = data[pi + 2] - bgB; if (dr * dr + dg * dg + db * db <= tolSq) { mask[i] = 1; } } // Flood fill 从边缘连通 var visited = new Uint8Array(w * h); var queue = []; var head = 0; var x2, y2; for (x2 = 0; x2 < w; x2++) { if (mask[x2] === 1 && !visited[x2]) { visited[x2] = 1; queue.push(x2); } var bIdx = (h - 1) * w + x2; if (mask[bIdx] === 1 && !visited[bIdx]) { visited[bIdx] = 1; queue.push(bIdx); } } for (y2 = 0; y2 < h; y2++) { var lIdx = y2 * w; if (mask[lIdx] === 1 && !visited[lIdx]) { visited[lIdx] = 1; queue.push(lIdx); } var rIdx = y2 * w + (w - 1); if (mask[rIdx] === 1 && !visited[rIdx]) { visited[rIdx] = 1; queue.push(rIdx); } } while (head < queue.length) { var qIdx = queue[head++]; var qx = qIdx % w; var qy = Math.floor(qIdx / w); var nb = []; if (qx > 0) nb.push(qIdx - 1); if (qx < w - 1) nb.push(qIdx + 1); if (qy > 0) nb.push(qIdx - w); if (qy < h - 1) nb.push(qIdx + w); for (var ni = 0; ni < nb.length; ni++) { var nIdx = nb[ni]; if (mask[nIdx] === 1 && !visited[nIdx]) { visited[nIdx] = 1; queue.push(nIdx); } } } cachedMask = visited; } // 步骤2:从原始图重新着色 var origCanvas = document.createElement('canvas'); origCanvas.width = w; origCanvas.height = h; var origCtx = origCanvas.getContext('2d'); origCtx.drawImage(originalImage, 0, 0); var origData = origCtx.getImageData(0, 0, w, h).data; var work = new Uint8ClampedArray(origData.length); var k; for (k = 0; k < origData.length; k++) { work[k] = origData[k]; } var featherPx = Math.round(featherR); var j; for (j = 0; j < work.length; j += 4) { var idx = j / 4; if (cachedMask[idx] === 1) { var alpha = 1; if (featherPx > 0) { alpha = getFeather(cachedMask, idx, w, h, featherPx); } work[j] = Math.round(work[j] * (1 - alpha) + target.r * alpha); work[j + 1] = Math.round(work[j + 1] * (1 - alpha) + target.g * alpha); work[j + 2] = Math.round(work[j + 2] * (1 - alpha) + target.b * alpha); } } // 亮度 if (brightness !== 0) { var bj; for (bj = 0; bj < work.length; bj += 4) { work[bj] = clamp(work[bj] + brightness, 0, 255); work[bj + 1] = clamp(work[bj + 1] + brightness, 0, 255); work[bj + 2] = clamp(work[bj + 2] + brightness, 0, 255); } } var resultID = new ImageData(work, w, h); var workCanvas = document.createElement('canvas'); workCanvas.width = w; workCanvas.height = h; var workCtx = workCanvas.getContext('2d'); workCtx.putImageData(resultID, 0, 0); // 裁剪 var finalCanvas = cropCanvas(workCanvas, currentSize.w, currentSize.h); resultDataURL = finalCanvas.toDataURL('image/png'); resultImage.src = resultDataURL; resultImage.style.display = 'block'; resultPH.style.display = 'none'; downloadBtn.disabled = false; if (!silent) { toast('换底完成!', 'success'); } } function getPixel(data, w, x, y) { var i = (y * w + x) * 4; return { r: data[i], g: data[i + 1], b: data[i + 2] }; } function getFeather(mask, idx, w, h, radius) { var y = Math.floor(idx / w); var x = idx % w; var fg = 0; var total = 0; var dy, dx; for (dy = -radius; dy <= radius; dy++) { for (dx = -radius; dx <= radius; dx++) { var nx = x + dx; var ny = y + dy; if (nx >= 0 && nx < w && ny >= 0 && ny < h) { total++; if (mask[ny * w + nx] === 0) fg++; } } } if (total === 0) return 1; var ratio = fg / total; if (ratio > 0.6) return 0; if (ratio > 0.3) return 0.5; return 1; } function cropCanvas(srcCanvas, tw, th) { var sw = srcCanvas.width; var sh = srcCanvas.height; var sa = sw / sh; var ta = tw / th; var cw, ch, ox, oy; if (sa > ta) { ch = sh; cw = Math.round(sh * ta); ox = Math.round((sw - cw) / 2); oy = 0; } else { cw = sw; ch = Math.round(sw / ta); ox = 0; oy = Math.round((sh - ch) / 2); } var fc = document.createElement('canvas'); fc.width = tw; fc.height = th; var fctx = fc.getContext('2d'); fctx.imageSmoothingEnabled = true; fctx.imageSmoothingQuality = 'high'; fctx.drawImage(srcCanvas, ox, oy, cw, ch, 0, 0, tw, th); return fc; } function clamp(v, min, max) { return Math.max(min, Math.min(max, v)); } // ==================== 按钮 ==================== processBtn.addEventListener('click', function() { processBtn.disabled = true; processBtn.innerHTML = '<span class="spinner"></span> 处理中...'; cachedMask = null; setTimeout(function() { try { doProcess(false); } catch(err) { console.error(err); toast('处理出错: ' + err.message, 'error'); } processBtn.disabled = false; processBtn.innerHTML = '🎯 开始换底'; }, 30); }); resetBtn.addEventListener('click', resetAll); downloadBtn.addEventListener('click', function() { if (!resultDataURL) return; var a = document.createElement('a'); a.download = '证件照_' + currentSize.name + '_' + Date.now() + '.png'; a.href = resultDataURL; document.body.appendChild(a); a.click(); document.body.removeChild(a); toast('下载中', 'success'); }); function resetAll() { originalImage = null; resultDataURL = null; cachedMask = null; if (autoTimer) clearTimeout(autoTimer); resultImage.style.display = 'none'; resultImage.src = ''; resultPH.style.display = ''; downloadBtn.disabled = true; processBtn.disabled = true; processBtn.innerHTML = '🎯 开始换底'; uploadZone.classList.remove('has-image'); var old = uploadZone.querySelector('.image-container'); if (old) old.remove(); var iconEl = uploadZone.querySelector('.upload-icon'); var textEl = uploadZone.querySelector('.upload-text'); var hintEl = uploadZone.querySelector('.upload-hint'); if (iconEl) iconEl.style.display = ''; if (textEl) textEl.style.display = ''; if (hintEl) hintEl.style.display = ''; fileInput.value = ''; tolSlider.value = 50; tolVal.textContent = '50'; feaSlider.value = 1; feaVal.textContent = '1'; briSlider.value = 0; briVal.textContent = '0'; currentColor = '#FF0000'; colorPicker.value = '#FF0000'; hexInput.value = 'FF0000'; hexInput.className = 'hex-input valid'; colorDot.style.background = '#FF0000'; var all = document.querySelectorAll('.color-preset'); for (var i = 0; i < all.length; i++) all[i].classList.remove('active'); var red = document.querySelector('.color-preset[data-color="#FF0000"]'); if (red) red.classList.add('active'); var allS = document.querySelectorAll('.size-preset'); for (var j = 0; j < allS.length; j++) allS[j].classList.remove('active'); var yc = document.querySelector('.size-preset[data-name="一寸"]'); if (yc) yc.classList.add('active'); currentSize = { w: 295, h: 413, name: '一寸' }; } // ==================== 快捷键 ==================== document.addEventListener('keydown', function(e) { if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') { e.preventDefault(); if (!processBtn.disabled) processBtn.click(); } if ((e.ctrlKey || e.metaKey) && e.key === 's') { e.preventDefault(); if (!downloadBtn.disabled) downloadBtn.click(); } }); console.log('证件照换底工具已就绪 - 离线处理 - 实时预览'); })(); </script> </body> </html> 2 个帖子 - 1 位参与者 阅读完整话题