Saving PNG to GIF

Image IO read and Image IO write are focus of this code. A key portion of working with Images, or any file, is to know location of the input and output directories.

import javax.imageio.ImageIO;
import java.io.File;
import java.io.IOException;
import java.awt.image.BufferedImage;

public class ImageIOTest {    

    public static void main( String[] args ){
       BufferedImage img = null;  // buffer type 
        try {
            // Name of file and directories
            String name = "MonaLisa";
            String in = "../images/";
            String out = "../images/tmp/";

            // Either use URL or File for reading image using ImageIO
            File imageFile = new File(in + name + ".png");
            img = ImageIO.read(imageFile);  // set buffer of image data

            // ImageIO Image write to gif in Java
            // Documentation https://docs.oracle.com/javase/tutorial/2d/images/index.html
            ImageIO.write(img, "gif", new File(out + name + ".gif") );  // write buffer to gif

        } catch (IOException e) {
              e.printStackTrace();
        }
        System.out.println("Success");
    }
}
ImageIOTest.main(null);
Success
  • The java.awt.Image class is the superclass that represents graphical images as rectangular arrays of pixels.

  • The java.awt.image.BufferedImage class, which extends the Image class to allow the application to operate directly with image data (for example, retrieving or setting up the pixel color). Applications can directly construct instances of this class.

  • The BufferedImage class is a cornerstone of the Java 2D immediate-mode imaging API. It manages the image in memory and provides methods for storing, interpreting, and obtaining pixel data. Since BufferedImage is a subclass of Image it can be rendered by the Graphics and Graphics2D methods that accept an Image parameter.

  • A BufferedImage is essentially an Image with an accessible data buffer. It is therefore more efficient to work directly with BufferedImage. A BufferedImage has a ColorModel and a Raster of image data. The ColorModel provides a color interpretation of the image's pixel data.

Reading/Loading an Image

  • Java 2D supports loading these external image formats into its BufferedImage format using its Image I/O API which is in the javax.imageio package. Image I/O has built-in support for GIF, PNG, JPEG, BMP, and WBMP. Image I/O is also extensible so that developers or administrators can "plug-in" support for additional formats. For example, plug-ins for TIFF and JPEG 2000 are separately available.

  • To load an image from a specific file use the following code, which is from LoadImageApp.java:

    BufferedImage img = null;

    try {

    img = ImageIO.read(new File("strawberry.jpg"));
    

    } catch (IOException e) {

    }

  • Image I/O recognises the contents of the file as a JPEG format image, and decodes it into a BufferedImage which can be directly used by Java 2D.

  • LoadImageApp.java shows how to display this image.

Image Scaling and ASCII Conversion

In this example we print out a row of text for each row in the image. However, it seems as if the image is too tall. To address this problem, try to output a single character per block of pixels. In particular, average the grayscale values in a rectangular block that’s twice as tall as it is wide, and print out a single character for this block.

import java.awt.Color;
import java.awt.image.BufferedImage;
import java.awt.Image;
import java.awt.Graphics2D;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

import javax.imageio.stream.ImageOutputStream;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.ImageReader;
import javax.imageio.ImageTypeSpecifier;

public class Pics {
    private final String inDir = "../images/"; // location of images
    private final String outDir = "../images/tmp/";  // location of created files
    private final String greyscaleDir = "../images/tmp/greyscale/"; 
    private final String redscaleDir = "../images/tmp/redscale/"; 
    private final String greenscaleDir = "../images/tmp/greenscale/"; 
    private final String bluescaleDir = "../images/tmp/bluescale/"; 
    private String inFile;
    private String resizedFile;
    private String greyscaleFile;
    private String redscaleFile;
    private String greenscaleFile;
    private String bluescaleFile;
    private String asciiFile;
    private String ext;   // extension of file
    private long bytes;
    private int width;
    private int height;

