From 3e86ea25be394de787a4703fe3ff991e1b49e4c3 Mon Sep 17 00:00:00 2001
From: HF
Date: Fri, 8 May 2020 15:41:03 +0200
Subject: [PATCH] Add converter in usermodal
---
package-lock.json | 5 +
package.json | 1 +
src/client.js | 2 +-
src/components/Converter.jsx | 329 ++++++++++++++++++++++++++++---
src/components/DailyRankings.jsx | 2 +-
src/components/LogInForm.jsx | 28 ++-
src/components/TotalRankings.jsx | 2 +-
src/components/UserAreaModal.jsx | 99 +++++++---
src/components/UserMessages.jsx | 2 +-
src/components/base.tcss | 4 +-
src/core/exportGPL.js | 37 ++++
11 files changed, 436 insertions(+), 75 deletions(-)
create mode 100644 src/core/exportGPL.js
diff --git a/package-lock.json b/package-lock.json
index cfd23eb..4a537ee 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11285,6 +11285,11 @@
"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": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
diff --git a/package.json b/package.json
index 9868902..cf7c00a 100644
--- a/package.json
+++ b/package.json
@@ -72,6 +72,7 @@
"redux-logger": "^3.0.6",
"redux-persist": "^6.0.0",
"redux-thunk": "^2.2.0",
+ "rgbquant": "^1.1.2",
"sequelize": "^5.21.7",
"sharp": "^0.25.2",
"startaudiocontext": "^1.2.1",
diff --git a/src/client.js b/src/client.js
index e7a2e8f..60cb922 100644
--- a/src/client.js
+++ b/src/client.js
@@ -84,7 +84,7 @@ function init() {
}
if (cnt > 1) {
document.body.style.setProperty(
- "-webkit-transform", "rotate(-180deg)",
+ '-webkit-transform', 'rotate(-180deg)',
null,
);
fetch('https://assets.pixelplanet.fun/iamabot');
diff --git a/src/components/Converter.jsx b/src/components/Converter.jsx
index e01a93a..d05545c 100644
--- a/src/components/Converter.jsx
+++ b/src/components/Converter.jsx
@@ -3,10 +3,12 @@
* @flow
*/
-import React, { useState } from 'react';
+import React, { useState, useEffect } from 'react';
import { connect } from 'react-redux';
import fileDownload from 'js-file-download';
+import RgbQuant from 'rgbquant';
+import printGIMPPalette from '../core/exportGPL';
import type { State } from '../reducers';
const titleStyle = {
@@ -18,7 +20,7 @@ const titleStyle = {
lineHeight: '24px',
fontSize: 16,
fontWeight: 500,
- // marginTop: 0,
+ marginTop: 4,
marginBottom: 0,
};
@@ -32,35 +34,117 @@ const textStyle = {
lineHeight: 'normal',
};
-function appendNumberText(number) {
- let appendStr = `${number} `;
- if (number < 10) appendStr += ' ';
- else if (number < 100) appendStr += ' ';
- return appendStr;
-}
-function appendHexColorText(clr) {
- let appendStr = ' #';
- clr.forEach((z) => {
- if (z < 16) appendStr += '0';
- appendStr += z.toString(16);
- });
- return appendStr;
+function downloadOutput() {
+ const output = document.getElementById('imgoutput');
+ output.toBlob((blob) => fileDownload(blob, 'ppfunconvert.png'));
}
+function readFile(
+ file,
+ selectFile,
+) {
+ if (!file) {
+ return;
+ }
+ const fr = new FileReader();
+ fr.onload = () => {
+ const img = new Image();
+ img.onload = () => {
+ selectFile(img);
+ };
+ img.src = fr.result;
+ };
+ fr.readAsDataURL(file);
+}
-function printGIMPPalette(title, description, colors) {
- let text = `GIMP Palette
-#Palette Name: Pixelplanet${title}
-#Description: ${description}
-#Colors: ${colors.length}`;
- colors.forEach((clr) => {
- text += '\n';
- clr.forEach((z) => {
- text += appendNumberText(z);
- });
- text += appendHexColorText(clr);
+function drawPixels(idxi8, width, height) {
+ const can = document.createElement('canvas');
+ can.width = width;
+ can.height = height;
+ const ctx = can.getContext('2d');
+ ctx.imageSmoothingEnabled = false;
+ ctx.mozImageSmoothingEnabled = false;
+ ctx.webkitImageSmoothingEnabled = false;
+ ctx.msImageSmoothingEnabled = false;
+
+ const imgd = ctx.createImageData(can.width, can.height);
+ const { data } = imgd;
+ for (let i = 0, len = data.length; i < len; ++i) data[i] = idxi8[i];
+
+ ctx.putImageData(imgd, 0, 0);
+ return can;
+}
+
+function addGrid(img, lightGrid, offsetX, offsetY) {
+ const can = document.createElement('canvas');
+ const ctx = can.getContext('2d');
+ can.width = img.width * 5;
+ can.height = img.height * 5;
+ ctx.imageSmoothingEnabled = false;
+ ctx.mozImageSmoothingEnabled = false;
+ ctx.webkitImageSmoothingEnabled = false;
+ ctx.msImageSmoothingEnabled = false;
+ ctx.save();
+ ctx.scale(5.0, 5.0);
+ ctx.drawImage(img, 0, 0);
+ ctx.restore();
+ ctx.fillStyle = (lightGrid) ? '#DDDDDD' : '#222222';
+
+ for (let i = 0; i <= img.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 <= img.height; j += 1) {
+ const thick = ((j + (offsetY * 1)) % 10 === 0) ? 2 : 1;
+ ctx.fillRect(0, j * 5, can.width, thick);
+ }
+
+ return can;
+}
+
+function renderOutputImage(
+ colors,
+ selectedFile,
+ selectedStrategy,
+ selectedSerp,
+ selectedColorDist,
+ selectedDithDelta,
+ selectedAddGrid,
+ selectedLightGrid,
+ selectedGridOffsetX,
+ selectedGridOffsetY,
+) {
+ if (!selectedFile) {
+ return;
+ }
+ const rgbQuant = new RgbQuant({
+ colors: colors.length,
+ dithKern: selectedStrategy,
+ dithDelta: selectedDithDelta / 100,
+ dithSerp: selectedSerp,
+ palette: colors,
+ reIndex: false,
+ useCache: false,
+ colorDist: selectedColorDist,
});
- fileDownload(text, `Pixelplanet${title}.gpl`);
+ rgbQuant.sample(selectedFile);
+ rgbQuant.palette();
+ const pxls = rgbQuant.reduce(selectedFile);
+ let can = drawPixels(pxls, selectedFile.width, selectedFile.height);
+ const output = document.getElementById('imgoutput');
+ if (selectedAddGrid) {
+ can = addGrid(
+ can,
+ selectedLightGrid,
+ selectedGridOffsetX,
+ selectedGridOffsetY,
+ );
+ }
+ output.width = can.width;
+ output.height = can.height;
+ const ctx = output.getContext('2d');
+ ctx.drawImage(can, 0, 0);
}
@@ -69,6 +153,41 @@ function Converter({
canvases,
}) {
const [selectedCanvas, selectCanvas] = useState(canvasId);
+ const [selectedFile, selectFile] = useState(null);
+ const [selectedStrategy, selectStrategy] = useState('');
+ const [selectedSerp, selectSerp] = useState(false);
+ 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 input = document.createElement('canvas');
+
+ useEffect(() => {
+ if (selectedFile) {
+ const canvas = canvases[selectedCanvas];
+ renderOutputImage(
+ canvas.colors.slice(canvas.cli),
+ selectedFile,
+ selectedStrategy,
+ selectedSerp,
+ selectedColorDist,
+ selectedDithDelta,
+ selectedAddGrid,
+ selectedLightGrid,
+ selectedGridOffsetX,
+ selectedGridOffsetY,
+ );
+ } else {
+ const output = document.getElementById('imgoutput');
+ const ctx = output.getContext('2d');
+ output.width = 128;
+ output.height = 100;
+ ctx.fillStyle = '#C4C4C4';
+ ctx.fillRect(0, 0, 128, 100);
+ }
+ });
return (
@@ -95,22 +214,172 @@ function Converter({
}
+ Palette Download
Palette for GIMP:
-
Credit for the Palette of the Moon goes to starhouse.
+ Credit for the Palette of the Moon goes to
+ starhouse.
+ Image Converter
+ Convert an image to canvas colors
+ {
+ const fileSel = evt.target;
+ const file = (!fileSel.files || !fileSel.files[0])
+ ? null : fileSel.files[0];
+ readFile(file, selectFile);
+ }}
+ />
+ Choose Strategy:
+
+
+
+ {
+ selectSerp(e.target.checked);
+ }}
+ />
+ Serpentine Pattern Dithering
+
+
+ {
+ const colorDist = (e.target.checked)
+ ? 'euclidean' : 'manhatten';
+ selectColorDist(colorDist);
+ }}
+ />
+ Manhatten Color Distance
+
+ Dithering Delta:
+ {
+ selectDithDelta(e.target.value);
+ }}
+ />%
+
+
+ {
+ selectAddGrid(e.target.checked);
+ }}
+ />
+ Add Grid
+
+ {(selectedAddGrid)
+ ? (
+
+ )
+ : null}
+
+
+
+
);
}
diff --git a/src/components/DailyRankings.jsx b/src/components/DailyRankings.jsx
index f83cff7..672519c 100644
--- a/src/components/DailyRankings.jsx
+++ b/src/components/DailyRankings.jsx
@@ -9,7 +9,7 @@ import { connect } from 'react-redux';
import type { State } from '../reducers';
const DailyRankings = ({ totalDailyRanking }) => (
-
+
# |
diff --git a/src/components/LogInForm.jsx b/src/components/LogInForm.jsx
index f25ca57..d3a71a9 100644
--- a/src/components/LogInForm.jsx
+++ b/src/components/LogInForm.jsx
@@ -20,7 +20,7 @@ function validate(nameoremail, password) {
return errors;
}
-async function submit_login(nameoremail, password, component) {
+async function submitLogin(nameoremail, password) {
const body = JSON.stringify({
nameoremail,
password,
@@ -37,8 +37,9 @@ async function submit_login(nameoremail, password, component) {
}
const inputStyles = {
- display: 'block',
+ display: 'inline-block',
width: '100%',
+ maxWidth: '35em',
};
class LogInForm extends React.Component {
@@ -59,6 +60,7 @@ class LogInForm extends React.Component {
e.preventDefault();
const { nameoremail, password, submitting } = this.state;
+ const { me: setMe } = this.props;
if (submitting) return;
const errors = validate(nameoremail, password);
@@ -67,7 +69,10 @@ class LogInForm extends React.Component {
if (errors.length > 0) return;
this.setState({ submitting: true });
- const { errors: resperrors, me } = await submit_login(nameoremail, password);
+ const { errors: resperrors, me } = await submitLogin(
+ nameoremail,
+ password,
+ );
if (resperrors) {
this.setState({
errors: resperrors,
@@ -75,32 +80,37 @@ class LogInForm extends React.Component {
});
return;
}
- this.props.me(me);
+ setMe(me);
}
render() {
- const { errors } = this.state;
+ const {
+ errors, nameoremail, password, submitting,
+ } = this.state;
return (
);
}
diff --git a/src/components/TotalRankings.jsx b/src/components/TotalRankings.jsx
index 95c9bab..a227f90 100644
--- a/src/components/TotalRankings.jsx
+++ b/src/components/TotalRankings.jsx
@@ -9,7 +9,7 @@ import { connect } from 'react-redux';
import type { State } from '../reducers';
const TotalRankings = ({ totalRanking }) => (
-
+
# |
diff --git a/src/components/UserAreaModal.jsx b/src/components/UserAreaModal.jsx
index 82a5479..47ab544 100644
--- a/src/components/UserAreaModal.jsx
+++ b/src/components/UserAreaModal.jsx
@@ -10,8 +10,6 @@ import Modal from './Modal';
import type { State } from '../reducers';
-const Converter = React.lazy(() => import(/* webpackChunkName: "converter" */ './Converter'));
-
import {
showRegisterModal, showForgotPasswordModal, setName, setMailreg, receiveMe,
@@ -21,23 +19,13 @@ import Tabs from './Tabs';
import UserArea from './UserArea';
import Rankings from './Rankings';
+// eslint-disable-next-line max-len
+const Converter = React.lazy(() => import(/* webpackChunkName: "converter" */ './Converter'));
+
const logoStyle = {
marginRight: 5,
};
-const titleStyle = {
- color: '#4f545c',
- marginLeft: 0,
- marginRight: 10,
- overflow: 'hidden',
- wordWrap: 'break-word',
- lineHeight: '24px',
- fontSize: 16,
- fontWeight: 500,
- // marginTop: 0,
- marginBottom: 0,
-};
-
const textStyle = {
color: 'hsla(218, 5%, 47%, .6)',
fontSize: 14,
@@ -55,32 +43,77 @@ const LogInArea = ({ register, forgot_password, me }) => (
Login to access more features and stats.
Login with Mail:
- I forgot my Password.
+
+ I forgot my Password.
or login with:
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
or register here:
);
const UserAreaModal = ({
- name, register, forgot_password, doMe, logout, setName, setMailreg, canvases,
+ name, register, forgot_password, doMe, logout, setUserName, setUserMailreg,
}) => (
{(name === null)
- ?
+ ? (
+
+ )
: (
@@ -93,7 +126,9 @@ const UserAreaModal = ({
)}
-
Also join our Discord: pixelplanet.fun/discord
+ Also join our Discord:
+ pixelplanet.fun/discord
+
);
@@ -109,14 +144,17 @@ function mapDispatchToProps(dispatch) {
doMe(me) {
dispatch(receiveMe(me));
},
- setName(name) {
+ setUserName(name) {
dispatch(setName(name));
},
- setMailreg(mailreg) {
+ setUserMailreg(mailreg) {
dispatch(setMailreg(mailreg));
},
async logout() {
- const response = await fetch('./api/auth/logout', { credentials: 'include' });
+ const response = await fetch(
+ './api/auth/logout',
+ { credentials: 'include' },
+ );
if (response.ok) {
const resp = await response.json();
dispatch(receiveMe(resp.me));
@@ -127,8 +165,7 @@ function mapDispatchToProps(dispatch) {
function mapStateToProps(state: State) {
const { name } = state.user;
- const { canvases } = state.canvas;
- return { name, canvases };
+ return { name };
}
export default connect(mapStateToProps, mapDispatchToProps)(UserAreaModal);
diff --git a/src/components/UserMessages.jsx b/src/components/UserMessages.jsx
index a4a5bbb..3c5c35a 100644
--- a/src/components/UserMessages.jsx
+++ b/src/components/UserMessages.jsx
@@ -79,7 +79,7 @@ class UserMessages extends React.Component {
{(messages.includes('not_verified') && messages.splice(messages.indexOf('not_verified'), 1))
? (
- Please verify your mail address or your account could get deleted after a few days.
+ Please verify your mail address or your account could get deleted after a few days.
{(this.state.verify_answer)
? {this.state.verify_answer}
: Click here to request a new verification mail.}
diff --git a/src/components/base.tcss b/src/components/base.tcss
index 5193967..4b36116 100644
--- a/src/components/base.tcss
+++ b/src/components/base.tcss
@@ -217,7 +217,8 @@ kbd {
padding: 20px 40px;
transform: translate(-50%, -50%);
height: 80%;
- max-height: 700px;
+ max-height: 900px;
+ width: 70%;
transition: all 0.5s ease 0s;
}
@media (max-width: 604px) {
@@ -228,6 +229,7 @@ kbd {
right: 0px;
bottom: 0px;
height: 100%;
+ width: 100%;
transform: none;
max-width: none;
max-height: none;
diff --git a/src/core/exportGPL.js b/src/core/exportGPL.js
new file mode 100644
index 0000000..f975e2e
--- /dev/null
+++ b/src/core/exportGPL.js
@@ -0,0 +1,37 @@
+/*
+ *
+ * @flow
+ */
+
+function appendNumberText(number) {
+ let appendStr = `${number} `;
+ if (number < 10) appendStr += ' ';
+ else if (number < 100) appendStr += ' ';
+ return appendStr;
+}
+function appendHexColorText(clr) {
+ let appendStr = ' #';
+ clr.forEach((z) => {
+ if (z < 16) appendStr += '0';
+ appendStr += z.toString(16);
+ });
+ return appendStr;
+}
+
+
+function printGIMPPalette(title, description, colors) {
+ let text = `GIMP Palette
+#Palette Name: Pixelplanet${title}
+#Description: ${description}
+#Colors: ${colors.length}`;
+ colors.forEach((clr) => {
+ text += '\n';
+ clr.forEach((z) => {
+ text += appendNumberText(z);
+ });
+ text += appendHexColorText(clr);
+ });
+ return text;
+}
+
+export default printGIMPPalette;