mirror of
https://github.com/itorr/one-last-image.git
synced 2026-01-02 11:56:02 +08:00
hw
This commit is contained in:
104
html/bezier-easing.js
Normal file
104
html/bezier-easing.js
Normal file
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* https://github.com/gre/bezier-easing
|
||||
* BezierEasing - use bezier curve for transition easing function
|
||||
* by Gaëtan Renaudeau 2014 - 2015 – MIT License
|
||||
*/
|
||||
|
||||
// These values are established by empiricism with tests (tradeoff: performance VS precision)
|
||||
var NEWTON_ITERATIONS = 4;
|
||||
var NEWTON_MIN_SLOPE = 0.001;
|
||||
var SUBDIVISION_PRECISION = 0.0000001;
|
||||
var SUBDIVISION_MAX_ITERATIONS = 10;
|
||||
|
||||
var kSplineTableSize = 11;
|
||||
var kSampleStepSize = 1.0 / (kSplineTableSize - 1.0);
|
||||
|
||||
var float32ArraySupported = typeof Float32Array === 'function';
|
||||
|
||||
function A (aA1, aA2) { return 1.0 - 3.0 * aA2 + 3.0 * aA1; }
|
||||
function B (aA1, aA2) { return 3.0 * aA2 - 6.0 * aA1; }
|
||||
function C (aA1) { return 3.0 * aA1; }
|
||||
|
||||
// Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2.
|
||||
function calcBezier (aT, aA1, aA2) { return ((A(aA1, aA2) * aT + B(aA1, aA2)) * aT + C(aA1)) * aT; }
|
||||
|
||||
// Returns dx/dt given t, x1, and x2, or dy/dt given t, y1, and y2.
|
||||
function getSlope (aT, aA1, aA2) { return 3.0 * A(aA1, aA2) * aT * aT + 2.0 * B(aA1, aA2) * aT + C(aA1); }
|
||||
|
||||
function binarySubdivide (aX, aA, aB, mX1, mX2) {
|
||||
var currentX, currentT, i = 0;
|
||||
do {
|
||||
currentT = aA + (aB - aA) / 2.0;
|
||||
currentX = calcBezier(currentT, mX1, mX2) - aX;
|
||||
if (currentX > 0.0) {
|
||||
aB = currentT;
|
||||
} else {
|
||||
aA = currentT;
|
||||
}
|
||||
} while (Math.abs(currentX) > SUBDIVISION_PRECISION && ++i < SUBDIVISION_MAX_ITERATIONS);
|
||||
return currentT;
|
||||
}
|
||||
|
||||
function newtonRaphsonIterate (aX, aGuessT, mX1, mX2) {
|
||||
for (var i = 0; i < NEWTON_ITERATIONS; ++i) {
|
||||
var currentSlope = getSlope(aGuessT, mX1, mX2);
|
||||
if (currentSlope === 0.0) {
|
||||
return aGuessT;
|
||||
}
|
||||
var currentX = calcBezier(aGuessT, mX1, mX2) - aX;
|
||||
aGuessT -= currentX / currentSlope;
|
||||
}
|
||||
return aGuessT;
|
||||
}
|
||||
|
||||
function LinearEasing (x) {
|
||||
return x;
|
||||
}
|
||||
|
||||
function bezier (mX1, mY1, mX2, mY2) {
|
||||
if (!(0 <= mX1 && mX1 <= 1 && 0 <= mX2 && mX2 <= 1)) {
|
||||
throw new Error('bezier x values must be in [0, 1] range');
|
||||
}
|
||||
|
||||
if (mX1 === mY1 && mX2 === mY2) {
|
||||
return LinearEasing;
|
||||
}
|
||||
|
||||
// Precompute samples table
|
||||
var sampleValues = float32ArraySupported ? new Float32Array(kSplineTableSize) : new Array(kSplineTableSize);
|
||||
for (var i = 0; i < kSplineTableSize; ++i) {
|
||||
sampleValues[i] = calcBezier(i * kSampleStepSize, mX1, mX2);
|
||||
}
|
||||
|
||||
function getTForX (aX) {
|
||||
var intervalStart = 0.0;
|
||||
var currentSample = 1;
|
||||
var lastSample = kSplineTableSize - 1;
|
||||
|
||||
for (; currentSample !== lastSample && sampleValues[currentSample] <= aX; ++currentSample) {
|
||||
intervalStart += kSampleStepSize;
|
||||
}
|
||||
--currentSample;
|
||||
|
||||
// Interpolate to provide an initial guess for t
|
||||
var dist = (aX - sampleValues[currentSample]) / (sampleValues[currentSample + 1] - sampleValues[currentSample]);
|
||||
var guessForT = intervalStart + dist * kSampleStepSize;
|
||||
|
||||
var initialSlope = getSlope(guessForT, mX1, mX2);
|
||||
if (initialSlope >= NEWTON_MIN_SLOPE) {
|
||||
return newtonRaphsonIterate(aX, guessForT, mX1, mX2);
|
||||
} else if (initialSlope === 0.0) {
|
||||
return guessForT;
|
||||
} else {
|
||||
return binarySubdivide(aX, intervalStart, intervalStart + kSampleStepSize, mX1, mX2);
|
||||
}
|
||||
}
|
||||
|
||||
return function BezierEasing (x) {
|
||||
// Because JavaScript number are imprecise, we should guarantee the extremes are right.
|
||||
if (x === 0 || x === 1) {
|
||||
return x;
|
||||
}
|
||||
return calcBezier(getTForX(x), mY1, mY2);
|
||||
};
|
||||
};
|
||||
111
html/color.js
Normal file
111
html/color.js
Normal file
@@ -0,0 +1,111 @@
|
||||
|
||||
|
||||
const 转换十进制=function(num){
|
||||
return parseInt(num,16)
|
||||
};
|
||||
const 十六进制转换十进制=function(color){
|
||||
if(!color)
|
||||
color='EEEEEE'
|
||||
return color.match(/\w\w/g).map(转换十进制)
|
||||
};
|
||||
const 转换十六进制=function(num){
|
||||
num=num.toString(16);
|
||||
return num.length==2?num:('0'+num)
|
||||
};
|
||||
const 十进制颜色转换十六进制=function(arr){
|
||||
return arr.map(转换十六进制).join('');
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
const rgb2hsl=function(o){
|
||||
var
|
||||
r=o[0],
|
||||
g=o[1],
|
||||
b=o[2];
|
||||
|
||||
r /= 255, g /= 255, b /= 255;
|
||||
var
|
||||
max = Math.max(r, g, b),
|
||||
min = Math.min(r, g, b);
|
||||
var
|
||||
h,
|
||||
s,
|
||||
l = (max + min) / 2;
|
||||
|
||||
if(max == min){
|
||||
h = s = 0; // achromatic
|
||||
}else{
|
||||
var d = max - min;
|
||||
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
||||
switch(max){
|
||||
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
|
||||
case g: h = (b - r) / d + 2; break;
|
||||
case b: h = (r - g) / d + 4; break;
|
||||
}
|
||||
h /= 6;
|
||||
//h *= 60;
|
||||
}
|
||||
|
||||
// h=Math.round(h*14);
|
||||
// s=Math.round(s*7);
|
||||
// l=Math.round(l*7);
|
||||
|
||||
|
||||
return [h, s, l];
|
||||
};
|
||||
const hsl2rgb=function(hsl) {
|
||||
// const hsl = /hsl\((\d+),\s*([\d.]+)%,\s*([\d.]+)%\)/g.exec(hslValue);
|
||||
var h = hsl[0];// / 360;
|
||||
var s = hsl[1];// / 100;
|
||||
var l = hsl[2];// / 100;
|
||||
// console.log(h,s,l);
|
||||
function hue2rgb(p, q, t) {
|
||||
if (t < 0) t += 1;
|
||||
if (t > 1) t -= 1;
|
||||
if (t < 1/6) return p + (q - p) * 6 * t;
|
||||
if (t < 1/2) return q;
|
||||
if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
|
||||
return p;
|
||||
}
|
||||
var r, g, b;
|
||||
if (s == 0) {
|
||||
r = g = b = l;
|
||||
} else {
|
||||
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
||||
var p = 2 * l - q;
|
||||
r = hue2rgb(p, q, h + 1/3);
|
||||
g = hue2rgb(p, q, h);
|
||||
b = hue2rgb(p, q, h - 1/3);
|
||||
}
|
||||
return [
|
||||
Math.round(r*255),
|
||||
Math.round(g*255),
|
||||
Math.round(b*255)
|
||||
]
|
||||
// return `rgb(${r * 255},${g * 255},${b * 255})`;
|
||||
};
|
||||
|
||||
const hax2burn=function(hax,burn){
|
||||
const hsl=rgb2hsl(十六进制转换十进制(hax))
|
||||
|
||||
hsl[1] = hsl[1]/5 + 0.6
|
||||
hsl[2]= burn
|
||||
// console.log(hsl)
|
||||
return 十进制颜色转换十六进制(hsl2rgb(hsl))
|
||||
};
|
||||
const hax2light=function(hax,l){
|
||||
const hsl=rgb2hsl(十六进制转换十进制(hax))
|
||||
|
||||
hsl[2]= l
|
||||
// console.log(hsl)
|
||||
return 十进制颜色转换十六进制(hsl2rgb(hsl))
|
||||
};
|
||||
|
||||
|
||||
// export {
|
||||
// 十六进制转换十进制,
|
||||
// hax2burn,
|
||||
// hax2light
|
||||
// }
|
||||
229
html/document.js
Normal file
229
html/document.js
Normal file
@@ -0,0 +1,229 @@
|
||||
|
||||
const readFileToURL = (file,onOver)=>{
|
||||
var reader = new FileReader();
|
||||
reader.onload = ()=>{
|
||||
const src = reader.result;
|
||||
onOver(src);
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
};
|
||||
|
||||
const readFileAndSetIMGSrc = file=>{
|
||||
readFileToURL(file,src=>{
|
||||
// const img = new Image();
|
||||
const img = document.querySelector('img')
|
||||
img.src = src;
|
||||
img.onload = ()=>{
|
||||
app.img = img;
|
||||
louvre(img,app.style, src=>{
|
||||
app.src = src;
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const isImageRegex = /^image\/(jpeg|gif|png|bmp|webp)$/;
|
||||
|
||||
document.addEventListener('paste',e=>{
|
||||
// console.log(e.clipboardData,e.clipboardData.files);
|
||||
|
||||
const clipboardData = e.clipboardData;
|
||||
if(clipboardData.items[0]){
|
||||
let file = clipboardData.items[0].getAsFile();
|
||||
|
||||
if(file && isImageRegex.test(file.type)){
|
||||
return readFileAndSetIMGSrc(file);
|
||||
}
|
||||
}
|
||||
|
||||
if(clipboardData.files.length){
|
||||
for(let i = 0;i<clipboardData.files.length;i++){
|
||||
if(isImageRegex.test(clipboardData.files[i].type)){
|
||||
console.log(clipboardData.files[i])
|
||||
readFileAndSetIMGSrc(clipboardData.files[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('dragover',e=>{
|
||||
e.preventDefault();
|
||||
});
|
||||
document.addEventListener('drop',e=>{
|
||||
e.preventDefault();
|
||||
|
||||
const file = e.dataTransfer.files[0];
|
||||
|
||||
if(file && file.type.match(isImageRegex)){
|
||||
readFileAndSetIMGSrc(file);
|
||||
}
|
||||
});
|
||||
|
||||
const _louvre = (img,style,callback)=>{
|
||||
|
||||
clearTimeout(louvre.T);
|
||||
louvre.T = setTimeout(()=>{
|
||||
louvre(img,style,callback);
|
||||
app.saveData();
|
||||
},100);
|
||||
};
|
||||
|
||||
const deepCopy = o=>JSON.parse(JSON.stringify(o));
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const creatConvoluteCenterHigh = (w,centerV)=>{
|
||||
const arr = [];
|
||||
const c = Math.floor((w*w)/2);
|
||||
|
||||
for(let x = 0; x < w; x++){
|
||||
for(let y = 0; y < w; y++){
|
||||
let i = x * w + y;
|
||||
arr[i] = -1;
|
||||
|
||||
if(i===c){
|
||||
arr[i] = centerV;
|
||||
}
|
||||
}
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
const creatConvoluteAverage = (w)=>new Array(w*w).fill(1/(w*w))
|
||||
|
||||
|
||||
const Convolutes = {
|
||||
// '右倾': [
|
||||
// 0, -1, 0,
|
||||
// -1, 2, 2,
|
||||
// 0, -1, 0
|
||||
// ],
|
||||
// '左倾': [
|
||||
// 0, -1, 0,
|
||||
// 3, 2, -2,
|
||||
// 0, -1, 0
|
||||
// ],
|
||||
'极精细': creatConvoluteAverage(3),
|
||||
'精细': creatConvoluteAverage(5),
|
||||
'一般': creatConvoluteAverage(7),
|
||||
'粗线条': creatConvoluteAverage(9),
|
||||
'超粗': creatConvoluteAverage(11),
|
||||
// '12421': [
|
||||
// -3,2,-3,
|
||||
// 2,4, 2,
|
||||
// -3,2,-3,
|
||||
// ],
|
||||
// '9,-1,8': [
|
||||
// -1 ,-1 ,-1 ,
|
||||
// -1 , 9 ,-1 ,
|
||||
// -1 ,-1 ,-1 ,
|
||||
// ],
|
||||
'25,-1,24':creatConvoluteCenterHigh(5,24),
|
||||
// '25,-1,25': creatConvoluteCenterHigh(5,25),
|
||||
// '25,-1,26': [
|
||||
// -1 , -1 , -1 , -1 , -1 ,
|
||||
// -1 , -1 , -1 , -1 , -1 ,
|
||||
// -1 , -1 , 26 , -1 , -1 ,
|
||||
// -1 , -1 , -1 , -1 , -1 ,
|
||||
// -1 , -1 , -1 , -1 , -1 ,
|
||||
// ],
|
||||
// '-1,0,16': [
|
||||
// -1 , -1 , -1 , -1 , -1 ,
|
||||
// -1 , 0 , 0 , 0 , -1 ,
|
||||
// -1 , 0 , 17 , 0 , -1 ,
|
||||
// -1 , 0 , 0 , 0 , -1 ,
|
||||
// -1 , -1 , -1 , -1 , -1 ,
|
||||
// ],
|
||||
'浮雕': [
|
||||
1, 1, 1,
|
||||
1, 1, -1,
|
||||
-1, -1, -1
|
||||
]
|
||||
}
|
||||
|
||||
const style = {
|
||||
zoom:1.3,
|
||||
light:0,
|
||||
s:80,
|
||||
l:50,
|
||||
black:true,
|
||||
convoluteName: '一般',
|
||||
convolute1Diff: true,
|
||||
convoluteName2: null,
|
||||
Convolutes,
|
||||
contrast: 30,
|
||||
invertLight: false,
|
||||
hue:false,
|
||||
hueGroup:255,
|
||||
lightGroup:1,
|
||||
lightCut: 128,
|
||||
darkCut: 120,
|
||||
};
|
||||
|
||||
|
||||
|
||||
const data = {
|
||||
img:null,
|
||||
style
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const chooseFile = callback=>{
|
||||
chooseFile.form.reset();
|
||||
chooseFile.input.onchange = function(){
|
||||
if(!this.files||!this.files[0])return;
|
||||
callback(this.files[0]);
|
||||
};
|
||||
chooseFile.input.click();
|
||||
};
|
||||
chooseFile.form = document.createElement('form');
|
||||
chooseFile.input = document.createElement('input');
|
||||
chooseFile.input.type = 'file';
|
||||
chooseFile.form.appendChild(chooseFile.input);
|
||||
|
||||
|
||||
|
||||
const app = new Vue({
|
||||
el:'.app',
|
||||
data,
|
||||
methods: {
|
||||
_louvre(){
|
||||
clearTimeout(app.T)
|
||||
app.T = setTimeout(this.louvre,300)
|
||||
},
|
||||
louvre(){
|
||||
app.src = louvre(this.img,{
|
||||
...this.style,
|
||||
Convolutes,
|
||||
});
|
||||
},
|
||||
setImageAndDraw(e){
|
||||
let img = e.target;
|
||||
|
||||
console.log(img);
|
||||
this.img = img;
|
||||
this.louvre();
|
||||
},
|
||||
output(){
|
||||
const a = document.createElement('a');
|
||||
a.href = this.src;
|
||||
a.download = `[lab.magiconch.com][90s-time-machine]-${+Date.now()}.jpg`;
|
||||
a.click();
|
||||
},
|
||||
chooseFile(){
|
||||
chooseFile(readFileAndSetIMGSrc)
|
||||
},
|
||||
},
|
||||
watch:{
|
||||
style:{
|
||||
deep:true,
|
||||
handler(){
|
||||
this._louvre();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
BIN
html/images/asuka.jpg
Normal file
BIN
html/images/asuka.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 152 KiB |
120
html/index.html
Normal file
120
html/index.html
Normal file
@@ -0,0 +1,120 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>卷积</title>
|
||||
<style>
|
||||
canvas{
|
||||
background:#FFF;
|
||||
}
|
||||
</style>
|
||||
<meta name="viewport" content="width=device-width,user-scalable=0">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="app">
|
||||
<div class="ctrl-box">
|
||||
<img src="images/Evangelion.3.0+1.01.Thrice.Upon.a.Time.2021.1080p.AMZN.WEB-DL.DD+.5.1.H.264-RMB-0025.jpg" @load="setImageAndDraw">
|
||||
|
||||
|
||||
<div class="label-box" style="padding:4px 0;">
|
||||
<b>light</b>
|
||||
<input type="range" v-model.number="style.light"
|
||||
min="-100" max="100" step=".1">
|
||||
<span v-text="style.light"></span>
|
||||
</div>
|
||||
|
||||
<div class="label-box" style="padding:4px 0;">
|
||||
<b>s</b>
|
||||
<input type="range" v-model.number="style.s"
|
||||
min="0" max="100" step="1">
|
||||
<span v-text="style.s"></span>
|
||||
</div>
|
||||
<div class="label-box" style="padding:4px 0;">
|
||||
<b>l</b>
|
||||
<input type="range" v-model.number="style.l"
|
||||
min="0" max="100" step="1">
|
||||
<span v-text="style.l"></span>
|
||||
</div>
|
||||
<div class="label-box" style="padding:10px 0;">
|
||||
<b>线条方案1</b>
|
||||
<label>
|
||||
<input type="radio" v-model="style.convoluteName" :value="null">null
|
||||
</label>
|
||||
<label v-for="con,convoluteName in Convolutes">
|
||||
<input type="radio" v-model="style.convoluteName" :value="convoluteName">{{convoluteName}}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- <div class="label-box" style="padding:4px 0;">
|
||||
<label>
|
||||
<input type="checkbox" v-model="style.convolute1Diff">卷曲diff
|
||||
</label>
|
||||
</div> -->
|
||||
|
||||
<div class="label-box" style="padding:4px 0;">
|
||||
<label>
|
||||
<input type="checkbox" v-model="style.black">铺调子
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- <div class="label-box" style="padding:4px 0;">
|
||||
<b>contrast</b>
|
||||
<input type="range" v-model.number="style.contrast"
|
||||
min="1" max="100" step=".1">
|
||||
<span v-text="style.contrast"></span>
|
||||
</div> -->
|
||||
|
||||
<!-- <div class="label-box" style="padding:10px 0;">
|
||||
<b>线条方案2</b>
|
||||
<label>
|
||||
<input type="radio" v-model="style.convoluteName2" :value="null">null
|
||||
</label>
|
||||
<label v-for="con,convoluteName in Convolutes">
|
||||
<input type="radio" v-model="style.convoluteName2" :value="convoluteName">{{convoluteName}}
|
||||
</label>
|
||||
</div> -->
|
||||
|
||||
<!-- <div class="label-box" style="padding:4px 0;">
|
||||
<label>
|
||||
<input type="checkbox" v-model="style.invertLight">颠倒黑白
|
||||
</label>
|
||||
</div> -->
|
||||
<!-- <div class="label-box" style="padding:4px 0;">
|
||||
<b>明度观察层</b><input type="range" v-model.number="style.lightGroup"
|
||||
min="1" max="10" step="1">
|
||||
<span v-text="style.lightGroup"></span>
|
||||
</div> -->
|
||||
|
||||
<!-- <div class="label-box" style="padding:4px 0;">
|
||||
<b>高亮切断</b>
|
||||
<input type="range" v-model.number="style.lightCut"
|
||||
min="0" max="128" step="1">
|
||||
<span v-text="style.lightCut"></span>
|
||||
</div> -->
|
||||
<div class="label-box" style="padding:4px 0;">
|
||||
<b>黑场切断</b>
|
||||
<input type="range" v-model.number="style.darkCut"
|
||||
min="0" max="127" step="1">
|
||||
<span v-text="style.darkCut"></span>
|
||||
</div>
|
||||
<!-- <div class="label-box" style="padding:4px 0;">
|
||||
<label>
|
||||
<input type="checkbox" v-model="style.hue">色轮
|
||||
</label>
|
||||
</div>
|
||||
<div class="label-box" style="padding:4px 0;">
|
||||
<b>色轮压缩</b><input type="range" v-model.number="style.hueGroup"
|
||||
min="10" max="255" step="1">
|
||||
<span v-text="style.hueGroup"></span>
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/vue.2.6.11.min.js"></script>
|
||||
<script src="color.js"></script>
|
||||
<script src="bezier-easing.js"></script>
|
||||
<script src="louvre.js"></script>
|
||||
<script src="document.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
541
html/louvre.js
Normal file
541
html/louvre.js
Normal file
@@ -0,0 +1,541 @@
|
||||
/**
|
||||
* @author itorr<https://github.com/itorr>
|
||||
* @date 2020-04-08
|
||||
* @Description 蒸汽波风格化处理
|
||||
* */
|
||||
|
||||
|
||||
|
||||
const rgb2yuv = (r, g, b) => {
|
||||
var y, u, v;
|
||||
|
||||
y = r * .299000 + g * .587000 + b * .114000;
|
||||
u = r * -.168736 + g * -.331264 + b * .500000 + 128;
|
||||
v = r * .500000 + g * -.418688 + b * -.081312 + 128;
|
||||
|
||||
y = Math.floor(y);
|
||||
u = Math.floor(u);
|
||||
v = Math.floor(v);
|
||||
|
||||
return [y, u, v];
|
||||
};
|
||||
|
||||
const yuv2rgb = (y, u, v) => {
|
||||
var r, g, b;
|
||||
|
||||
r = y + 1.4075 * (v - 128);
|
||||
g = y - 0.3455 * (u - 128) - (0.7169 * (v - 128));
|
||||
b = y + 1.7790 * (u - 128);
|
||||
|
||||
r = Math.floor(r);
|
||||
g = Math.floor(g);
|
||||
b = Math.floor(b);
|
||||
|
||||
r = (r < 0) ? 0 : r;
|
||||
r = (r > 255) ? 255 : r;
|
||||
|
||||
g = (g < 0) ? 0 : g;
|
||||
g = (g > 255) ? 255 : g;
|
||||
|
||||
b = (b < 0) ? 0 : b;
|
||||
b = (b > 255) ? 255 : b;
|
||||
|
||||
return [r, g, b];
|
||||
};
|
||||
|
||||
const randRange = (a, b) => Math.floor(Math.random() * (b - a) + a);
|
||||
|
||||
const inputImageEl = document.querySelector('#input');
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
document.body.appendChild(canvas)
|
||||
|
||||
let width = 640;
|
||||
let height = 480;
|
||||
let scale = width / height;
|
||||
|
||||
|
||||
|
||||
let lastConfigString = null;
|
||||
|
||||
|
||||
const canvasBlack = document.createElement('canvas');
|
||||
const canvasBlackMin = document.createElement('canvas');
|
||||
const canvasMin = document.createElement('canvas');
|
||||
|
||||
const louvre = (img, config, callback) => {
|
||||
if (!img || !config) return;
|
||||
|
||||
const configString = [
|
||||
JSON.stringify(config),
|
||||
img.src,
|
||||
].join('-');
|
||||
|
||||
if (lastConfigString === configString) return;
|
||||
|
||||
console.time('louvre');
|
||||
|
||||
lastConfigString = configString;
|
||||
|
||||
const oriWidth = img.naturalWidth;
|
||||
const oriHeight = img.naturalHeight;
|
||||
|
||||
let oriScale = oriWidth / oriHeight;
|
||||
|
||||
|
||||
|
||||
// const _width = Math.floor( width / config.zoom );
|
||||
// const _height = Math.floor( height / config.zoom );
|
||||
|
||||
const _width = Math.floor( oriWidth / config.zoom );
|
||||
const _height = Math.floor( oriHeight / config.zoom );
|
||||
|
||||
// const _width = 800;
|
||||
// const _height = 800;
|
||||
|
||||
|
||||
canvas.width = _width;
|
||||
canvas.height = _height;
|
||||
|
||||
let cutLeft = 0;
|
||||
let cutTop = 0;
|
||||
|
||||
let calcWidth = oriWidth;
|
||||
let calcHeight = oriHeight;
|
||||
|
||||
|
||||
// if(oriScale > 1){
|
||||
// cutLeft = (oriScale - 1) * oriHeight / 2;
|
||||
// calcWidth = oriHeight;
|
||||
// }else{
|
||||
// cutTop = (1 - oriScale) * oriHeight / 2;
|
||||
// calcHeight = oriWidth;
|
||||
// }
|
||||
|
||||
let setLeft = 0;
|
||||
let setTop = 0;
|
||||
|
||||
let setWidth = _width;
|
||||
let setHeight = _height;
|
||||
|
||||
|
||||
|
||||
|
||||
ctx.drawImage(
|
||||
img,
|
||||
cutLeft, cutTop,
|
||||
calcWidth, calcHeight,
|
||||
|
||||
setLeft, setTop,
|
||||
setWidth, setHeight
|
||||
);
|
||||
|
||||
|
||||
let pixel = ctx.getImageData(0, 0, _width, _height);
|
||||
|
||||
|
||||
|
||||
let pixelData = pixel.data;
|
||||
|
||||
|
||||
for (let i = 0; i < pixelData.length; i += 4) {
|
||||
// let yuv = rgb2yuv(
|
||||
// pixelData[i],
|
||||
// pixelData[i + 1],
|
||||
// pixelData[i + 2],
|
||||
// );
|
||||
const r = pixelData[i];
|
||||
const g = pixelData[i + 1];
|
||||
const b = pixelData[i + 2];
|
||||
|
||||
let y = r * .299000 + g * .587000 + b * .114000;
|
||||
y = Math.floor(y);
|
||||
|
||||
// if(i%10000) console.log(y);
|
||||
|
||||
pixelData[i ] = y;
|
||||
pixelData[i + 1] = y;
|
||||
pixelData[i + 2] = y;
|
||||
}
|
||||
let blackPixel;
|
||||
|
||||
if(config.black){
|
||||
// 处理暗面
|
||||
blackPixel = ctx.createImageData(_width, _height);
|
||||
|
||||
for (let i = 0; i < pixelData.length; i += 4) {
|
||||
let y = pixelData[i];
|
||||
|
||||
y = y > 80 ? 0 : (40 + Math.random() * 40 - 20);
|
||||
|
||||
// y = Math.max(255-y) * 0.6;
|
||||
|
||||
blackPixel.data[i ] = y;
|
||||
blackPixel.data[i + 1] = 128;
|
||||
blackPixel.data[i + 2] = 128;
|
||||
blackPixel.data[i + 3] = Math.floor(Math.random() * 255)//Math.ceil( y + Math.random() * 40 - 20);
|
||||
}
|
||||
|
||||
// /*
|
||||
// document.body.appendChild(canvasBlack)
|
||||
|
||||
const ctxBlack = canvasBlack.getContext('2d');
|
||||
const ctxBlackMin = canvasBlackMin.getContext('2d');
|
||||
|
||||
canvasBlack.width = _width;
|
||||
canvasBlack.height = _height;
|
||||
|
||||
console.log({blackPixel})
|
||||
|
||||
ctxBlack.putImageData(blackPixel, 0, 0);
|
||||
|
||||
// ctxBlack.fillText('123233',50,50);
|
||||
const blackZoom = 4;
|
||||
canvasBlackMin.width = Math.floor(_width/blackZoom);
|
||||
canvasBlackMin.height = Math.floor(_height/blackZoom);
|
||||
|
||||
ctxBlackMin.drawImage(
|
||||
canvasBlack,
|
||||
0,0,
|
||||
canvasBlackMin.width,canvasBlackMin.height
|
||||
);
|
||||
|
||||
ctxBlack.clearRect(0,0,_width,_height)
|
||||
ctxBlack.drawImage(
|
||||
canvasBlackMin,
|
||||
0,0,
|
||||
_width,_height
|
||||
);
|
||||
blackPixel = ctxBlack.getImageData(0,0,_width,_height);
|
||||
|
||||
}
|
||||
|
||||
const {
|
||||
light = 0,
|
||||
} = config;
|
||||
if(light){
|
||||
|
||||
|
||||
for (let i = 0; i < pixelData.length; i += 4) {
|
||||
let y = pixelData[i];
|
||||
|
||||
y = y + y * (light/100);
|
||||
|
||||
pixelData[i ] = y;
|
||||
pixelData[i + 1] = y;
|
||||
pixelData[i + 2] = y;
|
||||
}
|
||||
|
||||
// ctx.putImageData(pixel, 0, 0);
|
||||
// pixel = ctx.getImageData(0, 0, _width, _height);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
let pixel1 = config.convoluteName ? convoluteY(
|
||||
pixel,
|
||||
config.Convolutes[config.convoluteName]
|
||||
) : pixel;
|
||||
|
||||
// if(config.contrast){
|
||||
// for (let i = 0; i < pixel1.data.length; i +=4) {
|
||||
// let r = (pixel1.data[i]-128) * config.contrast + 128;
|
||||
// pixel1.data[i ] = r;
|
||||
// pixel1.data[i+1] = r;
|
||||
// pixel1.data[i+2] = r;
|
||||
// pixel1.data[i+3] = 255;
|
||||
// }
|
||||
// }
|
||||
|
||||
if(config.convolute1Diff){
|
||||
let pixel2 = config.convoluteName2 ? convoluteY(
|
||||
pixel,
|
||||
config.Convolutes[config.convoluteName2]
|
||||
) : pixel;
|
||||
|
||||
console.log(/pixel2/,config.Convolutes[config.convoluteName2],pixel2);
|
||||
// pixelData
|
||||
for (let i = 0; i < pixel2.data.length; i +=4) {
|
||||
let r = 128 + pixel2.data[i ] - pixel1.data[i ];
|
||||
pixel2.data[i ] = r;
|
||||
pixel2.data[i+1] = r;
|
||||
pixel2.data[i+2] = r;
|
||||
pixel2.data[i+3] = 255;
|
||||
}
|
||||
pixel = pixel2;
|
||||
}else{
|
||||
// 不对比
|
||||
pixel = pixel1;
|
||||
}
|
||||
|
||||
pixelData = pixel.data;
|
||||
|
||||
if(config.invertLight){
|
||||
for (let i = 0; i < pixelData.length; i += 4) {
|
||||
let r = 255 - pixelData[i]
|
||||
pixelData[i ] = r
|
||||
pixelData[i+1 ] = r
|
||||
pixelData[i+2 ] = r
|
||||
}
|
||||
}
|
||||
|
||||
if(config.lightGroup!==1){
|
||||
for (let i = 0; i < pixelData.length; i += 4) {
|
||||
let y = pixelData[i];
|
||||
|
||||
const isOdd = Math.floor(y / (255/config.lightGroup)) % 2 === 0;
|
||||
|
||||
|
||||
y = y % (255/config.lightGroup) * config.lightGroup;
|
||||
|
||||
if(isOdd) y = 255 - y;
|
||||
|
||||
pixelData[i+0 ] = y
|
||||
pixelData[i+1 ] = y
|
||||
pixelData[i+2 ] = y
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if(config.lightCut || config.darkCut){
|
||||
const scale = 255 / (255 - config.lightCut - config.darkCut);
|
||||
for (let i = 0; i < pixelData.length; i += 4) {
|
||||
let y = pixelData[i];
|
||||
|
||||
y = (y - config.darkCut) * scale;
|
||||
|
||||
y = Math.max(0,y);
|
||||
|
||||
pixelData[i+0 ] = y
|
||||
pixelData[i+1 ] = y
|
||||
pixelData[i+2 ] = y
|
||||
pixelData[i+3 ] = 255
|
||||
}
|
||||
}
|
||||
const hStart = 30;
|
||||
const hEnd = -184;
|
||||
|
||||
const be = bezier(0.57, 0.01, 0.43, 0.99);
|
||||
const s = config.s/100;
|
||||
|
||||
|
||||
const gradient = ctx.createLinearGradient(0,0, _width,_height);
|
||||
|
||||
// Add three color stops
|
||||
gradient.addColorStop(0, '#fbba30');
|
||||
gradient.addColorStop(0.4, '#fc7235');
|
||||
gradient.addColorStop(.6, '#fc354e');
|
||||
gradient.addColorStop(0.7, '#cf36df');
|
||||
gradient.addColorStop(.8, '#37b5d9');
|
||||
gradient.addColorStop(1, '#3eb6da');
|
||||
|
||||
// Set the fill style and draw a rectangle
|
||||
ctx.fillStyle = gradient;
|
||||
ctx.fillRect(0, 0, _width, _height);
|
||||
let gradientPixel = ctx.getImageData(0, 0, _width, _height);
|
||||
|
||||
for (let i = 0; i < pixelData.length; i += 4) {
|
||||
let y = pixelData[i];
|
||||
let p = Math.floor(i / 4);
|
||||
|
||||
let _h = Math.floor(p/_width);
|
||||
let _w = p % _width;
|
||||
|
||||
/*
|
||||
|
||||
// const
|
||||
// hScale = hScale * hScale;
|
||||
|
||||
let hScale = (_h + _w)/(_width + _height);
|
||||
|
||||
hScale = hScale * hScale;
|
||||
hScale = be(hScale);
|
||||
|
||||
// let h = Math.floor((hStart + (hScale) * (hEnd - hStart)));
|
||||
let [h] = rgb2hsl([
|
||||
gradientPixel.data[i + 0],
|
||||
gradientPixel.data[i + 1],
|
||||
gradientPixel.data[i + 2],
|
||||
]);
|
||||
const l = y/255;
|
||||
const rgb = hsl2rgb([h, s, l * (1 - config.l/100) + (config.l/100)]);
|
||||
|
||||
if(i%5677===0){
|
||||
// console.log(h,y,l,l * (config.l/100) + (1 - config.l/100))
|
||||
// console.log((_h + _w)/(_width + _height),hScale)
|
||||
}
|
||||
|
||||
pixelData[i+0 ] = rgb[0];
|
||||
pixelData[i+1 ] = rgb[1];
|
||||
pixelData[i+2 ] = rgb[2];
|
||||
pixelData[i+3 ] = 255;
|
||||
*/
|
||||
|
||||
pixelData[i+0 ] = gradientPixel.data[i + 0];
|
||||
pixelData[i+1 ] = gradientPixel.data[i + 1];
|
||||
pixelData[i+2 ] = gradientPixel.data[i + 2];
|
||||
|
||||
y = 255 - y;
|
||||
if(config.black){
|
||||
y = Math.max(
|
||||
y,
|
||||
blackPixel.data[i]
|
||||
);
|
||||
}
|
||||
pixelData[i+3 ] = y
|
||||
}
|
||||
|
||||
|
||||
if(config.hue){
|
||||
for (let i = 0; i < pixelData.length; i += 4) {
|
||||
let y = pixelData[i ]
|
||||
y = y % config.hueGroup * (255/config.hueGroup) /255;
|
||||
const [r,g,b] = hsl2rgb([y,.7,.5])
|
||||
pixelData[i+0 ] = r
|
||||
pixelData[i+1 ] = g
|
||||
pixelData[i+2 ] = b
|
||||
}
|
||||
}
|
||||
|
||||
// for(let i = 0;i < pixelData.length;i += 4){
|
||||
|
||||
// let _rgb = yuv2rgb(
|
||||
// pixelData[i],
|
||||
// pixelData[i+1],
|
||||
// pixelData[i+2],
|
||||
// );
|
||||
|
||||
// pixelData[i ] = _rgb[0];
|
||||
// pixelData[i+1 ] = _rgb[1];
|
||||
// pixelData[i+2 ] = _rgb[2];
|
||||
// }
|
||||
|
||||
// blurC();
|
||||
ctx.putImageData(pixel, 0, 0);
|
||||
|
||||
const ctxMin = canvasMin.getContext('2d');
|
||||
|
||||
canvasMin.width = Math.floor(_width/1.4);
|
||||
canvasMin.height = Math.floor(_height/1.3);
|
||||
|
||||
ctxMin.clearRect(0,0,canvasMin.width,canvasMin.height)
|
||||
ctxMin.drawImage(
|
||||
canvas,
|
||||
0,0,
|
||||
canvasMin.width,canvasMin.height
|
||||
);
|
||||
|
||||
ctx.clearRect(0,0,_width,_height)
|
||||
ctx.drawImage(
|
||||
canvasMin,
|
||||
0,0,
|
||||
canvasMin.width,canvasMin.height,
|
||||
0,0,_width,_height
|
||||
);
|
||||
|
||||
console.timeEnd('louvre');
|
||||
// return canvas.toDataURL('image/png');
|
||||
};
|
||||
|
||||
|
||||
|
||||
let convolute = (pixels, weights) => {
|
||||
const side = Math.round(Math.sqrt(weights.length));
|
||||
const halfSide = Math.floor(side / 2);
|
||||
|
||||
const src = pixels.data;
|
||||
const sw = pixels.width;
|
||||
const sh = pixels.height;
|
||||
|
||||
const w = sw;
|
||||
const h = sh;
|
||||
const output = ctx.createImageData(w, h);
|
||||
const dst = output.data;
|
||||
|
||||
|
||||
for (let y = 0; y < h; y++) {
|
||||
for (let x = 0; x < w; x++) {
|
||||
const sy = y;
|
||||
const sx = x;
|
||||
const dstOff = (y * w + x) * 4;
|
||||
let r = 0, g = 0, b = 0;
|
||||
for (let cy = 0; cy < side; cy++) {
|
||||
for (let cx = 0; cx < side; cx++) {
|
||||
const scy = Math.min(sh - 1, Math.max(0, sy + cy - halfSide));
|
||||
const scx = Math.min(sw - 1, Math.max(0, sx + cx - halfSide));
|
||||
const srcOff = (scy * sw + scx) * 4;
|
||||
const wt = weights[cy * side + cx];
|
||||
r += src[srcOff] * wt;
|
||||
g += src[srcOff + 1] * wt;
|
||||
b += src[srcOff + 2] * wt;
|
||||
}
|
||||
}
|
||||
dst[dstOff] = r;
|
||||
dst[dstOff + 1] = g;
|
||||
dst[dstOff + 2] = b;
|
||||
dst[dstOff + 3] = 255;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// for (let y=0; y<h; y++) {
|
||||
// for (let x=0; x<w; x++) {
|
||||
// const srcOff = (y*w+x)*4;
|
||||
// src[srcOff] = dst[srcOff];
|
||||
// }
|
||||
// }
|
||||
return output;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
const convoluteY = (pixels, weights) => {
|
||||
const side = Math.round( Math.sqrt( weights.length ) );
|
||||
const halfSide = Math.floor(side / 2);
|
||||
|
||||
const src = pixels.data;
|
||||
|
||||
const w = pixels.width;
|
||||
const h = pixels.height;
|
||||
const output = ctx.createImageData(w, h);
|
||||
const dst = output.data;
|
||||
|
||||
for (let sy = 0; sy < h; sy++) {
|
||||
for (let sx = 0; sx < w; sx++) {
|
||||
const dstOff = (sy * w + sx) * 4;
|
||||
let r = 0, g = 0, b = 0;
|
||||
|
||||
for (let cy = 0; cy < side; cy++) {
|
||||
for (let cx = 0; cx < side; cx++) {
|
||||
|
||||
const scy = Math.min(h - 1, Math.max(0, sy + cy - halfSide));
|
||||
const scx = Math.min(w - 1, Math.max(0, sx + cx - halfSide));
|
||||
|
||||
const srcOff = (scy * w + scx) * 4;
|
||||
const wt = weights[cy * side + cx];
|
||||
|
||||
r += src[srcOff] * wt;
|
||||
// g += src[srcOff + 1] * wt;
|
||||
// b += src[srcOff + 2] * wt;
|
||||
}
|
||||
}
|
||||
dst[dstOff] = r;
|
||||
dst[dstOff + 1] = r;
|
||||
dst[dstOff + 2] = r;
|
||||
dst[dstOff + 3] = 255;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// for (let y=0; y<h; y++) {
|
||||
// for (let x=0; x<w; x++) {
|
||||
// const srcOff = (y*w+x)*4;
|
||||
// src[srcOff] = dst[srcOff];
|
||||
// }
|
||||
// }
|
||||
return output;
|
||||
};
|
||||
Reference in New Issue
Block a user