    // Constructor obtains attributes of picture
    public Pics(String name, String ext) {
        this.ext = ext;
        this.inFile = this.inDir + name + "." + ext;
        this.resizedFile = this.outDir + name + "." + ext;
        this.greyscaleFile = this.greyscaleDir + name + "." + ext;
        this.redscaleFile = this.redscaleDir + name + "." + ext;
        this.greenscaleFile = this.greenscaleDir + name + "." + ext;
        this.bluescaleFile = this.bluescaleDir + name + "." + ext;
        this.asciiFile = this.outDir + name + ".txt";
        this.setStats();
    }

    
    // An image contains metadata, namely size, width, and height
    public void setStats() {
        BufferedImage img;
        try {
            Path path = Paths.get(this.inFile);
            this.bytes = Files.size(path);
            img = ImageIO.read(new File(this.inFile));
            this.width = img.getWidth();
            this.height = img.getHeight();
        } catch (IOException e) {
        }
    }

    // Console print of data
    public void printStats(String msg) {
        System.out.println(msg + ": " + this.bytes + " " + this.width + "x" + this.height + "  " + this.inFile);
    }

    // Convert scaled image into buffered image
    public static BufferedImage convertToBufferedImage(Image img) {

        // Create a buffered image with transparency
        BufferedImage bi = new BufferedImage(
                img.getWidth(null), img.getHeight(null),
                BufferedImage.TYPE_INT_ARGB);

        // magic?
        Graphics2D graphics2D = bi.createGraphics();
        graphics2D.drawImage(img, 0, 0, null);
        graphics2D.dispose();

        return bi;
    }
    
    // Scale or reduce to "scale" percentage provided
    public void resize(int scale) {
        BufferedImage img = null;
        Image resizedImg = null;  

        int width = (int) (this.width * (scale/100.0) + 0.5);
        int height = (int) (this.height * (scale/100.0) + 0.5);

        try {
            // read an image to BufferedImage for processing
            img = ImageIO.read(new File(this.inFile));  // set buffer of image data
            // create a new BufferedImage for drawing
            resizedImg = img.getScaledInstance(width, height, Image.SCALE_SMOOTH);
        } catch (IOException e) {
            return;
        }

        try {
            ImageIO.write(convertToBufferedImage(resizedImg), this.ext, new File(resizedFile));
        } catch (IOException e) {
            return;
        }
        
        this.inFile = this.resizedFile;  // use scaled file vs original file in Class
        this.setStats();
    }
    
    // convert every pixel to an ascii character (ratio does not seem correct)
    public void convertToAscii() {
        BufferedImage img = null;
        PrintWriter asciiPrt = null;
        FileWriter asciiWrt = null;

        try {
            File file = new File(this.asciiFile);
            Files.deleteIfExists(file.toPath()); 
        } catch (IOException e) {
            System.out.println("Delete File error: " + e);
        }

        try {
            asciiPrt = new PrintWriter(asciiWrt = new FileWriter(this.asciiFile, true));
        } catch (IOException e) {
            System.out.println("ASCII out file create error: " + e);
        }

        try {
            img = ImageIO.read(new File(this.inFile));
        } catch (IOException e) {
        }

        for (int i = 0; i < img.getHeight(); i++) {
            for (int j = 0; j < img.getWidth(); j++) {
                Color col = new Color(img.getRGB(j, i));
                double pixVal = (((col.getRed() * 0.30) + (col.getBlue() * 0.59) + (col
                        .getGreen() * 0.11)));
                try {
                    asciiPrt.print(asciiChar(pixVal));
                    asciiPrt.flush();
                    asciiWrt.flush();
                } catch (Exception ex) {
                }
            }
            try {
                asciiPrt.println("");
                asciiPrt.flush();
                asciiWrt.flush();
            } catch (Exception ex) {
            }
        }
    }

    // conversion table, there may be better out there ie https://www.billmongan.com/Ursinus-CS173-Fall2020/Labs/ASCIIArt
    public String asciiChar(double g) {
        String str = " ";
        if (g >= 240) {
            str = " ";
        } else if (g >= 210) {
            str = ".";
        } else if (g >= 190) {
            str = "*";
        } else if (g >= 170) {
            str = "+";
        } else if (g >= 120) {
            str = "^";
        } else if (g >= 110) {
            str = "&";
        } else if (g >= 80) {
            str = "8";
        } else if (g >= 60) {
            str = "#";
        } else {
            str = "@";
        }
        return str;
    }

    



