diff --git a/src/components/CanvasBlurCapture.tsx b/src/components/CanvasBlurCapture.tsx new file mode 100644 index 00000000..ae85b044 --- /dev/null +++ b/src/components/CanvasBlurCapture.tsx @@ -0,0 +1,76 @@ +'use client'; + +import { useEffect, useRef } from 'react'; + +export default function CanvasBlurCapture({ + imageUrl, + onCapture, + width, + height, + hidden = true, + edgeCompensation = 10, + scale = 0.5, + quality = 0.9, +}: { + imageUrl: string + onCapture: (blurData: string) => void + width: number + height: number + hidden?: boolean + edgeCompensation?: number + scale?: number + quality?: number +}) { + const ref = useRef(null); + + useEffect(() => { + let timeout: NodeJS.Timeout; + + const image = new Image(); + image.crossOrigin = 'anonymous'; + image.src = imageUrl; + image.onload = () => { + timeout = setTimeout(() => { + const canvas = ref.current; + if (canvas) { + canvas.width = width * scale; + canvas.height = height * scale; + canvas.style.width = `${width}px`; + canvas.style.height = `${height}px`; + const context = ref.current?.getContext('2d'); + if (context) { + context.scale(scale, scale); + context.filter = + 'contrast(1.2) saturate(1.2)' + + `blur(${scale * 10}px)`; + context.drawImage( + image, + -edgeCompensation, + -edgeCompensation, + width + edgeCompensation * 2, + width * image.height / image.width + + edgeCompensation * 2, + ); + onCapture(canvas.toDataURL('image/jpeg', quality)); + } + } else { + console.error('Cannot generate blur data: canvas not found'); + } + }, 2000); + }; + + return () => clearTimeout(timeout); + }, [ + imageUrl, + onCapture, + width, + height, + edgeCompensation, + scale, + quality, + ]); + + return ( + + ); +} diff --git a/src/photo/PhotoForm.tsx b/src/photo/PhotoForm.tsx index 2c68281f..664adae7 100644 --- a/src/photo/PhotoForm.tsx +++ b/src/photo/PhotoForm.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useEffect, useRef, useState } from 'react'; +import { useCallback, useState } from 'react'; import { FORM_METADATA_ENTRIES, PhotoFormData, @@ -11,12 +11,10 @@ import { createPhotoAction, updatePhotoAction } from './actions'; import SubmitButtonWithStatus from '@/components/SubmitButtonWithStatus'; import Link from 'next/link'; import { cc } from '@/utility/css'; +import CanvasBlurCapture from '@/components/CanvasBlurCapture'; const THUMBNAIL_WIDTH = 300; const THUMBNAIL_HEIGHT = 200; -const EDGE_BLUR_COMPENSATION = 10; -const BLUR_SCALE = 0.5; -const BLUE_JPEG_QUALITY = 0.9; export default function PhotoForm({ initialPhotoForm, @@ -30,55 +28,16 @@ export default function PhotoForm({ const [formData, setFormData] = useState>(initialPhotoForm); - const [showBlur, setShowBlur] = useState(debugBlur); - - const canvasRef = useRef(null); - const url = formData.url ?? ''; - useEffect(() => { - let timeout: NodeJS.Timeout; - - const image = new Image(); - image.crossOrigin = 'anonymous'; - image.src = url; - image.onload = () => { - timeout = setTimeout(() => { - const canvas = canvasRef.current; - if (canvas) { - canvas.width = THUMBNAIL_WIDTH * BLUR_SCALE; - canvas.height = THUMBNAIL_HEIGHT * BLUR_SCALE; - canvas.style.width = `${THUMBNAIL_WIDTH}px`; - canvas.style.height = `${THUMBNAIL_HEIGHT}px`; - const context = canvasRef.current?.getContext('2d'); - if (context) { - context.scale(BLUR_SCALE, BLUR_SCALE); - context.filter = - 'contrast(1.2) saturate(1.2)' + - `blur(${BLUR_SCALE * 10}px)`; - context.drawImage( - image, - -EDGE_BLUR_COMPENSATION, - -EDGE_BLUR_COMPENSATION, - THUMBNAIL_WIDTH + EDGE_BLUR_COMPENSATION * 2, - THUMBNAIL_WIDTH * image.height / image.width - + EDGE_BLUR_COMPENSATION * 2, - ); - if (type === 'create') { - setFormData(data => ({ - ...data, - blurData: canvas.toDataURL('image/jpeg', BLUE_JPEG_QUALITY), - })); - } - } - } else { - console.error('Cannot generate blur data: canvas not found'); - } - }, 2000); - }; - - return () => clearTimeout(timeout); - }, [url, type]); + const updateBlurData = useCallback((blurData: string) => { + if (type === 'create') { + setFormData(data => ({ + ...data, + blurData, + })); + } + }, [type]); const isFormValid = FORM_METADATA_ENTRIES.every(([key, { required }]) => !required || Boolean(formData[key])); @@ -96,12 +55,13 @@ export default function PhotoForm({ height={THUMBNAIL_HEIGHT} priority /> - setShowBlur(!showBlur)} + - {showBlur && formData.blurData && + {debugBlur && formData.blurData &&