From 7a15fa556b7e1c05c5e56f78e1dd42983ed87332 Mon Sep 17 00:00:00 2001 From: HF Date: Sun, 4 Feb 2024 14:33:19 +0100 Subject: [PATCH] first overlay render --- src/components/AddTemplate.jsx | 129 ---------------------------- src/components/TemplateItemEdit.jsx | 13 +-- src/components/TemplateSettings.jsx | 25 +++--- src/core/utils.js | 8 +- src/store/reducers/templates.js | 2 +- src/ui/Renderer2D.js | 11 ++- src/ui/render2Delements.js | 45 ++++++++++ src/ui/templateLoader.js | 42 +++++++-- 8 files changed, 112 insertions(+), 163 deletions(-) delete mode 100644 src/components/AddTemplate.jsx diff --git a/src/components/AddTemplate.jsx b/src/components/AddTemplate.jsx deleted file mode 100644 index 3f91d157..00000000 --- a/src/components/AddTemplate.jsx +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Settings of minimap / overlay - */ - -import React, { useState, useEffect, useRef } from 'react'; -import { useSelector, shallowEqual } from 'react-redux'; -import { t } from 'ttag'; - -import { coordsFromUrl } from '../core/utils'; -import templateLoader from '../ui/templateLoader'; - -const AddTemplate = ({ close, triggerClose: refClose }) => { - const [render, setRender] = useState(false); - const [coords, setCoords] = useState(null); - const [dimensions, setDimensions] = useState(null); - const [title, setTitle] = useState(''); - const [file, setFile] = useState(null); - const imgRef = useRef(); - const [ - canvasId, - canvases, - ] = useSelector((state) => [ - state.canvas.canvasId, - state.canvas.canvases, - ], shallowEqual); - const [selectedCanvas, selectCanvas] = useState(canvasId); - - useEffect(() => { - window.setTimeout(() => setRender(true), 10); - refClose.current = () => setRender(false); - }, [refClose]); - - useEffect(() => { - if (!file || !imgRef.current) { - return; - } - const fr = new FileReader(); - fr.onload = () => { imgRef.current.src = fr.result; }; - fr.readAsDataURL(file); - }, [file]); - - const canSubmit = (imgRef.current && file && coords && title && dimensions); - - return ( -
!render && close()} - > -
{ - event.preventDefault(); - if (canSubmit) { - await templateLoader.addFile( - file, title, selectedCanvas, ...coords, imgRef.current, - ); - setRender(false); - } - }} - > - preview setDimensions([ - evt.target.naturalWidth, - evt.target.naturalHeight, - ])} - onError={() => setDimensions(null)} - ref={imgRef} - /> - { - setDimensions(null); - setFile(evt.target.files?.[0]); - }} - /> - setTitle(evt.target.value)} - placeholder={t`Template Name`} - /> - - { - let co = evt.target.value.trim(); - co = coordsFromUrl(co) || co; - evt.target.value = co; - const newCoords = co.split('_').map((z) => parseInt(z, 10)); - setCoords((!newCoords.some(Number.isNaN) && newCoords.length === 2) - ? newCoords : null, - ); - }} - /> - -
-
- ); -}; - -export default React.memo(AddTemplate); diff --git a/src/components/TemplateItemEdit.jsx b/src/components/TemplateItemEdit.jsx index 23e9ae06..00e2aef2 100644 --- a/src/components/TemplateItemEdit.jsx +++ b/src/components/TemplateItemEdit.jsx @@ -127,11 +127,14 @@ const TemplateItemEdit = ({ selectCanvas(sel.options[sel.selectedIndex].value); }} > - {Object.keys(canvases).filter((c) => !canvases[c].v).map((canvas) => ( - - ))} + {Object.keys(canvases) + .filter((c) => !canvases[c].v && !canvases[c].ed) + .map((canvas) => ( + + ), + )}

