R/magiceye.R

Defines functions image_to_magiceye

Documented in image_to_magiceye

#' Convert a 2D image to a 3D autostereogram
#'
#' Convert a 2D greyscale image to a 3D autostereogram. Set dot colors used in final image.
#'
#' @param image Matrix image, usually from \code{png::readPNG()} or \code{jpeg::readJPEG()}.
#' @param colors Array of at least 2 color hex codes. Colors should be visually distinct and avoid using too many.
#' @param alpha_only Logical. Set to TRUE to only consider alpha channel of a png when rendering image.
#' @return A raster matrix for \code{plot()}.
#' @export
#' @examples
#' # Import a jpeg or png
#'  demo_file <- system.file("extdata", "demo_r.png",
#'                           package = "rstereogram", mustWork = TRUE)
#'  demo_image <- png::readPNG(demo_file)
#'
#'  demo_image %>%
#'     image_to_magiceye() %>%
#'     plot()
#'

image_to_magiceye <- function(image, colors = c("#00436b", "#ffed89"), alpha_only = F){

  #image and object size
  # Columns are the 2nd coordinate, which is x
  maxX = dim(image)[2]
  maxY = dim(image)[1]

  #Process image
  if(alpha_only){
    img2 <- image[,,4]
  } else{
    img2 <- 1-rowSums(image[,,-4], dims = 2)/3
  }

  #Algorithm
  DPI = 72 #DPI of output device... need to verify
  E = round(2.5 * DPI) # Eye separation is assumed to be 2.5 inches
  mu = 1/3 #Depth of field (fraction of viewing distance)

  #Stereo separation corresponding to position Z
  separation <- function(Z){
    round((1-mu*Z)/ (2-mu*Z) * E)
  }
  #Far plane = separation(0); near plane = separation(1)

  #Scan each line independently
  Z <- img2
  for(y in 1:maxY){
    pix <- rep(0, maxX) #Array of pixel colors for this row

    same <- 1:maxX #Array that points to a pixel to the right...
    #... that is constrained to be this color
    # Each pixel is initially linked with itself
    # Lines 23-24 in paper

    for(x in 1:maxX){
      s = separation(Z[y, x])
      #lines 28 and 29. Using mod to offset odd s
      # left = x - (s + round((y%%2)*(s%%2)))/2 #Pixels at left and right...
      left = round(x - s/2) #Pixels at left and right...
      right = left + s #... must be the same...

      #... or must they?
      if(left > 0 && right < maxX){
        visible  = TRUE #First perform hidden-surface removal
        t = 1 #We will check the points (x-t, y) and (x+t, y)
        zt = 0 #Z-coord of ray at these two points

        while(visible && zt < 1){
          zt = Z[y, x] + 2*(2 - mu*Z[y, x]*t/(mu*E))

          visible = (Z[y, x-t] < zt) && (Z[y, x+t] < zt) #false if obscured
          t = t+1
        }
        #Done with hidden-surface removal
        # Paper lines 42-48
        if(visible){
          k = same[left]
          while(k != left && k != right){
            if(k < right){
              left <- k
            } else {
              left <- right
              right <- k
            }
          }
          same[left] <- right
        }
      }
    }

    #Now set the pixels on this scan line
    for(x in maxX:1){
      if(same[x] == x){
        pix[x] <- sample(seq_along(colors), 1)
      } else {
        pix[x] <- pix[same[x]]
      }

      Z[y, x] <- pix[x]
    }
  }

  #Convert numbers into colors
  Z2 <- as.character(Z)
  for(ii in seq_along(colors)){
    Z2 <- replace(Z2,
                  Z2 == as.character(ii),
                  colors[ii]
    )
  }

  # out_list <- list(
  #   magic_eye = Z2,
  #   dims = c(maxY, maxX)
  # )
  #
  # return(out_list)
  return(grDevices::as.raster(matrix(Z2, nrow = maxY)))

}
ryantimpe/MagicEye documentation built on April 12, 2021, 5:59 p.m.