Daniel - 6 months ago 24

R Question

**TL;DR**: What is the most efficient way to crop a rectangular image to a circle?

Wall of text:

I'm working on some code in R that will display Spotify artist images as circles instead of the default rectanges/squares. I couldn't find any packages or commands that crop images in R, especially to a circle, so I wrote my own function,

`circ`

`Given an RGB(A) array:`

Find the center of the image, radius = min(x coord, y coord)

Pre-crop the image to a square of dimensions 2r x 2r

For every unique y value:

Determine the x coordinates on the circle

Make pixels outside of the circle transparent

Return the cropped image as an RGBA array

And the actual code:

`circ = function(a){`

# First part of the function finds the radius of the circle and crops the image accordingly

xc = floor(dim(a[,,1])[2]/2) # X coordinate of the center

yc = floor(dim(a[,,1])[1]/2) # Y coordinate of the center

r = min(xc, yc) - 1 # -1 to avoid reading nonexistent data

ma = array(data = c(a[,,1][(yc-r):(yc+r),(xc-r):(xc+r)], # Read in a pre-cropped image

a[,,2][(yc-r):(yc+r),(xc-r):(xc+r)], # Of dimensions 2r x 2r, centered

a[,,3][(yc-r):(yc+r),(xc-r):(xc+r)], # Around (xc, yc)

rep(1,length(a[,,1][(yc-r):(yc+r),(xc-r):(xc+r)]))), # Add fourth alpha layer

dim = c(length((yc-r):(yc+r)),length((xc-r):(xc+r)),4))

if(yc > xc) yc = xc else if(xc > yc) xc = yc # Re-evaluate your center for the cropped image

xmax = dim(ma[,,1])[2]; ymax = dim(ma[,,1])[1] # Find maximum x and y values

# Second part of the function traces circle by the parametric eqn. and makes outside pixels transparent

for(y in 1:ymax){ # For every y in the cropped image

theta = asin((y - yc) / r) # y = yc + r * sin(theta) by parametric equation for a circle

x = xc + r * cos(theta) # Then we can find the exact x coordinate using the same formula

x = which.min(abs(1:xmax - x)) # Find which x in array is closest to exact coordinate

if(!x - xc == 0 && !xmax - x == 0){ # If you're not at the "corners" of the circle

ma[,,4][y,c(1:(xmax-x), (x+1):xmax)] = 0 # Make pixels on either side of the circle trans.

} else if(!xmax - x == 0) ma[,,4][y,] = 0 # This line makes tops/bottoms transparent

}

return(ma)

}

This function is a tremendous improvement over my previous one, which checked the position of every pixel to see if it was inside or outside of the circle, but I still feel like it could be sped up further.

Is there a way I could check maybe half of the y-values instead of all of them, and mirror across the circle? Is there an actual cropping function I could use instead? Any and all help is much appreciated!

Answer

You can improve the performance of your `circ`

function if you do a vectorised subset-assign operation on your array (instead of looping) using the the fact that `(x-xc)^2 +(y-yc)^2 > r^2`

for points outside a circle.

To do this, replace the 2nd part of your function with

```
# Second part of the function traces circle by...
x = rep(1:xmax, ymax)
y = rep(1:ymax, each=xmax)
r2 = r^2
ma[,,4][which(( (x-xc)^2 + (y-yc)^2 ) > r2)] <- 0
return(ma)
```