diff --git a/src/components/TemplateSettings.jsx b/src/components/TemplateSettings.jsx index 84d783f1..104be418 100644 --- a/src/components/TemplateSettings.jsx +++ b/src/components/TemplateSettings.jsx @@ -7,7 +7,6 @@ import { useSelector } from 'react-redux'; import fileDownload from 'js-file-download'; import { t } from 'ttag'; -import AddTemplate from './AddTemplate'; import TemplateItem from './TemplateItem'; import TemplateItemEdit from './TemplateItemEdit'; import templateLoader from '../ui/templateLoader'; @@ -35,7 +34,7 @@ const TemplateSettings = () => { <>

{t`Templates`}

- {t`Tired of always spaming one single color? Want to create art instead, but you have to count pixels from some other image? Templates can help you with that! Templates can show as overlay and you can draw over them. One pixel on the template, should be one pixel on the canvas.`} + {t`Tired of always spaming one single color? Want to create art instead, but you have to count pixels from some other image? Templates can help you with that! Templates can show as overlay and you can draw over them. One pixel on the template, should be one pixel on the canvas.`}

{list.map(({ @@ -103,17 +102,17 @@ const TemplateSettings = () => { tabIndex={-1} className="modallink" onClick={async () => importRef.current?.click()} - >{t`Import templates`} - { - templateLoader.importTemplates(evt.target.files?.[0]); - }} - /> + >{t`Import templates`} + { + templateLoader.importTemplates(evt.target.files?.[0]); + }} + />
); diff --git a/src/core/utils.js b/src/core/utils.js index 35a5c3ca..ddfa1c17 100644 --- a/src/core/utils.js +++ b/src/core/utils.js @@ -693,20 +693,20 @@ export function bufferToBase64(array) { return new Promise((resolve) => { const blob = new Blob([array]); const reader = new FileReader(); - + reader.onload = (event) => { const dataUrl = event.target.result; const [_, base64] = dataUrl.split(','); - + resolve(base64); }; - + reader.readAsDataURL(blob); }); } export async function base64ToBuffer(base64) { - const dataUrl = "data:application/octet-binary;base64," + base64; + const dataUrl = `data:application/octet-binary;base64,${base64}`; const res = await fetch(dataUrl); return res.arrayBuffer(); } diff --git a/src/store/reducers/templates.js b/src/store/reducers/templates.js index 6771c1e1..c5233ec2 100644 --- a/src/store/reducers/templates.js +++ b/src/store/reducers/templates.js @@ -99,7 +99,7 @@ export default function templates( ], }; } - + case 'TEMPLATES_READY': return { ...state, diff --git a/src/ui/Renderer2D.js b/src/ui/Renderer2D.js index ddbef387..88e2d611 100644 --- a/src/ui/Renderer2D.js +++ b/src/ui/Renderer2D.js @@ -22,6 +22,7 @@ import { renderGrid, renderPlaceholder, renderPotatoPlaceholder, + renderOverlay, } from './render2Delements'; import PixelPainterControls from '../controls/PixelPainterControls'; @@ -272,6 +273,7 @@ class Renderer2D extends Renderer { const [x, y] = getPixelFromChunkOffset(i, j, offset, canvasSize); + // TODO centerChunk is scaled! const [canX, canY] = this.centerChunk .map((z) => (z + 0.5) * TILE_SIZE - canvasSize / 2); const { width: canvasWidth, height: canvasHeight } = this.canvas; @@ -340,9 +342,7 @@ class Renderer2D extends Renderer { viewport, viewscale: scale, } = this; - const { - canvasSize, - } = state.canvas; + const { canvasSize } = state.canvas; let { relScale } = this; @@ -422,6 +422,11 @@ class Renderer2D extends Renderer { } } context.restore(); + // TODO conditions + renderOverlay( + this.canvas, chunkPosition, canvasSize, scale, + this.tiledScale, this.scaleThreshold, + ); } diff --git a/src/ui/render2Delements.js b/src/ui/render2Delements.js index bdd97928..b15bb7a5 100644 --- a/src/ui/render2Delements.js +++ b/src/ui/render2Delements.js @@ -3,7 +3,9 @@ * */ +import templateLoader from './templateLoader'; import { screenToWorld, worldToScreen } from '../core/utils'; +import { TILE_SIZE } from '../core/constants'; const PLACEHOLDER_SIZE = 1.2; const PLACEHOLDER_BORDER = 1; @@ -100,3 +102,46 @@ export function renderGrid( viewportCtx.globalAlpha = 1; } + +/* + * Overlay draws onto offscreen canvas, so its doing weirder math + */ +export function renderOverlay( + $canvas, + centerChunk, + canvasSize, + scale, + tiledScale, + scaleThreshold, +) { + // world coordinates of center of center chunk + const [x, y] = centerChunk + .map((z) => z * TILE_SIZE / tiledScale + + TILE_SIZE / 2 / tiledScale - canvasSize / 2); + const { width, height } = $canvas; + const horizontalRadius = width / 2 / scale; + const verticalRadius = height / 2 / scale; + const templates = templateLoader.getTemplatesInView( + x, y, horizontalRadius, verticalRadius, + ); + + if (!templates.length) return; + const context = $canvas.getContext('2d'); + if (!context) return; + + // if scale > scaleThreshold, then scaling happens in renderer + // instead of offscreen canvas + const offscreenScale = (scale > scaleThreshold) ? 1.0 : scale; + + context.save(); + context.scale(offscreenScale, offscreenScale); + for (const template of templates) { + const image = templateLoader.getTemplateSync(template.imageId); + if (!image) continue; + context.drawImage(image, + template.x - x + width / 2 / offscreenScale, + template.y - y + height / 2 / offscreenScale, + ); + } + context.restore(); +} diff --git a/src/ui/templateLoader.js b/src/ui/templateLoader.js index 94616760..f4c99089 100644 --- a/src/ui/templateLoader.js +++ b/src/ui/templateLoader.js @@ -50,11 +50,35 @@ class TemplateLoader { return null; } - /* - getTemplatesInView() { - this.#store.templates + getTemplateSync(id) { + if (!this.ready) { + return null; + } + const template = this.#templates.get(id); + if (template) { + return template.image; + } + // TODO some store action when available + this.loadExistingTemplate(id); + return null; + } + + getTemplatesInView(x, y, horizontalRadius, verticalRadius) { + const topX = x - horizontalRadius; + const topY = y - verticalRadius; + const bottomX = x + horizontalRadius; + const bottomY = y + verticalRadius; + + const templates = []; + this.#store.getState().templates.list.forEach((template) => { + if (x < bottomX && y < bottomY + && x + template.width > topX && y + template.height > topY + ) { + templates.push(template); + } + }); + return templates; } - */ /* * sync database to store, @@ -152,7 +176,7 @@ class TemplateLoader { deleteTemplate(title) { const { list } = this.#store.getState().templates; - const tData = list.find((z) => z.title === title) + const tData = list.find((z) => z.title === title); if (!tData) { return; } @@ -161,7 +185,7 @@ class TemplateLoader { this.#fileStorage.deleteFile(imageId); this.#templates.delete(imageId); } - + async exportEnabledTemplates() { const { list } = this.#store.getState().templates; const tDataList = list.filter((z) => z.enabled); @@ -181,7 +205,7 @@ class TemplateLoader { } return serilizableObj; } - + async importTemplates(file) { const tDataList = JSON.parse(await file.text()); const bufferList = await Promise.all( @@ -197,7 +221,9 @@ class TemplateLoader { const imageIdList = await this.#fileStorage.saveFile(fileList); const idsToDelete = []; for (let i = 0; i < tDataList.length; i += 1) { - const { x, y, width, height, canvasId, title } = tDataList[i]; + const { + x, y, width, height, canvasId, title, + } = tDataList[i]; const imageId = imageIdList[i]; const existing = list.find((z) => z.title === title); if (existing) {