    //greyscale hack, reference website: https://medium.com/javarevisited/converting-rgb-image-to-the-grayscale-image-in-java-9e1edc5bd6e7
    public void greyscale() {
        BufferedImage img = null; //In Java programming, null can be assigned to any variable of a reference type (that is, a non-primitive type) to indicate that the variable does not refer to any object or array.
        BufferedImage greyscaleImg = null;

        try {
            // read an image to BufferedImage for processing
            img = ImageIO.read(new File(this.inFile));  // set buffer of image data
            // create a new BufferedImage for drawing
            greyscaleImg = img;

            for (int i=0; i<img.getHeight(); i++) {
                for (int j=0; j<img.getWidth(); j++) {
                    Color c = new Color(img.getRGB(j, i));
                    int red = (int) (c.getRed() * 0.299);
                    int green = (int) (c.getGreen() * 0.587);
                    int blue = (int) (c.getBlue() * 0.114);
                    Color newColor = new Color (red+green+blue, red+green+blue, red+green+blue);
                    greyscaleImg.setRGB(j, i, newColor.getRGB());
                }
            }

        } catch (IOException e) {
            return;
        }

        try {
            ImageIO.write(convertToBufferedImage(greyscaleImg), this.ext, new File(greyscaleFile));
        } catch (IOException e) {
            return;
        }
        
        this.inFile = this.greyscaleFile;  // use scaled file vs original file in Class
        this.setStats();
    }



    public void redscale() {
        BufferedImage img = null; //In Java programming, null can be assigned to any variable of a reference type (that is, a non-primitive type) to indicate that the variable does not refer to any object or array.
        BufferedImage redscaleImg = null;

        try {
            // read an image to BufferedImage for processing
            img = ImageIO.read(new File(this.inFile));  // set buffer of image data
            // create a new BufferedImage for drawing
            redscaleImg = img;

            for (int i=0; i<img.getHeight(); i++) {
                for (int j=0; j<img.getWidth(); j++) {
                    Color c = new Color(img.getRGB(j, i));
                    int red = (int) (c.getRed());
                    int green = 0;
                    int blue = 0;
                    Color newColor = new Color (red, green, blue);
                    redscaleImg.setRGB(j, i, newColor.getRGB());
                }
            }

        } catch (IOException e) {
            return;
        }

        try {
            ImageIO.write(convertToBufferedImage(redscaleImg), this.ext, new File(redscaleFile));
        } catch (IOException e) {
            return;
        }
        
        this.inFile = this.redscaleFile;  // use scaled file vs original file in Class
        this.setStats();
    }
    

    public void greenscale() {
        BufferedImage img = null; //In Java programming, null can be assigned to any variable of a reference type (that is, a non-primitive type) to indicate that the variable does not refer to any object or array.
        BufferedImage greenscaleImg = null;

        try {
            // read an image to BufferedImage for processing
            img = ImageIO.read(new File(this.inFile));  // set buffer of image data
            // create a new BufferedImage for drawing
            greenscaleImg = img;

            for (int i=0; i<img.getHeight(); i++) {
                for (int j=0; j<img.getWidth(); j++) {
                    Color c = new Color(img.getRGB(j, i));
                    int red = 0;
                    int green = (int) (c.getGreen());
                    int blue = 0;
                    Color newColor = new Color (red, green, blue);
                    greenscaleImg.setRGB(j, i, newColor.getRGB());
                }
            }

        } catch (IOException e) {
            return;
        }

        try {
            ImageIO.write(convertToBufferedImage(greenscaleImg), this.ext, new File(greenscaleFile));
        } catch (IOException e) {
            return;
        }
        
        this.inFile = this.greenscaleFile;  // use scaled file vs original file in Class
        this.setStats();
    }


