免费 GPT4o生成图片自动矫正真实色彩

  • 主题发起人 主题发起人 Scare
  • 开始时间 开始时间

Scare

0xFF|主权幽灵
07
908
172
奇源币
0
管理成员
工作人员
版主
VIP



image
image1347×740 144 KB


image
image1067×512 126 KB

算法全靠论坛o3,调试一下感觉还不错,
有兴趣的可以来试试
代码如下:


<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>动漫图像自动色偏校正(对比版)</title>
<style>
.container{max-width:900px;margin:0 auto;padding:20px;}
.compare{display:flex;gap:20px;flex-wrap:wrap;}
figure{margin:0;max-width:48%;}
figcaption{text-align:center;font-size:14px;margin-bottom:6px;}
img,canvas{max-width:100%;display:block;}
/* 处理用画布默认隐藏,降级模式会改成显示 */
#work{display:none;}
.controls{margin:22px 0;}
.slider-container{margin:10px 0;}
</style>
</head>
<body>
<div class="container">
<h2>自动色偏校正(支持对比)</h2>

<div class="compare">
<figure>
<figcaption>原始图</figcaption>
<img id="origImg">
</figure>

<figure>
<figcaption>校正图</figcaption>
<!-- 两个备选:正常情况用 <img>,跨域降级用 <canvas> -->
<img id="corrImg" style="display:none;">
<canvas id="work"></canvas>
</figure>
</div>

<div class="controls">
<input type="file" id="fileInput" accept="image/*">
<button id="pasteBtn">粘贴图片</button>

<div class="slider-container">
<label>色温:</label>
<input id="temperature" type="range" min="-100" max="100" value="0">
<span id="tVal">0</span>
</div>
<div class="slider-container">
<label>饱和度:</label>
<input id="saturation" type="range" min="0" max="200" value="100">
<span id="sVal">100</span>
</div>
<div class="slider-container">
<label>对比度:</label>
<input id="contrast" type="range" min="0" max="200" value="100">
<span id="cVal">100</span>
</div>

<button id="autoBtn">自动还原真实色彩</button>
<button id="resetBtn">重置</button>
</div>
</div>

<script>
/* ---------- DOM ---------- */
const origImg = document.getElementById('origImg');
const corrImg = document.getElementById('corrImg'); // 成功导出 dataURL 时使用
const workCv = document.getElementById('work'); // 既做处理也可降级展示
const ctx = workCv.getContext('2d');

const tSlider = document.getElementById('temperature');
const sSlider = document.getElementById('saturation');
const cSlider = document.getElementById('contrast');
const tVal = document.getElementById('tVal');
const sVal = document.getElementById('sVal');
const cVal = document.getElementById('cVal');

let originalURL = ''; // 原图 URL(blob 或网络地址)

/* ---------- 事件 ---------- */
document.getElementById('fileInput').addEventListener('change', e=>{
originalURL = URL.createObjectURL(e.target.files[0]);
loadOriginal(originalURL);
});
document.getElementById('pasteBtn').addEventListener('click', pasteImage);
document.getElementById('autoBtn').addEventListener('click', autoBalance);
document.getElementById('resetBtn').addEventListener('click', ()=>reset(false));
[tSlider,sSlider,cSlider].forEach(el=>el.addEventListener('input', applyFilter));

/* ---------- 加载原图 ---------- */
function loadOriginal(url){
origImg.onload = ()=>reset(false);
/* 尝试 anonymous,若服务器支持 CORS 可避免“画布污染” */
origImg.crossOrigin = 'anonymous';
origImg.src = url;
}

/* ---------- 粘贴 ---------- */
async function pasteImage(){
try{
const items = await navigator.clipboard.read();
for(const it of items){
for(const tp of it.types){
if(tp.startsWith('image/')){
const blob = await it.getType(tp);
originalURL = URL.createObjectURL(blob);
loadOriginal(originalURL);
return;
}
}
}
alert('剪贴板里没有图片');
}catch(e){ console.error(e); }
}

