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..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) } } @@ -126,6 +127,7 @@ export interface Props { disableBoundaryChecks?: boolean disableHiDPIScaling?: boolean disableCanvasRotation?: boolean + borderColor?: [number, number, number, number?] } export interface Position { @@ -592,6 +594,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 +731,7 @@ class AvatarEditor extends React.Component { disableCanvasRotation, showGrid, gridColor, + borderColor, ...rest } = this.props