Created
April 28, 2014 12:00
-
-
Save kiliankoe/11369756 to your computer and use it in GitHub Desktop.
Aufgabe 1: Bildverarbeitung Medien und Medienströme @ TU Dresden SS14
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package de.tudresden.inf.mms; | |
import java.awt.Image; | |
import java.awt.image.BufferedImage; | |
/** | |
* @author Kilian Koeltzsch, 3848487 | |
* | |
*/ | |
@SuppressWarnings("JavadocReference") | |
public class ImageEditor { | |
/** | |
* Originalbild (Ausgangspunkt fuer Berechnungen) | |
*/ | |
private BufferedImage image; | |
/** | |
* Temporaere Kopie bzw. Ergebnisbild | |
*/ | |
private BufferedImage tmpImg; | |
public ImageEditor(BufferedImage image) { | |
super(); | |
this.image = image; | |
tmpImg = new BufferedImage(image.getWidth(), image.getHeight(), | |
image.getType()); | |
} | |
/** | |
* Konvertiert das vorgegebene RGB-Bild ({@link ImageEditor#image}) in ein | |
* Graustufenbild. | |
* | |
* @return {@link java.awt.image.BufferedImage} | |
*/ | |
public Image grayscale() { | |
int gray; | |
int[] rgb; | |
for (int x = 0; x < image.getWidth(); x++) { | |
for (int y = 0; y < image.getHeight(); y++) { | |
rgb = ImageHelper.toRGBArray(image.getRGB(x, y)); | |
gray = (int) Math.floor((rgb[0] + rgb[1] + rgb[2]) / 3); | |
rgb[0] = gray; | |
rgb[1] = gray; | |
rgb[2] = gray; | |
tmpImg.setRGB(x, y, ImageHelper.toIntRGB(rgb)); | |
} | |
} | |
return tmpImg; | |
} | |
/** | |
* @see Aufgabe 1.1.1 | |
* @return {@link java.awt.image.BufferedImage} | |
*/ | |
public Image invert() { | |
// Jeden Farbwert durch Subtraktion von 255 invertieren. | |
int[] rgb; | |
for (int x = 0; x < image.getWidth(); x++) { | |
for (int y = 0; y < image.getHeight(); y++) { | |
rgb = ImageHelper.toRGBArray(image.getRGB(x, y)); | |
rgb[0] = 255 - rgb[0]; | |
rgb[1] = 255 - rgb[1]; | |
rgb[2] = 255 - rgb[2]; | |
tmpImg.setRGB(x, y, ImageHelper.toIntRGB(rgb)); | |
} | |
} | |
return tmpImg; | |
} | |
/** | |
* @see Aufgabe 1.1.2 | |
* @return {@link java.awt.image.BufferedImage} | |
*/ | |
public Image rotate() { | |
// Pixel von oben links lesen und unten links schreiben. | |
// Bild wird so verkehrt herum wieder aufgebaut. | |
int[] pixel; | |
int new_x; | |
int new_y; | |
for (int x = 0; x < image.getWidth(); x++) { | |
for (int y = 0; y < image.getHeight(); y++) { | |
pixel = ImageHelper.toRGBArray(image.getRGB(x, y)); | |
new_x = image.getWidth() - (x + 1); | |
new_y = image.getHeight() - (y + 1); | |
tmpImg.setRGB(new_x, new_y, ImageHelper.toIntRGB(pixel)); | |
} | |
} | |
return tmpImg; | |
} | |
// konvertiere rgb matrix nach ycbcr | |
// relevant fuer Aufgabe 1.1.3 und 1.1.2 | |
private int[] convertToYCBCR(int[] rgb) { | |
int[] ycbcr = new int[3]; | |
ycbcr[0] = (int) Math.floor((0.299 * rgb[0]) + (0.587 * rgb[1]) + (0.114 * rgb[2])); | |
ycbcr[1] = (int) Math.floor(128 - (0.168736 * rgb[0]) - (0.331264 * rgb[1]) + (0.5 * rgb[2])); | |
ycbcr[2] = (int) Math.floor(128 + (0.5 * rgb[0]) - (0.418688 * rgb[1]) - (0.081312 * rgb[2])); | |
return ycbcr; | |
} | |
// konvertiere ycbcr matrix nach rgb | |
// relevant fuer Aufgabe 1.1.3 und 1.1.2 | |
private int[] convertToRGB(int[] ycbcr) { | |
int[] rgb = new int[3]; | |
rgb[0] = (int) Math.floor(ycbcr[0] + 1.402 * (ycbcr[2] - 128)); | |
rgb[1] = (int) Math.floor(ycbcr[0] - 0.34414 * (ycbcr[1] - 128) - 0.71414 * (ycbcr[2] - 128)); | |
rgb[2] = (int) Math.floor(ycbcr[0] + 1.772 * (ycbcr[1] - 128)); | |
return rgb; | |
} | |
/** | |
* @see Aufgabe 1.1.3 | |
* @return {@link java.awt.image.BufferedImage} | |
*/ | |
public Image linHist() { | |
int rgb[]; | |
int ycbcr[] = new int[3]; | |
double ywerte[] = new double[256]; | |
double sumpercent; | |
int amountpix = image.getHeight() * image.getWidth(); | |
// Berechne fuer jeden Pixel den Y (Helligkeit) Wert und summiere | |
// das Auftreten dieser Helligkeitswerte in einem Array (ywerte) | |
for (int x = 0; x < image.getWidth(); x++) { | |
for (int y = 0; y < image.getHeight(); y++) { | |
rgb = ImageHelper.toRGBArray(image.getRGB(x, y)); | |
// aus Performancegruenden hier nur der Y Wert und nicht mit convertToYCBCR() | |
ycbcr[0] = (int) Math.floor((0.299 * rgb[0]) + (0.587 * rgb[1]) + (0.114 * rgb[2])); | |
ywerte[ycbcr[0]]++; | |
} | |
} | |
// Wandle Werte in ywerte in prozentuale Anteile um | |
for (int i = 0; i < ywerte.length; i++) { | |
ywerte[i] = ywerte[i] / amountpix; | |
} | |
// Durchlaufe erneut alle Pixel | |
for (int x = 0; x < image.getWidth(); x++) { | |
for (int y = 0; y < image.getHeight(); y++) { | |
rgb = ImageHelper.toRGBArray(image.getRGB(x, y)); | |
// Berechne Y, Cb und Cr Werte | |
ycbcr = convertToYCBCR(rgb); | |
// Summiere die Auftrittswahrscheinlichkeiten aller Pixel mit einem | |
// niedrigerem Y Wert | |
sumpercent = 0; | |
for (int i = 0; i < ycbcr[0]; i++) { | |
sumpercent += ywerte[i]; | |
} | |
// Berechne den neuen Y Wert fuer diesen Pixel | |
ycbcr[0] = (int) Math.floor(255 * sumpercent); | |
// Wandle Y, Cb und Cr wieder in RGB Werte um | |
rgb = convertToRGB(ycbcr); | |
tmpImg.setRGB(x, y, ImageHelper.toIntRGB(rgb)); | |
} | |
} | |
return tmpImg; | |
} | |
/** | |
* @see Aufgabe 1.2.1 | |
* @return {@link java.awt.image.BufferedImage} | |
*/ | |
public Image subsampling() { | |
// Schreibe die rgb Daten eines jeden Pixels in | |
// sich selbst und die relevanten Nachbarn. | |
// Das Ergebnis koennte man als eine Version des Bildes | |
// mit Pixeln der doppelten Groesse in beide Richtungen | |
// beschreiben. | |
int[] rgb; | |
for (int x = 0; x < image.getWidth(); x += 2) { | |
for (int y = 0; y < image.getHeight(); y += 2) { | |
rgb = ImageHelper.toRGBArray(image.getRGB(x, y)); | |
tmpImg.setRGB(x ,y ,ImageHelper.toIntRGB(rgb)); | |
tmpImg.setRGB(x+1,y ,ImageHelper.toIntRGB(rgb)); | |
tmpImg.setRGB(x ,y+1,ImageHelper.toIntRGB(rgb)); | |
tmpImg.setRGB(x+1,y+1,ImageHelper.toIntRGB(rgb)); | |
} | |
} | |
return tmpImg; | |
} | |
/** | |
* @see Aufgabe 1.2.2 | |
* @return {@link java.awt.image.BufferedImage} | |
*/ | |
public Image colorSubsampling() { | |
// Dieser Algorithmus scheint sein Werk ziemlich gut zu erledigen... | |
// Unterschiede zwischen Original und Output sind kaum zu erkennen, | |
// bei genauerer Betrachtung (mit ImageMagick's compare -> http://i.imgur.com/UDXp6gO.png) | |
// jedoch ersichtlich. | |
// pixel reihenfolge im array | |
// self, ost, suedost, sued | |
int[][] rgb = new int[4][3]; | |
int[][] ycbcr = new int[4][3]; | |
int avcb; | |
int avcr; | |
for (int x = 0; x < image.getWidth() - 1; x++) { | |
for (int y = 0; y < image.getHeight() - 1; y++) { | |
// hole relevante pixel | |
rgb[0] = ImageHelper.toRGBArray(image.getRGB(x ,y )); | |
rgb[1] = ImageHelper.toRGBArray(image.getRGB(x+1,y )); | |
rgb[2] = ImageHelper.toRGBArray(image.getRGB(x+1,y+1)); | |
rgb[3] = ImageHelper.toRGBArray(image.getRGB(x ,y+1)); | |
// konvertiere alle ins ycbcr modell | |
for (int i = 0; i < 4; i++) { | |
ycbcr[i] = convertToYCBCR(rgb[i]); | |
} | |
// berechne mittelwerte fuer cb und cr | |
avcb = 0; | |
avcr = 0; | |
for (int i = 0; i < 4; i++) { | |
avcb += ycbcr[i][1]; | |
avcr += ycbcr[i][2]; | |
} | |
avcb /= 4; | |
avcr /= 4; | |
// schreibe mittelwerte zurueck nach ycbcr | |
for (int i = 0; i < 4; i++) { | |
ycbcr[i][1] = avcb; | |
ycbcr[i][2] = avcr; | |
} | |
// konvertiere alle zurueck zu rgb | |
for (int i = 0; i < 4; i++) { | |
rgb[i] = convertToRGB(ycbcr[i]); | |
} | |
// schreibe ins tmpImg | |
tmpImg.setRGB(x ,y ,ImageHelper.toIntRGB(rgb[0])); | |
tmpImg.setRGB(x+1,y ,ImageHelper.toIntRGB(rgb[1])); | |
tmpImg.setRGB(x+1,y+1,ImageHelper.toIntRGB(rgb[2])); | |
tmpImg.setRGB(x ,y+1,ImageHelper.toIntRGB(rgb[3])); | |
} | |
} | |
return tmpImg; | |
} | |
/** | |
* @see Aufgabe 1.3.1 | |
* @return {@link java.awt.image.BufferedImage} | |
*/ | |
public Image colorQuant() { | |
// Dank der gegebenen Methode getColorFromPalette kann die naechste | |
// Palettfarbe einfach geholt und in das tmpImage geschrieben werden. | |
int[] rgb; | |
for (int x = 0; x < image.getWidth(); x++) { | |
for (int y = 0; y < image.getHeight(); y++) { | |
rgb = ImageHelper.toRGBArray(image.getRGB(x, y)); | |
rgb = ImageHelper.getColorFromPalette(rgb); | |
tmpImg.setRGB(x, y, ImageHelper.toIntRGB(rgb)); | |
} | |
} | |
return tmpImg; | |
} | |
/** | |
* @see Aufgabe 1.3.2 | |
* @return {@link java.awt.image.BufferedImage} | |
*/ | |
public Image orderedDithering() { | |
// Bevor die Farbe aus der Palette geholt wird, wird erst noch der gegebene | |
// Fehlerwert aus der Bayer Matrix auf die rgb Werte des aktuellen Pixels addiert. | |
int[] rgb; | |
for (int x = 0; x < image.getWidth(); x++) { | |
for (int y = 0; y < image.getHeight(); y++) { | |
rgb = ImageHelper.toRGBArray(image.getRGB(x, y)); | |
rgb[0] += ImageHelper.BAYER8x8[x % 8][y % 8]; | |
rgb[1] += ImageHelper.BAYER8x8[x % 8][y % 8]; | |
rgb[2] += ImageHelper.BAYER8x8[x % 8][y % 8]; | |
rgb = ImageHelper.getColorFromPalette(rgb); | |
tmpImg.setRGB(x, y, ImageHelper.toIntRGB(rgb)); | |
} | |
} | |
return tmpImg; | |
} | |
/** | |
* @see Aufgabe 1.3.3 | |
* @return {@link java.awt.image.BufferedImage} | |
*/ | |
public Image floydSteinbergDithering() { | |
// Berechne fuer jeden Pixel (solang er in den passenden Boundaries liegt) | |
// einen Fehlerwert zwischen Original- und passender Farbe aus der Palette. | |
// Dieser Fehler wird dann je nach Position mit dem passendem Wert aus | |
// der Floyd-Steinberg-Matrix zusammenaddiert. | |
// da in diesem Algorithmus mit nur einem Bild gearbeitet wird, ist | |
// dies notwendig, damit nach dem Ausfuehren von Floyd-Steinberg | |
// auch die anderen Algorithmen noch funktionieren. | |
tmpImg.setData(image.getData()); | |
int[] oldpixel; | |
int[] newpixel; | |
int[] error = new int[3]; | |
int[] rgb; | |
for (int x = 0; x < tmpImg.getWidth(); x++) { | |
for (int y = 0; y < tmpImg.getHeight(); y++) { | |
oldpixel = ImageHelper.toRGBArray(tmpImg.getRGB(x, y)); | |
newpixel = ImageHelper.getColorFromPalette(oldpixel); | |
tmpImg.setRGB(x, y, ImageHelper.toIntRGB(newpixel)); | |
// Berechne fuer jeden Pixel den Fehler aus dem Unterschied | |
// zwischen dem Pixel selbst und seiner "Palette-Version" | |
// Dieser Fehler wird dann auf die entsprechenden Pixel | |
// in der Umgebung multipliziert, bevor diese einzeln | |
// betrachtet werden. | |
// Die einzelnen if Bedingungen existieren um die Randfaelle | |
// abzudecken, die Logik fuer die einzelnen Bloecke bleibt | |
// identisch. | |
if (x < tmpImg.getWidth() - 1) { | |
for (int i = 0; i < 3; i++) { | |
error[i] = oldpixel[i] - newpixel[i]; | |
rgb = ImageHelper.toRGBArray(tmpImg.getRGB(x + 1, y)); | |
rgb[i] += error[i] * 7 / 16; | |
tmpImg.setRGB(x + 1, y, ImageHelper.toIntRGB(rgb)); | |
} | |
} | |
if (x > 0 && y < tmpImg.getHeight() - 1) { | |
for (int i = 0; i < 3; i++) { | |
error[i] = oldpixel[i] - newpixel[i]; | |
rgb = ImageHelper.toRGBArray(tmpImg.getRGB(x - 1, y + 1)); | |
rgb[i] += error[i] * 3 / 16; | |
tmpImg.setRGB(x - 1, y + 1, ImageHelper.toIntRGB(rgb)); | |
} | |
} | |
if (y < tmpImg.getHeight() - 1) { | |
for (int i = 0; i < 3; i++) { | |
error[i] = oldpixel[i] - newpixel[i]; | |
rgb = ImageHelper.toRGBArray(tmpImg.getRGB(x, y + 1)); | |
rgb[i] += error[i] * 5 / 16; | |
tmpImg.setRGB(x, y + 1, ImageHelper.toIntRGB(rgb)); | |
} | |
} | |
if (x < tmpImg.getWidth() - 1 && y < tmpImg.getHeight() - 1) { | |
for (int i = 0; i < 3; i++) { | |
error[i] = oldpixel[i] - newpixel[i]; | |
rgb = ImageHelper.toRGBArray(tmpImg.getRGB(x + 1, y + 1)); | |
rgb[i] += error[i] / 16; | |
tmpImg.setRGB(x + 1, y + 1, ImageHelper.toIntRGB(rgb)); | |
} | |
} | |
} | |
} | |
return tmpImg; | |
} | |
// Vergleich der drei Farbreduktionsverfahren | |
// | |
// Waehrend colorQuant das Bild wirklich nur in eine Palette aus 16 Farben | |
// zwaengt und so einheitliche Farbflaechen erzeugt in denen jedoch Details | |
// des Bildes verloren gehen, verteilen die anderen beiden Algorithmen die | |
// Farben so, dass gewisse Details erhalten bleiben. | |
// Im Falle des Ordered Ditherings werden die Farbwerte eines Pixels mit | |
// einer festen Fehlermatrix multipliziert bevor man sie mit der Palette | |
// abgleicht. Durch diese Werte ergibt sich eine bessere Verteilung. | |
// Fuer Floyd-Steinberg wird dieser Fehler dynamisch berechnet um eine noch | |
// bessere Verteilung zu generieren. | |
// | |
// Fuer eine Palette von nur 16 Farben erzeugt Floyd-Steinberg ein | |
// beeindruckendes Ergebnis. Details bleiben trotz geringer Palette erhalten. | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment