Nyckelord: Array

Svartvita bilder i Java
Vi har tidigare visat hur vi kan arbeta med en flerdimensionell array i Java. Fräscha upp minnet om du behöver repetera!
En svartvit bild kan representeras av en array i två dimensioner där varje värde är en pixel. Varje pixel tolkas som ett tal mellan 0 och 255 där 0 är svart och 255 vit. Alla värden mellan 0 och 255 är alltså en gråskala. I denna uppgift ska vi se hur vi kan modifiera bilder genom att modifiera värdena i en array.
För att underlätta har vi skapat ett programskelett som kan läsa in en bildfil och tolka denna som en 2D-array.
Betrakta följande kod:
import java.awt.image.WritableRaster;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.Iterator;
import javax.imageio.IIOException;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.ImageWriter;
import javax.imageio.stream.FileImageInputStream;
import javax.imageio.stream.FileImageOutputStream;
public class Uppgift11 {
public static void main(String args[]) throws Exception{
// Read image
String filename = "src/strawberry.jpg";
int[][] img = read(filename);
// Manipulate images
int[][] upsidedown = upsidedown(img);
int[][] mirror = mirror(img);
int[][] inverted = inverted(img);
write("blackwhite.jpg", img);
// Save images
write("upsidedown.jpg", upsidedown);
write("mirror.jpg", mirror);
write("inverted.jpg", inverted);
}
public static int[][] upsidedown(int[][] img){
// Skriv din kod här
return img;
}
public static int[][] mirror(int[][] img){
// Skriv din kod här
return img;
}
public static int[][] inverted(int[][] img){
// Skriv din kod här
return img;
}
public static int[][] read(String filename) throws Exception {
BufferedImage source = ImageIO.read(new File(filename));
if (source == null) throw new IIOException("Bildformatet kunde inte läsas");
int width = source.getWidth();
int height = source.getHeight();
BufferedImage gray = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
java.awt.Graphics2D graphics = gray.createGraphics();
graphics.drawImage(source, 0, 0, null);
graphics.dispose();
int[][] pixels = new int[height][width];
for (int row = 0; row < height; row++) {
for (int col = 0; col < width; col++) {
pixels[row][col] = gray.getRaster().getSample(col, row, 0);
}
}
return pixels;
}
public static void write(String filename, int[][] img) throws Exception {
int dot = filename.lastIndexOf('.');
if (dot < 0) throw new IIOException("Filändelse saknas");
String format = filename.substring(dot + 1);
if (!ImageIO.write(createBufferedImage(img), format, new File(filename))) {
throw new IIOException("Bildformatet kunde inte skrivas");
}
}
private static BufferedImage createBufferedImage(int[][] img) {
if (img.length == 0 || img[0].length == 0) throw new IllegalArgumentException("Bilden är tom");
int height = img.length;
int width = img[0].length;
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
for (int row = 0; row < height; row++) {
if (img[row].length != width) throw new IllegalArgumentException("Bilden måste vara rektangulär");
for (int col = 0; col < width; col++) {
int value = img[row][col];
if (value < 0 || value > 255) throw new IllegalArgumentException("Pixelvärdet måste vara 0–255");
image.getRaster().setSample(col, row, 0, value);
}
}
return image;
}
}
I koden finns tre hjälpmetoder read(), write() och createBufferedImage().
read(): Läser in en bild och returnerar en 2D-array där varje pixelvärde är ett tal mellan 0 till 255 (svartvit bild).
write(): Sparar ner en ny bild givet en 2D-array.
createBufferedImage(): hjälpmetod för att kunna spara en ny fil.
Dessa tre metoder är skapade från början och ingenting du behöver röra. Vi använder dessa metoder för att läsa in och spara ner bilder.
Börja med att kopiera över koden till din dator så att du kan testköra programmet.
Börja sedan på att kolla närmre på huvudskriptet:
public static void main(String args[]) throws Exception{
// Read image
String filename = "src/strawberry.jpg";
int[][] img = read(filename);
// Manipulate images
int[][] upsidedown = upsidedown(img);
int[][] mirror = mirror(img);
int[][] inverted = inverted(img);
// Save images
write("upsidedown.jpg", upsidedown);
write("mirror.jpg", mirror);
write("inverted.jpg", inverted);
}
Huvudskriptet läser in en bild och modifierar denna med metoderna upsidedown(), mirror() och inverted(). Det är dessa tre metoder vi ska skapa i denna uppgift. När programmet har kört dessa tre metoder, sparas nya bilder med resultatet.
I exemplet använder vi en bild strawberry.jpg som är sparad direkt under src-foldern i det nya projektet. Spara ner filen till ditt egna program eller använd en egen bild.