/* ---------- 自动 LAB 校正 ---------- */
function autoBalance(){
if(!origImg.complete || origImg.naturalWidth===0){
alert('请先加载图片');return;
}

/* 把原图画进画布(适当缩放加速) */
const maxSide = 600;
const scale = Math.min(1, maxSide/Math.max(origImg.naturalWidth,origImg.naturalHeight));
workCv.width = Math.round(origImg.naturalWidth * scale);
workCv.height = Math.round(origImg.naturalHeight* scale);
ctx.clearRect(0,0,workCv.width,workCv.height);
ctx.drawImage(origImg,0,0,workCv.width,workCv.height);

const imgData = ctx.getImageData(0,0,workCv.width,workCv.height);
const data = imgData.data;
const N = data.length/4;

/* 求 a,b 平均值 */
let aSum=0,bSum=0;
for(let i=0;i<data.length;i+=4){
const [ ,a,b] = rgb2lab(data,data[i+1],data[i+2]);
aSum+=a; bSum+=b;
}
const aAvg=aSum/N, bAvg=bSum/N, k=1; // k 可调节强度

/* 校正 */
for(let i=0;i<data.length;i+=4){
let [L,a,b] = rgb2lab(data,data[i+1],data[i+2]);
a -= k*aAvg;
b -= k*bAvg;
const [r,g,bl] = lab2rgb(L,a,b);
data=r; data[i+1]=g; data[i+2]=bl;
}
ctx.putImageData(imgData,0,0);

/* 尝试导出 PNG;若失败就直接把 canvas 显示出来 */
try{
const url = workCv.toDataURL('image/png');
corrImg.src = url;
corrImg.style.display='block';
workCv.style.display='none';
}catch(err){
console.warn('跨域图片,已改为显示 canvas(无法导出 dataURL)');
corrImg.style.display='none';
workCv.style.display='block'; // 直接把校正后的画布当图用
}

reset(true); // 清滑块但保留两张图
}

/* ---------- 滤镜 ---------- */
function applyFilter(){
const t=+tSlider.value, s=+sSlider.value, c=+cSlider.value;
[tVal.textContent,sVal.textContent,cVal.textContent] = [t,s,c];
const target = (corrImg.style.display!=='none') ? corrImg :
(workCv.style.display!=='none') ? workCv : origImg;

const tFilter = t<0 ? `sepia(${Math.abs(t)/100}) hue-rotate(180deg)`
: `sepia(${t/100})`;
target.style.filter = `${tFilter} saturate(${s}%) contrast(${c}%)`;
}

/* ---------- 重置 ---------- */
function reset(keepCorr){
[tSlider.value,sSlider.value,cSlider.value] = [0,100,100];
[tVal.textContent,sVal.textContent,cVal.textContent] = [0,100,100];
[origImg,corrImg,workCv].forEach(el=>el.style.filter='');

if(!keepCorr){
corrImg.style.display='none';
workCv.style.display='none';
corrImg.src='';
}
}

/* ======================================================
sRGB ↔ LAB 转换
====================================================== */
const M = { // 参考白点 D65
xn:0.95047, yn:1, zn:1.08883
};
function srgb2lin(x){ x/=255; return (x<=0.04045)? x/12.92 : Math.pow((x+0.055)/1.055,2.4);}
function lin2srgb(x){ x=Math.max(0,Math.min(1,x)); const v=(x<=0.0031308)?12.92*x:1.055*Math.pow(x,1/2.4)-0.055; return Math.round(v*255);}
function rgb2xyz(r,g,b){
r=srgb2lin(r); g=srgb2lin(g); b=srgb2lin(b);
return [
0.4124*r+0.3576*g+0.1805*b,
0.2126*r+0.7152*g+0.0722*b,
0.0193*r+0.1192*g+0.9505*b
];
}
function xyz2rgb(x,y,z){
const r= 3.2406*x-1.5372*y-0.4986*z;
const g=-0.9689*x+1.8758*y+0.0415*z;
const b= 0.0557*x-0.2040*y+1.0570*z;
return [lin2srgb(r),lin2srgb(g),lin2srgb(b)];
}
function xyz2lab(x,y,z){
const {xn,yn,zn}=M;
const f=t=> (t>0.008856)? Math.cbrt(t) : (7.787*t+16/116);
const fx=f(x/xn), fy=f(y/yn), fz=f(z/zn);
return [116*fy-16, 500*(fx-fy), 200*(fy-fz)];
}
function lab2xyz(L,a,b){
const {xn,yn,zn}=M;
const fy=(L+16)/116, fx=a/500+fy, fz=fy-b/200;
const fInv=t=> {const t3=t*t*t; return (t3>0.008856)? t3 : (t-16/116)/7.787;};
return [xn*fInv(fx), yn*fInv(fy), zn*fInv(fz)];
}
function rgb2lab(r,g,b){return xyz2lab(...rgb2xyz(r,g,b));}
function lab2rgb(L,a,b){return xyz2rgb(...lab2xyz(L,a,b));}
</script>
</body>
</html>
 
后退
顶部