@@ -68,7 +68,7 @@ const ProofreadingEditor = forwardRef(
6868 const [ tool , setTool ] = useState ( "paint" ) ; // 'paint', 'erase', or 'hand'
6969 const [ paintBrushSize , setPaintBrushSize ] = useState ( 10 ) ;
7070 const [ eraseBrushSize , setEraseBrushSize ] = useState ( 10 ) ;
71- const [ showMask , setShowMask ] = useState ( false ) ;
71+ const [ showMask , setShowMask ] = useState ( true ) ;
7272 const [ zoom , setZoom ] = useState ( 1.0 ) ;
7373 const [ isDrawing , setIsDrawing ] = useState ( false ) ;
7474 const [ cursorPos , setCursorPos ] = useState ( { x : 0 , y : 0 } ) ; // Image coordinates
@@ -93,9 +93,15 @@ const ProofreadingEditor = forwardRef(
9393 width : 0 ,
9494 height : 0 ,
9595 } ) ;
96+ const lastCanvasSizeRef = useRef ( { width : 0 , height : 0 } ) ;
9697 const lastDrawRef = useRef ( null ) ;
9798 const minimapRafRef = useRef ( null ) ;
9899 const axisViewStateRef = useRef ( { } ) ;
100+ const mousePosRafRef = useRef ( null ) ;
101+ const pendingMousePosRef = useRef ( null ) ;
102+ const minimapLayoutRef = useRef ( null ) ;
103+ const minimapSourceRef = useRef ( null ) ;
104+ const minimapOverlayRef = useRef ( null ) ;
99105
100106 // Expose handleSave to parent
101107 useImperativeHandle ( ref , ( ) => ( {
@@ -138,6 +144,17 @@ const ProofreadingEditor = forwardRef(
138144 overlayActiveAlpha ,
139145 ] ) ;
140146
147+ useEffect ( ( ) => {
148+ return ( ) => {
149+ if ( minimapRafRef . current ) {
150+ cancelAnimationFrame ( minimapRafRef . current ) ;
151+ }
152+ if ( mousePosRafRef . current ) {
153+ cancelAnimationFrame ( mousePosRafRef . current ) ;
154+ }
155+ } ;
156+ } , [ ] ) ;
157+
141158 const resolveImageSource = ( source ) => {
142159 if ( ! source ) return "" ;
143160 if ( source . startsWith ( "data:image" ) || source . startsWith ( "blob:" ) ) {
@@ -146,6 +163,30 @@ const ProofreadingEditor = forwardRef(
146163 return `data:image/png;base64,${ source } ` ;
147164 } ;
148165
166+ const getMinimapLayout = (
167+ targetWidth ,
168+ targetHeight ,
169+ sourceWidth ,
170+ sourceHeight ,
171+ ) => {
172+ if ( ! targetWidth || ! targetHeight || ! sourceWidth || ! sourceHeight ) {
173+ return null ;
174+ }
175+ const scale = Math . min (
176+ targetWidth / sourceWidth ,
177+ targetHeight / sourceHeight ,
178+ ) ;
179+ const drawWidth = sourceWidth * scale ;
180+ const drawHeight = sourceHeight * scale ;
181+ return {
182+ scale,
183+ drawWidth,
184+ drawHeight,
185+ drawX : ( targetWidth - drawWidth ) / 2 ,
186+ drawY : ( targetHeight - drawHeight ) / 2 ,
187+ } ;
188+ } ;
189+
149190 const loadRenderableImage = async ( source ) => {
150191 const normalized = resolveImageSource ( source ) ;
151192 if ( ! normalized ) throw new Error ( "Image source is missing" ) ;
@@ -185,6 +226,26 @@ const ProofreadingEditor = forwardRef(
185226 ) ;
186227 if ( baseImage ?. close ) baseImage . close ( ) ;
187228
229+ const previousCanvas = lastCanvasSizeRef . current ;
230+ if ( previousCanvas . width > 0 && previousCanvas . height > 0 ) {
231+ const scaleX = canvas . width / previousCanvas . width ;
232+ const scaleY = canvas . height / previousCanvas . height ;
233+ if (
234+ Number . isFinite ( scaleX ) &&
235+ Number . isFinite ( scaleY ) &&
236+ scaleX > 0 &&
237+ scaleY > 0
238+ ) {
239+ setOffset ( ( prev ) => ( {
240+ x : prev . x * scaleX ,
241+ y : prev . y * scaleY ,
242+ } ) ) ;
243+ }
244+ }
245+ lastCanvasSizeRef . current = {
246+ width : canvas . width ,
247+ height : canvas . height ,
248+ } ;
188249 setCanvasDimensions ( { width : canvas . width , height : canvas . height } ) ;
189250
190251 if ( maskBase64 ) {
@@ -227,14 +288,14 @@ const ProofreadingEditor = forwardRef(
227288 ctx . imageSmoothingEnabled = false ;
228289 ctx . putImageData ( imageDataRef . current , 0 , 0 ) ;
229290
230- if ( overlayAllRef . current && overlayAllAlpha > 0.001 ) {
291+ if ( showMask && overlayAllRef . current && overlayAllAlpha > 0.001 ) {
231292 ctx . save ( ) ;
232293 ctx . globalAlpha = overlayAllAlpha ;
233294 ctx . drawImage ( overlayAllRef . current , 0 , 0 , canvas . width , canvas . height ) ;
234295 ctx . restore ( ) ;
235296 }
236297
237- if ( overlayActiveAlpha > 0.001 ) {
298+ if ( showMask && overlayActiveAlpha > 0.001 ) {
238299 // Use editable mask as the only active-layer source to avoid opacity
239300 // compounding when repainting already-active pixels.
240301 if ( maskDataRef . current ) {
@@ -301,9 +362,24 @@ const ProofreadingEditor = forwardRef(
301362 const rect = canvas . getBoundingClientRect ( ) ;
302363 const mx = e . clientX - rect . left ;
303364 const my = e . clientY - rect . top ;
304-
305- const ix = ( mx / rect . width ) * canvasDimensions . width ;
306- const iy = ( my / rect . height ) * canvasDimensions . height ;
365+ const layout = minimapLayoutRef . current ;
366+ if ( ! layout ) return ;
367+ const clampedX = Math . max (
368+ layout . drawX ,
369+ Math . min ( mx , layout . drawX + layout . drawWidth ) ,
370+ ) ;
371+ const clampedY = Math . max (
372+ layout . drawY ,
373+ Math . min ( my , layout . drawY + layout . drawHeight ) ,
374+ ) ;
375+ const relX =
376+ layout . drawWidth > 0 ? ( clampedX - layout . drawX ) / layout . drawWidth : 0 ;
377+ const relY =
378+ layout . drawHeight > 0
379+ ? ( clampedY - layout . drawY ) / layout . drawHeight
380+ : 0 ;
381+ const ix = relX * canvasDimensions . width ;
382+ const iy = relY * canvasDimensions . height ;
307383
308384 setOffset ( {
309385 x : - ( ix - canvasDimensions . width / 2 ) * zoom ,
@@ -340,22 +416,82 @@ const ProofreadingEditor = forwardRef(
340416 ctx . fillStyle = "#000" ;
341417 ctx . fillRect ( 0 , 0 , width , height ) ;
342418
343- // Mirror the currently rendered viewport source so minimap stays faithful.
344- const mainCanvas = canvasRef . current ;
345- if ( mainCanvas ) {
346- ctx . drawImage ( mainCanvas , 0 , 0 , width , height ) ;
347- } else {
348- const tempCanvas = document . createElement ( "canvas" ) ;
349- tempCanvas . width = canvasDimensions . width ;
350- tempCanvas . height = canvasDimensions . height ;
351- const tempCtx = tempCanvas . getContext ( "2d" ) ;
352- tempCtx . putImageData ( imageDataRef . current , 0 , 0 ) ;
353- ctx . drawImage ( tempCanvas , 0 , 0 , width , height ) ;
419+ if ( ! minimapSourceRef . current ) {
420+ minimapSourceRef . current = document . createElement ( "canvas" ) ;
421+ }
422+ if ( ! minimapOverlayRef . current ) {
423+ minimapOverlayRef . current = document . createElement ( "canvas" ) ;
424+ }
425+ const sourceCanvas = minimapSourceRef . current ;
426+ sourceCanvas . width = canvasDimensions . width ;
427+ sourceCanvas . height = canvasDimensions . height ;
428+ const sourceCtx = sourceCanvas . getContext ( "2d" ) ;
429+ sourceCtx . clearRect ( 0 , 0 , sourceCanvas . width , sourceCanvas . height ) ;
430+ sourceCtx . putImageData ( imageDataRef . current , 0 , 0 ) ;
431+
432+ if ( showMask && overlayAllRef . current && overlayAllAlpha > 0.001 ) {
433+ sourceCtx . save ( ) ;
434+ sourceCtx . globalAlpha = overlayAllAlpha ;
435+ sourceCtx . drawImage (
436+ overlayAllRef . current ,
437+ 0 ,
438+ 0 ,
439+ sourceCanvas . width ,
440+ sourceCanvas . height ,
441+ ) ;
442+ sourceCtx . restore ( ) ;
443+ }
444+
445+ if ( showMask && overlayActiveAlpha > 0.001 ) {
446+ if ( maskDataRef . current ) {
447+ const overlayCanvas = minimapOverlayRef . current ;
448+ overlayCanvas . width = sourceCanvas . width ;
449+ overlayCanvas . height = sourceCanvas . height ;
450+ const overlayCtx = overlayCanvas . getContext ( "2d" ) ;
451+ overlayCtx . clearRect ( 0 , 0 , overlayCanvas . width , overlayCanvas . height ) ;
452+ overlayCtx . putImageData (
453+ createMaskOverlay (
454+ maskDataRef . current ,
455+ activeColor ,
456+ overlayActiveAlpha ,
457+ ) ,
458+ 0 ,
459+ 0 ,
460+ ) ;
461+ sourceCtx . drawImage ( overlayCanvas , 0 , 0 ) ;
462+ } else if ( overlayActiveRef . current ) {
463+ sourceCtx . save ( ) ;
464+ sourceCtx . globalAlpha = overlayActiveAlpha ;
465+ sourceCtx . drawImage (
466+ overlayActiveRef . current ,
467+ 0 ,
468+ 0 ,
469+ sourceCanvas . width ,
470+ sourceCanvas . height ,
471+ ) ;
472+ sourceCtx . restore ( ) ;
473+ }
354474 }
355475
476+ const layout = getMinimapLayout (
477+ width ,
478+ height ,
479+ canvasDimensions . width ,
480+ canvasDimensions . height ,
481+ ) ;
482+ if ( ! layout ) return ;
483+ minimapLayoutRef . current = layout ;
484+ ctx . drawImage (
485+ sourceCanvas ,
486+ layout . drawX ,
487+ layout . drawY ,
488+ layout . drawWidth ,
489+ layout . drawHeight ,
490+ ) ;
491+
356492 // Draw viewport rectangle
357- const scaleX = width / canvasDimensions . width ;
358- const scaleY = height / canvasDimensions . height ;
493+ const scaleX = layout . drawWidth / canvasDimensions . width ;
494+ const scaleY = layout . drawHeight / canvasDimensions . height ;
359495
360496 const halfW = container . clientWidth / 2 ;
361497 const halfH = container . clientHeight / 2 ;
@@ -387,15 +523,15 @@ const ProofreadingEditor = forwardRef(
387523 ctx . strokeStyle = "#ff4d4f" ;
388524 ctx . lineWidth = 2 ;
389525 ctx . strokeRect (
390- clampedLeft * scaleX ,
391- clampedTop * scaleY ,
526+ layout . drawX + clampedLeft * scaleX ,
527+ layout . drawY + clampedTop * scaleY ,
392528 rectW * scaleX ,
393529 rectH * scaleY ,
394530 ) ;
395531 ctx . fillStyle = "rgba(255, 77, 79, 0.2)" ;
396532 ctx . fillRect (
397- clampedLeft * scaleX ,
398- clampedTop * scaleY ,
533+ layout . drawX + clampedLeft * scaleX ,
534+ layout . drawY + clampedTop * scaleY ,
399535 rectW * scaleX ,
400536 rectH * scaleY ,
401537 ) ;
@@ -614,8 +750,16 @@ const ProofreadingEditor = forwardRef(
614750 const container = containerRef . current ;
615751 if ( container ) {
616752 const rect = container . getBoundingClientRect ( ) ;
617- // Use client coordinates directly for more speed-resilient tracking
618- setMousePos ( { x : e . clientX - rect . left , y : e . clientY - rect . top } ) ;
753+ pendingMousePosRef . current = {
754+ x : e . clientX - rect . left ,
755+ y : e . clientY - rect . top ,
756+ } ;
757+ if ( ! mousePosRafRef . current ) {
758+ mousePosRafRef . current = requestAnimationFrame ( ( ) => {
759+ mousePosRafRef . current = null ;
760+ setMousePos ( pendingMousePosRef . current ) ;
761+ } ) ;
762+ }
619763 }
620764
621765 const coords = getCanvasCoordinates ( e ) ;
@@ -638,6 +782,7 @@ const ProofreadingEditor = forwardRef(
638782 setIsDrawing ( false ) ;
639783 setIsPanning ( false ) ;
640784 lastDrawRef . current = null ;
785+ setMousePos ( null ) ;
641786 drawMinimap ( ) ;
642787 } ;
643788
0 commit comments