    public void bluescale() {
        BufferedImage img = null; //In Java programming, null can be assigned to any variable of a reference type (that is, a non-primitive type) to indicate that the variable does not refer to any object or array.
        BufferedImage bluescaleImg = null;

        try {
            // read an image to BufferedImage for processing
            img = ImageIO.read(new File(this.inFile));  // set buffer of image data
            // create a new BufferedImage for drawing
            bluescaleImg = img;

            for (int i=0; i<img.getHeight(); i++) {
                for (int j=0; j<img.getWidth(); j++) {
                    Color c = new Color(img.getRGB(j, i));
                    int red = 0;
                    int green = 0;
                    int blue = (int) (c.getBlue());
                    Color newColor = new Color (red, green, blue);
                    bluescaleImg.setRGB(j, i, newColor.getRGB());
                }
            }

        } catch (IOException e) {
            return;
        }

        try {
            ImageIO.write(convertToBufferedImage(bluescaleImg), this.ext, new File(bluescaleFile));
        } catch (IOException e) {
            return;
        }
        
        this.inFile = this.bluescaleFile;  // use scaled file vs original file in Class
        this.setStats();
    }





// tester/driver
public static void main(String[] args) throws IOException {
    Pics monaLisa = new Pics("MonaLisa", "png");
    monaLisa.printStats("Original");
    monaLisa.resize(33);  //33 percent of the original
    monaLisa.printStats("Scaled");
    monaLisa.convertToAscii();
    monaLisa.greyscale();
    monaLisa.redscale();
    monaLisa.greenscale();
    monaLisa.bluescale();

    Pics pumpkin = new Pics("pumpkin", "png");
    pumpkin.printStats("Original");
    pumpkin.resize(33);
    pumpkin.printStats("Scaled");
    pumpkin.convertToAscii();
    pumpkin.greyscale();
    pumpkin.greenscale();
    monaLisa.bluescale();
}
}
Pics.main(null);
Original: 499298 389x413  ../images/MonaLisa.png
Scaled: 55625 128x136  ../images/tmp/MonaLisa.png
Original: 39392 302x265  ../images/pumpkin.png
Scaled: 10497 100x87  ../images/tmp/pumpkin.png

Problem (greyscale)

don't know what's wrong with the .setRGB()

(output) | greyscaleImg.setRGB((int)(j), (int)(i), (int)(newColor.getRGB())); cannot find symbol symbol: method setRGB(int,int,int)

solved:

changed Image greyscaleImg = null; to BufferedImage greyscaleImg = null;

Problem (greyscale)

(output) Original: 499298 389x413 ../images/MonaLisa.png Scaled: 55625 128x136 ../images/tmp/greyscale/MonaLisa.png


java.lang.NullPointerException: null at java.base/java.io.File.(File.java:278) at Pics.greyscale(#34:1) at Pics.main(#34:1) at .(#60:1)</p>

picture appeared in the tmp/greyscale/ is not greyscaled (original image)

solved:

public Pics(String name, String ext) { this.ext = ext; this.inFile = this.inDir + name + "." + ext; this.resizedFile = this.outDir + name + "." + ext; this.resizedFile = this.greyscaleDir + name + "." + ext; this.asciiFile = this.outDir + name + ".txt"; this.setStats(); }

public Pics(String name, String ext) { this.ext = ext; this.inFile = this.inDir + name + "." + ext; this.resizedFile = this.outDir + name + "." + ext; this.greyscaleFile = this.greyscaleDir + name + "." + ext; this.asciiFile = this.outDir + name + ".txt"; this.setStats(); }

forgot to change resizedFile to greyscaleFile, so the image appeared in the greyscale directory is the resized image

</div> </div> </div>

Hacks

Continue to work with Classes, Arrays, and 2D arrays. FYI, you may need to make a directory /tmp under notebook images.

  1. Look at comments above and see if there is better conversions for ASCII to reduce elongation and distortion.
  2. Try to convert images into Grey Scale, Red Scale, Blue Scale, and Green Scale.

Additional Image Code

Runtime using Thymeleaf

random notes when listening to the lecture:

image buffer (look additional resources if don't understand)

changing float into int because you can't do half a pixel

not one pixel at a time -> four by eight or eight by four

average the rgb values - greyscale make a new array and work with value inside the array for averaging

resource - conversion table, there may be better out there ie https://www.billmongan.com/Ursinus-CS173-Fall2020/Labs/ASCIIArt

</div>