När programmet körs, innehåller variabeln img en 2D array som har läst in bilden och formaterat den till svart-vit:
int[][] img = read(filename); // Svart-vit bild

Denna fil sparas när programmet körs som blackwhite.jpg.
A: upp och ner
Gör färdigt metoden upsidedown(). Metoden tar in en 2D-array och returnerar en ny 2D-array med den modifierade bilden. Metoden ska gå igenom pixel för pixel i den svart-vita bilden och göra den "upp-och-ned". Resultatet ska alltså bli:

När du kör programmet, skapas en ny fil upsidedown.jpg. Kontrollera att du gjort rätt genom att öppna denna fil.
Ledtråd
Skapa först en ny array med två dimensioner där du kan spara in de nya värdena. Använd img för att få samma storlek på den nya bilden:
int[][] newImg = new int[img.length][img[0].length];
Ledtråd
Skapa en nästlad for-loop där yttre loopen går igenom alla rader i bilden. Den inre loopen går igenom alla kolumner. Relationen mellan två pixlar i den gamla och nya bilden kan beskrivas enligt:
newImg[row][col] = img[img.length - row - 1][col];
B: Spegelvänd
Gör färdigt metoden mirror(). Metoden tar in en 2D-array och returnerar en ny 2D-array med den modifierade bilden. Metoden ska gå igenom pixel för pixel i den svart-vita bilden och göra den "spegelvänd". Resultatet ska alltså bli:

När du kör programmet, skapas en ny fil mirror.jpg. Kontrollera att du gjort rätt genom att öppna denna fil.
Ledtråd
Relationen mellan en pixel i den gamla och nya bilden kan beskrivas enligt:
newImg[row][col] = img[row][img[0].length - col - 1];
C: Inverterad
Gör färdigt metoden inverted(). Metoden tar in en 2D-array och returnerar en ny 2D-array med den modifierade bilden. Metoden ska gå igenom pixel för pixel i den svart-vita bilden och göra den "inverterad". Det innebär att varje pixelvärde ska bytas ut mot dess motsvarande inverterade värde. Alltså, värde 0 byts ut mot 255, 1 mot 254 osv.
Resultatet ska alltså bli:

När du kör programmet, skapas en ny fil inverted.jpg. Kontrollera att du gjort rätt genom att öppna denna fil.
Ledtråd
Inverteringen kan göras genom att ta 255 - det gamla pixelvärdet.
newImg[row][col] = 255 - img[row][col];
D: Slå ihop alla metoder
Slå nu ihop alla metoder. Börja med att göra bilden upp och ned, spegelvänd den sedan och avsluta med att invertera den. Spara bilden som modifiedImage.jpg.
Bilden ska alltså bli:

