Search code examples
javaarraysimagematrixppm

Creating a PPM Image to be Written to a File Java


so I am working on a program in java which creates the a rectangular image (see link below) as a ppm image that would be further written into a ppm file. Creating and writing the image to the file I get. However, I am having difficulty creating the image dynamically such that it works for any width and height specified. From my understanding, a p3 ppm file simply follows the following format for a 4x4 image.

P3
4 4
15
 0  0  0    0  0  0    0  0  0   15  0 15
 0  0  0    0 15  7    0  0  0    0  0  0
 0  0  0    0  0  0    0 15  7    0  0  0
15  0 15    0  0  0    0  0  0    0  0  0

Where the first three numbers are the headings and the rest is simply the rgb values of each pixel. But I am having trouble figuring out how I can create the above matrix for the image below and for any dimensions specified as it does not include solid colors in a straight line?

Image to be created:

enter image description here

I figured I could create an arraylist which holds an array of rgb values such that each index in the list is one rgb set followed by the next rgb set to the right. However, I am quite confused on what the rgb values would be. Here is what I have:

   public static void createImage(int width, int height){
        pic = new ArrayList();
        int[] rgb = new int[3];

        for(int i = 0; i <= width; i++){
            for(int j = 0; i <= height; j++){
                rgb[0] = 255-j;   //random values as im not sure what they should be or how to calculate them            
                rgb[1] = 0+j; 
                rgb[1] = 0+j; 
                pic.add(rgb);
            } 
        }   
    }

Thanks in advance.


EDITED::Updated code I have managed to fix most of the issues, however, the image generated does not match the one posted above. With this code. I get the following image:

enter image description here

 package ppm;

    import java.awt.Color;
    import java.awt.image.BufferedImage;
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileNotFoundException;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.util.ArrayList;


    public class PPM {

    private BufferedImage img;
    private static final String imageDir = "Image/rect.ppm";
    private final static String filename = "assignment1_q1.ppm";

    private static byte bytes[]=null;      // bytes which make up binary PPM image
    private static double doubles[] = null;
    private static int height = 0;
    private static int width = 0;
    private static ArrayList pic;
    private static String matrix="";

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) throws IOException {
        createImage(200, 200);
        writeImage(filename);
    }


    public static void createImage(int width, int height){
        pic = new ArrayList();
        int[] rgb = new int[3];
        matrix +="P3\n" + width + "\n" + height + "\n255\n";
        for(int i = 0; i <= height; i++){
            for(int j = 0; j <= width; j++){ 
                Color c = getColor(width, height, j, i);
                //System.out.println(c);
                if(c==Color.red){
                      rgb[0] = (int) (255*factor(width, height, j, i));
                      rgb[1] = 0;
                      rgb[2] = 0;
                }else if(c==Color.green){
                      rgb[0] = 0;
                      rgb[1] = (int) (255*factor(width, height, j, i));
                      rgb[2] = 0;
                }else if(c==Color.blue){
                      rgb[0] = 0;
                      rgb[1] = 0;
                      rgb[2] = (int) (255*factor(width, height, j, i));
                }else if(c== Color.white){
                      rgb[0] = (int) (255*factor(width, height, j, i));
                      rgb[1] = (int) (255*factor(width, height, j, i));
                      rgb[2] = (int) (255*factor(width, height, j, i));
                }
                matrix += ""+ rgb[0] + " " + rgb[1] + " " + rgb[2] + "  " ;
                //System.out.println(""+ rgb[0] + " " + rgb[1] + " " + rgb[2] + "  ");
                //pic.add(rgb);
            } 
            matrix += "\n";
        }   
    }

    public static Color getColor(int width, int height, int a, int b){
        double d1 = ((double) width / height) * a;
        double d2 = (((double) -width / height) * a + height);

        if(d1 > b && d2 > b) return Color.green;
        if(d1 > b && d2 < b) return Color.blue;
        if(d1 < b && d2 > b) return Color.red;
        return Color.white;
    }

    public static double  factor(int width, int height, int a, int b){
        double factorX = (double) Math.min(a, width - a) / width * 2;
        double factorY = (double) Math.min(b, height - b) / height * 2;

        //System.out.println(Math.min(factorX, factorY));

        return Math.min(factorX, factorY);
    }

    public static void writeImage(String fn) throws FileNotFoundException, IOException {

        //if (pic != null) {

               FileOutputStream fos = new FileOutputStream(fn);
                fos.write(new String(matrix).getBytes());

                //fos.write(data.length);
                //System.out.println(data.length);
                fos.close();
       // }
    }
}

Solution

  • You can use Linear functions to model the diagonals in the picture. Keep in mind though that in the coordinates (0, 0) lie in the top-left corner of the image!

    Say you want to create an image with the dimensions width and height, the diagonal from the top-left to bottom-right would cross the points (0, 0) and (width, height):

    y = ax + t
    
    0      = a *     0 + t    => t = 0
    height = a * width + 0    => a = height / width
    
    d1(x) = (height / width) * x
    

    Now we can calculate the function for the second diagonal. This diagonal goes through the points (0, height) and (width, 0), so:

    y = ax + t
    
    height = a *     0 + t      => t = height
    0      = a * width + height => a = -(height/width)
    
    d2(x) = -(height/width) * x + height
    

    From this we can determine whether a certain point in the image lies below or above a diagonal. As an example for the point (a, b):

    • if d1(a) > b: (a, b) lies above the first diagonal (left-top to right-bottom), thus it must be either blue or green. Otherwise it must be either red or white

    • if d2(a) > b: (a, b) lies above the second diagonal, thus it must be either red or green. Otherwise it must be white or blue

    By applying both relationships it's easy to determine to which of the four colors a certain point belongs:

    Color getColor(int width, int height, int a, int b){
        double d1 = ((double) height / width) * a;
        double d2 = ((double) -height / width) * a + height;
    
        if(d1 > b && d2 > b) return greenColor;
        if(d1 > b && d2 < b) return blueColor;
        if(d1 < b && d2 > b) return redColor;
        return whiteColor;
    }
    

    Now there's one last thing that we need to take into account: the image darkens towards it's borders.

    A darker version of a color can be created by multiplying each channel with a factor. The lower the factor the darker the resulting the color. For the sake of simplicity I'll assume the change in brightness is linear from the center of the image.

    Since the brightness changes along two axis independently, we need to model this by calculating the change alongside both axis and using the maximum.

    The brightness change as a function of the distance of the center can be modeled using the distance to the closer border of the image in relation to the distance to the center (only on one axis):

    deltaX = min(a, width - a) / (width / 2)
    deltaY = min(b, height - b) / (height / 2)
    

    So we can get the factor to multiply each color-channel by this way:

    double factor(int width, int height, int a, int b){
         double factorX = (double) Math.min(a, width - a) / width * 2;
         double factorY = (double) Math.min(b, height - b) / height * 2;
    
         return Math.min(factorX, factorY);
    }