Daniel - 8 months ago 31
R Question

# Most efficient way to crop image to circle (in R)?

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`
, which reads 3-Dimensional (or 4-Dimensional) RGB(A) arrays and crops them to a circle using the parametric equation of a circle to determine the x values for every unique y. Here's my psuedocode:

``````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!

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.
``````  # Second part of the function traces circle by...