forked from ppfun/pixelplanet
change from rgbquant to image-q
This commit is contained in:
parent
9493172034
commit
d1502882c1
20
package-lock.json
generated
20
package-lock.json
generated
|
@ -7839,6 +7839,21 @@
|
||||||
"minimatch": "^3.0.4"
|
"minimatch": "^3.0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"image-q": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/image-q/-/image-q-2.1.2.tgz",
|
||||||
|
"integrity": "sha1-husyiz8bK7RiP5WjTdHOTLbOz+k=",
|
||||||
|
"requires": {
|
||||||
|
"@types/node": "9.4.6"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": {
|
||||||
|
"version": "9.4.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-9.4.6.tgz",
|
||||||
|
"integrity": "sha512-CTUtLb6WqCCgp6P59QintjHWqzf4VL1uPA27bipLAPxFqrtK1gEYllePzTICGqQ8rYsCbpnsNypXjjDzGAAjEQ=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"immediate": {
|
"immediate": {
|
||||||
"version": "3.0.6",
|
"version": "3.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
|
||||||
|
@ -11285,11 +11300,6 @@
|
||||||
"any-promise": "^1.3.0"
|
"any-promise": "^1.3.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"rgbquant": {
|
|
||||||
"version": "1.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/rgbquant/-/rgbquant-1.1.2.tgz",
|
|
||||||
"integrity": "sha1-2V6Imo/LHmwaT6LMyL46QaL80YU="
|
|
||||||
},
|
|
||||||
"rimraf": {
|
"rimraf": {
|
||||||
"version": "3.0.2",
|
"version": "3.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
|
||||||
|
|
|
@ -41,6 +41,7 @@
|
||||||
"global": "^4.3.2",
|
"global": "^4.3.2",
|
||||||
"hammerjs": "^2.0.8",
|
"hammerjs": "^2.0.8",
|
||||||
"http-proxy-agent": "^4.0.1",
|
"http-proxy-agent": "^4.0.1",
|
||||||
|
"image-q": "^2.1.2",
|
||||||
"ip-address": "^6.3.0",
|
"ip-address": "^6.3.0",
|
||||||
"isomorphic-fetch": "^2.2.1",
|
"isomorphic-fetch": "^2.2.1",
|
||||||
"js-file-download": "^0.4.12",
|
"js-file-download": "^0.4.12",
|
||||||
|
@ -72,7 +73,6 @@
|
||||||
"redux-logger": "^3.0.6",
|
"redux-logger": "^3.0.6",
|
||||||
"redux-persist": "^6.0.0",
|
"redux-persist": "^6.0.0",
|
||||||
"redux-thunk": "^2.2.0",
|
"redux-thunk": "^2.2.0",
|
||||||
"rgbquant": "^1.1.2",
|
|
||||||
"sequelize": "^5.21.7",
|
"sequelize": "^5.21.7",
|
||||||
"sharp": "^0.25.2",
|
"sharp": "^0.25.2",
|
||||||
"startaudiocontext": "^1.2.1",
|
"startaudiocontext": "^1.2.1",
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import fileDownload from 'js-file-download';
|
import fileDownload from 'js-file-download';
|
||||||
import RgbQuant from 'rgbquant';
|
import { utils, applyPalette } from 'image-q';
|
||||||
|
|
||||||
import printGIMPPalette from '../core/exportGPL';
|
import printGIMPPalette from '../core/exportGPL';
|
||||||
import type { State } from '../reducers';
|
import type { State } from '../reducers';
|
||||||
|
@ -42,8 +42,7 @@ function downloadOutput() {
|
||||||
function readFile(
|
function readFile(
|
||||||
file,
|
file,
|
||||||
selectFile,
|
selectFile,
|
||||||
selectScaleWidth,
|
setScaleData,
|
||||||
selectScaleHeight,
|
|
||||||
) {
|
) {
|
||||||
if (!file) {
|
if (!file) {
|
||||||
return;
|
return;
|
||||||
|
@ -52,8 +51,12 @@ function readFile(
|
||||||
fr.onload = () => {
|
fr.onload = () => {
|
||||||
const img = new Image();
|
const img = new Image();
|
||||||
img.onload = () => {
|
img.onload = () => {
|
||||||
selectScaleWidth(img.width);
|
setScaleData({
|
||||||
selectScaleHeight(img.height);
|
enabled: false,
|
||||||
|
width: img.width,
|
||||||
|
height: img.height,
|
||||||
|
aa: true,
|
||||||
|
});
|
||||||
selectFile(img);
|
selectFile(img);
|
||||||
};
|
};
|
||||||
img.src = fr.result;
|
img.src = fr.result;
|
||||||
|
@ -106,82 +109,123 @@ function addGrid(img, lightGrid, offsetX, offsetY) {
|
||||||
|
|
||||||
function scaleImage(img, width, height, doAA) {
|
function scaleImage(img, width, height, doAA) {
|
||||||
const can = document.createElement('canvas');
|
const can = document.createElement('canvas');
|
||||||
const ctx = can.getContext('2d');
|
const ctxo = can.getContext('2d');
|
||||||
can.width = width;
|
can.width = width;
|
||||||
can.height = height;
|
can.height = height;
|
||||||
|
const scaleX = width / img.width;
|
||||||
|
const scaleY = height / img.height;
|
||||||
if (doAA) {
|
if (doAA) {
|
||||||
ctx.imageSmoothingEnabled = true;
|
// scale with canvas for antialiasing
|
||||||
ctx.mozImageSmoothingEnabled = true;
|
ctxo.save();
|
||||||
ctx.webkitImageSmoothingEnabled = true;
|
ctxo.scale(scaleX, scaleY);
|
||||||
ctx.msImageSmoothingEnabled = true;
|
ctxo.drawImage(img, 0, 0);
|
||||||
} else {
|
ctxo.restore();
|
||||||
ctx.imageSmoothingEnabled = false;
|
return can;
|
||||||
ctx.mozImageSmoothingEnabled = false;
|
|
||||||
ctx.webkitImageSmoothingEnabled = false;
|
|
||||||
ctx.msImageSmoothingEnabled = false;
|
|
||||||
}
|
}
|
||||||
ctx.save();
|
// scale manually
|
||||||
ctx.scale(width / img.width, height / img.height);
|
const imdo = ctxo.createImageData(width, height);
|
||||||
ctx.drawImage(img, 0, 0);
|
const { data: datao } = imdo;
|
||||||
ctx.restore();
|
const cani = document.createElement('canvas');
|
||||||
|
cani.width = img.width;
|
||||||
|
cani.height = img.height;
|
||||||
|
const ctxi = cani.getContext('2d');
|
||||||
|
ctxi.drawImage(img, 0, 0);
|
||||||
|
const imdi = ctxi.getImageData(0, 0, img.width, img.height);
|
||||||
|
const { data: datai } = imdi;
|
||||||
|
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)
|
||||||
|
* img.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;
|
return can;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderOutputImage(
|
let newOpts = null;
|
||||||
colors,
|
let rendering = false;
|
||||||
selectedFile,
|
async function renderOutputImage(opts) {
|
||||||
selectedStrategy,
|
if (!opts.file) {
|
||||||
selectedSerp,
|
|
||||||
selectedColorDist,
|
|
||||||
selectedDithDelta,
|
|
||||||
selectedAddGrid,
|
|
||||||
selectedLightGrid,
|
|
||||||
selectedGridOffsetX,
|
|
||||||
selectedGridOffsetY,
|
|
||||||
selectedDoScaling,
|
|
||||||
selectedScaleWidth,
|
|
||||||
selectedScaleHeight,
|
|
||||||
selectedScaleAA,
|
|
||||||
) {
|
|
||||||
if (!selectedFile) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let image = selectedFile;
|
if (rendering) {
|
||||||
if (selectedDoScaling) {
|
newOpts = opts;
|
||||||
image = scaleImage(
|
return;
|
||||||
image,
|
|
||||||
selectedScaleWidth,
|
|
||||||
selectedScaleHeight,
|
|
||||||
selectedScaleAA,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
const rgbQuant = new RgbQuant({
|
rendering = true;
|
||||||
colors: colors.length,
|
let renderOpts = opts;
|
||||||
dithKern: selectedStrategy,
|
while (renderOpts) {
|
||||||
dithDelta: selectedDithDelta / 100,
|
newOpts = null;
|
||||||
dithSerp: selectedSerp,
|
const {
|
||||||
palette: colors,
|
file, dither, grid, scaling,
|
||||||
reIndex: false,
|
} = renderOpts;
|
||||||
useCache: false,
|
if (file) {
|
||||||
colorDist: selectedColorDist,
|
let image = file;
|
||||||
});
|
let pointContainer = null;
|
||||||
rgbQuant.sample(image);
|
if (scaling.enabled) {
|
||||||
rgbQuant.palette();
|
// scale
|
||||||
const pxls = rgbQuant.reduce(image);
|
const { width, height, aa } = scaling;
|
||||||
image = drawPixels(pxls, image.width, image.height);
|
image = scaleImage(
|
||||||
const output = document.getElementById('imgoutput');
|
file,
|
||||||
if (selectedAddGrid) {
|
width,
|
||||||
image = addGrid(
|
height,
|
||||||
image,
|
aa,
|
||||||
selectedLightGrid,
|
);
|
||||||
selectedGridOffsetX,
|
pointContainer = utils.PointContainer.fromHTMLCanvasElement(image);
|
||||||
selectedGridOffsetY,
|
} else {
|
||||||
);
|
pointContainer = utils.PointContainer.fromHTMLImageElement(image);
|
||||||
|
}
|
||||||
|
// dither
|
||||||
|
const { colors, strategy, colorDist } = dither;
|
||||||
|
const palette = new utils.Palette();
|
||||||
|
palette.add(utils.Point.createByRGBA(0, 0, 0, 0));
|
||||||
|
colors.forEach((clr) => {
|
||||||
|
const [r, g, b] = clr;
|
||||||
|
const point = utils.Point.createByRGBA(r, g, b, 255);
|
||||||
|
palette.add(point);
|
||||||
|
});
|
||||||
|
const progEl = document.getElementById('qprog');
|
||||||
|
progEl.style.display = 'block';
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
pointContainer = await applyPalette(pointContainer, palette, {
|
||||||
|
colorDistanceFormula: colorDist,
|
||||||
|
imageQuantization: strategy,
|
||||||
|
onProgress: (progress) => {
|
||||||
|
progEl.innerHTML = `Loading... ${Math.round(progress)} %`;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
progEl.style.display = 'none';
|
||||||
|
image = drawPixels(
|
||||||
|
pointContainer.toUint8Array(),
|
||||||
|
image.width,
|
||||||
|
image.height,
|
||||||
|
);
|
||||||
|
// grid
|
||||||
|
if (grid.enabled) {
|
||||||
|
const { light, offsetX, offsetY } = grid;
|
||||||
|
image = addGrid(
|
||||||
|
image,
|
||||||
|
light,
|
||||||
|
offsetX,
|
||||||
|
offsetY,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// draw
|
||||||
|
const output = document.getElementById('imgoutput');
|
||||||
|
output.width = image.width;
|
||||||
|
output.height = image.height;
|
||||||
|
const ctx = output.getContext('2d');
|
||||||
|
ctx.drawImage(image, 0, 0);
|
||||||
|
}
|
||||||
|
// render again if requested in the meantime
|
||||||
|
renderOpts = newOpts;
|
||||||
}
|
}
|
||||||
output.width = image.width;
|
rendering = false;
|
||||||
output.height = image.height;
|
|
||||||
const ctx = output.getContext('2d');
|
|
||||||
ctx.drawImage(image, 0, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -191,41 +235,39 @@ function Converter({
|
||||||
}) {
|
}) {
|
||||||
const [selectedCanvas, selectCanvas] = useState(canvasId);
|
const [selectedCanvas, selectCanvas] = useState(canvasId);
|
||||||
const [selectedFile, selectFile] = useState(null);
|
const [selectedFile, selectFile] = useState(null);
|
||||||
const [selectedStrategy, selectStrategy] = useState('');
|
const [selectedStrategy, selectStrategy] = useState('nearest');
|
||||||
const [selectedSerp, selectSerp] = useState(false);
|
|
||||||
const [selectedColorDist, selectColorDist] = useState('euclidean');
|
const [selectedColorDist, selectColorDist] = useState('euclidean');
|
||||||
const [selectedDithDelta, selectDithDelta] = useState(0);
|
|
||||||
const [selectedAddGrid, selectAddGrid] = useState(true);
|
|
||||||
const [selectedLightGrid, selectLightGrid] = useState(false);
|
|
||||||
const [selectedGridOffsetX, selectGridOffsetX] = useState(0);
|
|
||||||
const [selectedGridOffsetY, selectGridOffsetY] = useState(0);
|
|
||||||
const [selectedDoScaling, selectDoScaling] = useState(false);
|
|
||||||
const [selectedScaleWidth, selectScaleWidth] = useState(0);
|
|
||||||
const [selectedScaleHeight, selectScaleHeight] = useState(0);
|
|
||||||
const [selectedScaleKeepRatio, selectScaleKeepRatio] = useState(true);
|
const [selectedScaleKeepRatio, selectScaleKeepRatio] = useState(true);
|
||||||
const [selectedScaleAA, selectScaleAA] = useState(false);
|
const [scaleData, setScaleData] = useState({
|
||||||
|
enabled: false,
|
||||||
|
width: 10,
|
||||||
|
height: 10,
|
||||||
|
aa: true,
|
||||||
|
});
|
||||||
|
const [gridData, setGridData] = useState({
|
||||||
|
enabled: true,
|
||||||
|
light: false,
|
||||||
|
offsetX: 0,
|
||||||
|
offsetY: 0,
|
||||||
|
});
|
||||||
const input = document.createElement('canvas');
|
const input = document.createElement('canvas');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedFile) {
|
if (selectedFile) {
|
||||||
const canvas = canvases[selectedCanvas];
|
const canvas = canvases[selectedCanvas];
|
||||||
renderOutputImage(
|
const dither = {
|
||||||
canvas.colors.slice(canvas.cli),
|
colors: canvas.colors.slice(canvas.cli),
|
||||||
selectedFile,
|
strategy: selectedStrategy,
|
||||||
selectedStrategy,
|
colorDist: selectedColorDist,
|
||||||
selectedSerp,
|
};
|
||||||
selectedColorDist,
|
renderOutputImage({
|
||||||
selectedDithDelta,
|
file: selectedFile,
|
||||||
selectedAddGrid,
|
dither,
|
||||||
selectedLightGrid,
|
grid: gridData,
|
||||||
selectedGridOffsetX,
|
scaling: scaleData,
|
||||||
selectedGridOffsetY,
|
});
|
||||||
selectedDoScaling,
|
|
||||||
selectedScaleWidth,
|
|
||||||
selectedScaleHeight,
|
|
||||||
selectedScaleAA,
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
|
// draw gray rectanglue if no file selected
|
||||||
const output = document.getElementById('imgoutput');
|
const output = document.getElementById('imgoutput');
|
||||||
const ctx = output.getContext('2d');
|
const ctx = output.getContext('2d');
|
||||||
output.width = 128;
|
output.width = 128;
|
||||||
|
@ -235,6 +277,19 @@ function Converter({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
enabled: gridEnabled,
|
||||||
|
light: gridLight,
|
||||||
|
offsetX: gridOffsetX,
|
||||||
|
offsetY: gridOffsetY,
|
||||||
|
} = gridData;
|
||||||
|
const {
|
||||||
|
enabled: scalingEnabled,
|
||||||
|
width: scalingWidth,
|
||||||
|
height: scalingHeight,
|
||||||
|
aa: scalingAA,
|
||||||
|
} = scaleData;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<p style={{ textAlign: 'center' }}>
|
<p style={{ textAlign: 'center' }}>
|
||||||
<p style={textStyle}>Choose Canvas:
|
<p style={textStyle}>Choose Canvas:
|
||||||
|
@ -291,7 +346,7 @@ 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];
|
||||||
readFile(file, selectFile, selectScaleWidth, selectScaleHeight);
|
readFile(file, selectFile, setScaleData);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<p style={textStyle}>Choose Strategy:
|
<p style={textStyle}>Choose Strategy:
|
||||||
|
@ -301,20 +356,18 @@ function Converter({
|
||||||
selectStrategy(sel.options[sel.selectedIndex].value);
|
selectStrategy(sel.options[sel.selectedIndex].value);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<option
|
|
||||||
value=""
|
|
||||||
selected={(selectedStrategy === '')}
|
|
||||||
>Default</option>
|
|
||||||
{
|
{
|
||||||
['FloydSteinberg',
|
['nearest',
|
||||||
'Stucki',
|
'riemersma',
|
||||||
'Atkinson',
|
'floyd-steinberg',
|
||||||
'Jarvis',
|
'false-floyd-steinberg',
|
||||||
'Burkes',
|
'stucki',
|
||||||
'Sierra',
|
'atkinson',
|
||||||
'TwoSierra',
|
'jarvis',
|
||||||
'SierraLite',
|
'burkes',
|
||||||
'FalseFloydSteinberg'].map((strat) => (
|
'sierra',
|
||||||
|
'two-sierra',
|
||||||
|
'sierra-lite'].map((strat) => (
|
||||||
<option
|
<option
|
||||||
value={strat}
|
value={strat}
|
||||||
selected={(selectedStrategy === strat)}
|
selected={(selectedStrategy === strat)}
|
||||||
|
@ -323,50 +376,47 @@ function Converter({
|
||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
</p>
|
</p>
|
||||||
<span style={{ ...textStyle, fontHeight: 16 }}>
|
<p style={textStyle}>Choose Color Mode:
|
||||||
<input
|
<select
|
||||||
type="checkbox"
|
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
selectSerp(e.target.checked);
|
const sel = e.target;
|
||||||
|
selectColorDist(sel.options[sel.selectedIndex].value);
|
||||||
}}
|
}}
|
||||||
/>
|
>
|
||||||
Serpentine Pattern Dithering
|
{
|
||||||
</span>
|
['cie94-textiles',
|
||||||
<span style={{ ...textStyle, fontHeight: 16 }}>
|
'cie94-graphic-arts',
|
||||||
<input
|
'ciede2000',
|
||||||
type="checkbox"
|
'color-metric',
|
||||||
onClick={(e) => {
|
'euclidean',
|
||||||
const colorDist = (e.target.checked)
|
'euclidean-bt709-noalpha',
|
||||||
? 'euclidean' : 'manhatten';
|
'euclidean-bt709',
|
||||||
selectColorDist(colorDist);
|
'manhattan',
|
||||||
}}
|
'manhattan-bt709',
|
||||||
/>
|
'manhattan-nommyde',
|
||||||
Manhatten Color Distance
|
'pngquant'].map((strat) => (
|
||||||
</span>
|
<option
|
||||||
<p style={textStyle}>Dithering Delta:
|
value={strat}
|
||||||
<input
|
selected={(selectedColorDist === strat)}
|
||||||
type="number"
|
>{strat}</option>
|
||||||
step="1"
|
))
|
||||||
min="0"
|
}
|
||||||
max="100"
|
</select>
|
||||||
style={{ width: '3em' }}
|
|
||||||
value={selectedDithDelta}
|
|
||||||
onChange={(e) => {
|
|
||||||
selectDithDelta(e.target.value);
|
|
||||||
}}
|
|
||||||
/>%
|
|
||||||
</p>
|
</p>
|
||||||
<p style={{ ...textStyle, fontHeight: 16 }}>
|
<p style={{ ...textStyle, fontHeight: 16 }}>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={selectedAddGrid}
|
checked={gridEnabled}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
selectAddGrid(e.target.checked);
|
setGridData({
|
||||||
|
...gridData,
|
||||||
|
enabled: e.target.checked,
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
Add Grid (uncheck if you need a 1:1 template)
|
Add Grid (uncheck if you need a 1:1 template)
|
||||||
</p>
|
</p>
|
||||||
{(selectedAddGrid)
|
{(gridEnabled)
|
||||||
? (
|
? (
|
||||||
<div style={{
|
<div style={{
|
||||||
borderStyle: 'solid',
|
borderStyle: 'solid',
|
||||||
|
@ -379,8 +429,12 @@ function Converter({
|
||||||
<p style={{ ...textStyle, fontHeight: 16 }}>
|
<p style={{ ...textStyle, fontHeight: 16 }}>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
|
checked={gridLight}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
selectLightGrid(e.target.checked);
|
setGridData({
|
||||||
|
...gridData,
|
||||||
|
light: e.target.checked,
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
Light Grid
|
Light Grid
|
||||||
|
@ -392,9 +446,12 @@ function Converter({
|
||||||
min="0"
|
min="0"
|
||||||
max="10"
|
max="10"
|
||||||
style={{ width: '2em' }}
|
style={{ width: '2em' }}
|
||||||
value={selectedGridOffsetX}
|
value={gridOffsetX}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
selectGridOffsetX(e.target.value);
|
setGridData({
|
||||||
|
...gridData,
|
||||||
|
offsetX: e.target.value,
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
/>%
|
/>%
|
||||||
</span>
|
</span>
|
||||||
|
@ -405,9 +462,12 @@ function Converter({
|
||||||
min="0"
|
min="0"
|
||||||
max="10"
|
max="10"
|
||||||
style={{ width: '2em' }}
|
style={{ width: '2em' }}
|
||||||
value={selectedGridOffsetY}
|
value={gridOffsetY}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
selectGridOffsetY(e.target.value);
|
setGridData({
|
||||||
|
...gridData,
|
||||||
|
offsetY: e.target.value,
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
/>%
|
/>%
|
||||||
</span>
|
</span>
|
||||||
|
@ -417,14 +477,17 @@ function Converter({
|
||||||
<p style={{ ...textStyle, fontHeight: 16 }}>
|
<p style={{ ...textStyle, fontHeight: 16 }}>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={selectedDoScaling}
|
checked={scalingEnabled}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
selectDoScaling(e.target.checked);
|
setScaleData({
|
||||||
|
...scaleData,
|
||||||
|
enabled: e.target.checked,
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
Scale Image
|
Scale Image
|
||||||
</p>
|
</p>
|
||||||
{(selectedDoScaling)
|
{(scalingEnabled)
|
||||||
? (
|
? (
|
||||||
<div style={{
|
<div style={{
|
||||||
borderStyle: 'solid',
|
borderStyle: 'solid',
|
||||||
|
@ -439,18 +502,27 @@ function Converter({
|
||||||
type="number"
|
type="number"
|
||||||
step="1"
|
step="1"
|
||||||
min="1"
|
min="1"
|
||||||
max="16564"
|
max="1024"
|
||||||
style={{ width: '3em' }}
|
style={{ width: '3em' }}
|
||||||
value={selectedScaleWidth}
|
value={scalingWidth}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const newWidth = e.target.value;
|
const newWidth = (e.target.value > 1024)
|
||||||
|
? 1024 : e.target.value;
|
||||||
if (!newWidth) return;
|
if (!newWidth) return;
|
||||||
if (selectedScaleKeepRatio && selectedFile) {
|
if (selectedScaleKeepRatio && selectedFile) {
|
||||||
const ratio = selectedFile.width / selectedFile.height;
|
const ratio = selectedFile.width / selectedFile.height;
|
||||||
const newHeight = Math.round(newWidth / ratio);
|
const newHeight = Math.round(newWidth / ratio);
|
||||||
selectScaleHeight(newHeight);
|
setScaleData({
|
||||||
|
...scaleData,
|
||||||
|
width: newWidth,
|
||||||
|
height: newHeight,
|
||||||
|
});
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
selectScaleWidth(newWidth);
|
setScaleData({
|
||||||
|
...scaleData,
|
||||||
|
width: newWidth,
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
/>%
|
/>%
|
||||||
</span>
|
</span>
|
||||||
|
@ -459,18 +531,27 @@ function Converter({
|
||||||
type="number"
|
type="number"
|
||||||
step="1"
|
step="1"
|
||||||
min="1"
|
min="1"
|
||||||
max="16564"
|
max="1024"
|
||||||
style={{ width: '3em' }}
|
style={{ width: '3em' }}
|
||||||
value={selectedScaleHeight}
|
value={scalingHeight}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const nuHeight = e.target.value;
|
const nuHeight = (e.target.value > 1024)
|
||||||
|
? 1024 : e.target.value;
|
||||||
if (!nuHeight) return;
|
if (!nuHeight) return;
|
||||||
if (selectedScaleKeepRatio && selectedFile) {
|
if (selectedScaleKeepRatio && selectedFile) {
|
||||||
const ratio = selectedFile.width / selectedFile.height;
|
const ratio = selectedFile.width / selectedFile.height;
|
||||||
const nuWidth = Math.round(ratio * nuHeight);
|
const nuWidth = Math.round(ratio * nuHeight);
|
||||||
selectScaleWidth(nuWidth);
|
setScaleData({
|
||||||
|
...scaleData,
|
||||||
|
width: nuWidth,
|
||||||
|
height: nuHeight,
|
||||||
|
});
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
selectScaleHeight(nuHeight);
|
setScaleData({
|
||||||
|
...scaleData,
|
||||||
|
height: nuHeight,
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
/>%
|
/>%
|
||||||
</span>
|
</span>
|
||||||
|
@ -487,8 +568,12 @@ function Converter({
|
||||||
<p style={{ ...textStyle, fontHeight: 16 }}>
|
<p style={{ ...textStyle, fontHeight: 16 }}>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
|
checked={scalingAA}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
selectScaleAA(e.target.checked);
|
setScaleData({
|
||||||
|
...scaleData,
|
||||||
|
aa: e.target.checked,
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
Anti Aliasing
|
Anti Aliasing
|
||||||
|
@ -497,8 +582,11 @@ function Converter({
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (selectedFile) {
|
if (selectedFile) {
|
||||||
selectScaleWidth(selectedFile.width);
|
setScaleData({
|
||||||
selectScaleHeight(selectedFile.height);
|
...scaleData,
|
||||||
|
width: selectedFile.width,
|
||||||
|
height: selectedFile.height,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -507,6 +595,7 @@ function Converter({
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
: null}
|
: null}
|
||||||
|
<p id="qprog" />
|
||||||
<p>
|
<p>
|
||||||
<canvas
|
<canvas
|
||||||
id="imgoutput"
|
id="imgoutput"
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
* LICENSE.txt file in the root directory of this source tree.
|
* LICENSE.txt file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import fs from 'fs';
|
||||||
import webpack from 'webpack';
|
import webpack from 'webpack';
|
||||||
import webpackConfig from './webpack.config';
|
import webpackConfig from './webpack.config';
|
||||||
|
|
||||||
|
@ -14,6 +15,25 @@ import webpackConfig from './webpack.config';
|
||||||
* Creates application bundles from the source files.
|
* Creates application bundles from the source files.
|
||||||
*/
|
*/
|
||||||
function bundle() {
|
function bundle() {
|
||||||
|
try {
|
||||||
|
/* fix image-q imports here
|
||||||
|
* Pretty dirty, but we did write an issue and they might
|
||||||
|
* update one day
|
||||||
|
*/
|
||||||
|
console.log('Pathing image-q set-immediate import');
|
||||||
|
const regex = /core-js\/fn\/set-immediate/g;
|
||||||
|
const files = [
|
||||||
|
'./node_modules/image-q/dist/esm/basicAPI.js',
|
||||||
|
'./node_modules/image-q/dist/esm/helper.js',
|
||||||
|
];
|
||||||
|
files.forEach((file) => {
|
||||||
|
let fileContent = fs.readFileSync(file,'utf8');
|
||||||
|
fileContent = fileContent.replace(regex, 'core-js/features/set-immediate');
|
||||||
|
fs.writeFileSync(file, fileContent);
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
console.log('Error while patching image-q');
|
||||||
|
}
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
webpack(webpackConfig).run((err, stats) => {
|
webpack(webpackConfig).run((err, stats) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user