TL DR : What is the most efficient way to crop a rectangular image into a circle?
Explanation / Background:
I am working on some code in R that will display Spotify artist images as circles instead of standard rectangles / squares. I could not find packages or commands that crop images in R, especially in a circle, so I wrote my own circ function that reads 3-dimensional (or 4-dimensional) RGB (A) arrays and processes them into a circle using parametric circle equation to determine x values ββfor every single y. Here is 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
This feature is a huge improvement over my previous one, which checks the position of each pixel to see if it was inside or outside the circle, but I still feel it can be accelerated.
Is there a way to check maybe half of the y values, and not all of them, and the mirror in a circle? Is there an actual cropping function that I could use instead? Any help is appreciated!
Edited to add copy-paste-start code (thanks @lukeA):
My original cropping method:
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 # Radius is the smaller of the two -1 to avoid reading nonexistent data ma = array(data = c(a[,,1][(yc-r):(yc+r),(xc-r):(xc+r)], # Read in the 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) } library(jpeg) a = readJPEG("http://1.bp.blogspot.com/-KYvXCEvK9T4/Uyv8xyDQnTI/AAAAAAAAHFY/swaAHLS-ql0/s1600/pink-smiley-face-balls-laughing-HD-image-for-faacebook-sharing.jpg") par(bg = "grey"); plot(1:2, type="n") # Color background to check transparency rasterImage(circ(a),1,1,2,2)
Modified version (thanks @dww):
dwwcirc = 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 # Radius is the smaller of the two -1 to avoid reading nonexistent data ma = array(data = c(a[,,1][(yc-r):(yc+r),(xc-r):(xc+r)], # Read in the 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 x = rep(1:xmax, ymax) # Vector containing all x values y = rep(1:ymax, each=xmax) # Value containing all y values r2 = r^2 ma[,,4][which(( (x-xc)^2 + (y-yc)^2 ) > r2)] = 0 return(ma) } library(jpeg) a = readJPEG("http://1.bp.blogspot.com/-KYvXCEvK9T4/Uyv8xyDQnTI/AAAAAAAAHFY/swaAHLS-ql0/s1600/pink-smiley-face-balls-laughing-HD-image-for-faacebook-sharing.jpg") par(bg = "grey"); plot(1:2, type="n") # Color background to check transparency rasterImage(dwwcirc(a),1,1,2,2)
Version using magick and plotrix (thanks @lukeA and @hrbrmstr):
library(plotrix) jpeg(tf <- tempfile(fileext = "jpeg"), 1000, 1000) par(mar = rep(0,4), yaxs="i", xaxs = "i") plot(0, type = "n", ylim = c(0, 1), xlim = c(0,1), axes=F, xlab=NA, ylab=NA) draw.circle(.5,.5,.5,col="black") dev.off() library(magick) img = image_read("http://1.bp.blogspot.com/-KYvXCEvK9T4/Uyv8xyDQnTI/AAAAAAAAHFY/swaAHLS-ql0/s1600/pink-smiley-face-balls-laughing-HD-image-for-faacebook-sharing.jpg") mask = image_read(tf) radius = min(c(image_info(img)$width, image_info(img)$height)) mask = image_scale(mask, as.character(radius)) par(bg = "grey"); plot(1:2, type="n") rasterImage(as.raster(image_composite(image = mask, composite_image = img, operator = "plus")),1,1,2,2)