From 2b2eca58515551ebf3251a8299e157ac231c1fb4 Mon Sep 17 00:00:00 2001 From: Ben Eggers Date: Thu, 21 Nov 2024 13:09:18 -0800 Subject: [PATCH] it works! ( and sucks ) --- src/App.tsx | 12 +++++++---- src/components/DownloadButton.tsx | 1 + src/components/ImageCanvas.tsx | 35 ++++++++++++++++++++++++++++++- 3 files changed, 43 insertions(+), 5 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 6f6f5de..718d87d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,12 +8,16 @@ import EffectsSidebar from './components/EffectsSidebar'; import { FabricImage } from 'fabric'; function App() { + // Base user-uploaded image. const [image, setImage] = useState(null); const [isCropping, setIsCropping] = useState(false); const [fileName, setFileName] = useState(''); - // Yeah yeah we should probably have a sub-component to hold this state. + // Yeah yeah we should probably have a sub-component to hold the + // state after this point. const [overlayImages, setOverlayImages] = useState([]); + // This is an abomination unto god. Holds the complete image (with overlays) for download. + const [downloadableImage, setDownloadableImage] = useState(null); return (
@@ -27,14 +31,14 @@ function App() { {!!image && isCropping && ( { setImage(blob); setIsCropping(false); }} + setImage={(blob) => { setImage(blob); setDownloadableImage(blob); setIsCropping(false); }} onClose={() => setIsCropping(false)} /> )} {!!image && !isCropping && ( - + )} - +
diff --git a/src/components/DownloadButton.tsx b/src/components/DownloadButton.tsx index 60ed4e9..4087fb8 100644 --- a/src/components/DownloadButton.tsx +++ b/src/components/DownloadButton.tsx @@ -7,6 +7,7 @@ function DownloadButton({ image, fileName }: DownloadButtonProps) { const handleDownload = () => { if (!image) return; + // I am ashamed to have written this. There must be a better way. const link = document.createElement('a'); const imageUrl = URL.createObjectURL(image); link.href = imageUrl; diff --git a/src/components/ImageCanvas.tsx b/src/components/ImageCanvas.tsx index 2ef2961..d1f95f1 100644 --- a/src/components/ImageCanvas.tsx +++ b/src/components/ImageCanvas.tsx @@ -4,12 +4,35 @@ import { useEffect, useRef } from 'react'; interface ImageCanvasProps { image: Blob | null; overlayImages: FabricImage[]; + setDownloadableImage: (image: Blob | null) => void; } -function ImageCanvas({ image, overlayImages }: ImageCanvasProps) { +function ImageCanvas({ image, overlayImages, setDownloadableImage }: ImageCanvasProps) { + // TODO at least one of these, probably both, should be in the parent + // component so clicking the download button can trigger the final image render. const canvasRef = useRef(null); const fabricCanvasRef = useRef(null); + function updateDownloadableImage() { + if (!fabricCanvasRef.current) return; + + // TODO other image types + const downloadableImageUrl = fabricCanvasRef.current.toDataURL({ + format: 'png', + multiplier: 1 + }); + const base64Data = downloadableImageUrl.replace(/^data:image\/png;base64,/, ''); + + const byteCharacters = atob(base64Data); + const byteNumbers = new Array(byteCharacters.length); + for (let i = 0; i < byteCharacters.length; i++) { + byteNumbers[i] = byteCharacters.charCodeAt(i); + } + const byteArray = new Uint8Array(byteNumbers); + const blob = new Blob([byteArray], { type: 'image/png' }); + setDownloadableImage(blob); + } + useEffect(() => { if (!fabricCanvasRef.current && canvasRef.current) { const canvasElement = canvasRef.current; @@ -22,6 +45,15 @@ function ImageCanvas({ image, overlayImages }: ImageCanvasProps) { const newCanvas = new Canvas(canvasElement); fabricCanvasRef.current = newCanvas; + // https://github.com/fabricjs/fabric.js/wiki/Working-with-events + // TODO We should be able to do this in a for-loop, but then TS + // complains about using a string as type CanvasEvent. + fabricCanvasRef.current.on('object:added', updateDownloadableImage); + fabricCanvasRef.current.on('object:modified', updateDownloadableImage); + fabricCanvasRef.current.on('object:removed', updateDownloadableImage); + fabricCanvasRef.current.on('object:rotating', updateDownloadableImage); + fabricCanvasRef.current.on('object:scaling', updateDownloadableImage); + fabricCanvasRef.current.on('object:moving', updateDownloadableImage); } } @@ -48,6 +80,7 @@ function ImageCanvas({ image, overlayImages }: ImageCanvasProps) { } }; reader.readAsDataURL(image); + } }, [image, overlayImages]);