Reputation: 1078
I have hundreds of small (300x300 pixel) pure black and white PNG images. I want to convert them to different two colors in R and save them again as PNG. (Firstly I want to actually invert them: all black to white and all white to black - but later I will need other colors, e.g. black to red and white to green, etc.) This seems real simple, but whatever I try I run into problems.
For example, the simplest solution seems using as.raster
in base R and the png
package (at least for reading):
img = readPNG(newfile) # read black (background) and white (figure) image
img <- as.raster(img)
img_white = img
img_white[img_white == "#000000"] <- 'red' # temporarily convert back to red as placeholder
img_white[img_white == "#FFFFFF"] <- '#000000' # convert white to black
img_white[img_white == "red"] <- '#FFFFFF' # convert originally black to white
(Here by the way I needed a placeholder because the goal color is the same as the other original - but that's beside the point.)
So this works nicely and I can plot it with plot(img_white)
, but incredibly I find no way of automatically saving the image as file. I tried e.g. writePNG
, writeRaster
, writeGDAL
, but they all give various error messages due to wrong class or wrong format or similar. (I also tried various conversions without success.)
Among others I also tried the imager
package, which nicely saves the image after manipulating it, but I cannot find the way to convert a single specified color in the entire image.
All in all, I'm open to any possible solutions as long as it gives a full working code. I don't much care whatever package I need to use, though if possible I'd prefer as simple code as possible and hence as few packages as possible.
SOLUTION:
Based on Allan Cameron's answer, I wrote this function:
change_cols = function(replace_black, replace_white, theimg) {
r_b = col2rgb(replace_black) / 255
r_w = col2rgb(replace_white) / 255
theimg[theimg == 1] <- 2
for (i in 1:3) {
theimg[,,i][theimg[,,i] == 0] <- r_b[i]
}
for (i in 1:3) {
theimg[,,i][theimg[,,i] == 2] <- r_w[i]
}
return(theimg)
}
Then it's as simple as:
img = readPNG(newfile)
newimg = change_cols("#FF0000", "#00FF00", img)
writePNG(newimg, "fileout.png")
(See also Allan Cameron's function which converts the raster
object.)
Upvotes: 3
Views: 4156
Reputation: 174476
You need to write the PNG as a numeric array, just as it was when you loaded it. Since you only have black and white images, it shouldn't be a problem to manually swap black and white (they have value black = 0, white = 1).
You only need to convert it to a raster for plotting:
library(png)
newfile = "~/face.png"
img = readPNG(newfile) # read black (background) and white (figure) image
img_white = 1-img
Now
plot(raster::as.raster(img))
plot(raster::as.raster(img_white))
Or if you want to invert a single channel (in this case red):
img[,,1] <- 1 - img[,,1]
plot(raster::as.raster(img))
EDIT
After further comments from the OP, I thought it was reasonable to take this answer to its conclusion by writing a function that takes a raster object and saves it as a PNG file:
save_raster_as_PNG <- function(raster_object, path)
{
if(class(raster_object) != "raster") stop("This is not a raster object.")
dims <- dim(raster_object)
red <- as.numeric(paste0("0x", substr(raster_object, 2 , 3)))/255
dim(red) <- rev(dims)
green <- as.numeric(paste0("0x", substr(raster_object, 4 , 5)))/255
dim(green) <- rev(dims)
blue <- as.numeric(paste0("0x", substr(raster_object, 6 , 7)))/255
dim(blue) <- rev(dims)
result <- numeric(3 * dims[1] * dims[2])
dim(result) <- c(dims, 3)
result[,,1] <- t(red)
result[,,2] <- t(blue)
result[,,3] <- t(green)
tryCatch(png::writePNG(result, path), error = function(e) stop(e))
cat("Raster successfully saved to", path.expand(path))
}
img <- raster::as.raster(img)
save_raster_as_PNG(img, "~/face3.png")
# Raster successfully saved to C:/Users/AllanCameron/SO/R/face3.png
Upvotes: 4