Files
vtuber-stickers/src/App.jsx

276 lines
8.0 KiB
JavaScript

import "./App.css";
import Canvas from "./components/Canvas";
import { useState, useEffect } from "react";
import characters from "./characters.json";
import Slider from "@mui/material/Slider";
import TextField from "@mui/material/TextField";
import Button from "@mui/material/Button";
import Switch from "@mui/material/Switch";
import Picker from "./components/Picker";
import Info from "./components/Info";
import getConfiguration from "./utils/config";
import log from "./utils/log";
const { ClipboardItem } = window;
function App() {
useEffect(() => {
try {
getConfiguration();
} catch (error) {
console.log(error);
}
}, []);
const [infoOpen, setInfoOpen] = useState(false);
const handleClickOpen = () => {
setInfoOpen(true);
};
const handleClose = () => {
setInfoOpen(false);
};
const [character, setCharacter] = useState(49);
const [text, setText] = useState(characters[character].defaultText.text);
const [position, setPosition] = useState({
x: characters[character].defaultText.x,
y: characters[character].defaultText.y,
});
const [fontSize, setFontSize] = useState(characters[character].defaultText.s);
const [spaceSize, setSpaceSize] = useState(1);
const [rotate, setRotate] = useState(characters[character].defaultText.r);
const [curve, setCurve] = useState(false);
const [loaded, setLoaded] = useState(false);
const img = new Image();
useEffect(() => {
setText(characters[character].defaultText.text);
setPosition({
x: characters[character].defaultText.x,
y: characters[character].defaultText.y,
});
setRotate(characters[character].defaultText.r);
setFontSize(characters[character].defaultText.s);
setLoaded(false);
}, [character]);
img.src = "/img/" + characters[character].img;
img.onload = () => {
setLoaded(true);
};
let angle = (Math.PI * text.length) / 7;
const draw = (ctx) => {
ctx.canvas.width = 296;
ctx.canvas.height = 256;
if (loaded && document.fonts.check("12px YurukaStd")) {
var hRatio = ctx.canvas.width / img.width;
var vRatio = ctx.canvas.height / img.height;
var ratio = Math.min(hRatio, vRatio);
var centerShift_x = (ctx.canvas.width - img.width * ratio) / 2;
var centerShift_y = (ctx.canvas.height - img.height * ratio) / 2;
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.drawImage(
img,
0,
0,
img.width,
img.height,
centerShift_x,
centerShift_y,
img.width * ratio,
img.height * ratio
);
ctx.font = `${fontSize}px YurukaStd, SSFangTangTi`;
ctx.lineWidth = 9;
ctx.save();
ctx.translate(position.x, position.y);
ctx.rotate(rotate / 10);
ctx.textAlign = "center";
ctx.strokeStyle = "white";
ctx.fillStyle = characters[character].color;
var lines = text.split("\n");
if (curve) {
for (let line of lines) {
for (let i = 0; i < line.length; i++) {
ctx.rotate(angle / line.length / 2.5);
ctx.save();
ctx.translate(0, -1 * fontSize * 3.5);
ctx.strokeText(line[i], 0, 0);
ctx.fillText(line[i], 0, 0);
ctx.restore();
}
}
} else {
for (var i = 0, k = 0; i < lines.length; i++) {
ctx.strokeText(lines[i], 0, k);
ctx.fillText(lines[i], 0, k);
k += spaceSize;
}
ctx.restore();
}
}
};
const download = () => {
const canvas = document.getElementsByTagName("canvas")[0];
const link = document.createElement("a");
link.download = `${characters[character].name}_st.ayaka.one.png`;
link.href = canvas.toDataURL();
link.click();
log(characters[character].id, characters[character].name, "download");
};
function b64toBlob(b64Data, contentType = null, sliceSize = null) {
contentType = contentType || "image/png";
sliceSize = sliceSize || 512;
let byteCharacters = atob(b64Data);
let byteArrays = [];
for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
let slice = byteCharacters.slice(offset, offset + sliceSize);
let byteNumbers = new Array(slice.length);
for (let i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
var byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
return new Blob(byteArrays, { type: contentType });
}
const copy = async () => {
const canvas = document.getElementsByTagName("canvas")[0];
await navigator.clipboard.write([
new ClipboardItem({
"image/png": b64toBlob(canvas.toDataURL().split(",")[1]),
}),
]);
log(characters[character].id, characters[character].name, "copy");
};
return (
<div className="App">
<Info open={infoOpen} handleClose={handleClose} />
<div className="container">
<div className="vertical">
<div className="canvas">
<Canvas draw={draw} />
</div>
<Slider
value={curve ? 256 - position.y + fontSize * 3 : 256 - position.y}
onChange={(e, v) =>
setPosition({
...position,
y: curve ? 256 + fontSize * 3 - v : 256 - v,
})
}
min={0}
max={256}
step={1}
orientation="vertical"
track={false}
color="secondary"
/>
</div>
<div className="horizontal">
<Slider
className="slider-horizontal"
value={position.x}
onChange={(e, v) => setPosition({ ...position, x: v })}
min={0}
max={296}
step={1}
track={false}
color="secondary"
/>
<div className="settings">
<div>
<label>Rotate: </label>
<Slider
value={rotate}
onChange={(e, v) => setRotate(v)}
min={-10}
max={10}
step={0.2}
track={false}
color="secondary"
/>
</div>
<div>
<label>
<nobr>Font size: </nobr>
</label>
<Slider
value={fontSize}
onChange={(e, v) => setFontSize(v)}
min={10}
max={100}
step={1}
track={false}
color="secondary"
/>
</div>
<div>
<label>
<nobr>Spacing: </nobr>
</label>
<Slider
value={spaceSize}
onChange={(e, v) => setSpaceSize(v)}
min={18}
max={100}
step={1}
track={false}
color="secondary"
/>
</div>
<div>
<label>Curve (Beta): </label>
<Switch
checked={curve}
onChange={(e) => setCurve(e.target.checked)}
color="secondary"
/>
</div>
</div>
<div className="text">
<TextField
label="Text"
size="small"
color="secondary"
value={text}
multiline={true}
fullWidth
onChange={(e) => setText(e.target.value)}
/>
</div>
<div className="picker">
<Picker setCharacter={setCharacter} />
</div>
<div className="buttons">
<Button color="secondary" onClick={copy}>
copy
</Button>
<Button color="secondary" onClick={download}>
download
</Button>
</div>
</div>
<div className="footer">
<Button color="secondary" onClick={handleClickOpen}>
Info
</Button>
</div>
</div>
</div>
);
}
export default App;