From 1fd9c83600186d42f940d0b6d2e837421133bdfa Mon Sep 17 00:00:00 2001 From: Veli-Pekka Parhiala Date: Mon, 29 Jul 2024 18:15:34 +0300 Subject: [PATCH 1/2] feat: add mask border Adds new optional prop `borderColor` ([number, number, number, number?]). If given, a border is drawn around the mask. This helps to visually distinguish the mask area, especially when the image and mask colors are similar. The border color can be customized to improve visibility and user experience. - Implement border drawing in the `paint` method - Add `borderColor` to Props interface - Update README with new prop documentation - Add border color controls to demo app --- README.md | 1 + packages/demo/src/App.tsx | 38 +++++++++++++++++++++++++++++++++++++- packages/lib/src/index.ts | 18 ++++++++++++++++++ 3 files changed, 56 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ba128ce..844afda 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,7 @@ export default MyEditor | border | Number\|Number[] | The cropping border. Image will be visible through the border, but cut off in the resulting image. Treated as horizontal and vertical borders when passed an array. | | borderRadius | Number | The cropping area border radius. | | color | Number[] | The color of the cropping border, in the form: [red (0-255), green (0-255), blue (0-255), alpha (0.0-1.0)]. | +| borderColor | Number[] | The color of the 1px border around the mask, in the form: [red (0-255), green (0-255), blue (0-255), alpha (0.0-1.0)]. If not provided, no border will be drawn. | | backgroundColor | String | The background color of the image if it's transparent. | | style | Object | Styles for the canvas element. | | scale | Number | The scale of the image. You can use this to add your own resizing slider. | diff --git a/packages/demo/src/App.tsx b/packages/demo/src/App.tsx index 365de97..9b6e41c 100644 --- a/packages/demo/src/App.tsx +++ b/packages/demo/src/App.tsx @@ -31,6 +31,7 @@ type State = { isTransparent: boolean backgroundColor?: string showGrid: boolean + borderColor: string } const App = () => { @@ -49,6 +50,7 @@ const App = () => { isTransparent: false, backgroundColor: undefined, showGrid: false, + borderColor: '#ffffff80', // Default border color (white with 50% opacity) }) const handleNewImage = (e: ChangeEvent) => { @@ -153,6 +155,10 @@ const App = () => { const handleShowGrid = (e: ChangeEvent) => setState({ ...state, showGrid: e.target.checked }) + const handleBorderColorChange = (e: ChangeEvent) => { + setState({ ...state, borderColor: e.target.value }) + } + return (
{ onImageReady={logCallback.bind(this, 'onImageReady')} image={state.image} disableCanvasRotation={state.disableCanvasRotation} + borderColor={hexToRgba(state.borderColor)} /> {
)}
+ Border Color: + + Opacity: + { + const opacity = parseInt(e.target.value).toString(16).padStart(2, '0') + setState({ ...state, borderColor: state.borderColor.slice(0, 7) + opacity }) + }} + /> +

{state.preview && ( @@ -322,4 +349,13 @@ const App = () => { ) } -export default App +// Helper function to convert hex color with alpha to RGBA array +function hexToRgba(hex: string): [number, number, number, number] { + const r = parseInt(hex.slice(1, 3), 16) + const g = parseInt(hex.slice(3, 5), 16) + const b = parseInt(hex.slice(5, 7), 16) + const a = hex.length === 9 ? parseInt(hex.slice(7, 9), 16) / 255 : 1 + return [r, g, b, a] +} + +export default App \ No newline at end of file diff --git a/packages/lib/src/index.ts b/packages/lib/src/index.ts index 5f953d5..a008b6a 100644 --- a/packages/lib/src/index.ts +++ b/packages/lib/src/index.ts @@ -126,6 +126,7 @@ export interface Props { disableBoundaryChecks?: boolean disableHiDPIScaling?: boolean disableCanvasRotation?: boolean + borderColor?: [number, number, number, number?] } export interface Position { @@ -592,6 +593,22 @@ class AvatarEditor extends React.Component { context.rect(width, 0, -width, height) // outer rect, drawn "counterclockwise" context.fill('evenodd') + // Draw 1px border around the mask only if borderColor is provided + if (this.props.borderColor) { + context.strokeStyle = 'rgba(' + this.props.borderColor.slice(0, 4).join(',') + ')' + context.lineWidth = 1 + context.beginPath() + drawRoundedRect( + context, + borderSizeX + 0.5, + borderSizeY + 0.5, + width - borderSizeX * 2 - 1, + height - borderSizeY * 2 - 1, + borderRadius, + ) + context.stroke() + } + if (this.props.showGrid) { drawGrid( context, @@ -713,6 +730,7 @@ class AvatarEditor extends React.Component { disableCanvasRotation, showGrid, gridColor, + borderColor, ...rest } = this.props From dea2658e662715fa46b027db425150a852318296 Mon Sep 17 00:00:00 2001 From: Veli-Pekka Parhiala Date: Mon, 29 Jul 2024 18:46:34 +0300 Subject: [PATCH 2/2] fix: ensure border fully surrounds mask Call `closePath` in `drawRoundedRect` function. This ensures the border goes fully around the mask, fixing the issue where the left border would disappear when applying border radius. --- packages/lib/src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/lib/src/index.ts b/packages/lib/src/index.ts index a008b6a..76c727f 100644 --- a/packages/lib/src/index.ts +++ b/packages/lib/src/index.ts @@ -56,6 +56,7 @@ const drawRoundedRect = ( Math.PI * 0.5, Math.PI, ) + context.closePath() context.translate(-x, -y) } }