'react native circular crop

using react-image-crop package trying to create circular bagde, cropping as square works fine but need to create badge as circular png file

here is the code:

    import React, { useState, useRef, forwardRef, useImperativeHandle } from 'react'
    import ReactCrop, {
      centerCrop,
      makeAspectCrop,
      Crop,
      PixelCrop,
      convertToPixelCrop,
    } from 'react-image-crop'

    
    export interface ImageCropperProps {
      imgSrc: string
      file: File
    }
    
    export const ImageCropper = forwardRef(({ imgSrc, file }: ImageCropperProps, ref) => {
      const imgRef = useRef<HTMLImageElement>(null)
      const [crop, setCrop] = useState<Crop>({
        unit: '%', // Can be 'px' or '%'
        x: 25,
        y: 25,
        width: 50,
        height: 50,
      })
      const [completedCrop, setCompletedCrop] = useState<PixelCrop>()
      const [scale, setScale] = useState(1.0)
      const [circularCrop, setCircularCrop] = useState(true)
      const [rotate, setRotate] = useState(0)
      const previewCanvasRef = useRef<HTMLCanvasElement>(null)
    
      function onImageLoad(e: React.SyntheticEvent<HTMLImageElement>) {
        // @ts-ignore
        imgRef.current = e.currentTarget
    
        const { width, height } = e.currentTarget
        // This is to demonstate how to make and center a % aspect crop
        // which is a bit trickier so we use some helper functions.
        const newCrop = centerCrop(
          makeAspectCrop(
            {
              unit: '%',
              width: 50,
            },
            1,
            width,
            height,
          ),
          width,
          height,
        )
        const newPXCrop = centerCrop(
          makeAspectCrop(
            {
              unit: 'px',
              width: width / 2,
            },
            1,
            width,
            height,
          ),
          width,
          height,
        )
        setCrop(newCrop)
        setCompletedCrop(convertToPixelCrop(newPXCrop, newPXCrop.width, newPXCrop.height))
      }
    
      useImperativeHandle(ref, () => ({
        async onCrop() {
          debugger // eslint-disable-line no-debugger
          if (crop?.height || crop?.width) {
            if (imgRef.current) {
              const { blob: croppedBlob, blobUrl, revokeUrl } = await cropImage(
                imgRef.current,
                file,
                completedCrop,
                true,
                scale,
              )
// this is always square eventhough cropping as circle
              console.log(`blobUrl`, blobUrl) 

              return { croppedBlob, blobUrl, revokeUrl }
            }
          }
        },
      }))
    
      return (
        <div
          style={{
            display: 'flex',
            flexDirection: 'column',
            alignItems: 'center',
            justifyContent: 'center',
          }}
        >
          {Boolean(imgSrc) && (
            <ReactCrop
              crop={crop}
              onChange={(_, percentCrop) => {
                console.log(`_`, _)
                console.log(`percentCrop`, percentCrop)
                setCrop(percentCrop)
              }}
              onComplete={c => setCompletedCrop(c)}
              aspect={1}
              circularCrop={circularCrop}
              keepSelection
              locked
            >
              <img
                alt="Crop me"
                src={imgSrc}
                style={{ transform: `scale(${scale}) rotate(${rotate}deg)` }}
                onLoad={onImageLoad}
              />
            </ReactCrop>
          )}
          <div style={{ display: 'flex', justifyContent: 'center' }}>
            <IconButton disabled={scale <= 0.8} onClick={() => setScale(scale - 0.1)}>
              <Remove />
            </IconButton>
            <Resizer step={0.1} min={0.8} max={1.8} value={scale} setValue={value => setScale(value)} />
            <IconButton
              disabled={scale >= 1.8}
              onClick={() => {
                setScale(scale + 0.1)
              }}
            >
              <Add />
            </IconButton>
          </div>
          <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
            <Checkbox
              color="primary"
              onChange={e => setCircularCrop(e.target.checked)}
              checked={circularCrop}
            />
            <Text text="Circular Crop" variant="body1Medium" />
          </div>
        </div>
      )
    })

cropImage function:

const cropImage = async (
  imageElm: HTMLImageElement,
  file: File,
  crop: PixelCrop | undefined,
  withUrl = false,
  scale = 1,
) => {
  const canvas = document.createElement('canvas'),
    ctx = canvas.getContext('2d')

  const scaleX = imageElm.naturalWidth / imageElm.width
  const scaleY = imageElm.naturalHeight / imageElm.height
  const pixelRatio = window.devicePixelRatio || 1

  if (crop) {
    canvas.width = Math.floor(crop.width * scaleX * pixelRatio)
    canvas.height = Math.floor(crop.height * scaleY * pixelRatio)
  }

  ctx?.scale(pixelRatio, pixelRatio)
  if (ctx?.imageSmoothingQuality) {
    ctx.imageSmoothingQuality = 'high'
  }

  const cropX = crop ? crop.x * scaleX : scaleX
  const cropY = crop ? crop?.y * scaleY : scaleY

  const rotateRads = 0
  const centerX = imageElm.naturalWidth / 2
  const centerY = imageElm.naturalHeight / 2

  ctx?.save()

  // 5) Move the crop origin to the canvas origin (0,0)
  ctx?.translate(-cropX, -cropY)
  // 4) Move the origin to the center of the original position
  ctx?.translate(centerX, centerY)
  // 3) Rotate around the origin
  ctx?.rotate(rotateRads)
  // 2) Scale the image
  ctx?.scale(scale, scale)
  // 1) Move the center of the image to the origin (0,0)
  ctx?.translate(-centerX, -centerY)
  ctx?.drawImage(
    imageElm,
    0,
    0,
    imageElm.naturalWidth,
    imageElm.naturalHeight,
    0,
    0,
    imageElm.naturalWidth,
    imageElm.naturalHeight,
  )
  ctx?.restore()
  return getBlobFromCanvas(canvas, file, withUrl)
}

export default cropImage


Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source