forked from ppfun/pixelplanet
refactor image converter and 3d stuff
This commit is contained in:
parent
e59df4fb62
commit
6170d35631
|
@ -4,6 +4,9 @@
|
||||||
],
|
],
|
||||||
"parser":"@babel/eslint-parser",
|
"parser":"@babel/eslint-parser",
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
|
"babelOptions":{
|
||||||
|
"rootMode": "upward"
|
||||||
|
},
|
||||||
"ecmaFeatures": {
|
"ecmaFeatures": {
|
||||||
"jsx": true
|
"jsx": true
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
* Create canvases.json with localized translated
|
* Create canvases.json with localized translated
|
||||||
* descriptions.
|
* descriptions.
|
||||||
*
|
*
|
||||||
* @flow
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import canvases from './canvases.json';
|
import canvases from './canvases.json';
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
/**
|
/**
|
||||||
*
|
* Converts images to canvas palettes
|
||||||
* @flow
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
|
@ -11,8 +10,10 @@ import { jt, t } from 'ttag';
|
||||||
import {
|
import {
|
||||||
ColorDistanceCalculators,
|
ColorDistanceCalculators,
|
||||||
ImageQuantizerKernels,
|
ImageQuantizerKernels,
|
||||||
|
readFileIntoCanvas,
|
||||||
|
scaleImage,
|
||||||
quantizeImage,
|
quantizeImage,
|
||||||
getImageDataOfFile,
|
addGrid,
|
||||||
} from '../utils/image';
|
} from '../utils/image';
|
||||||
import printGIMPPalette from '../core/exportGPL';
|
import printGIMPPalette from '../core/exportGPL';
|
||||||
import { copyCanvasToClipboard } from '../utils/clipboard';
|
import { copyCanvasToClipboard } from '../utils/clipboard';
|
||||||
|
@ -23,119 +24,45 @@ function downloadOutput() {
|
||||||
output.toBlob((blob) => fileDownload(blob, 'ppfunconvert.png'));
|
output.toBlob((blob) => fileDownload(blob, 'ppfunconvert.png'));
|
||||||
}
|
}
|
||||||
|
|
||||||
function createCanvasFromImageData(imgData) {
|
|
||||||
const { width, height } = imgData;
|
|
||||||
const inputCanvas = document.createElement('canvas');
|
|
||||||
inputCanvas.width = width;
|
|
||||||
inputCanvas.height = height;
|
|
||||||
const inputCtx = inputCanvas.getContext('2d');
|
|
||||||
inputCtx.putImageData(imgData, 0, 0);
|
|
||||||
return inputCanvas;
|
|
||||||
}
|
|
||||||
|
|
||||||
function addGrid(imgData, lightGrid, offsetX, offsetY) {
|
|
||||||
const image = createCanvasFromImageData(imgData);
|
|
||||||
const { width, height } = image;
|
|
||||||
const can = document.createElement('canvas');
|
|
||||||
const ctx = can.getContext('2d');
|
|
||||||
can.width = width * 5;
|
|
||||||
can.height = height * 5;
|
|
||||||
ctx.imageSmoothingEnabled = false;
|
|
||||||
ctx.mozImageSmoothingEnabled = false;
|
|
||||||
ctx.webkitImageSmoothingEnabled = false;
|
|
||||||
ctx.msImageSmoothingEnabled = false;
|
|
||||||
ctx.save();
|
|
||||||
ctx.scale(5.0, 5.0);
|
|
||||||
ctx.drawImage(image, 0, 0);
|
|
||||||
ctx.restore();
|
|
||||||
ctx.fillStyle = (lightGrid) ? '#DDDDDD' : '#222222';
|
|
||||||
for (let i = 0; i <= width; i += 1) {
|
|
||||||
const thick = ((i + (offsetX * 1)) % 10 === 0) ? 2 : 1;
|
|
||||||
ctx.fillRect(i * 5, 0, thick, can.height);
|
|
||||||
}
|
|
||||||
for (let j = 0; j <= height; j += 1) {
|
|
||||||
const thick = ((j + (offsetY * 1)) % 10 === 0) ? 2 : 1;
|
|
||||||
ctx.fillRect(0, j * 5, can.width, thick);
|
|
||||||
}
|
|
||||||
return ctx.getImageData(0, 0, can.width, can.height);
|
|
||||||
}
|
|
||||||
|
|
||||||
function scaleImage(imgData, width, height, doAA) {
|
|
||||||
const can = document.createElement('canvas');
|
|
||||||
const ctxo = can.getContext('2d');
|
|
||||||
can.width = width;
|
|
||||||
can.height = height;
|
|
||||||
const scaleX = width / imgData.width;
|
|
||||||
const scaleY = height / imgData.height;
|
|
||||||
if (doAA) {
|
|
||||||
// scale with canvas for antialiasing
|
|
||||||
const image = createCanvasFromImageData(imgData);
|
|
||||||
ctxo.save();
|
|
||||||
ctxo.scale(scaleX, scaleY);
|
|
||||||
ctxo.drawImage(image, 0, 0);
|
|
||||||
ctxo.restore();
|
|
||||||
return ctxo.getImageData(0, 0, width, height);
|
|
||||||
}
|
|
||||||
// scale manually
|
|
||||||
const imdo = ctxo.createImageData(width, height);
|
|
||||||
const { data: datao } = imdo;
|
|
||||||
const { data: datai } = imgData;
|
|
||||||
for (let y = 0; y < height; y += 1) {
|
|
||||||
for (let x = 0; x < width; x += 1) {
|
|
||||||
let posi = (Math.round(x / scaleX) + Math.round(y / scaleY)
|
|
||||||
* imgData.width) * 4;
|
|
||||||
let poso = (x + y * width) * 4;
|
|
||||||
datao[poso++] = datai[posi++];
|
|
||||||
datao[poso++] = datai[posi++];
|
|
||||||
datao[poso++] = datai[posi++];
|
|
||||||
datao[poso] = datai[posi];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return imdo;
|
|
||||||
}
|
|
||||||
|
|
||||||
let renderOpts = null;
|
let renderOpts = null;
|
||||||
let rendering = false;
|
let rendering = false;
|
||||||
async function renderOutputImage(opts) {
|
async function renderOutputImage(opts) {
|
||||||
if (!opts.file) {
|
if (!opts.imgCanvas) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
renderOpts = opts;
|
renderOpts = opts;
|
||||||
if (rendering) {
|
if (rendering) {
|
||||||
console.log('skip rendering');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.log('render');
|
|
||||||
rendering = true;
|
rendering = true;
|
||||||
while (renderOpts) {
|
while (renderOpts) {
|
||||||
const {
|
const {
|
||||||
file, dither, grid, scaling,
|
colors, imgCanvas, ditherOpts, grid, scaling,
|
||||||
} = renderOpts;
|
} = renderOpts;
|
||||||
renderOpts = null;
|
renderOpts = null;
|
||||||
if (file) {
|
if (imgCanvas) {
|
||||||
let image = file;
|
let image = imgCanvas;
|
||||||
if (scaling.enabled) {
|
if (scaling.enabled) {
|
||||||
// scale
|
// scale
|
||||||
const { width, height, aa } = scaling;
|
const { width, height, aa } = scaling;
|
||||||
image = scaleImage(
|
image = scaleImage(
|
||||||
file,
|
imgCanvas,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
aa,
|
aa,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// dither
|
// dither
|
||||||
const { colors, strategy, colorDist } = dither;
|
|
||||||
const progEl = document.getElementById('qprog');
|
const progEl = document.getElementById('qprog');
|
||||||
|
progEl.innerText = 'Loading...';
|
||||||
// eslint-disable-next-line no-await-in-loop
|
// eslint-disable-next-line no-await-in-loop
|
||||||
image = await quantizeImage(colors, image, {
|
image = await quantizeImage(colors, image, {
|
||||||
strategy,
|
...ditherOpts,
|
||||||
colorDist,
|
|
||||||
onProgress: (progress) => {
|
onProgress: (progress) => {
|
||||||
progEl.innerHTML = `Loading... ${Math.round(progress)} %`;
|
progEl.innerText = `Loading... ${Math.round(progress)} %`;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
progEl.innerHTML = 'Done';
|
progEl.innerText = 'Done';
|
||||||
// grid
|
// grid
|
||||||
if (grid.enabled) {
|
if (grid.enabled) {
|
||||||
const { light, offsetX, offsetY } = grid;
|
const { light, offsetX, offsetY } = grid;
|
||||||
|
@ -151,7 +78,7 @@ async function renderOutputImage(opts) {
|
||||||
output.width = image.width;
|
output.width = image.width;
|
||||||
output.height = image.height;
|
output.height = image.height;
|
||||||
const ctx = output.getContext('2d');
|
const ctx = output.getContext('2d');
|
||||||
ctx.putImageData(image, 0, 0);
|
ctx.drawImage(image, 0, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rendering = false;
|
rendering = false;
|
||||||
|
@ -170,10 +97,15 @@ function Converter() {
|
||||||
], shallowEqual);
|
], shallowEqual);
|
||||||
|
|
||||||
const [selectedCanvas, selectCanvas] = useState(canvasId);
|
const [selectedCanvas, selectCanvas] = useState(canvasId);
|
||||||
const [inputImageData, setInputImageData] = useState(null);
|
const [inputImageCanvas, setInputImageCanvas] = useState(null);
|
||||||
const [selectedStrategy, selectStrategy] = useState('Nearest');
|
const [selectedStrategy, selectStrategy] = useState('Nearest');
|
||||||
const [selectedColorDist, selectColorDist] = useState('Euclidean');
|
const [selectedColorDist, selectColorDist] = useState('Euclidean');
|
||||||
const [selectedScaleKeepRatio, selectScaleKeepRatio] = useState(true);
|
const [selectedScaleKeepRatio, selectScaleKeepRatio] = useState(true);
|
||||||
|
const [extraOpts, setExtraOpts] = useState({
|
||||||
|
serpentine: true,
|
||||||
|
minColorDistance: 0,
|
||||||
|
GIMPerror: false,
|
||||||
|
});
|
||||||
const [scaleData, setScaleData] = useState({
|
const [scaleData, setScaleData] = useState({
|
||||||
enabled: false,
|
enabled: false,
|
||||||
width: 10,
|
width: 10,
|
||||||
|
@ -181,36 +113,45 @@ function Converter() {
|
||||||
aa: true,
|
aa: true,
|
||||||
});
|
});
|
||||||
const [gridData, setGridData] = useState({
|
const [gridData, setGridData] = useState({
|
||||||
enabled: true,
|
enabled: false,
|
||||||
light: false,
|
light: false,
|
||||||
offsetX: 0,
|
offsetX: 0,
|
||||||
offsetY: 0,
|
offsetY: 0,
|
||||||
});
|
});
|
||||||
|
const [extraRender, setExtraRender] = useState(false);
|
||||||
|
const [gridRender, setGridRender] = useState(false);
|
||||||
|
const [scalingRender, setScalingRender] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (inputImageData) {
|
if (inputImageCanvas) {
|
||||||
const canvas = canvases[selectedCanvas];
|
const canvas = canvases[selectedCanvas];
|
||||||
const dither = {
|
|
||||||
colors: canvas.colors.slice(canvas.cli),
|
|
||||||
strategy: selectedStrategy,
|
|
||||||
colorDist: selectedColorDist,
|
|
||||||
};
|
|
||||||
renderOutputImage({
|
renderOutputImage({
|
||||||
file: inputImageData,
|
colors: canvas.colors.slice(canvas.cli),
|
||||||
dither,
|
imgCanvas: inputImageCanvas,
|
||||||
|
ditherOpts: {
|
||||||
|
strategy: selectedStrategy,
|
||||||
|
colorDist: selectedColorDist,
|
||||||
|
...extraOpts,
|
||||||
|
},
|
||||||
grid: gridData,
|
grid: gridData,
|
||||||
scaling: scaleData,
|
scaling: scaleData,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
inputImageData,
|
selectedCanvas,
|
||||||
|
inputImageCanvas,
|
||||||
selectedStrategy,
|
selectedStrategy,
|
||||||
selectedColorDist,
|
selectedColorDist,
|
||||||
|
extraOpts,
|
||||||
scaleData,
|
scaleData,
|
||||||
selectedCanvas,
|
|
||||||
gridData,
|
gridData,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const {
|
||||||
|
serpentine,
|
||||||
|
minColorDistance,
|
||||||
|
GIMPerror,
|
||||||
|
} = extraOpts;
|
||||||
const {
|
const {
|
||||||
enabled: gridEnabled,
|
enabled: gridEnabled,
|
||||||
light: gridLight,
|
light: gridLight,
|
||||||
|
@ -224,6 +165,24 @@ function Converter() {
|
||||||
aa: scalingAA,
|
aa: scalingAA,
|
||||||
} = scaleData;
|
} = scaleData;
|
||||||
|
|
||||||
|
const showExtraOptions = selectedStrategy !== 'Nearest'
|
||||||
|
&& selectedStrategy !== 'Riemersma';
|
||||||
|
useEffect(() => {
|
||||||
|
if (showExtraOptions) {
|
||||||
|
setTimeout(() => setExtraRender(true), 10);
|
||||||
|
}
|
||||||
|
}, [selectedStrategy]);
|
||||||
|
useEffect(() => {
|
||||||
|
if (gridEnabled) {
|
||||||
|
setTimeout(() => setGridRender(true), 10);
|
||||||
|
}
|
||||||
|
}, [gridData.enabled]);
|
||||||
|
useEffect(() => {
|
||||||
|
if (scalingEnabled) {
|
||||||
|
setTimeout(() => setScalingRender(true), 10);
|
||||||
|
}
|
||||||
|
}, [scaleData.enabled]);
|
||||||
|
|
||||||
const gimpLink = <a href="https://www.gimp.org">GIMP</a>;
|
const gimpLink = <a href="https://www.gimp.org">GIMP</a>;
|
||||||
const starhouseLink = (
|
const starhouseLink = (
|
||||||
<a href="https://twitter.com/starhousedev">
|
<a href="https://twitter.com/starhousedev">
|
||||||
|
@ -291,15 +250,15 @@ function Converter() {
|
||||||
const fileSel = evt.target;
|
const fileSel = evt.target;
|
||||||
const file = (!fileSel.files || !fileSel.files[0])
|
const file = (!fileSel.files || !fileSel.files[0])
|
||||||
? null : fileSel.files[0];
|
? null : fileSel.files[0];
|
||||||
const imageData = await getImageDataOfFile(file);
|
const imageData = await readFileIntoCanvas(file);
|
||||||
setInputImageData(null);
|
setInputImageCanvas(null);
|
||||||
setScaleData({
|
setScaleData({
|
||||||
enabled: false,
|
enabled: false,
|
||||||
width: imageData.width,
|
width: imageData.width,
|
||||||
height: imageData.height,
|
height: imageData.height,
|
||||||
aa: true,
|
aa: true,
|
||||||
});
|
});
|
||||||
setInputImageData(imageData);
|
setInputImageCanvas(imageData);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<p className="modalcotext">{t`Choose Strategy`}:
|
<p className="modalcotext">{t`Choose Strategy`}:
|
||||||
|
@ -319,6 +278,59 @@ function Converter() {
|
||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
</p>
|
</p>
|
||||||
|
{(showExtraOptions || extraRender) && (
|
||||||
|
<div
|
||||||
|
className={(showExtraOptions && extraRender)
|
||||||
|
? 'convBox show'
|
||||||
|
: 'convBox'}
|
||||||
|
onTransitionEnd={() => {
|
||||||
|
if (!showExtraOptions) setExtraRender(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<p style={{ fontHeight: 16 }} className="modalcotext">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={serpentine}
|
||||||
|
onChange={(e) => {
|
||||||
|
setExtraOpts({
|
||||||
|
...extraOpts,
|
||||||
|
serpentine: e.target.checked,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{t`Serpentine`}
|
||||||
|
</p>
|
||||||
|
<span className="modalcotext">{t`Minimum Color Distance`}:
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
step="1"
|
||||||
|
min="0"
|
||||||
|
max="100"
|
||||||
|
style={{ width: '4em' }}
|
||||||
|
value={minColorDistance}
|
||||||
|
onChange={(e) => {
|
||||||
|
setExtraOpts({
|
||||||
|
...extraOpts,
|
||||||
|
minColorDistance: e.target.value,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<p style={{ fontHeight: 16 }} className="modalcotext">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={GIMPerror}
|
||||||
|
onChange={(e) => {
|
||||||
|
setExtraOpts({
|
||||||
|
...extraOpts,
|
||||||
|
GIMPerror: e.target.checked,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{t`Calculate like GIMP`}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<p className="modalcotext">{t`Choose Color Mode`}:
|
<p className="modalcotext">{t`Choose Color Mode`}:
|
||||||
<select
|
<select
|
||||||
value={selectedColorDist}
|
value={selectedColorDist}
|
||||||
|
@ -349,64 +361,60 @@ function Converter() {
|
||||||
/>
|
/>
|
||||||
{t`Add Grid (uncheck if you need a 1:1 template)`}
|
{t`Add Grid (uncheck if you need a 1:1 template)`}
|
||||||
</p>
|
</p>
|
||||||
{(gridEnabled)
|
{(gridEnabled || gridRender) && (
|
||||||
? (
|
<div
|
||||||
<div style={{
|
className={(gridEnabled && gridRender) ? 'convBox show' : 'convBox'}
|
||||||
borderStyle: 'solid',
|
onTransitionEnd={() => {
|
||||||
borderColor: '#D4D4D4',
|
if (!gridEnabled) setGridRender(false);
|
||||||
borderWidth: 2,
|
|
||||||
padding: 5,
|
|
||||||
display: 'inline-block',
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<p style={{ fontHeight: 16 }} className="modalcotext">
|
<p style={{ fontHeight: 16 }} className="modalcotext">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={gridLight}
|
checked={gridLight}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setGridData({
|
setGridData({
|
||||||
...gridData,
|
...gridData,
|
||||||
light: e.target.checked,
|
light: e.target.checked,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{t`Light Grid`}
|
{t`Light Grid`}
|
||||||
</p>
|
</p>
|
||||||
<span className="modalcotext">{t`Offset`} X:
|
<span className="modalcotext">{t`Offset`} X:
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
step="1"
|
step="1"
|
||||||
min="0"
|
min="0"
|
||||||
max="10"
|
max="10"
|
||||||
style={{ width: '2em' }}
|
style={{ width: '2em' }}
|
||||||
value={gridOffsetX}
|
value={gridOffsetX}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setGridData({
|
setGridData({
|
||||||
...gridData,
|
...gridData,
|
||||||
offsetX: e.target.value,
|
offsetX: e.target.value,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
<span className="modalcotext">{t`Offset`} Y:
|
<span className="modalcotext">{t`Offset`} Y:
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
step="1"
|
step="1"
|
||||||
min="0"
|
min="0"
|
||||||
max="10"
|
max="10"
|
||||||
style={{ width: '2em' }}
|
style={{ width: '2em' }}
|
||||||
value={gridOffsetY}
|
value={gridOffsetY}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setGridData({
|
setGridData({
|
||||||
...gridData,
|
...gridData,
|
||||||
offsetY: e.target.value,
|
offsetY: e.target.value,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)
|
)}
|
||||||
: null}
|
|
||||||
<p style={{ fontHeight: 16 }} className="modalcotext">
|
<p style={{ fontHeight: 16 }} className="modalcotext">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
|
@ -420,117 +428,117 @@ function Converter() {
|
||||||
/>
|
/>
|
||||||
{t`Scale Image`}
|
{t`Scale Image`}
|
||||||
</p>
|
</p>
|
||||||
{(scalingEnabled)
|
{(scalingEnabled || scalingRender) && (
|
||||||
? (
|
<div
|
||||||
<div style={{
|
className={(scalingEnabled && scalingRender)
|
||||||
borderStyle: 'solid',
|
? 'convBox show'
|
||||||
borderColor: '#D4D4D4',
|
: 'convBox'}
|
||||||
borderWidth: 2,
|
onTransitionEnd={() => {
|
||||||
padding: 5,
|
if (!scalingEnabled) setScalingRender(false);
|
||||||
display: 'inline-block',
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className="modalcotext">{t`Width`}:
|
<span className="modalcotext">{t`Width`}:
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
step="1"
|
step="1"
|
||||||
min="1"
|
min="1"
|
||||||
max="1024"
|
max="1024"
|
||||||
style={{ width: '3em' }}
|
style={{ width: '3em' }}
|
||||||
value={scalingWidth}
|
value={scalingWidth}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const newWidth = (e.target.value > 1024)
|
const newWidth = (e.target.value > 1024)
|
||||||
? 1024 : e.target.value;
|
? 1024 : e.target.value;
|
||||||
if (!newWidth) return;
|
if (!newWidth) return;
|
||||||
if (selectedScaleKeepRatio && inputImageData) {
|
if (selectedScaleKeepRatio && inputImageCanvas) {
|
||||||
const ratio = inputImageData.width / inputImageData.height;
|
const ratio = inputImageCanvas.width
|
||||||
const newHeight = Math.round(newWidth / ratio);
|
/ inputImageCanvas.height;
|
||||||
if (newHeight <= 0) return;
|
const newHeight = Math.round(newWidth / ratio);
|
||||||
setScaleData({
|
if (newHeight <= 0) return;
|
||||||
...scaleData,
|
|
||||||
width: newWidth,
|
|
||||||
height: newHeight,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setScaleData({
|
setScaleData({
|
||||||
...scaleData,
|
...scaleData,
|
||||||
width: newWidth,
|
width: newWidth,
|
||||||
|
height: newHeight,
|
||||||
});
|
});
|
||||||
}}
|
return;
|
||||||
/>
|
}
|
||||||
</span>
|
setScaleData({
|
||||||
<span className="modalcotext">{t`Height`}:
|
...scaleData,
|
||||||
<input
|
width: newWidth,
|
||||||
type="number"
|
});
|
||||||
step="1"
|
}}
|
||||||
min="1"
|
/>
|
||||||
max="1024"
|
</span>
|
||||||
style={{ width: '3em' }}
|
<span className="modalcotext">{t`Height`}:
|
||||||
value={scalingHeight}
|
<input
|
||||||
onChange={(e) => {
|
type="number"
|
||||||
const nuHeight = (e.target.value > 1024)
|
step="1"
|
||||||
? 1024 : e.target.value;
|
min="1"
|
||||||
if (!nuHeight) return;
|
max="1024"
|
||||||
if (selectedScaleKeepRatio && inputImageData) {
|
style={{ width: '3em' }}
|
||||||
const ratio = inputImageData.width / inputImageData.height;
|
value={scalingHeight}
|
||||||
const nuWidth = Math.round(ratio * nuHeight);
|
onChange={(e) => {
|
||||||
if (nuWidth <= 0) return;
|
const nuHeight = (e.target.value > 1024)
|
||||||
setScaleData({
|
? 1024 : e.target.value;
|
||||||
...scaleData,
|
if (!nuHeight) return;
|
||||||
width: nuWidth,
|
if (selectedScaleKeepRatio && inputImageCanvas) {
|
||||||
height: nuHeight,
|
const ratio = inputImageCanvas.width
|
||||||
});
|
/ inputImageCanvas.height;
|
||||||
return;
|
const nuWidth = Math.round(ratio * nuHeight);
|
||||||
}
|
if (nuWidth <= 0) return;
|
||||||
setScaleData({
|
setScaleData({
|
||||||
...scaleData,
|
...scaleData,
|
||||||
|
width: nuWidth,
|
||||||
height: nuHeight,
|
height: nuHeight,
|
||||||
});
|
});
|
||||||
}}
|
return;
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
<p style={{ fontHeight: 16 }} className="modalcotext">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
checked={selectedScaleKeepRatio}
|
|
||||||
onChange={(e) => {
|
|
||||||
selectScaleKeepRatio(e.target.checked);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{t`Keep Ratio`}
|
|
||||||
</p>
|
|
||||||
<p style={{ fontHeight: 16 }} className="modalcotext">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
checked={scalingAA}
|
|
||||||
onChange={(e) => {
|
|
||||||
setScaleData({
|
|
||||||
...scaleData,
|
|
||||||
aa: e.target.checked,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{t`Anti Aliasing`}
|
|
||||||
</p>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => {
|
|
||||||
if (inputImageData) {
|
|
||||||
setScaleData({
|
|
||||||
...scaleData,
|
|
||||||
width: inputImageData.width,
|
|
||||||
height: inputImageData.height,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
setScaleData({
|
||||||
|
...scaleData,
|
||||||
|
height: nuHeight,
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
>
|
/>
|
||||||
{t`Reset`}
|
</span>
|
||||||
</button>
|
<p style={{ fontHeight: 16 }} className="modalcotext">
|
||||||
</div>
|
<input
|
||||||
)
|
type="checkbox"
|
||||||
: null}
|
checked={selectedScaleKeepRatio}
|
||||||
{(inputImageData)
|
onChange={(e) => {
|
||||||
|
selectScaleKeepRatio(e.target.checked);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{t`Keep Ratio`}
|
||||||
|
</p>
|
||||||
|
<p style={{ fontHeight: 16 }} className="modalcotext">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={scalingAA}
|
||||||
|
onChange={(e) => {
|
||||||
|
setScaleData({
|
||||||
|
...scaleData,
|
||||||
|
aa: e.target.checked,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{t`Anti Aliasing`}
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
if (inputImageCanvas) {
|
||||||
|
setScaleData({
|
||||||
|
...scaleData,
|
||||||
|
width: inputImageCanvas.width,
|
||||||
|
height: inputImageCanvas.height,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t`Reset`}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{(inputImageCanvas)
|
||||||
? (
|
? (
|
||||||
<div>
|
<div>
|
||||||
<p id="qprog">...</p>
|
<p id="qprog">...</p>
|
||||||
|
|
|
@ -17,9 +17,9 @@ const Menu = () => {
|
||||||
const menuOpen = useSelector((state) => state.gui.menuOpen);
|
const menuOpen = useSelector((state) => state.gui.menuOpen);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.setTimeout(() => {
|
if (menuOpen) {
|
||||||
if (menuOpen) setRender(true);
|
setTimeout(() => setRender(true), 10);
|
||||||
}, 10);
|
}
|
||||||
}, [menuOpen]);
|
}, [menuOpen]);
|
||||||
|
|
||||||
const onTransitionEnd = () => {
|
const onTransitionEnd = () => {
|
||||||
|
|
|
@ -26,7 +26,7 @@ const UserMessages = () => {
|
||||||
&& (
|
&& (
|
||||||
<p className="usermessages">
|
<p className="usermessages">
|
||||||
{t`Please verify your mail address
|
{t`Please verify your mail address
|
||||||
or your account could get deleted after a few days.`}
|
or your account could get deleted after a few days.`}
|
||||||
{(verifyAnswer)
|
{(verifyAnswer)
|
||||||
? (
|
? (
|
||||||
<span
|
<span
|
||||||
|
|
|
@ -828,7 +828,7 @@ class VoxelPainterControls extends EventDispatcher {
|
||||||
// so camera.up is the orbit axis
|
// so camera.up is the orbit axis
|
||||||
const quat = new Quaternion()
|
const quat = new Quaternion()
|
||||||
.setFromUnitVectors(object.up, new Vector3(0, 1, 0));
|
.setFromUnitVectors(object.up, new Vector3(0, 1, 0));
|
||||||
const quatInverse = quat.clone().inverse();
|
const quatInverse = quat.clone().invert();
|
||||||
|
|
||||||
const lastPosition = new Vector3();
|
const lastPosition = new Vector3();
|
||||||
const lastQuaternion = new Quaternion();
|
const lastQuaternion = new Quaternion();
|
||||||
|
@ -1042,8 +1042,4 @@ class VoxelPainterControls extends EventDispatcher {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// VoxelPainterControls.prototype = Object.create(EventDispatcher.prototype);
|
|
||||||
// VoxelPainterControls.prototype.constructor = VoxelPainterControls;
|
|
||||||
|
|
||||||
|
|
||||||
export default VoxelPainterControls;
|
export default VoxelPainterControls;
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
/* @flow */
|
|
||||||
|
|
||||||
const OP_CODE = 0xA6;
|
const OP_CODE = 0xA6;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
OP_CODE,
|
OP_CODE,
|
||||||
dehydrate(): ArrayBuffer {
|
|
||||||
|
dehydrate() {
|
||||||
|
// Server (sender)
|
||||||
const buffer = new ArrayBuffer(1);
|
const buffer = new ArrayBuffer(1);
|
||||||
const view = new DataView(buffer);
|
const view = new DataView(buffer);
|
||||||
view.setInt8(0, OP_CODE);
|
view.setInt8(0, OP_CODE);
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
/* @flow */
|
|
||||||
|
|
||||||
|
|
||||||
const OP_CODE = 0xC2;
|
const OP_CODE = 0xC2;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
OP_CODE,
|
OP_CODE,
|
||||||
hydrate(data: DataView) {
|
hydrate(data) {
|
||||||
|
// client (receiver)
|
||||||
return data.getUint32(1);
|
return data.getUint32(1);
|
||||||
},
|
},
|
||||||
dehydrate(wait): Buffer {
|
dehydrate(wait) {
|
||||||
|
// Server (sender)
|
||||||
const buffer = Buffer.allocUnsafe(1 + 4);
|
const buffer = Buffer.allocUnsafe(1 + 4);
|
||||||
buffer.writeUInt8(OP_CODE, 0);
|
buffer.writeUInt8(OP_CODE, 0);
|
||||||
buffer.writeUInt32BE(wait, 1);
|
buffer.writeUInt32BE(wait, 1);
|
||||||
|
|
|
@ -1,16 +1,13 @@
|
||||||
/* @flow */
|
|
||||||
|
|
||||||
|
|
||||||
const OP_CODE = 0xA2;
|
const OP_CODE = 0xA2;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
OP_CODE,
|
OP_CODE,
|
||||||
hydrate(data: Buffer) {
|
hydrate(data) {
|
||||||
// SERVER (Client)
|
// SERVER (Receiver)
|
||||||
const i = data[1] << 8 | data[2];
|
const i = data[1] << 8 | data[2];
|
||||||
return i;
|
return i;
|
||||||
},
|
},
|
||||||
dehydrate(chunkid): Buffer {
|
dehydrate(chunkid) {
|
||||||
// CLIENT (Sender)
|
// CLIENT (Sender)
|
||||||
const buffer = new ArrayBuffer(1 + 2);
|
const buffer = new ArrayBuffer(1 + 2);
|
||||||
const view = new DataView(buffer);
|
const view = new DataView(buffer);
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
/* @flow */
|
|
||||||
|
|
||||||
|
|
||||||
const OP_CODE = 0xA4;
|
const OP_CODE = 0xA4;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
OP_CODE,
|
OP_CODE,
|
||||||
dehydrate(chunks: Array): ArrayBuffer {
|
/*
|
||||||
|
* @param chunks Array of chunks
|
||||||
|
*/
|
||||||
|
dehydrate(chunks) {
|
||||||
// CLIENT (Sender)
|
// CLIENT (Sender)
|
||||||
const buffer = new ArrayBuffer(1 + 1 + chunks.length * 2);
|
const buffer = new ArrayBuffer(1 + 1 + chunks.length * 2);
|
||||||
const view = new Uint16Array(buffer);
|
const view = new Uint16Array(buffer);
|
||||||
|
|
|
@ -1,20 +1,14 @@
|
||||||
/* @flow */
|
|
||||||
|
|
||||||
type OnlineCounterPacket = {
|
|
||||||
online: number,
|
|
||||||
};
|
|
||||||
|
|
||||||
const OP_CODE = 0xA7;
|
const OP_CODE = 0xA7;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
OP_CODE,
|
OP_CODE,
|
||||||
hydrate(data: DataView): OnlineCounterPacket {
|
hydrate(data) {
|
||||||
// CLIENT
|
// CLIENT (receiver)
|
||||||
const online = data.getInt16(1);
|
const online = data.getInt16(1);
|
||||||
return { online };
|
return { online };
|
||||||
},
|
},
|
||||||
dehydrate({ online }: OnlineCounterPacket): Buffer {
|
dehydrate({ online }) {
|
||||||
// SERVER
|
// SERVER (sender)
|
||||||
if (!process.env.BROWSER) {
|
if (!process.env.BROWSER) {
|
||||||
const buffer = Buffer.allocUnsafe(1 + 2);
|
const buffer = Buffer.allocUnsafe(1 + 2);
|
||||||
buffer.writeUInt8(OP_CODE, 0);
|
buffer.writeUInt8(OP_CODE, 0);
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
/* @flow */
|
|
||||||
|
|
||||||
|
|
||||||
const OP_CODE = 0xC3;
|
const OP_CODE = 0xC3;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
OP_CODE,
|
OP_CODE,
|
||||||
hydrate(data: DataView) {
|
hydrate(data) {
|
||||||
|
// Client (receiver)
|
||||||
const retCode = data.getUint8(1);
|
const retCode = data.getUint8(1);
|
||||||
const wait = data.getUint32(2);
|
const wait = data.getUint32(2);
|
||||||
const coolDownSeconds = data.getInt16(6);
|
const coolDownSeconds = data.getInt16(6);
|
||||||
|
@ -17,7 +15,8 @@ export default {
|
||||||
pxlCnt,
|
pxlCnt,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
dehydrate(retCode, wait, coolDown, pxlCnt): Buffer {
|
dehydrate(retCode, wait, coolDown, pxlCnt) {
|
||||||
|
// Server (sender)
|
||||||
const buffer = Buffer.allocUnsafe(1 + 1 + 4 + 2 + 1);
|
const buffer = Buffer.allocUnsafe(1 + 1 + 4 + 2 + 1);
|
||||||
buffer.writeUInt8(OP_CODE, 0);
|
buffer.writeUInt8(OP_CODE, 0);
|
||||||
buffer.writeUInt8(retCode, 1);
|
buffer.writeUInt8(retCode, 1);
|
||||||
|
|
|
@ -1,22 +1,18 @@
|
||||||
/*
|
/*
|
||||||
* Packet for sending and receiving pixels per chunk
|
* Packet for sending and receiving pixels per chunk
|
||||||
* Multiple pixels can be sent at once
|
* Multiple pixels can be sent at once
|
||||||
|
* Client side
|
||||||
*
|
*
|
||||||
* @flow
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
type PixelUpdatePacket = {
|
|
||||||
x: number,
|
|
||||||
y: number,
|
|
||||||
pixels: Array,
|
|
||||||
};
|
|
||||||
|
|
||||||
const OP_CODE = 0xC1;
|
const OP_CODE = 0xC1;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
OP_CODE,
|
OP_CODE,
|
||||||
hydrate(data: DataView): PixelUpdatePacket {
|
/*
|
||||||
|
* @param data DataVies
|
||||||
|
*/
|
||||||
|
hydrate(data) {
|
||||||
/*
|
/*
|
||||||
* chunk coordinates
|
* chunk coordinates
|
||||||
*/
|
*/
|
||||||
|
@ -40,7 +36,7 @@ export default {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
dehydrate(i, j, pixels): Buffer {
|
dehydrate(i, j, pixels) {
|
||||||
const buffer = new ArrayBuffer(1 + 1 + 1 + pixels.length * 4);
|
const buffer = new ArrayBuffer(1 + 1 + 1 + pixels.length * 4);
|
||||||
const view = new DataView(buffer);
|
const view = new DataView(buffer);
|
||||||
view.setUint8(0, OP_CODE);
|
view.setUint8(0, OP_CODE);
|
||||||
|
|
|
@ -1,17 +1,15 @@
|
||||||
/* @flow */
|
/*
|
||||||
|
* Packet for sending and receiving pixels per chunk
|
||||||
|
* Multiple pixels can be sent at once
|
||||||
type PixelUpdatePacket = {
|
* Server side.
|
||||||
x: number,
|
*
|
||||||
y: number,
|
* */
|
||||||
pixels: Array,
|
|
||||||
};
|
|
||||||
|
|
||||||
const OP_CODE = 0xC1;
|
const OP_CODE = 0xC1;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
OP_CODE,
|
OP_CODE,
|
||||||
hydrate(data: Buffer): PixelUpdatePacket {
|
hydrate(data) {
|
||||||
/*
|
/*
|
||||||
* chunk coordinates
|
* chunk coordinates
|
||||||
*/
|
*/
|
||||||
|
@ -45,7 +43,7 @@ export default {
|
||||||
* @param chunkId id consisting of chunk coordinates
|
* @param chunkId id consisting of chunk coordinates
|
||||||
* @param pixels Buffer with offset and color of one or more pixels
|
* @param pixels Buffer with offset and color of one or more pixels
|
||||||
*/
|
*/
|
||||||
dehydrate(chunkId, pixels): Buffer {
|
dehydrate(chunkId, pixels) {
|
||||||
const index = new Uint8Array([OP_CODE, chunkId >> 8, chunkId & 0xFF]);
|
const index = new Uint8Array([OP_CODE, chunkId >> 8, chunkId & 0xFF]);
|
||||||
return Buffer.concat([index, pixels]);
|
return Buffer.concat([index, pixels]);
|
||||||
},
|
},
|
||||||
|
|
6
src/socket/packets/README.md
Normal file
6
src/socket/packets/README.md
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
# Binary Websocket Packages
|
||||||
|
|
||||||
|
Note that the node Server receives in [Buffer](https://nodejs.org/api/buffer.html), while the client receives [DataViews](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView) and sends ArrayBuffers.
|
||||||
|
Therefor the server can't share the same code with the client for hydrate / dehydrate.
|
||||||
|
Most packages are unidirectional so hydrate is for either client or server and dehydrate for the other one.
|
||||||
|
Bidrectional packages have two files, one for Client, another one for Server.
|
|
@ -1,16 +1,13 @@
|
||||||
/* @flow */
|
|
||||||
|
|
||||||
|
|
||||||
const OP_CODE = 0xA0;
|
const OP_CODE = 0xA0;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
OP_CODE,
|
OP_CODE,
|
||||||
hydrate(data: Buffer) {
|
hydrate(data) {
|
||||||
// SERVER (Client)
|
// SERVER (Receiver)
|
||||||
const canvasId = data[1];
|
const canvasId = data[1];
|
||||||
return canvasId;
|
return canvasId;
|
||||||
},
|
},
|
||||||
dehydrate(canvasId): ArrayBuffer {
|
dehydrate(canvasId) {
|
||||||
// CLIENT (Sender)
|
// CLIENT (Sender)
|
||||||
const buffer = new ArrayBuffer(1 + 1);
|
const buffer = new ArrayBuffer(1 + 1);
|
||||||
const view = new DataView(buffer);
|
const view = new DataView(buffer);
|
||||||
|
|
|
@ -1,16 +1,13 @@
|
||||||
/* @flow */
|
|
||||||
|
|
||||||
|
|
||||||
const OP_CODE = 0xA1;
|
const OP_CODE = 0xA1;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
OP_CODE,
|
OP_CODE,
|
||||||
hydrate(data: Buffer) {
|
hydrate(data) {
|
||||||
// SERVER (Client)
|
// SERVER (Receiver)
|
||||||
const i = data[1] << 8 | data[2];
|
const i = data[1] << 8 | data[2];
|
||||||
return i;
|
return i;
|
||||||
},
|
},
|
||||||
dehydrate(chunkid): ArrayBuffer {
|
dehydrate(chunkid) {
|
||||||
// CLIENT (Sender)
|
// CLIENT (Sender)
|
||||||
const buffer = new ArrayBuffer(1 + 2);
|
const buffer = new ArrayBuffer(1 + 2);
|
||||||
const view = new DataView(buffer);
|
const view = new DataView(buffer);
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
/* @flow */
|
|
||||||
|
|
||||||
|
|
||||||
const OP_CODE = 0xA3;
|
const OP_CODE = 0xA3;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
OP_CODE,
|
OP_CODE,
|
||||||
dehydrate(chunks: Array): ArrayBuffer {
|
/*
|
||||||
|
* @param chunks Array of chunks
|
||||||
|
*/
|
||||||
|
dehydrate(chunks) {
|
||||||
// CLIENT (Sender)
|
// CLIENT (Sender)
|
||||||
const buffer = new ArrayBuffer(1 + 1 + chunks.length * 2);
|
const buffer = new ArrayBuffer(1 + 1 + chunks.length * 2);
|
||||||
const view = new Uint16Array(buffer);
|
const view = new Uint16Array(buffer);
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Hooks for renderer
|
* Hooks for renderer
|
||||||
*
|
*
|
||||||
* @flow
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
|
|
@ -354,6 +354,22 @@ tr:nth-child(even) {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.convBox {
|
||||||
|
border-style: solid;
|
||||||
|
border-color: #D4D4D4;
|
||||||
|
border-width: 2px;
|
||||||
|
padding: 2px;
|
||||||
|
display: inline-block;
|
||||||
|
max-height: 0px;
|
||||||
|
visibility: hidden;
|
||||||
|
transition: 0.3s;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.convBox.show {
|
||||||
|
visibility: visible;
|
||||||
|
max-height: 160px;
|
||||||
|
}
|
||||||
|
|
||||||
#helpbutton {
|
#helpbutton {
|
||||||
left: 16px;
|
left: 16px;
|
||||||
top: 180px;
|
top: 180px;
|
||||||
|
|
|
@ -3,123 +3,93 @@
|
||||||
* https://github.com/Fyrestar/THREE.InfiniteGridHelper
|
* https://github.com/Fyrestar/THREE.InfiniteGridHelper
|
||||||
* MIT License
|
* MIT License
|
||||||
*
|
*
|
||||||
* @flow
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* eslint-disable max-len */
|
/* eslint-disable max-len */
|
||||||
|
|
||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
|
|
||||||
const InfiniteGridHelper = function InfiniteGridHelper(
|
export default class InfiniteGridHelper extends THREE.Mesh {
|
||||||
size1,
|
constructor(size1, size2, color, distance, axes = 'xzy') {
|
||||||
size2,
|
color = color || new THREE.Color('white');
|
||||||
color,
|
size1 = size1 || 10;
|
||||||
distance,
|
size2 = size2 || 100;
|
||||||
) {
|
|
||||||
color = color || new THREE.Color('white');
|
|
||||||
size1 = size1 || 10;
|
|
||||||
size2 = size2 || 100;
|
|
||||||
|
|
||||||
distance = distance || 8000;
|
distance = distance || 8000;
|
||||||
|
const planeAxes = axes.substr(0, 2);
|
||||||
|
const geometry = new THREE.PlaneBufferGeometry(2, 2, 1, 1);
|
||||||
|
const material = new THREE.ShaderMaterial({
|
||||||
|
|
||||||
const geometry = new THREE.PlaneBufferGeometry(2, 2, 1, 1);
|
side: THREE.DoubleSide,
|
||||||
|
|
||||||
const material = new THREE.ShaderMaterial({
|
uniforms: {
|
||||||
|
uSize1: { value: size1 },
|
||||||
side: THREE.DoubleSide,
|
uSize2: {
|
||||||
|
value: size2,
|
||||||
uniforms: {
|
},
|
||||||
uSize1: {
|
uColor: {
|
||||||
value: size1,
|
value: color,
|
||||||
|
},
|
||||||
|
uDistance: {
|
||||||
|
value: distance,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
uSize2: {
|
transparent: true,
|
||||||
value: size2,
|
|
||||||
|
vertexShader: `
|
||||||
|
varying vec3 worldPosition;
|
||||||
|
uniform float uDistance;
|
||||||
|
void main() {
|
||||||
|
vec3 pos = position.${axes} * uDistance;
|
||||||
|
pos.${planeAxes} += cameraPosition.${planeAxes};
|
||||||
|
|
||||||
|
worldPosition = pos;
|
||||||
|
|
||||||
|
gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
|
||||||
|
fragmentShader: `
|
||||||
|
varying vec3 worldPosition;
|
||||||
|
|
||||||
|
uniform float uSize1;
|
||||||
|
uniform float uSize2;
|
||||||
|
uniform vec3 uColor;
|
||||||
|
uniform float uDistance;
|
||||||
|
|
||||||
|
float getGrid(float size) {
|
||||||
|
vec2 r = worldPosition.${planeAxes} / size;
|
||||||
|
|
||||||
|
vec2 grid = abs(fract(r - 0.5) - 0.5) / fwidth(r);
|
||||||
|
float line = min(grid.x, grid.y);
|
||||||
|
|
||||||
|
return 1.0 - min(line, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
float d = 1.0 - min(distance(cameraPosition.${planeAxes}, worldPosition.${planeAxes}) / uDistance, 1.0);
|
||||||
|
|
||||||
|
float g1 = getGrid(uSize1);
|
||||||
|
float g2 = getGrid(uSize2);
|
||||||
|
|
||||||
|
|
||||||
|
gl_FragColor = vec4(uColor.rgb, mix(g2, g1, g1) * pow(d, 3.0));
|
||||||
|
gl_FragColor.a = mix(0.5 * gl_FragColor.a, gl_FragColor.a, g2);
|
||||||
|
|
||||||
|
if ( gl_FragColor.a <= 0.0 ) discard;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
`,
|
||||||
|
|
||||||
|
extensions: {
|
||||||
|
derivatives: true,
|
||||||
},
|
},
|
||||||
uColor: {
|
});
|
||||||
value: color,
|
|
||||||
},
|
|
||||||
uDistance: {
|
|
||||||
value: distance,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
transparent: true,
|
|
||||||
vertexShader: `
|
|
||||||
|
|
||||||
varying vec3 worldPosition;
|
|
||||||
|
|
||||||
uniform float uDistance;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
|
|
||||||
vec3 pos = position.xzy * uDistance;
|
|
||||||
pos.xz += cameraPosition.xz;
|
|
||||||
|
|
||||||
worldPosition = pos;
|
|
||||||
|
|
||||||
gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
|
|
||||||
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
|
|
||||||
|
super(geometry, material);
|
||||||
|
|
||||||
fragmentShader: `
|
this.frustumCulled = false;
|
||||||
|
}
|
||||||
varying vec3 worldPosition;
|
}
|
||||||
|
|
||||||
uniform float uSize1;
|
|
||||||
uniform float uSize2;
|
|
||||||
uniform vec3 uColor;
|
|
||||||
uniform float uDistance;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
float getGrid(float size) {
|
|
||||||
|
|
||||||
vec2 r = worldPosition.xz / size;
|
|
||||||
|
|
||||||
|
|
||||||
vec2 grid = abs(fract(r - 0.5) - 0.5) / fwidth(r);
|
|
||||||
float line = min(grid.x, grid.y);
|
|
||||||
|
|
||||||
|
|
||||||
return 1.0 - min(line, 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
|
|
||||||
|
|
||||||
float d = 1.0 - min(distance(cameraPosition.xz, worldPosition.xz) / uDistance, 1.0);
|
|
||||||
|
|
||||||
float g1 = getGrid(uSize1);
|
|
||||||
float g2 = getGrid(uSize2);
|
|
||||||
|
|
||||||
|
|
||||||
gl_FragColor = vec4(uColor.rgb, mix(g2, g1, g1) * pow(d, 3.0));
|
|
||||||
gl_FragColor.a = mix(0.5 * gl_FragColor.a, gl_FragColor.a, g2);
|
|
||||||
|
|
||||||
if ( gl_FragColor.a <= 0.0 ) discard;
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
`,
|
|
||||||
|
|
||||||
extensions: {
|
|
||||||
derivatives: true,
|
|
||||||
},
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
THREE.Mesh.call(this, geometry, material);
|
|
||||||
|
|
||||||
this.frustumCulled = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
InfiniteGridHelper.prototype = {
|
|
||||||
...THREE.Mesh.prototype,
|
|
||||||
...THREE.Object3D.prototype,
|
|
||||||
...THREE.EventDispatcher.prototype,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default InfiniteGridHelper;
|
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
import { Sky } from './Sky';
|
import Sky from './Sky';
|
||||||
|
|
||||||
import InfiniteGridHelper from './InfiniteGridHelper';
|
import InfiniteGridHelper from './InfiniteGridHelper';
|
||||||
import VoxelPainterControls from '../controls/VoxelPainterControls';
|
import VoxelPainterControls from '../controls/VoxelPainterControls';
|
||||||
|
@ -99,7 +99,6 @@ class Renderer {
|
||||||
rayleigh: 2,
|
rayleigh: 2,
|
||||||
mieCoefficient: 0.005,
|
mieCoefficient: 0.005,
|
||||||
mieDirectionalG: 0.8,
|
mieDirectionalG: 0.8,
|
||||||
luminance: 1,
|
|
||||||
inclination: 0.49, // elevation / inclination
|
inclination: 0.49, // elevation / inclination
|
||||||
azimuth: 0.25, // Facing front,
|
azimuth: 0.25, // Facing front,
|
||||||
sun: !true,
|
sun: !true,
|
||||||
|
@ -107,7 +106,6 @@ class Renderer {
|
||||||
const { uniforms } = sky.material;
|
const { uniforms } = sky.material;
|
||||||
uniforms.turbidity.value = effectController.turbidity;
|
uniforms.turbidity.value = effectController.turbidity;
|
||||||
uniforms.rayleigh.value = effectController.rayleigh;
|
uniforms.rayleigh.value = effectController.rayleigh;
|
||||||
uniforms.luminance.value = effectController.luminance;
|
|
||||||
uniforms.mieCoefficient.value = effectController.mieCoefficient;
|
uniforms.mieCoefficient.value = effectController.mieCoefficient;
|
||||||
uniforms.mieDirectionalG.value = effectController.mieDirectionalG;
|
uniforms.mieDirectionalG.value = effectController.mieDirectionalG;
|
||||||
uniforms.sunPosition.value.set(400000, 400000, 400000);
|
uniforms.sunPosition.value.set(400000, 400000, 400000);
|
||||||
|
|
340
src/ui/Sky.js
340
src/ui/Sky.js
|
@ -1,12 +1,13 @@
|
||||||
/**
|
/**
|
||||||
* @author zz85 / https://github.com/zz85
|
* From example code at:
|
||||||
|
* https://github.com/mrdoob/three.js/blob/dev/examples/js/objects/Sky.js
|
||||||
*
|
*
|
||||||
* Based on "A Practical Analytic Model for Daylight"
|
* Based on "A Practical Analytic Model for Daylight"
|
||||||
* aka The Preetham Model, the de facto standard analytic skydome model
|
* aka The Preetham Model, the de facto standard analytic skydome model
|
||||||
* http://www.cs.utah.edu/~shirley/papers/sunsky/sunsky.pdf
|
* https://www.researchgate.net/publication/220720443_A_Practical_Analytic_Model_for_Daylight
|
||||||
*
|
*
|
||||||
* First implemented by Simon Wallner
|
* First implemented by Simon Wallner
|
||||||
* http://www.simonwallner.at/projects/atmospheric-scattering
|
* http://simonwallner.at/project/atmospheric-scattering/
|
||||||
*
|
*
|
||||||
* Improved by Martin Upitis
|
* Improved by Martin Upitis
|
||||||
* http://blenderartists.org/forum/showthread.php?245954-preethams-sky-impementation-HDR
|
* http://blenderartists.org/forum/showthread.php?245954-preethams-sky-impementation-HDR
|
||||||
|
@ -14,224 +15,207 @@
|
||||||
* Three.js integration by zz85 http://twitter.com/blurspline
|
* Three.js integration by zz85 http://twitter.com/blurspline
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/* eslint-disable max-len */
|
||||||
* taken from three.js examples
|
|
||||||
*/
|
|
||||||
/* eslint-disable */
|
|
||||||
|
|
||||||
import {
|
import * as THREE from 'three';
|
||||||
BackSide,
|
|
||||||
BoxBufferGeometry,
|
|
||||||
Mesh,
|
|
||||||
ShaderMaterial,
|
|
||||||
UniformsUtils,
|
|
||||||
Vector3
|
|
||||||
} from 'three';
|
|
||||||
|
|
||||||
var Sky = function () {
|
export default class Sky extends THREE.Mesh {
|
||||||
|
static isSky = true;
|
||||||
|
|
||||||
var shader = Sky.SkyShader;
|
constructor() {
|
||||||
|
const shader = Sky.SkyShader;
|
||||||
var material = new ShaderMaterial( {
|
const material = new THREE.ShaderMaterial({
|
||||||
fragmentShader: shader.fragmentShader,
|
name: 'SkyShader',
|
||||||
vertexShader: shader.vertexShader,
|
fragmentShader: shader.fragmentShader,
|
||||||
uniforms: UniformsUtils.clone( shader.uniforms ),
|
vertexShader: shader.vertexShader,
|
||||||
side: BackSide
|
uniforms: THREE.UniformsUtils.clone(shader.uniforms),
|
||||||
} );
|
side: THREE.BackSide,
|
||||||
|
depthWrite: false,
|
||||||
Mesh.call( this, new BoxBufferGeometry( 1, 1, 1 ), material );
|
});
|
||||||
|
super(new THREE.BoxGeometry(1, 1, 1), material);
|
||||||
};
|
}
|
||||||
|
}
|
||||||
Sky.prototype = Object.create( Mesh.prototype );
|
|
||||||
|
|
||||||
Sky.SkyShader = {
|
Sky.SkyShader = {
|
||||||
|
uniforms: {
|
||||||
|
turbidity: {
|
||||||
|
value: 2,
|
||||||
|
},
|
||||||
|
rayleigh: {
|
||||||
|
value: 1,
|
||||||
|
},
|
||||||
|
mieCoefficient: {
|
||||||
|
value: 0.005,
|
||||||
|
},
|
||||||
|
mieDirectionalG: {
|
||||||
|
value: 0.8,
|
||||||
|
},
|
||||||
|
sunPosition: {
|
||||||
|
value: new THREE.Vector3(),
|
||||||
|
},
|
||||||
|
up: {
|
||||||
|
value: new THREE.Vector3(0, 1, 0),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
vertexShader:
|
||||||
|
/* glsl */
|
||||||
|
`
|
||||||
|
uniform vec3 sunPosition;
|
||||||
|
uniform float rayleigh;
|
||||||
|
uniform float turbidity;
|
||||||
|
uniform float mieCoefficient;
|
||||||
|
uniform vec3 up;
|
||||||
|
|
||||||
uniforms: {
|
varying vec3 vWorldPosition;
|
||||||
"luminance": { value: 1 },
|
varying vec3 vSunDirection;
|
||||||
"turbidity": { value: 2 },
|
varying float vSunfade;
|
||||||
"rayleigh": { value: 1 },
|
varying vec3 vBetaR;
|
||||||
"mieCoefficient": { value: 0.005 },
|
varying vec3 vBetaM;
|
||||||
"mieDirectionalG": { value: 0.8 },
|
varying float vSunE;
|
||||||
"sunPosition": { value: new Vector3() },
|
|
||||||
"up": { value: new Vector3( 0, 1, 0 ) }
|
|
||||||
},
|
|
||||||
|
|
||||||
vertexShader: [
|
// constants for atmospheric scattering
|
||||||
'uniform vec3 sunPosition;',
|
const float e = 2.71828182845904523536028747135266249775724709369995957;
|
||||||
'uniform float rayleigh;',
|
const float pi = 3.141592653589793238462643383279502884197169;
|
||||||
'uniform float turbidity;',
|
|
||||||
'uniform float mieCoefficient;',
|
|
||||||
'uniform vec3 up;',
|
|
||||||
|
|
||||||
'varying vec3 vWorldPosition;',
|
// wavelength of used primaries, according to preetham
|
||||||
'varying vec3 vSunDirection;',
|
const vec3 lambda = vec3( 680E-9, 550E-9, 450E-9 );
|
||||||
'varying float vSunfade;',
|
// this pre-calcuation replaces older TotalRayleigh(vec3 lambda) function:
|
||||||
'varying vec3 vBetaR;',
|
// (8.0 * pow(pi, 3.0) * pow(pow(n, 2.0) - 1.0, 2.0) * (6.0 + 3.0 * pn)) / (3.0 * N * pow(lambda, vec3(4.0)) * (6.0 - 7.0 * pn))
|
||||||
'varying vec3 vBetaM;',
|
const vec3 totalRayleigh = vec3( 5.804542996261093E-6, 1.3562911419845635E-5, 3.0265902468824876E-5 );
|
||||||
'varying float vSunE;',
|
|
||||||
|
|
||||||
// constants for atmospheric scattering
|
// mie stuff
|
||||||
'const float e = 2.71828182845904523536028747135266249775724709369995957;',
|
// K coefficient for the primaries
|
||||||
'const float pi = 3.141592653589793238462643383279502884197169;',
|
const float v = 4.0;
|
||||||
|
const vec3 K = vec3( 0.686, 0.678, 0.666 );
|
||||||
|
// MieConst = pi * pow( ( 2.0 * pi ) / lambda, vec3( v - 2.0 ) ) * K
|
||||||
|
const vec3 MieConst = vec3( 1.8399918514433978E14, 2.7798023919660528E14, 4.0790479543861094E14 );
|
||||||
|
|
||||||
// wavelength of used primaries, according to preetham
|
// earth shadow hack
|
||||||
'const vec3 lambda = vec3( 680E-9, 550E-9, 450E-9 );',
|
// cutoffAngle = pi / 1.95;
|
||||||
// this pre-calcuation replaces older TotalRayleigh(vec3 lambda) function:
|
const float cutoffAngle = 1.6110731556870734;
|
||||||
// (8.0 * pow(pi, 3.0) * pow(pow(n, 2.0) - 1.0, 2.0) * (6.0 + 3.0 * pn)) / (3.0 * N * pow(lambda, vec3(4.0)) * (6.0 - 7.0 * pn))
|
const float steepness = 1.5;
|
||||||
'const vec3 totalRayleigh = vec3( 5.804542996261093E-6, 1.3562911419845635E-5, 3.0265902468824876E-5 );',
|
const float EE = 1000.0;
|
||||||
|
|
||||||
// mie stuff
|
float sunIntensity( float zenithAngleCos ) {
|
||||||
// K coefficient for the primaries
|
zenithAngleCos = clamp( zenithAngleCos, -1.0, 1.0 );
|
||||||
'const float v = 4.0;',
|
return EE * max( 0.0, 1.0 - pow( e, -( ( cutoffAngle - acos( zenithAngleCos ) ) / steepness ) ) );
|
||||||
'const vec3 K = vec3( 0.686, 0.678, 0.666 );',
|
}
|
||||||
// MieConst = pi * pow( ( 2.0 * pi ) / lambda, vec3( v - 2.0 ) ) * K
|
|
||||||
'const vec3 MieConst = vec3( 1.8399918514433978E14, 2.7798023919660528E14, 4.0790479543861094E14 );',
|
|
||||||
|
|
||||||
// earth shadow hack
|
vec3 totalMie( float T ) {
|
||||||
// cutoffAngle = pi / 1.95;
|
float c = ( 0.2 * T ) * 10E-18;
|
||||||
'const float cutoffAngle = 1.6110731556870734;',
|
return 0.434 * c * MieConst;
|
||||||
'const float steepness = 1.5;',
|
}
|
||||||
'const float EE = 1000.0;',
|
|
||||||
|
|
||||||
'float sunIntensity( float zenithAngleCos ) {',
|
void main() {
|
||||||
' zenithAngleCos = clamp( zenithAngleCos, -1.0, 1.0 );',
|
|
||||||
' return EE * max( 0.0, 1.0 - pow( e, -( ( cutoffAngle - acos( zenithAngleCos ) ) / steepness ) ) );',
|
|
||||||
'}',
|
|
||||||
|
|
||||||
'vec3 totalMie( float T ) {',
|
vec4 worldPosition = modelMatrix * vec4( position, 1.0 );
|
||||||
' float c = ( 0.2 * T ) * 10E-18;',
|
vWorldPosition = worldPosition.xyz;
|
||||||
' return 0.434 * c * MieConst;',
|
|
||||||
'}',
|
|
||||||
|
|
||||||
'void main() {',
|
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
|
||||||
|
gl_Position.z = gl_Position.w; // set z to camera.far
|
||||||
|
|
||||||
' vec4 worldPosition = modelMatrix * vec4( position, 1.0 );',
|
vSunDirection = normalize( sunPosition );
|
||||||
' vWorldPosition = worldPosition.xyz;',
|
|
||||||
|
|
||||||
' gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );',
|
vSunE = sunIntensity( dot( vSunDirection, up ) );
|
||||||
' gl_Position.z = gl_Position.w;', // set z to camera.far
|
|
||||||
|
|
||||||
' vSunDirection = normalize( sunPosition );',
|
vSunfade = 1.0 - clamp( 1.0 - exp( ( sunPosition.y / 450000.0 ) ), 0.0, 1.0 );
|
||||||
|
|
||||||
' vSunE = sunIntensity( dot( vSunDirection, up ) );',
|
float rayleighCoefficient = rayleigh - ( 1.0 * ( 1.0 - vSunfade ) );
|
||||||
|
|
||||||
' vSunfade = 1.0 - clamp( 1.0 - exp( ( sunPosition.y / 450000.0 ) ), 0.0, 1.0 );',
|
// extinction (absorbtion + out scattering)
|
||||||
|
// rayleigh coefficients
|
||||||
|
vBetaR = totalRayleigh * rayleighCoefficient;
|
||||||
|
|
||||||
' float rayleighCoefficient = rayleigh - ( 1.0 * ( 1.0 - vSunfade ) );',
|
// mie coefficients
|
||||||
|
vBetaM = totalMie( turbidity ) * mieCoefficient;
|
||||||
|
|
||||||
// extinction (absorbtion + out scattering)
|
}`,
|
||||||
// rayleigh coefficients
|
fragmentShader:
|
||||||
' vBetaR = totalRayleigh * rayleighCoefficient;',
|
/* glsl */
|
||||||
|
`
|
||||||
|
varying vec3 vWorldPosition;
|
||||||
|
varying vec3 vSunDirection;
|
||||||
|
varying float vSunfade;
|
||||||
|
varying vec3 vBetaR;
|
||||||
|
varying vec3 vBetaM;
|
||||||
|
varying float vSunE;
|
||||||
|
|
||||||
// mie coefficients
|
uniform float mieDirectionalG;
|
||||||
' vBetaM = totalMie( turbidity ) * mieCoefficient;',
|
uniform vec3 up;
|
||||||
|
|
||||||
'}'
|
const vec3 cameraPos = vec3( 0.0, 0.0, 0.0 );
|
||||||
].join( '\n' ),
|
|
||||||
|
|
||||||
fragmentShader: [
|
// constants for atmospheric scattering
|
||||||
'varying vec3 vWorldPosition;',
|
const float pi = 3.141592653589793238462643383279502884197169;
|
||||||
'varying vec3 vSunDirection;',
|
|
||||||
'varying float vSunfade;',
|
|
||||||
'varying vec3 vBetaR;',
|
|
||||||
'varying vec3 vBetaM;',
|
|
||||||
'varying float vSunE;',
|
|
||||||
|
|
||||||
'uniform float luminance;',
|
const float n = 1.0003; // refractive index of air
|
||||||
'uniform float mieDirectionalG;',
|
const float N = 2.545E25; // number of molecules per unit volume for air at 288.15K and 1013mb (sea level -45 celsius)
|
||||||
'uniform vec3 up;',
|
|
||||||
|
|
||||||
'const vec3 cameraPos = vec3( 0.0, 0.0, 0.0 );',
|
// optical length at zenith for molecules
|
||||||
|
const float rayleighZenithLength = 8.4E3;
|
||||||
|
const float mieZenithLength = 1.25E3;
|
||||||
|
// 66 arc seconds -> degrees, and the cosine of that
|
||||||
|
const float sunAngularDiameterCos = 0.999956676946448443553574619906976478926848692873900859324;
|
||||||
|
|
||||||
// constants for atmospheric scattering
|
// 3.0 / ( 16.0 * pi )
|
||||||
'const float pi = 3.141592653589793238462643383279502884197169;',
|
const float THREE_OVER_SIXTEENPI = 0.05968310365946075;
|
||||||
|
// 1.0 / ( 4.0 * pi )
|
||||||
|
const float ONE_OVER_FOURPI = 0.07957747154594767;
|
||||||
|
|
||||||
'const float n = 1.0003;', // refractive index of air
|
float rayleighPhase( float cosTheta ) {
|
||||||
'const float N = 2.545E25;', // number of molecules per unit volume for air at 288.15K and 1013mb (sea level -45 celsius)
|
return THREE_OVER_SIXTEENPI * ( 1.0 + pow( cosTheta, 2.0 ) );
|
||||||
|
}
|
||||||
|
|
||||||
// optical length at zenith for molecules
|
float hgPhase( float cosTheta, float g ) {
|
||||||
'const float rayleighZenithLength = 8.4E3;',
|
float g2 = pow( g, 2.0 );
|
||||||
'const float mieZenithLength = 1.25E3;',
|
float inverse = 1.0 / pow( 1.0 - 2.0 * g * cosTheta + g2, 1.5 );
|
||||||
// 66 arc seconds -> degrees, and the cosine of that
|
return ONE_OVER_FOURPI * ( ( 1.0 - g2 ) * inverse );
|
||||||
'const float sunAngularDiameterCos = 0.999956676946448443553574619906976478926848692873900859324;',
|
}
|
||||||
|
|
||||||
// 3.0 / ( 16.0 * pi )
|
void main() {
|
||||||
'const float THREE_OVER_SIXTEENPI = 0.05968310365946075;',
|
|
||||||
// 1.0 / ( 4.0 * pi )
|
|
||||||
'const float ONE_OVER_FOURPI = 0.07957747154594767;',
|
|
||||||
|
|
||||||
'float rayleighPhase( float cosTheta ) {',
|
vec3 direction = normalize( vWorldPosition - cameraPos );
|
||||||
' return THREE_OVER_SIXTEENPI * ( 1.0 + pow( cosTheta, 2.0 ) );',
|
|
||||||
'}',
|
|
||||||
|
|
||||||
'float hgPhase( float cosTheta, float g ) {',
|
// optical length
|
||||||
' float g2 = pow( g, 2.0 );',
|
// cutoff angle at 90 to avoid singularity in next formula.
|
||||||
' float inverse = 1.0 / pow( 1.0 - 2.0 * g * cosTheta + g2, 1.5 );',
|
float zenithAngle = acos( max( 0.0, dot( up, direction ) ) );
|
||||||
' return ONE_OVER_FOURPI * ( ( 1.0 - g2 ) * inverse );',
|
float inverse = 1.0 / ( cos( zenithAngle ) + 0.15 * pow( 93.885 - ( ( zenithAngle * 180.0 ) / pi ), -1.253 ) );
|
||||||
'}',
|
float sR = rayleighZenithLength * inverse;
|
||||||
|
float sM = mieZenithLength * inverse;
|
||||||
|
|
||||||
// Filmic ToneMapping http://filmicgames.com/archives/75
|
// combined extinction factor
|
||||||
'const float A = 0.15;',
|
vec3 Fex = exp( -( vBetaR * sR + vBetaM * sM ) );
|
||||||
'const float B = 0.50;',
|
|
||||||
'const float C = 0.10;',
|
|
||||||
'const float D = 0.20;',
|
|
||||||
'const float E = 0.02;',
|
|
||||||
'const float F = 0.30;',
|
|
||||||
|
|
||||||
'const float whiteScale = 1.0748724675633854;', // 1.0 / Uncharted2Tonemap(1000.0)
|
// in scattering
|
||||||
|
float cosTheta = dot( direction, vSunDirection );
|
||||||
|
|
||||||
'vec3 Uncharted2Tonemap( vec3 x ) {',
|
float rPhase = rayleighPhase( cosTheta * 0.5 + 0.5 );
|
||||||
' return ( ( x * ( A * x + C * B ) + D * E ) / ( x * ( A * x + B ) + D * F ) ) - E / F;',
|
vec3 betaRTheta = vBetaR * rPhase;
|
||||||
'}',
|
|
||||||
|
|
||||||
|
float mPhase = hgPhase( cosTheta, mieDirectionalG );
|
||||||
|
vec3 betaMTheta = vBetaM * mPhase;
|
||||||
|
|
||||||
'void main() {',
|
vec3 Lin = pow( vSunE * ( ( betaRTheta + betaMTheta ) / ( vBetaR + vBetaM ) ) * ( 1.0 - Fex ), vec3( 1.5 ) );
|
||||||
// optical length
|
Lin *= mix( vec3( 1.0 ), pow( vSunE * ( ( betaRTheta + betaMTheta ) / ( vBetaR + vBetaM ) ) * Fex, vec3( 1.0 / 2.0 ) ), clamp( pow( 1.0 - dot( up, vSunDirection ), 5.0 ), 0.0, 1.0 ) );
|
||||||
// cutoff angle at 90 to avoid singularity in next formula.
|
|
||||||
' float zenithAngle = acos( max( 0.0, dot( up, normalize( vWorldPosition - cameraPos ) ) ) );',
|
|
||||||
' float inverse = 1.0 / ( cos( zenithAngle ) + 0.15 * pow( 93.885 - ( ( zenithAngle * 180.0 ) / pi ), -1.253 ) );',
|
|
||||||
' float sR = rayleighZenithLength * inverse;',
|
|
||||||
' float sM = mieZenithLength * inverse;',
|
|
||||||
|
|
||||||
// combined extinction factor
|
// nightsky
|
||||||
' vec3 Fex = exp( -( vBetaR * sR + vBetaM * sM ) );',
|
float theta = acos( direction.y ); // elevation --> y-axis, [-pi/2, pi/2]
|
||||||
|
float phi = atan( direction.z, direction.x ); // azimuth --> x-axis [-pi/2, pi/2]
|
||||||
|
vec2 uv = vec2( phi, theta ) / vec2( 2.0 * pi, pi ) + vec2( 0.5, 0.0 );
|
||||||
|
vec3 L0 = vec3( 0.1 ) * Fex;
|
||||||
|
|
||||||
// in scattering
|
// composition + solar disc
|
||||||
' float cosTheta = dot( normalize( vWorldPosition - cameraPos ), vSunDirection );',
|
float sundisk = smoothstep( sunAngularDiameterCos, sunAngularDiameterCos + 0.00002, cosTheta );
|
||||||
|
L0 += ( vSunE * 19000.0 * Fex ) * sundisk;
|
||||||
|
|
||||||
' float rPhase = rayleighPhase( cosTheta * 0.5 + 0.5 );',
|
vec3 texColor = ( Lin + L0 ) * 0.04 + vec3( 0.0, 0.0003, 0.00075 );
|
||||||
' vec3 betaRTheta = vBetaR * rPhase;',
|
|
||||||
|
|
||||||
' float mPhase = hgPhase( cosTheta, mieDirectionalG );',
|
vec3 retColor = pow( texColor, vec3( 1.0 / ( 1.2 + ( 1.2 * vSunfade ) ) ) );
|
||||||
' vec3 betaMTheta = vBetaM * mPhase;',
|
|
||||||
|
|
||||||
' vec3 Lin = pow( vSunE * ( ( betaRTheta + betaMTheta ) / ( vBetaR + vBetaM ) ) * ( 1.0 - Fex ), vec3( 1.5 ) );',
|
gl_FragColor = vec4( retColor, 1.0 );
|
||||||
' Lin *= mix( vec3( 1.0 ), pow( vSunE * ( ( betaRTheta + betaMTheta ) / ( vBetaR + vBetaM ) ) * Fex, vec3( 1.0 / 2.0 ) ), clamp( pow( 1.0 - dot( up, vSunDirection ), 5.0 ), 0.0, 1.0 ) );',
|
|
||||||
|
|
||||||
// nightsky
|
#include <tonemapping_fragment>
|
||||||
' vec3 direction = normalize( vWorldPosition - cameraPos );',
|
#include <encodings_fragment>
|
||||||
' float theta = acos( direction.y ); // elevation --> y-axis, [-pi/2, pi/2]',
|
|
||||||
' float phi = atan( direction.z, direction.x ); // azimuth --> x-axis [-pi/2, pi/2]',
|
|
||||||
' vec2 uv = vec2( phi, theta ) / vec2( 2.0 * pi, pi ) + vec2( 0.5, 0.0 );',
|
|
||||||
' vec3 L0 = vec3( 0.1 ) * Fex;',
|
|
||||||
|
|
||||||
// composition + solar disc
|
|
||||||
' float sundisk = smoothstep( sunAngularDiameterCos, sunAngularDiameterCos + 0.00002, cosTheta );',
|
|
||||||
' L0 += ( vSunE * 19000.0 * Fex ) * sundisk;',
|
|
||||||
|
|
||||||
' vec3 texColor = ( Lin + L0 ) * 0.04 + vec3( 0.0, 0.0003, 0.00075 );',
|
|
||||||
|
|
||||||
' vec3 curr = Uncharted2Tonemap( ( log2( 2.0 / pow( luminance, 4.0 ) ) ) * texColor );',
|
|
||||||
' vec3 color = curr * whiteScale;',
|
|
||||||
|
|
||||||
' vec3 retColor = pow( color, vec3( 1.0 / ( 1.2 + ( 1.2 * vSunfade ) ) ) );',
|
|
||||||
|
|
||||||
' gl_FragColor = vec4( retColor, 1.0 );',
|
|
||||||
|
|
||||||
'}'
|
|
||||||
].join( '\n' )
|
|
||||||
|
|
||||||
|
}`,
|
||||||
};
|
};
|
||||||
|
|
||||||
export { Sky };
|
|
||||||
|
|
|
@ -1,9 +1,3 @@
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @flow
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
export default class Counter<T> {
|
export default class Counter<T> {
|
||||||
map: Map<T, number>;
|
map: Map<T, number>;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* rate limiter utils
|
* rate limiter utils
|
||||||
* @flow
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
@ -11,11 +10,11 @@
|
||||||
* @param onCooldown If we force to wait the whole burst time once the limit is reached
|
* @param onCooldown If we force to wait the whole burst time once the limit is reached
|
||||||
*/
|
*/
|
||||||
class RateLimiter {
|
class RateLimiter {
|
||||||
msPerTick: number;
|
msPerTick;
|
||||||
burstTime: number;
|
burstTime;
|
||||||
cooldownCompletely: boolean;
|
cooldownCompletely;
|
||||||
onCooldown: boolean;
|
onCooldown;
|
||||||
wait: number;
|
wait;
|
||||||
|
|
||||||
constructor(ticksPerMin = 20, burst = 20, cooldownCompletely = false) {
|
constructor(ticksPerMin = 20, burst = 20, cooldownCompletely = false) {
|
||||||
this.wait = Date.now();
|
this.wait = Date.now();
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @flow
|
* check for captcha requirement
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import logger from '../core/logger';
|
import logger from '../core/logger';
|
||||||
|
@ -51,7 +51,7 @@ function evaluateChar(charC, charU) {
|
||||||
* Compare captcha to result
|
* Compare captcha to result
|
||||||
* @return true if same
|
* @return true if same
|
||||||
*/
|
*/
|
||||||
function evaluateResult(captchaText: string, userText: string) {
|
function evaluateResult(captchaText, userText) {
|
||||||
if (captchaText.length !== userText.length) {
|
if (captchaText.length !== userText.length) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -74,8 +74,8 @@ function evaluateResult(captchaText: string, userText: string) {
|
||||||
* @param ttl time to be valid in seconds
|
* @param ttl time to be valid in seconds
|
||||||
*/
|
*/
|
||||||
export function setCaptchaSolution(
|
export function setCaptchaSolution(
|
||||||
text: string,
|
text,
|
||||||
ip: string,
|
ip,
|
||||||
) {
|
) {
|
||||||
const key = `capt:${ip}`;
|
const key = `capt:${ip}`;
|
||||||
return redis.setAsync(key, text, 'EX', CAPTCHA_TIMEOUT);
|
return redis.setAsync(key, text, 'EX', CAPTCHA_TIMEOUT);
|
||||||
|
@ -91,8 +91,8 @@ export function setCaptchaSolution(
|
||||||
* 2 if wrong
|
* 2 if wrong
|
||||||
*/
|
*/
|
||||||
export async function checkCaptchaSolution(
|
export async function checkCaptchaSolution(
|
||||||
text: string,
|
text,
|
||||||
ip: string,
|
ip,
|
||||||
) {
|
) {
|
||||||
const ipn = getIPv6Subnet(ip);
|
const ipn = getIPv6Subnet(ip);
|
||||||
const key = `capt:${ip}`;
|
const key = `capt:${ip}`;
|
||||||
|
@ -119,13 +119,13 @@ export async function checkCaptchaSolution(
|
||||||
* @param ip
|
* @param ip
|
||||||
* @return boolean true if needed
|
* @return boolean true if needed
|
||||||
*/
|
*/
|
||||||
export async function needCaptcha(ip: string) {
|
export async function needCaptcha(ip) {
|
||||||
if (!CAPTCHA_URL) {
|
if (!CAPTCHA_URL) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const key = `human:${getIPv6Subnet(ip)}`;
|
const key = `human:${getIPv6Subnet(ip)}`;
|
||||||
const ttl: number = await redis.ttlAsync(key);
|
const ttl = await redis.ttlAsync(key);
|
||||||
if (ttl > 0) {
|
if (ttl > 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,3 @@
|
||||||
/* @flow
|
|
||||||
*/
|
|
||||||
|
|
||||||
function fallbackCopyTextToClipboard(text) {
|
function fallbackCopyTextToClipboard(text) {
|
||||||
const textArea = document.createElement('textarea');
|
const textArea = document.createElement('textarea');
|
||||||
textArea.value = text;
|
textArea.value = text;
|
||||||
|
|
|
@ -1,15 +1,14 @@
|
||||||
/*
|
/*
|
||||||
* password hashing
|
* password hashing
|
||||||
* @flow
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import bcrypt from 'bcrypt';
|
import bcrypt from 'bcrypt';
|
||||||
|
|
||||||
export function generateHash(password: string) {
|
export function generateHash(password) {
|
||||||
return bcrypt.hashSync(password, bcrypt.genSaltSync(8), null);
|
return bcrypt.hashSync(password, bcrypt.genSaltSync(8), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function compareToHash(password: string, hash: string) {
|
export function compareToHash(password, hash) {
|
||||||
if (!password || !hash) return false;
|
if (!password || !hash) return false;
|
||||||
return bcrypt.compareSync(password, hash);
|
return bcrypt.compareSync(password, hash);
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,9 @@
|
||||||
import { utils, distance, image } from 'image-q';
|
import { utils, distance, image } from 'image-q';
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* available color distance calculators
|
||||||
|
*/
|
||||||
export const ColorDistanceCalculators = [
|
export const ColorDistanceCalculators = [
|
||||||
'Euclidean',
|
'Euclidean',
|
||||||
'Manhattan',
|
'Manhattan',
|
||||||
|
@ -21,6 +24,9 @@ export const ColorDistanceCalculators = [
|
||||||
'ManhattanNommyde',
|
'ManhattanNommyde',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/*
|
||||||
|
* available dithers
|
||||||
|
*/
|
||||||
export const ImageQuantizerKernels = [
|
export const ImageQuantizerKernels = [
|
||||||
'Nearest',
|
'Nearest',
|
||||||
'Riemersma',
|
'Riemersma',
|
||||||
|
@ -35,7 +41,74 @@ export const ImageQuantizerKernels = [
|
||||||
'SierraLite',
|
'SierraLite',
|
||||||
];
|
];
|
||||||
|
|
||||||
export function getImageDataOfFile(file) {
|
export function addGrid(imgCanvas, lightGrid, offsetX, offsetY) {
|
||||||
|
const { width, height } = imgCanvas;
|
||||||
|
const can = document.createElement('canvas');
|
||||||
|
const ctx = can.getContext('2d');
|
||||||
|
can.width = width * 5;
|
||||||
|
can.height = height * 5;
|
||||||
|
ctx.imageSmoothingEnabled = false;
|
||||||
|
ctx.mozImageSmoothingEnabled = false;
|
||||||
|
ctx.webkitImageSmoothingEnabled = false;
|
||||||
|
ctx.msImageSmoothingEnabled = false;
|
||||||
|
ctx.save();
|
||||||
|
ctx.scale(5.0, 5.0);
|
||||||
|
ctx.drawImage(imgCanvas, 0, 0);
|
||||||
|
ctx.restore();
|
||||||
|
ctx.fillStyle = (lightGrid) ? '#DDDDDD' : '#222222';
|
||||||
|
for (let i = 0; i <= width; i += 1) {
|
||||||
|
const thick = ((i + (offsetX * 1)) % 10 === 0) ? 2 : 1;
|
||||||
|
ctx.fillRect(i * 5, 0, thick, can.height);
|
||||||
|
}
|
||||||
|
for (let j = 0; j <= height; j += 1) {
|
||||||
|
const thick = ((j + (offsetY * 1)) % 10 === 0) ? 2 : 1;
|
||||||
|
ctx.fillRect(0, j * 5, can.width, thick);
|
||||||
|
}
|
||||||
|
return can;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function scaleImage(imgCanvas, width, height, doAA) {
|
||||||
|
const can = document.createElement('canvas');
|
||||||
|
can.width = width;
|
||||||
|
can.height = height;
|
||||||
|
const ctxo = can.getContext('2d');
|
||||||
|
const scaleX = width / imgCanvas.width;
|
||||||
|
const scaleY = height / imgCanvas.height;
|
||||||
|
if (doAA) {
|
||||||
|
// scale with canvas for antialiasing
|
||||||
|
ctxo.save();
|
||||||
|
ctxo.scale(scaleX, scaleY);
|
||||||
|
ctxo.drawImage(imgCanvas, 0, 0);
|
||||||
|
ctxo.restore();
|
||||||
|
} else {
|
||||||
|
// scale manually
|
||||||
|
const ctxi = imgCanvas.getContext('2d');
|
||||||
|
const imdi = ctxi.getImageData(0, 0, imgCanvas.width, imgCanvas.height);
|
||||||
|
const imdo = ctxo.createImageData(width, height);
|
||||||
|
const { data: datai } = imdi;
|
||||||
|
const { data: datao } = imdo;
|
||||||
|
for (let y = 0; y < height; y += 1) {
|
||||||
|
for (let x = 0; x < width; x += 1) {
|
||||||
|
let posi = (Math.round(x / scaleX) + Math.round(y / scaleY)
|
||||||
|
* imgCanvas.width) * 4;
|
||||||
|
let poso = (x + y * width) * 4;
|
||||||
|
datao[poso++] = datai[posi++];
|
||||||
|
datao[poso++] = datai[posi++];
|
||||||
|
datao[poso++] = datai[posi++];
|
||||||
|
datao[poso] = datai[posi];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctxo.putImageData(imdo, 0, 0);
|
||||||
|
}
|
||||||
|
return can;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* read File object into canvas
|
||||||
|
* @param file
|
||||||
|
* @return HTMLCanvas
|
||||||
|
*/
|
||||||
|
export function readFileIntoCanvas(file) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const fr = new FileReader();
|
const fr = new FileReader();
|
||||||
fr.onload = () => {
|
fr.onload = () => {
|
||||||
|
@ -46,8 +119,7 @@ export function getImageDataOfFile(file) {
|
||||||
cani.height = img.height;
|
cani.height = img.height;
|
||||||
const ctxi = cani.getContext('2d');
|
const ctxi = cani.getContext('2d');
|
||||||
ctxi.drawImage(img, 0, 0);
|
ctxi.drawImage(img, 0, 0);
|
||||||
const imdi = ctxi.getImageData(0, 0, img.width, img.height);
|
resolve(cani);
|
||||||
resolve(imdi);
|
|
||||||
};
|
};
|
||||||
img.onerror = (error) => reject(error);
|
img.onerror = (error) => reject(error);
|
||||||
img.src = fr.result;
|
img.src = fr.result;
|
||||||
|
@ -57,49 +129,12 @@ export function getImageDataOfFile(file) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function* loadData(data, pointArray) {
|
/*
|
||||||
let i = 0;
|
* converts pointContainer to HTMLCanvas
|
||||||
while (i < data.length) {
|
* @param pointContainer
|
||||||
/*
|
* @return HTMLCanvas
|
||||||
if (!(i % 80)) {
|
*/
|
||||||
yield Math.floor(i / data.length);
|
function createCanvasFromPointContainer(pointContainer) {
|
||||||
}
|
|
||||||
*/
|
|
||||||
const point = utils.Point.createByRGBA(
|
|
||||||
data[i++],
|
|
||||||
data[i++],
|
|
||||||
data[i++],
|
|
||||||
data[i++],
|
|
||||||
);
|
|
||||||
pointArray.push(point);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function createPointContainerFromImageData(imageData, onProgress) {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
const { width, height, data } = imageData;
|
|
||||||
const pointContainer = new utils.PointContainer();
|
|
||||||
pointContainer.setWidth(width);
|
|
||||||
pointContainer.setHeight(height);
|
|
||||||
const pointArray = pointContainer.getPointArray();
|
|
||||||
|
|
||||||
const iterator = loadData(data, pointArray);
|
|
||||||
const next = () => {
|
|
||||||
const result = iterator.next();
|
|
||||||
if (result.done) {
|
|
||||||
resolve(pointContainer);
|
|
||||||
} else {
|
|
||||||
if (onProgress) {
|
|
||||||
onProgress(result.value);
|
|
||||||
}
|
|
||||||
setTimeout(next, 0);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
setTimeout(next, 0);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function createImageDataFromPointContainer(pointContainer) {
|
|
||||||
const width = pointContainer.getWidth();
|
const width = pointContainer.getWidth();
|
||||||
const height = pointContainer.getHeight();
|
const height = pointContainer.getHeight();
|
||||||
const data = pointContainer.toUint8Array();
|
const data = pointContainer.toUint8Array();
|
||||||
|
@ -109,9 +144,24 @@ function createImageDataFromPointContainer(pointContainer) {
|
||||||
const ctx = can.getContext('2d');
|
const ctx = can.getContext('2d');
|
||||||
const idata = ctx.createImageData(width, height);
|
const idata = ctx.createImageData(width, height);
|
||||||
idata.data.set(data);
|
idata.data.set(data);
|
||||||
return idata;
|
ctx.putImageData(idata, 0, 0);
|
||||||
|
return can;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* quantizes point container
|
||||||
|
* @param colors Array of [r, g, b] color Arrays
|
||||||
|
* @param pointContainer pointContainer of input image
|
||||||
|
* @param opts Object with configuration:
|
||||||
|
* strategy: String of dithering strategy (ImageQuantizerKernels)
|
||||||
|
* colorDist: String of color distance calc (ColorDistanceCalculators)
|
||||||
|
* onProgress: function that gets called with integer of progress percentage
|
||||||
|
* and only available for not Nearest or Riemersma
|
||||||
|
* serpentine: type of dithering
|
||||||
|
* minColorDistance: minimal distance on which we start to dither
|
||||||
|
* GIMPerror: calculate error like GIMP
|
||||||
|
* @return Promise that resolves to HTMLCanvasElement of output image
|
||||||
|
*/
|
||||||
function quantizePointContainer(colors, pointContainer, opts) {
|
function quantizePointContainer(colors, pointContainer, opts) {
|
||||||
const strategy = opts.strategy || 'Nearest';
|
const strategy = opts.strategy || 'Nearest';
|
||||||
const colorDist = opts.colorDist || 'Euclidean';
|
const colorDist = opts.colorDist || 'Euclidean';
|
||||||
|
@ -164,7 +214,7 @@ function quantizePointContainer(colors, pointContainer, opts) {
|
||||||
default:
|
default:
|
||||||
distCalc = new distance.Euclidean();
|
distCalc = new distance.Euclidean();
|
||||||
}
|
}
|
||||||
// idk why i need this :/
|
// could be needed for some reason sometimes
|
||||||
// eslint-disable-next-line no-underscore-dangle
|
// eslint-disable-next-line no-underscore-dangle
|
||||||
if (distCalc._setDefaults) distCalc._setDefaults();
|
if (distCalc._setDefaults) distCalc._setDefaults();
|
||||||
// construct image quantizer
|
// construct image quantizer
|
||||||
|
@ -174,12 +224,23 @@ function quantizePointContainer(colors, pointContainer, opts) {
|
||||||
} else if (strategy === 'Riemersma') {
|
} else if (strategy === 'Riemersma') {
|
||||||
imageQuantizer = new image.ErrorDiffusionRiemersma(distCalc);
|
imageQuantizer = new image.ErrorDiffusionRiemersma(distCalc);
|
||||||
} else {
|
} else {
|
||||||
|
// minColorDistance is a percentage
|
||||||
|
let minColorDistance = 0;
|
||||||
|
// eslint-disable-next-line no-prototype-builtins
|
||||||
|
if (opts.hasOwnProperty('minColorDistance')) {
|
||||||
|
const mcdNumber = Number(opts.minColorDistance);
|
||||||
|
if (!Number.isNaN(mcdNumber)) {
|
||||||
|
minColorDistance = mcdNumber / 100 * 0.2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
imageQuantizer = new image.ErrorDiffusionArray(
|
imageQuantizer = new image.ErrorDiffusionArray(
|
||||||
distCalc,
|
distCalc,
|
||||||
image.ErrorDiffusionArrayKernel[strategy],
|
image.ErrorDiffusionArrayKernel[strategy],
|
||||||
true,
|
// eslint-disable-next-line no-prototype-builtins
|
||||||
0,
|
opts.hasOwnProperty('serpentine') ? opts.serpentine : true,
|
||||||
false,
|
minColorDistance,
|
||||||
|
!!opts.GIMPerror,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// quantize
|
// quantize
|
||||||
|
@ -188,7 +249,7 @@ function quantizePointContainer(colors, pointContainer, opts) {
|
||||||
try {
|
try {
|
||||||
const result = iterator.next();
|
const result = iterator.next();
|
||||||
if (result.done) {
|
if (result.done) {
|
||||||
resolve(createImageDataFromPointContainer(pointContainer));
|
resolve(createCanvasFromPointContainer(pointContainer));
|
||||||
} else {
|
} else {
|
||||||
if (result.value.pointContainer) {
|
if (result.value.pointContainer) {
|
||||||
pointContainer = result.value.pointContainer;
|
pointContainer = result.value.pointContainer;
|
||||||
|
@ -208,17 +269,17 @@ function quantizePointContainer(colors, pointContainer, opts) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function quantizeImage(colors, imageData, opts) {
|
/*
|
||||||
return createPointContainerFromImageData(
|
* quantize HTMLCanvas to palette
|
||||||
imageData,
|
* see quantizePointContainer for parameter meanings
|
||||||
(value) => {
|
*/
|
||||||
if (opts.onProgress) {
|
export function quantizeImage(colors, imageCanvas, opts) {
|
||||||
opts.onProgress(value);
|
const pointContainer = utils.PointContainer.fromHTMLCanvasElement(
|
||||||
}
|
imageCanvas,
|
||||||
},
|
);
|
||||||
).then((pointContainer) => quantizePointContainer(
|
return quantizePointContainer(
|
||||||
colors,
|
colors,
|
||||||
pointContainer,
|
pointContainer,
|
||||||
opts,
|
opts,
|
||||||
));
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user