Ledtråd
Ta resultatet i föregående metod som input i nästa.
Lösning
A:
upsidedown():
Skapa en ny 2D-array enligt:
int[][] newImg = new int[img.length][img[0].length];
Skapa sedan två nästlade for-loopar som går igenom alla pixelvärden i bilden och byt ut pixeln mot dess motsvarande värde "upp-och-ner".
public static int[][] upsidedown(int[][] img){
int[][] newImg = new int[img.length][img[0].length];
for (int row = 0; row < img.length; row++) {
for (int col = 0; col < img[row].length; col++) {
newImg[row][col] = img[img.length - row - 1][col];
}
}
return newImg;
}
B:
mirror():
Motsvarande kod kan användas för att spegelvända bilden.
public static int[][] mirror(int[][] img){
int[][] newImg = new int[img.length][img[0].length];
for (int row = 0; row < img.length; row++) {
for (int col = 0; col < img[row].length; col++) {
newImg[row][col] = img[row][img[0].length - col - 1];
}
}
return newImg;
}
C:
inverted():
Motsvarande kod kan även användas i denna metod. Skifta varje pixel genom att subtrahera varje pixelvärde från 255.
public static int[][] inverted(int[][] img){
int[][] newImg = new int[img.length][img[0].length];
for (int row = 0; row < img.length; row++) {
for (int col = 0; col < img[row].length; col++) {
newImg[row][col] = 255 - img[row][col];
}
}
return newImg;
}
D:
Slå nu ihop alla metoder och spara ner resultatet i en ny fil:
int[][] modifiedImage = inverted(mirror(upsidedown(img)));
write("modifiedImage.jpg", modifiedImage);
Full kod:
import java.awt.image.WritableRaster;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.Iterator;
import javax.imageio.IIOException;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.ImageWriter;
import javax.imageio.stream.FileImageInputStream;
import javax.imageio.stream.FileImageOutputStream;
public class Uppgift11 {
public static void main(String args[]) throws Exception{
// Read image
String filename = "src/strawberry.jpg";
int[][] img = read(filename);
// Manipulate images
int[][] upsidedown = upsidedown(img);
int[][] mirror = mirror(img);
int[][] inverted = inverted(img);
int[][] modifiedImage = inverted(mirror(upsidedown(img)));
// Save images
write("blackwhite.jpg", img);
write("upsidedown.jpg", upsidedown);
write("mirror.jpg", mirror);
write("inverted.jpg", inverted);
write("modifiedImage.jpg", modifiedImage);
}
public static int[][] upsidedown(int[][] img){
int[][] newImg = new int[img.length][img[0].length];
for (int row = 0; row < img.length; row++) {
for (int col = 0; col < img[row].length; col++) {
newImg[row][col] = img[img.length - row - 1][col];
}
}
return newImg;
}
public static int[][] mirror(int[][] img){
int[][] newImg = new int[img.length][img[0].length];
for (int row = 0; row < img.length; row++) {
for (int col = 0; col < img[row].length; col++) {
newImg[row][col] = img[row][img[0].length - col - 1];
}
}
return newImg;
}
public static int[][] inverted(int[][] img){
int[][] newImg = new int[img.length][img[0].length];
for (int row = 0; row < img.length; row++) {
for (int col = 0; col < img[row].length; col++) {
newImg[row][col] = 255 - img[row][col];
}
}
return newImg;
}
public static int[][] read(String filename) throws Exception {
BufferedImage source = ImageIO.read(new File(filename));
if (source == null) throw new IIOException("Bildformatet kunde inte läsas");
int width = source.getWidth();
int height = source.getHeight();
BufferedImage gray = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
java.awt.Graphics2D graphics = gray.createGraphics();
graphics.drawImage(source, 0, 0, null);
graphics.dispose();
int[][] pixels = new int[height][width];
for (int row = 0; row < height; row++) {
for (int col = 0; col < width; col++) {
pixels[row][col] = gray.getRaster().getSample(col, row, 0);
}
}
return pixels;
}
public static void write(String filename, int[][] img) throws Exception {
int dot = filename.lastIndexOf('.');
if (dot < 0) throw new IIOException("Filändelse saknas");
String format = filename.substring(dot + 1);
if (!ImageIO.write(createBufferedImage(img), format, new File(filename))) {
throw new IIOException("Bildformatet kunde inte skrivas");
}
}
private static BufferedImage createBufferedImage(int[][] img) {
if (img.length == 0 || img[0].length == 0) throw new IllegalArgumentException("Bilden är tom");
int height = img.length;
int width = img[0].length;
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
for (int row = 0; row < height; row++) {
if (img[row].length != width) throw new IllegalArgumentException("Bilden måste vara rektangulär");
for (int col = 0; col < width; col++) {
int value = img[row][col];
if (value < 0 || value > 255) throw new IllegalArgumentException("Pixelvärdet måste vara 0–255");
image.getRaster().setSample(col, row, 0, value);
}
}
return image;
}
}
Vi använder icke-tomma rektangulära bilder med gråvärden 0–255. Läsaren konverterar färgbilder till gråskala; att bara läsa den första färgkanalen skulle inte göra det. JPEG är ett förstörande format, så välj PNG om du behöver spara pixelvärden exakt.