Kompresor obrazów w oparciu o algorytm JPEG
Jest to implementacja algorytmu JPEG. starałam się wykonać w pełni poszczególne kroki z algorytmu. Poniżej zamieszczone są kluczowe fragmenty kodu.
- Wczytanie pliku
Do wczytywania pliku wykorzystuję klasy File oraz BufferedImage.
public BufferedImage open() throws IOException { JFileChooser jf = new JFileChooser(); jf.showOpenDialog(null); File file = jf.getSelectedFile(); BufferedImage img = ImageIO.read(file); return img; }
private void channels(BufferedImage img) { this.Cb = new int[img.getWidth()][img.getHeight()]; this.Cr = new int[img.getWidth()][img.getHeight()]; this.Y = new int[img.getWidth()][img.getHeight()]; this.alfa = new int[img.getWidth()][img.getHeight()]; // System.out.println("Pobiera kolory"); for (int i = 0; i < this.img.getWidth(); i++) { for (int j = 0; j < this.img.getHeight(); j++) { alfa[i][j] = (img.getRGB(i, j) >> 24) & 0xff; double pixel_r = (img.getRGB(i, j) >> 16) & 0xff; double pixel_g = (img.getRGB(i, j) >> 8) & 0xff; double pixel_b = (img.getRGB(i, j)) & 0xff; Y[i][j] = (int) (0.299 * (double) pixel_r + 0.587 * (double) pixel_g + 0.114 * (double) pixel_b); Cb[i][j] = (int) (128 - 0.168736 * (double) pixel_r - 0.331264 * (double) pixel_g + 0.5 * (double) pixel_b); Cr[i][j] = (int) (128 + 0.5 * (double) pixel_r - 0.418688 * (double) pixel_g - 0.081312 * (double) pixel_b); } } }
private void quant(int q, int[][] blok) { double wsp = (double) q; if (q > 50) { for (int y = 0; y < 8; y++) { for (int x = 0; x < 8; x++) { blok[y][x] = (int) Math.floor((blok[y][x]) / (((100 - wsp) / 50) * quantization[y][x])); } } } else if (q < 50) { for (int y = 0; y < 8; y++) { for (int x = 0; x < 8; x++) { blok[y][x] = (int) Math.floor((blok[y][x]) / ((50 / wsp) * quantization[y][x])); } } } else { for (int y = 0; y < 8; y++) { for (int x = 0; x < 8; x++) { blok[y][x] = (int) Math.floor(blok[y][x] / quantization[y][x]); } } } }
private int[][] dct2d(int[][] blok) { int[][] tmpImg = new int[8][8]; double Cu, Cv; for (int u = 0; u < 8; u++) { for (int v = 0; v < 8; v++) { double z = 0.0, pixel = 0.0; if (u == 0) { Cu = 0.353553391; } else { Cu = 0.25; } if (v == 0) { Cv = 0.353553391; } else { Cv = 0.25; } for (int x = 0; x < 8; x++) { for (int y = 0; y < 8; y++) { pixel = blok[x][y]; z += pixel * Math.cos((double) (2 * x + 1) * (double) u * Math.PI / 16.0) * Math.cos((double) (2 * y + 1) * (double) v * Math.PI / 16.0); } } tmpImg[u][v] = (int) (z * Cu * Cv); } } return tmpImg; }
Transformata oraz Zig-Zag wykonywane są na kolejnych blokach o rozmiarze 8x8, na każdym kanale oddzielnie.
public int[] Zig_Zag(int data[][]) { int i = 1; int j = 1; int[] tab = new int[64]; for (int element = 0; element < 64; element++) { tab[element] = data[i - 1][j - 1]; if ((i + j) % 2 == 0) { if (j < 8) { j++; } else { i += 2; } if (i > 1) { i--; } } else { if (i < 8) { i++; } else { j += 2; } if (j > 1) { j--; } } } return tab; }
Kodowanie odbywa się podczas zapisu pliku, przy użyciu klasy Deflater. Sam zapis odbywa się przy użyciu File, FileOutputStream, DataOutputStream oraz ByteArrayOutputStream.
- Dekoder przebiega w następujących etapach:
- Zdekodowanie odczytanych danych z pliku
- Cofnięcie Zig-zag-a
- Odwrotna transformata
- Dekwantyzacja
- Zmiana kanałów YCrCb na RGB
- Połączanie obrazka w całość
Zastosowana kompresja pozwala na znaczne zmniejszenie rozmiaru obrazu, jednak wyraźnie widać stratę jakości.