
// Stereogram.java
// Andrew Davison, August 2023, ad@coe.psu.ac.th

// Based on code by Islidius Fabian, 2015
// https://github.com/Islidius/3D-Stereogram


/* Displays a stero image using a user-supplied image 
   as a depth map to populate a random dot autostereogram.
   The resulting image is also saved to a PNG file.

   An autostereogram is a 2D image that can create the optical 
   illusion of a 3D scene. Autostereograms use only one image 
   to accomplish the effect while normal stereograms require two. 

   See https://en.wikipedia.org/wiki/Autostereogram

   This code uses a similar appraoch to the well-known
   "Magic Eye" books -- a random dot autostereogram. 
   Every pixel in the image is computed from a 
   pattern strip and a depth map. 

   Usage:

     > javac Stereogram.java
     > java Stereogram <grayscale or B&W image fnm>
   e.g.
     > java Stereogram egg.png
         -- displays a stero image using this image as a depth map
            and saves the image to eggOut.png
*/


import java.io.*;
import java.util.*;
import java.awt.*;
import java.awt.image.*;
import javax.imageio.*;


public class Stereogram
{
  private static final int TILE_SIZE = 100;

  private static final int DEPTH_OFFSET = 40;


  public static void main(String[] args)
  {
    if (args.length != 1) {
      System.out.println("Usage: java Stereogram <B&W image fnm>");
      return;
    }

    BufferedImage depthIm = ImageUtils.toGray(
                               ImageUtils.loadImage(args[0]));
    int w = depthIm.getWidth();
    int h = depthIm.getHeight();
    if (w < 3*TILE_SIZE) {
      System.out.println("Image is too small; must be at least " +
                    3*TILE_SIZE + " pixels wide");
      return;
    }

    // ImageViewer.view("Depth map", depthIm);

    BufferedImage outIm = makeBackground(w+TILE_SIZE, h); 
                             // wider than the depth map
    genStereo(depthIm, outIm); 

    String outFnm = ImageUtils.getName(args[0]) + "Out.png";
    ImageUtils.saveImage(outIm, outFnm);
    ImageViewer.view("Stereo output", outFnm);
  }  // end of main()



  private static BufferedImage makeBackground(int w, int h)  
  // draw a pattern repeatedly as the background
  {
    BufferedImage patt = genPattern(TILE_SIZE); 
    BufferedImage im = new BufferedImage(w, h, 
                                BufferedImage.TYPE_INT_RGB);
    for (int i = 0; i < w; i++)
      for (int j = 0; j < h; j++)
        im.setRGB(i, j, patt.getRGB(i%TILE_SIZE, j%TILE_SIZE));
    return im;
  }  // end of makeBackground()



  private static BufferedImage genPattern(int sz)  
  // returns a noisy grayscale pattern
  {
    Random rand = new Random();
    BufferedImage im = new BufferedImage(sz, sz, 
                                    BufferedImage.TYPE_INT_RGB);
    Color col;
    for (int i = 0; i < sz; i++)
      for (int j = 0; j < sz; j++) {
        int f = rand.nextInt(255);
        col = new Color(f, f, f);   // a gray pixel
        im.setRGB(i, j, col.getRGB());
      }

    return im;
  } /* genPattern */



  private static void genStereo(BufferedImage depthIm,
                                BufferedImage outIm)  
  /* The grays in the depth map are converted to offsets which
     are used to move dots in the image along the x-axis 
     to create the stereo effect.
  */
  {
    int outWidth = outIm.getWidth();
    int w = depthIm.getWidth();
    int h = depthIm.getHeight();

    for (int i = 0; i < w; i++) {
      for (int j = 0; j < h; j++) {
        int gray = ImageUtils.getGray(depthIm.getRGB(i, j));
        int depth = (int)Math.round((gray/255.0) * DEPTH_OFFSET);
           // convert grays to depths
        if (depth > 0) {
          int offset = i + TILE_SIZE - depth;
          if (offset < outWidth) 
            // outIm.setRGB(offset, j, Color.BLUE.getRGB());  
                       // used for debugging
            outIm.setRGB(offset, j, outIm.getRGB(i, j)); 
               // use an offset to get the stereogram effect
        }
      }
    }

    // add two dots to help with eye-crossing
    Graphics2D g2d = outIm.createGraphics();
    g2d.setColor(Color.BLACK);
    g2d.fillOval(outWidth/2-50, h-20, 10, 10);
    g2d.fillOval(outWidth/2+50, h-20, 10, 10);
    g2d.dispose();

  } /* genStereo */



}   // end of Sterogram class
