gaspar
gaspar

Reputation: 1078

Change image pixel colors in R and save image

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

Answers (1)

Allan Cameron
Allan Cameron

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))

enter image description here And

plot(raster::as.raster(img_white))

enter image description here

Or if you want to invert a single channel (in this case red):

img[,,1] <- 1 - img[,,1]
plot(raster::as.raster(img))

enter image description here


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

Related Questions