From aae1b7112fc3c70ab94d2fea3c4f046c4f26f6f2 Mon Sep 17 00:00:00 2001 From: Veli-Pekka Parhiala Date: Wed, 30 Oct 2024 10:08:30 +0200 Subject: [PATCH] feat: add bleed area Add horizontal and vertical bleed area indicators to help users ensure important content isn't too close to the edges. This is particularly useful for responsive layouts where images might be cropped differently at different viewport sizes. - Add horizontalBleed and verticalBleed props (numbers between 0-1) - Implement semi-transparent red overlay to indicate bleed areas - Bleed areas are drawn inside the cropping boundaries - 0.1 value equals 10% bleed on each side --- packages/demo/src/App.tsx | 38 +++++++++++++++++++++++ packages/lib/src/index.ts | 65 +++++++++++++++++++++++++++++++++++---- 2 files changed, 97 insertions(+), 6 deletions(-) diff --git a/packages/demo/src/App.tsx b/packages/demo/src/App.tsx index 365de97..ed27d41 100644 --- a/packages/demo/src/App.tsx +++ b/packages/demo/src/App.tsx @@ -31,6 +31,8 @@ type State = { isTransparent: boolean backgroundColor?: string showGrid: boolean + horizontalBleed: number + verticalBleed: number } const App = () => { @@ -49,6 +51,8 @@ const App = () => { isTransparent: false, backgroundColor: undefined, showGrid: false, + horizontalBleed: 0, + verticalBleed: 0, }) const handleNewImage = (e: ChangeEvent) => { @@ -153,6 +157,14 @@ const App = () => { const handleShowGrid = (e: ChangeEvent) => setState({ ...state, showGrid: e.target.checked }) + const handleHorizontalBleed = (e: ChangeEvent) => { + setState({ ...state, horizontalBleed: parseFloat(e.target.value) }) + } + + const handleVerticalBleed = (e: ChangeEvent) => { + setState({ ...state, verticalBleed: parseFloat(e.target.value) }) + } + return (
{ onImageReady={logCallback.bind(this, 'onImageReady')} image={state.image} disableCanvasRotation={state.disableCanvasRotation} + horizontalBleed={state.horizontalBleed} + verticalBleed={state.verticalBleed} /> {
)}
+ Horizontal Bleed: + + {(state.horizontalBleed * 100).toFixed(0)}% +
+ Vertical Bleed: + + {(state.verticalBleed * 100).toFixed(0)}% +

{state.preview && ( diff --git a/packages/lib/src/index.ts b/packages/lib/src/index.ts index 5f953d5..8ef999b 100644 --- a/packages/lib/src/index.ts +++ b/packages/lib/src/index.ts @@ -126,6 +126,8 @@ export interface Props { disableBoundaryChecks?: boolean disableHiDPIScaling?: boolean disableCanvasRotation?: boolean + horizontalBleed?: number + verticalBleed?: number } export interface Position { @@ -559,19 +561,21 @@ class AvatarEditor extends React.Component { return { x, y, height, width } } - paint(context: CanvasRenderingContext2D) { + paint = (context: CanvasRenderingContext2D) => { context.save() context.scale(this.pixelRatio, this.pixelRatio) context.translate(0, 0) - context.fillStyle = 'rgba(' + this.props.color.slice(0, 4).join(',') + ')' - - let borderRadius = this.props.borderRadius + const dimensions = this.getDimensions() const [borderSizeX, borderSizeY] = this.getBorders(dimensions.border) const height = dimensions.canvas.height const width = dimensions.canvas.width - - // clamp border radius between zero (perfect rectangle) and half the size without borders (perfect circle or "pill") + + // Draw the main mask + context.fillStyle = 'rgba(' + this.props.color.slice(0, 4).join(',') + ')' + let borderRadius = this.props.borderRadius + + // clamp border radius between zero (perfect rectangle) and half the size without borders borderRadius = Math.max(borderRadius, 0) borderRadius = Math.min( borderRadius, @@ -592,6 +596,55 @@ class AvatarEditor extends React.Component { context.rect(width, 0, -width, height) // outer rect, drawn "counterclockwise" context.fill('evenodd') + // Draw bleed areas if specified + if (this.props.horizontalBleed || this.props.verticalBleed) { + const bleedColor = 'rgba(255, 0, 0, 0.2)' // Semi-transparent red + context.fillStyle = bleedColor + + const innerWidth = width - borderSizeX * 2 + const innerHeight = height - borderSizeY * 2 + + if (this.props.horizontalBleed) { + const bleedWidth = innerWidth * this.props.horizontalBleed + + // Left bleed + context.fillRect( + borderSizeX, + borderSizeY, + bleedWidth, + innerHeight + ) + + // Right bleed + context.fillRect( + width - borderSizeX - bleedWidth, + borderSizeY, + bleedWidth, + innerHeight + ) + } + + if (this.props.verticalBleed) { + const bleedHeight = innerHeight * this.props.verticalBleed + + // Top bleed + context.fillRect( + borderSizeX, + borderSizeY, + innerWidth, + bleedHeight + ) + + // Bottom bleed + context.fillRect( + borderSizeX, + height - borderSizeY - bleedHeight, + innerWidth, + bleedHeight + ) + } + } + if (this.props.showGrid) { drawGrid( context,