From 4bcffe8896c08c9f60b2707da71bb39a64618d93 Mon Sep 17 00:00:00 2001 From: Svjatoslav Agejenko Date: Sun, 16 Sep 2012 11:25:44 +0300 Subject: [PATCH] Moved bit input and output streams into Svjatoslav Commons library. Added license and copyright notice to every source file. --- .classpath | 11 +- .project | 44 +- .settings/org.eclipse.core.resources.prefs | 3 + .settings/org.eclipse.jdt.core.prefs | 16 +- .settings/org.eclipse.m2e.core.prefs | 4 + README.TXT | 12 - TODO | 7 + pom.xml | 150 ++--- .../imagesqueeze/codec/Approximator.java | 66 +- .../imagesqueeze/codec/BitInputStream.java | 56 -- .../imagesqueeze/codec/BitOutputStream.java | 63 -- .../imagesqueeze/codec/Channel.java | 57 +- .../svjatoslav/imagesqueeze/codec/Color.java | 66 +- .../imagesqueeze/codec/ColorStats.java | 44 +- .../svjatoslav/imagesqueeze/codec/Image.java | 104 +-- .../imagesqueeze/codec/ImageDecoder.java | 259 ++++---- .../imagesqueeze/codec/ImageEncoder.java | 609 +++++++++--------- .../imagesqueeze/codec/ImageMetaData.java | 18 +- .../imagesqueeze/codec/OperatingContext.java | 73 ++- .../svjatoslav/imagesqueeze/codec/Table.java | 242 +++---- .../sampleApplication/ImageFrame.java | 54 +- .../sampleApplication/ImagePanel.java | 123 ++-- .../imagesqueeze/sampleApplication/Main.java | 35 +- 23 files changed, 1094 insertions(+), 1022 deletions(-) create mode 100644 .settings/org.eclipse.core.resources.prefs create mode 100644 .settings/org.eclipse.m2e.core.prefs delete mode 100755 README.TXT create mode 100644 TODO delete mode 100755 src/main/java/eu/svjatoslav/imagesqueeze/codec/BitInputStream.java delete mode 100755 src/main/java/eu/svjatoslav/imagesqueeze/codec/BitOutputStream.java diff --git a/.classpath b/.classpath index 3b25ab3..48ef814 100644 --- a/.classpath +++ b/.classpath @@ -1,5 +1,8 @@ + - - - - \ No newline at end of file + + + + + + diff --git a/.project b/.project index cf45093..446ef18 100755 --- a/.project +++ b/.project @@ -1,17 +1,29 @@ + - imagesqueeze - ImageSqueeze - image codec optimized for photos. NO_M2ECLIPSE_SUPPORT: Project files created with the maven-eclipse-plugin are not supported in M2Eclipse. - - - - org.eclipse.jdt.core.javabuilder - - - org.maven.ide.eclipse.maven2Builder - - - - org.maven.ide.eclipse.maven2Nature - org.eclipse.jdt.core.javanature - - \ No newline at end of file + imagesqueeze + ImageSqueeze - image codec optimized for photos. NO_M2ECLIPSE_SUPPORT: Project files created with the maven-eclipse-plugin are not supported in M2Eclipse. + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.maven.ide.eclipse.maven2Builder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.m2e.core.maven2Nature + org.maven.ide.eclipse.maven2Nature + org.eclipse.jdt.core.javanature + + diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..8fb0e26 --- /dev/null +++ b/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,3 @@ +eclipse.preferences.version=1 +encoding//src/main/java=UTF-8 +encoding//src/main/resources=UTF-8 diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs index beb3db6..2e6e466 100644 --- a/.settings/org.eclipse.jdt.core.prefs +++ b/.settings/org.eclipse.jdt.core.prefs @@ -1,10 +1,16 @@ -#Sun Aug 28 02:19:45 EEST 2011 +eclipse.preferences.version=1 encoding//src/main/java=UTF-8 -org.eclipse.jdt.core.compiler.compliance=1.6 encoding//src/main/resources=UTF-8 -encoding//src/test/resources=UTF-8 encoding//src/test/java=UTF-8 -org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +encoding//src/test/resources=UTF-8 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 -eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning org.eclipse.jdt.core.compiler.source=1.6 diff --git a/.settings/org.eclipse.m2e.core.prefs b/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 0000000..ff7698f --- /dev/null +++ b/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=false +version=1 diff --git a/README.TXT b/README.TXT deleted file mode 100755 index 5e92643..0000000 --- a/README.TXT +++ /dev/null @@ -1,12 +0,0 @@ -Imagesqueeze -- Lossy image codec. Optimized for photos and speed. -By Svjatoslav Agejenko, svjatoslavagejenko@gmail.com, http://svjatoslav.eu - -See doc directory for more info. - -Todo: - * Javadoc - - * micro header possibility, 16 bits header version, 2x8 bits, image dimension - - * Performance test - \ No newline at end of file diff --git a/TODO b/TODO new file mode 100644 index 0000000..8dfe395 --- /dev/null +++ b/TODO @@ -0,0 +1,7 @@ +better documentation describing the idea + +JavaDoc + +possibility for smaller image header, on smaller images + +runnable performance tests diff --git a/pom.xml b/pom.xml index c73ed5d..1875e57 100644 --- a/pom.xml +++ b/pom.xml @@ -1,82 +1,82 @@ - - 4.0.0 - eu.svjatoslav - imagesqueeze - 1.0-SNAPSHOT - imagesqueeze - jar + + 4.0.0 + eu.svjatoslav + imagesqueeze + 1.0-SNAPSHOT + imagesqueeze + jar - ImageSqueeze - image codec optimized for photos + ImageSqueeze - image codec optimized for photos - - imagesqueeze - - - false - src/main/resources - - - true - src/main/resources - - *.xml - - - - - - true - src/test/resources - - - - - org.apache.maven.plugins - maven-compiler-plugin - 2.3.1 - - 1.6 - 1.6 - true - UTF-8 - - - - org.apache.maven.plugins - maven-resources-plugin - 2.4.3 - - UTF-8 - - - - + + imagesqueeze + + + false + src/main/resources + + + true + src/main/resources + + *.xml + + + + + + true + src/test/resources + + + + + org.apache.maven.plugins + maven-compiler-plugin + 2.3.1 + + 1.6 + 1.6 + true + UTF-8 + + + + org.apache.maven.plugins + maven-resources-plugin + 2.4.3 + + UTF-8 + + + + + + + eu.svjatoslav + svjatoslavcommons + 1.0-SNAPSHOT + + + + + svjatoslav.eu + Svjatoslav repository + http://svjatoslav.eu/static/maven/ + + - - - - svjatoslav.eu - Svjatoslav repository - http://svjatoslav.eu/maven/ - - - - - - - - - - - - svjatoslav.eu - svjatoslav.eu - scp://svjatoslav.eu/opt/svjatoslav.eu/webcontent/maven - - + + + svjatoslav.eu + svjatoslav.eu + scp://svjatoslav.eu/var/lib/tomcat6/_svjatoslav.eu/static/maven + + diff --git a/src/main/java/eu/svjatoslav/imagesqueeze/codec/Approximator.java b/src/main/java/eu/svjatoslav/imagesqueeze/codec/Approximator.java index 622ae80..a4f78af 100755 --- a/src/main/java/eu/svjatoslav/imagesqueeze/codec/Approximator.java +++ b/src/main/java/eu/svjatoslav/imagesqueeze/codec/Approximator.java @@ -1,11 +1,22 @@ +/* + * Imagesqueeze - Image codec optimized for photos. + * Copyright (C) 2012, Svjatoslav Agejenko, svjatoslav@svjatoslav.eu + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public License + * as published by the Free Software Foundation. + */ + package eu.svjatoslav.imagesqueeze.codec; import java.io.IOException; +import eu.svjatoslav.commons.data.BitInputStream; +import eu.svjatoslav.commons.data.BitOutputStream; + /** - * Since it is a lossy codec, instead of storing - * exact values, approximated values are stored - * to save on bit count. + * Since it is a lossy codec, instead of storing exact values, approximated + * values are stored to save on bit count. */ public class Approximator implements Comparable { @@ -14,29 +25,38 @@ public class Approximator implements Comparable { public Table uTable = new Table(); public Table vTable = new Table(); - public Approximator(){ + public Approximator() { } - public int compareTo(Approximator o) { + @Override + public int compareTo(final Approximator o) { int result = yTable.compareTo(o.yTable); - if (result != 0) return result; + if (result != 0) + return result; result = uTable.compareTo(o.uTable); - if (result != 0) return result; + if (result != 0) + return result; result = vTable.compareTo(o.vTable); return result; } - - public void initialize(){ + + public void computeLookupTables() { + yTable.computeLookupTables(); + uTable.computeLookupTables(); + vTable.computeLookupTables(); + } + + public void initialize() { yTable.reset(); uTable.reset(); vTable.reset(); - + yTable.addEntry(0, 6, 0); yTable.addEntry(27, 30, 4); yTable.addEntry(255, 255, 6); - + uTable.addEntry(0, 9, 0); uTable.addEntry(27, 30, 4); uTable.addEntry(255, 255, 6); @@ -44,26 +64,20 @@ public class Approximator implements Comparable { vTable.addEntry(0, 9, 0); vTable.addEntry(27, 30, 4); vTable.addEntry(255, 255, 6); - + computeLookupTables(); } - - public void save(BitOutputStream outputStream) throws IOException{ - yTable.save(outputStream); - uTable.save(outputStream); - vTable.save(outputStream); - } - - public void load(BitInputStream inputStream) throws IOException { + + public void load(final BitInputStream inputStream) throws IOException { yTable.load(inputStream); uTable.load(inputStream); - vTable.load(inputStream); + vTable.load(inputStream); } - public void computeLookupTables(){ - yTable.computeLookupTables(); - uTable.computeLookupTables(); - vTable.computeLookupTables(); + public void save(final BitOutputStream outputStream) throws IOException { + yTable.save(outputStream); + uTable.save(outputStream); + vTable.save(outputStream); } - + } diff --git a/src/main/java/eu/svjatoslav/imagesqueeze/codec/BitInputStream.java b/src/main/java/eu/svjatoslav/imagesqueeze/codec/BitInputStream.java deleted file mode 100755 index b5ba69a..0000000 --- a/src/main/java/eu/svjatoslav/imagesqueeze/codec/BitInputStream.java +++ /dev/null @@ -1,56 +0,0 @@ -package eu.svjatoslav.imagesqueeze.codec; - -/** - * Read individual bits from the input stream. - */ - -import java.io.IOException; -import java.io.InputStream; - -public class BitInputStream { - - - int currentByte; - int currentBytePointer = -1; - - InputStream inputStream; - - public BitInputStream(InputStream inputStream){ - this.inputStream = inputStream; - } - - public int readBits(int bitCount) throws IOException { - - int readableByte = 0; - for (int i=0; i < bitCount; i++){ - - readableByte = readableByte << 1; - - if (currentBytePointer == -1){ - currentBytePointer = 7; - currentByte = inputStream.read(); - } - - int mask = 1; - mask = mask << currentBytePointer; - - int currentBit = currentByte & mask; - - if (currentBit != 0){ - readableByte = readableByte | 1; - } - - currentBytePointer--; - } - return readableByte; - } - - public int readIntegerCompressed8() throws IOException{ - if (readBits(1) == 0){ - return readBits(8); - } else { - return readBits(32); - } - } - -} diff --git a/src/main/java/eu/svjatoslav/imagesqueeze/codec/BitOutputStream.java b/src/main/java/eu/svjatoslav/imagesqueeze/codec/BitOutputStream.java deleted file mode 100755 index 469bb7b..0000000 --- a/src/main/java/eu/svjatoslav/imagesqueeze/codec/BitOutputStream.java +++ /dev/null @@ -1,63 +0,0 @@ -package eu.svjatoslav.imagesqueeze.codec; - -/** - * Write individual bits to the output stream. - */ - -import java.io.IOException; -import java.io.OutputStream; - -public class BitOutputStream { - - int currentByte; - int currentBytePointer; - - OutputStream outputStream; - - public BitOutputStream(OutputStream outputStream){ - currentByte = 0; - currentBytePointer = 0; - this.outputStream = outputStream; - }; - - - public void storeBits(int data, int bitCount) throws IOException { - for (int i=bitCount-1; i >= 0; i--){ - - int mask = 1; - mask = mask << i; - - int currentBit = data & mask; - currentByte = currentByte << 1; - - if (currentBit != 0){ - currentByte = currentByte | 1; - } - currentBytePointer++; - - if (currentBytePointer == 8){ - currentBytePointer = 0; - outputStream.write(currentByte); - currentByte = 0; - } - } - } - - public void storeIntegerCompressed8(int data) throws IOException{ - if (data < 256){ - storeBits(0, 1); - storeBits(data, 8); - } else { - storeBits(1, 1); - storeBits(data, 32); - } - } - - public void finishByte() throws IOException { - if (currentBytePointer != 0){ - outputStream.write(currentByte); - currentBytePointer = 0; - } - } - -} diff --git a/src/main/java/eu/svjatoslav/imagesqueeze/codec/Channel.java b/src/main/java/eu/svjatoslav/imagesqueeze/codec/Channel.java index 879326e..a4f01cc 100755 --- a/src/main/java/eu/svjatoslav/imagesqueeze/codec/Channel.java +++ b/src/main/java/eu/svjatoslav/imagesqueeze/codec/Channel.java @@ -1,52 +1,61 @@ +/* + * Imagesqueeze - Image codec optimized for photos. + * Copyright (C) 2012, Svjatoslav Agejenko, svjatoslav@svjatoslav.eu + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public License + * as published by the Free Software Foundation. + */ + package eu.svjatoslav.imagesqueeze.codec; public class Channel { - byte [] rangeMap; - byte [] map; + byte[] rangeMap; + byte[] map; - byte [] decodedRangeMap; - byte [] decodedMap; + byte[] decodedRangeMap; + byte[] decodedMap; int bitCount; - - public Channel(int width, int height) { + + public Channel(final int width, final int height) { rangeMap = new byte[width * height]; - map = new byte[width * height]; + map = new byte[width * height]; decodedRangeMap = new byte[width * height]; - decodedRangeMap[0] = (byte)255; + decodedRangeMap[0] = (byte) 255; decodedMap = new byte[width * height]; }; - - public void reset(){ - - for (int i=0; i < decodedMap.length; i++){ + public void printStatistics() { + final float bitsPerPixel = (float) bitCount / (float) rangeMap.length; + System.out.println((bitCount / 8) + " bytes. " + bitsPerPixel + + " bits per pixel."); + } + + public void reset() { + + for (int i = 0; i < decodedMap.length; i++) { decodedMap[i] = 0; } - for (int i=0; i < decodedRangeMap.length; i++){ + for (int i = 0; i < decodedRangeMap.length; i++) { decodedRangeMap[i] = 0; } - decodedRangeMap[0] = (byte)255; - - for (int i=0; i < map.length; i++){ + decodedRangeMap[0] = (byte) 255; + + for (int i = 0; i < map.length; i++) { map[i] = 0; } - for (int i=0; i < rangeMap.length; i++){ + for (int i = 0; i < rangeMap.length; i++) { rangeMap[i] = 0; } - + bitCount = 0; } - - public void printStatistics(){ - float bitsPerPixel = (float)bitCount / (float)rangeMap.length; - System.out.println( (bitCount/8) + " bytes. " + bitsPerPixel + " bits per pixel."); - } - + } diff --git a/src/main/java/eu/svjatoslav/imagesqueeze/codec/Color.java b/src/main/java/eu/svjatoslav/imagesqueeze/codec/Color.java index 1d8b7a6..08535a2 100755 --- a/src/main/java/eu/svjatoslav/imagesqueeze/codec/Color.java +++ b/src/main/java/eu/svjatoslav/imagesqueeze/codec/Color.java @@ -1,3 +1,12 @@ +/* + * Imagesqueeze - Image codec optimized for photos. + * Copyright (C) 2012, Svjatoslav Agejenko, svjatoslav@svjatoslav.eu + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public License + * as published by the Free Software Foundation. + */ + package eu.svjatoslav.imagesqueeze.codec; /** @@ -13,37 +22,48 @@ public class Color { int u; int v; - public void YUV2RGB(){ + public void RGB2YUV() { - b = (int)(y + 1.4075 * (v - 128)); - g = (int)(y - 0.3455 * (u - 128) - (0.7169 * (v - 128))); - r = (int)(y + 1.7790 * (u - 128)); + y = (int) ((r * 0.299000) + (g * 0.587000) + (b * 0.114000)); + u = (int) ((r * -0.168736) + (g * -0.331264) + (b * 0.500000) + 128); + v = (int) ((r * 0.500000) + (g * -0.418688) + (b * -0.081312) + 128); - if (r < 0) r = 0; - if (g < 0) g = 0; - if (b < 0) b = 0; + if (y < 0) + y = 0; + if (u < 0) + u = 0; + if (v < 0) + v = 0; - if (r > 255) r = 255; - if (g > 255) g = 255; - if (b > 255) b = 255; + if (y > 255) + y = 255; + if (u > 255) + u = 255; + if (v > 255) + v = 255; } - public void RGB2YUV(){ + public void YUV2RGB() { + + b = (int) (y + (1.4075 * (v - 128))); + g = (int) (y - (0.3455 * (u - 128)) - (0.7169 * (v - 128))); + r = (int) (y + (1.7790 * (u - 128))); - y = (int)(r * 0.299000 + g * 0.587000 + b * 0.114000); - u = (int)(r * -0.168736 + g * -0.331264 + b * 0.500000 + 128); - v = (int)(r * 0.500000 + g * -0.418688 + b * -0.081312 + 128); + if (r < 0) + r = 0; + if (g < 0) + g = 0; + if (b < 0) + b = 0; - if (y < 0) y = 0; - if (u < 0) u = 0; - if (v < 0) v = 0; + if (r > 255) + r = 255; + if (g > 255) + g = 255; + if (b > 255) + b = 255; - if (y > 255) y = 255; - if (u > 255) u = 255; - if (v > 255) v = 255; - } - -}; +}; diff --git a/src/main/java/eu/svjatoslav/imagesqueeze/codec/ColorStats.java b/src/main/java/eu/svjatoslav/imagesqueeze/codec/ColorStats.java index b856fe5..8d206cf 100755 --- a/src/main/java/eu/svjatoslav/imagesqueeze/codec/ColorStats.java +++ b/src/main/java/eu/svjatoslav/imagesqueeze/codec/ColorStats.java @@ -1,3 +1,12 @@ +/* + * Imagesqueeze - Image codec optimized for photos. + * Copyright (C) 2012, Svjatoslav Agejenko, svjatoslav@svjatoslav.eu + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public License + * as published by the Free Software Foundation. + */ + package eu.svjatoslav.imagesqueeze.codec; public class ColorStats { @@ -5,33 +14,30 @@ public class ColorStats { int ySum; int uSum; int vSum; - + int pixelCount; - - - public ColorStats(){ + + public ColorStats() { reset(); } - public void reset(){ - ySum = 0; - uSum = 0; - vSum = 0; - pixelCount = 0; - } - - public int getAverageY(){ - return ySum / pixelCount; + public int getAverageU() { + return uSum / pixelCount; } - public int getAverageU(){ - return uSum / pixelCount; + public int getAverageV() { + return vSum / pixelCount; } - public int getAverageV(){ - return vSum / pixelCount; + public int getAverageY() { + return ySum / pixelCount; } - -} + public void reset() { + ySum = 0; + uSum = 0; + vSum = 0; + pixelCount = 0; + } +} diff --git a/src/main/java/eu/svjatoslav/imagesqueeze/codec/Image.java b/src/main/java/eu/svjatoslav/imagesqueeze/codec/Image.java index c644eaa..6ba0d1b 100755 --- a/src/main/java/eu/svjatoslav/imagesqueeze/codec/Image.java +++ b/src/main/java/eu/svjatoslav/imagesqueeze/codec/Image.java @@ -1,3 +1,12 @@ +/* + * Imagesqueeze - Image codec optimized for photos. + * Copyright (C) 2012, Svjatoslav Agejenko, svjatoslav@svjatoslav.eu + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public License + * as published by the Free Software Foundation. + */ + package eu.svjatoslav.imagesqueeze.codec; import java.awt.image.BufferedImage; @@ -10,6 +19,9 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import eu.svjatoslav.commons.data.BitInputStream; +import eu.svjatoslav.commons.data.BitOutputStream; + /** * Main class representing compressed image. */ @@ -21,100 +33,104 @@ public class Image { public BufferedImage bufferedImage; ImageEncoder encoder; - - public Image(){}; + + public Image() { + }; /** - * Initialize imagesqueeze image based on {@link BufferedImage}. {@link BufferedImage} must be of type BufferedImage.TYPE_3BYTE_BGR . + * Initialize imagesqueeze image based on {@link BufferedImage}. + * {@link BufferedImage} must be of type BufferedImage.TYPE_3BYTE_BGR . */ - public Image(BufferedImage image){ + public Image(final BufferedImage image) { - this.bufferedImage = image; + bufferedImage = image; metaData = new ImageMetaData(); metaData.version = 1; metaData.width = image.getWidth(); - metaData.height = image.getHeight(); + metaData.height = image.getHeight(); } /** * Initialize empty imagesqueeze image. */ - public Image(int width, int height){ + public Image(final int width, final int height) { - this.bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR); + bufferedImage = new BufferedImage(width, height, + BufferedImage.TYPE_3BYTE_BGR); metaData = new ImageMetaData(); metaData.version = 1; metaData.width = width; - metaData.height = height; - } - - /** - * Load ImgSqz image from {@link InputStream}. - */ - public void loadImage(InputStream source) throws IOException{ - BitInputStream bitInputStream = new BitInputStream(source); - - metaData = new ImageMetaData(); - metaData.load(bitInputStream); - - bufferedImage = new BufferedImage(metaData.width, metaData.height, BufferedImage.TYPE_3BYTE_BGR); - - ImageDecoder imageDecoder = new ImageDecoder(this, bitInputStream); - - imageDecoder.decode(); + metaData.height = height; } /** * Load ImgSqz image from {@link File}. */ - public void loadImage(File source) throws IOException{ + public void loadImage(final File source) throws IOException { - byte [] fileContent = new byte[(int)source.length()]; + final byte[] fileContent = new byte[(int) source.length()]; - FileInputStream fileInputStream = new FileInputStream(source); + final FileInputStream fileInputStream = new FileInputStream(source); fileInputStream.read(fileContent); fileInputStream.close(); - ByteArrayInputStream inputStream = new ByteArrayInputStream(fileContent); + final ByteArrayInputStream inputStream = new ByteArrayInputStream( + fileContent); loadImage(inputStream); } /** - * Save image into ImgSqz file format. + * Load ImgSqz image from {@link InputStream}. */ - public void saveImage(OutputStream outputStream) throws IOException{ + public void loadImage(final InputStream source) throws IOException { + final BitInputStream bitInputStream = new BitInputStream(source); - BitOutputStream bitOutputStream = new BitOutputStream(outputStream); + metaData = new ImageMetaData(); + metaData.load(bitInputStream); - metaData.save(bitOutputStream); - - if (encoder == null){ - encoder = new ImageEncoder(this); - } + bufferedImage = new BufferedImage(metaData.width, metaData.height, + BufferedImage.TYPE_3BYTE_BGR); - encoder.encode(bitOutputStream); + final ImageDecoder imageDecoder = new ImageDecoder(this, bitInputStream); - bitOutputStream.finishByte(); + imageDecoder.decode(); } - /** * Save image into ImgSqz file format. */ - public void saveImage(File file) throws IOException{ - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + public void saveImage(final File file) throws IOException { + final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); saveImage(outputStream); - byte [] buffer = outputStream.toByteArray(); - FileOutputStream fileOutputStream = new FileOutputStream(file); + final byte[] buffer = outputStream.toByteArray(); + final FileOutputStream fileOutputStream = new FileOutputStream(file); fileOutputStream.write(buffer); fileOutputStream.close(); } + /** + * Save image into ImgSqz file format. + */ + public void saveImage(final OutputStream outputStream) throws IOException { + + final BitOutputStream bitOutputStream = new BitOutputStream( + outputStream); + + metaData.save(bitOutputStream); + + if (encoder == null) { + encoder = new ImageEncoder(this); + } + + encoder.encode(bitOutputStream); + + bitOutputStream.finishByte(); + } } diff --git a/src/main/java/eu/svjatoslav/imagesqueeze/codec/ImageDecoder.java b/src/main/java/eu/svjatoslav/imagesqueeze/codec/ImageDecoder.java index 37f3c0e..bfcaeb6 100755 --- a/src/main/java/eu/svjatoslav/imagesqueeze/codec/ImageDecoder.java +++ b/src/main/java/eu/svjatoslav/imagesqueeze/codec/ImageDecoder.java @@ -1,3 +1,12 @@ +/* + * Imagesqueeze - Image codec optimized for photos. + * Copyright (C) 2012, Svjatoslav Agejenko, svjatoslav@svjatoslav.eu + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public License + * as published by the Free Software Foundation. + */ + package eu.svjatoslav.imagesqueeze.codec; /** @@ -8,77 +17,77 @@ import java.awt.image.DataBufferByte; import java.awt.image.WritableRaster; import java.io.IOException; +import eu.svjatoslav.commons.data.BitInputStream; + public class ImageDecoder { int width, height; Image image; - byte [] decodedYRangeMap; - byte [] decodedYMap; + byte[] decodedYRangeMap; + byte[] decodedYMap; - byte [] decodedURangeMap; - byte [] decodedUMap; + byte[] decodedURangeMap; + byte[] decodedUMap; - byte [] decodedVRangeMap; - byte [] decodedVMap; + byte[] decodedVRangeMap; + byte[] decodedVMap; Color tmpColor = new Color(); - + Approximator approximator; BitInputStream bitInputStream; - + ColorStats colorStats = new ColorStats(); OperatingContext context = new OperatingContext(); - - public ImageDecoder (Image image, BitInputStream bitInputStream) { + + public ImageDecoder(final Image image, final BitInputStream bitInputStream) { approximator = new Approximator(); - this.image = image; + this.image = image; this.bitInputStream = bitInputStream; width = image.metaData.width; height = image.metaData.height; decodedYRangeMap = new byte[width * height]; - decodedYRangeMap[0] = (byte)(255); + decodedYRangeMap[0] = (byte) (255); decodedYMap = new byte[width * height]; decodedURangeMap = new byte[width * height]; - decodedURangeMap[0] = (byte)(255); + decodedURangeMap[0] = (byte) (255); decodedUMap = new byte[width * height]; decodedVRangeMap = new byte[width * height]; - decodedVRangeMap[0] = (byte)(255); + decodedVRangeMap[0] = (byte) (255); decodedVMap = new byte[width * height]; } - public void decode() throws IOException { approximator.load(bitInputStream); approximator.computeLookupTables(); - - WritableRaster raster = image.bufferedImage.getRaster(); - DataBufferByte dbi = (DataBufferByte)raster.getDataBuffer(); - byte [] pixels = dbi.getData(); - - // load top-, left-most pixel. - decodedYMap[0] = (byte)bitInputStream.readBits(8); - decodedUMap[0] = (byte)bitInputStream.readBits(8); - decodedVMap[0] = (byte)bitInputStream.readBits(8); - - Color color = new Color(); + + final WritableRaster raster = image.bufferedImage.getRaster(); + final DataBufferByte dbi = (DataBufferByte) raster.getDataBuffer(); + final byte[] pixels = dbi.getData(); + + // load top-, left-most pixel. + decodedYMap[0] = (byte) bitInputStream.readBits(8); + decodedUMap[0] = (byte) bitInputStream.readBits(8); + decodedVMap[0] = (byte) bitInputStream.readBits(8); + + final Color color = new Color(); color.y = ImageEncoder.byteToInt(decodedYMap[0]); color.u = ImageEncoder.byteToInt(decodedUMap[0]); color.v = ImageEncoder.byteToInt(decodedVMap[0]); - + color.YUV2RGB(); - - pixels[0] = (byte)color.r; - pixels[0+1] = (byte)color.g; - pixels[0+2] = (byte)color.b; - + pixels[0] = (byte) color.r; + pixels[0 + 1] = (byte) color.g; + pixels[0 + 2] = (byte) color.b; + // detect initial step int largestDimension; int initialStep = 2; @@ -87,146 +96,154 @@ public class ImageDecoder { } else { largestDimension = height; } - - while (initialStep < largestDimension){ + + while (initialStep < largestDimension) { initialStep = initialStep * 2; } grid(initialStep, pixels); } - - public void grid(int step, byte [] pixels) throws IOException { + public void grid(final int step, final byte[] pixels) throws IOException { gridDiagonal(step / 2, step / 2, step, pixels); gridSquare(step / 2, 0, step, pixels); gridSquare(0, step / 2, step, pixels); - if (step > 2) grid(step / 2, pixels); - } - - - public void gridSquare(int offsetX, int offsetY, int step, byte [] pixels) throws IOException{ - - for (int y = offsetY; y < height; y = y + step){ - for (int x = offsetX; x < width; x = x + step){ - - - int halfStep = step / 2; - - context.initialize(image, decodedYMap, decodedUMap, decodedVMap); - context.measureNeighborEncode(x - halfStep, y); - context.measureNeighborEncode(x + halfStep, y); - context.measureNeighborEncode(x, y - halfStep); - context.measureNeighborEncode(x, y + halfStep); - - loadPixel(step, offsetX, offsetY, x, y, pixels, - context.colorStats.getAverageY(), - context.colorStats.getAverageU(), - context.colorStats.getAverageV()); - - } - } + if (step > 2) + grid(step / 2, pixels); } + public void gridDiagonal(final int offsetX, final int offsetY, + final int step, final byte[] pixels) throws IOException { - public void gridDiagonal(int offsetX, int offsetY, int step, byte [] pixels) throws IOException{ + for (int y = offsetY; y < height; y = y + step) { + for (int x = offsetX; x < width; x = x + step) { - for (int y = offsetY; y < height; y = y + step){ - for (int x = offsetX; x < width; x = x + step){ - - int halfStep = step / 2; + final int halfStep = step / 2; context.initialize(image, decodedYMap, decodedUMap, decodedVMap); context.measureNeighborEncode(x - halfStep, y - halfStep); context.measureNeighborEncode(x + halfStep, y - halfStep); context.measureNeighborEncode(x - halfStep, y + halfStep); context.measureNeighborEncode(x + halfStep, y + halfStep); - - loadPixel(step, offsetX, offsetY, x, y, pixels, + + loadPixel(step, offsetX, offsetY, x, y, pixels, context.colorStats.getAverageY(), context.colorStats.getAverageU(), context.colorStats.getAverageV()); - } - } + } + } } + public void gridSquare(final int offsetX, final int offsetY, + final int step, final byte[] pixels) throws IOException { - public void loadPixel(int step, int offsetX, int offsetY, int x, int y, byte[] pixels, - int averageDecodedY, int averageDecodedU, int averageDecodedV) - throws IOException{ - - int index = (y * width) + x; - - int halfStep = step / 2; - - int parentIndex; - if (offsetX > 0){ - if (offsetY > 0){ - // diagonal approach - parentIndex = ((y - halfStep) * width) + (x - halfStep); - } else { - // take left pixel - parentIndex = (y * width) + (x - halfStep); - } - } else { - // take upper pixel - parentIndex = ((y - halfStep) * width) + x; - } - + for (int y = offsetY; y < height; y = y + step) { + for (int x = offsetX; x < width; x = x + step) { + final int halfStep = step / 2; - int colorBufferIndex = index * 3; + context.initialize(image, decodedYMap, decodedUMap, decodedVMap); + context.measureNeighborEncode(x - halfStep, y); + context.measureNeighborEncode(x + halfStep, y); + context.measureNeighborEncode(x, y - halfStep); + context.measureNeighborEncode(x, y + halfStep); - Color color = new Color(); - color.y = loadChannel(decodedYRangeMap, decodedYMap, approximator.yTable, averageDecodedY, index, parentIndex); - color.u = loadChannel(decodedURangeMap, decodedUMap, approximator.uTable, averageDecodedU, index, parentIndex); - color.v = loadChannel(decodedVRangeMap, decodedVMap, approximator.vTable, averageDecodedV, index, parentIndex); - - color.YUV2RGB(); - - pixels[colorBufferIndex] = (byte)color.r; - pixels[colorBufferIndex+1] = (byte)color.g; - pixels[colorBufferIndex+2] = (byte)color.b; + loadPixel(step, offsetX, offsetY, x, y, pixels, + context.colorStats.getAverageY(), + context.colorStats.getAverageU(), + context.colorStats.getAverageV()); + } + } } - - private int loadChannel(byte [] decodedRangeMap, byte [] decodedMap, Table table, int averageDecodedValue, int index, - int parentIndex) throws IOException { + private int loadChannel(final byte[] decodedRangeMap, + final byte[] decodedMap, final Table table, + final int averageDecodedValue, final int index, + final int parentIndex) throws IOException { int decodedValue = averageDecodedValue; - int inheritedRange = ImageEncoder.byteToInt(decodedRangeMap[parentIndex]); + final int inheritedRange = ImageEncoder + .byteToInt(decodedRangeMap[parentIndex]); int computedRange = inheritedRange; - int bitCount = table.proposeBitcountForRange(inheritedRange); + final int bitCount = table.proposeBitcountForRange(inheritedRange); int computedRangeBitCount = 0; - if ( bitCount > 0){ + if (bitCount > 0) { - int rangeDecreases = bitInputStream.readBits(1); - if (rangeDecreases != 0){ + final int rangeDecreases = bitInputStream.readBits(1); + if (rangeDecreases != 0) { computedRange = table.proposeDecreasedRange(inheritedRange); - } + } - decodedRangeMap[index] = (byte)computedRange; - computedRangeBitCount = table.proposeBitcountForRange(computedRange); + decodedRangeMap[index] = (byte) computedRange; + computedRangeBitCount = table + .proposeBitcountForRange(computedRange); - if (computedRangeBitCount > 0){ + if (computedRangeBitCount > 0) { - int encodedDifference = bitInputStream.readBits(computedRangeBitCount); + final int encodedDifference = bitInputStream + .readBits(computedRangeBitCount); - int decodedDifference = ImageEncoder.decodeValueFromGivenBits(encodedDifference, computedRange, computedRangeBitCount); + final int decodedDifference = ImageEncoder + .decodeValueFromGivenBits(encodedDifference, + computedRange, computedRangeBitCount); decodedValue = averageDecodedValue - decodedDifference; - if (decodedValue > 255) decodedValue = 255; - if (decodedValue < 0) decodedValue = 0; + if (decodedValue > 255) + decodedValue = 255; + if (decodedValue < 0) + decodedValue = 0; } } else { - decodedRangeMap[index] = (byte)inheritedRange; + decodedRangeMap[index] = (byte) inheritedRange; } - decodedMap[index] = (byte)decodedValue; + decodedMap[index] = (byte) decodedValue; return decodedValue; } + public void loadPixel(final int step, final int offsetX, final int offsetY, + final int x, final int y, final byte[] pixels, + final int averageDecodedY, final int averageDecodedU, + final int averageDecodedV) throws IOException { + + final int index = (y * width) + x; + + final int halfStep = step / 2; + + int parentIndex; + if (offsetX > 0) { + if (offsetY > 0) { + // diagonal approach + parentIndex = ((y - halfStep) * width) + (x - halfStep); + } else { + // take left pixel + parentIndex = (y * width) + (x - halfStep); + } + } else { + // take upper pixel + parentIndex = ((y - halfStep) * width) + x; + } + + final int colorBufferIndex = index * 3; + + final Color color = new Color(); + color.y = loadChannel(decodedYRangeMap, decodedYMap, + approximator.yTable, averageDecodedY, index, parentIndex); + color.u = loadChannel(decodedURangeMap, decodedUMap, + approximator.uTable, averageDecodedU, index, parentIndex); + color.v = loadChannel(decodedVRangeMap, decodedVMap, + approximator.vTable, averageDecodedV, index, parentIndex); + + color.YUV2RGB(); + + pixels[colorBufferIndex] = (byte) color.r; + pixels[colorBufferIndex + 1] = (byte) color.g; + pixels[colorBufferIndex + 2] = (byte) color.b; + + } + } diff --git a/src/main/java/eu/svjatoslav/imagesqueeze/codec/ImageEncoder.java b/src/main/java/eu/svjatoslav/imagesqueeze/codec/ImageEncoder.java index 46c2135..613064c 100755 --- a/src/main/java/eu/svjatoslav/imagesqueeze/codec/ImageEncoder.java +++ b/src/main/java/eu/svjatoslav/imagesqueeze/codec/ImageEncoder.java @@ -1,3 +1,12 @@ +/* + * Imagesqueeze - Image codec optimized for photos. + * Copyright (C) 2012, Svjatoslav Agejenko, svjatoslav@svjatoslav.eu + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public License + * as published by the Free Software Foundation. + */ + package eu.svjatoslav.imagesqueeze.codec; /** @@ -8,6 +17,7 @@ import java.awt.image.DataBufferByte; import java.awt.image.WritableRaster; import java.io.IOException; +import eu.svjatoslav.commons.data.BitOutputStream; public class ImageEncoder { @@ -17,78 +27,81 @@ public class ImageEncoder { Channel yChannel; Channel uChannel; Channel vChannel; - + Approximator approximator; int bitsForY; int bitsForU; int bitsForV; - - //ColorStats colorStats = new ColorStats(); + + // ColorStats colorStats = new ColorStats(); OperatingContext context = new OperatingContext(); OperatingContext context2 = new OperatingContext(); BitOutputStream bitOutputStream; - public ImageEncoder(Image image){ + public ImageEncoder(final Image image) { approximator = new Approximator(); - //bitOutputStream = outputStream; + // bitOutputStream = outputStream; this.image = image; } - - public void encode(BitOutputStream bitOutputStream) throws IOException { + public void encode(final BitOutputStream bitOutputStream) + throws IOException { this.bitOutputStream = bitOutputStream; - + approximator.initialize(); - + approximator.save(bitOutputStream); - + width = image.metaData.width; height = image.metaData.height; - WritableRaster raster = image.bufferedImage.getRaster(); - DataBufferByte dbi = (DataBufferByte)raster.getDataBuffer(); - byte [] pixels = dbi.getData(); + final WritableRaster raster = image.bufferedImage.getRaster(); + final DataBufferByte dbi = (DataBufferByte) raster.getDataBuffer(); + final byte[] pixels = dbi.getData(); - if (yChannel == null){ - yChannel = new Channel(width, height); + if (yChannel == null) { + yChannel = new Channel(width, height); } else { yChannel.reset(); } - - if (uChannel == null){ - uChannel = new Channel(width, height); + + if (uChannel == null) { + uChannel = new Channel(width, height); } else { uChannel.reset(); } - - if (vChannel == null){ - vChannel = new Channel(width, height); + + if (vChannel == null) { + vChannel = new Channel(width, height); } else { vChannel.reset(); } - + // create YUV map out of RGB raster data - Color color = new Color(); - - for (int y=0; y < height; y++){ - for (int x=0; x < width; x++){ + final Color color = new Color(); + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { - int index = (y * width) + x; - int colorBufferIndex = index * 3; + final int index = (y * width) + x; + final int colorBufferIndex = index * 3; - int blue = pixels[colorBufferIndex]; - if (blue < 0) blue = blue + 256; + int blue = pixels[colorBufferIndex]; + if (blue < 0) + blue = blue + 256; - int green = pixels[colorBufferIndex+1]; - if (green < 0) green = green + 256; + int green = pixels[colorBufferIndex + 1]; + if (green < 0) + green = green + 256; - int red = pixels[colorBufferIndex+2]; - if (red < 0) red = red + 256; + int red = pixels[colorBufferIndex + 2]; + if (red < 0) + red = red + 256; color.r = red; color.g = green; @@ -96,19 +109,19 @@ public class ImageEncoder { color.RGB2YUV(); - yChannel.map[index] = (byte)color.y; - uChannel.map[index] = (byte)color.u; - vChannel.map[index] = (byte)color.v; + yChannel.map[index] = (byte) color.y; + uChannel.map[index] = (byte) color.u; + vChannel.map[index] = (byte) color.v; } } yChannel.decodedMap[0] = yChannel.map[0]; uChannel.decodedMap[0] = uChannel.map[0]; vChannel.decodedMap[0] = vChannel.map[0]; - - bitOutputStream.storeBits(byteToInt(yChannel.map[0]), 8); - bitOutputStream.storeBits(byteToInt(uChannel.map[0]), 8); - bitOutputStream.storeBits(byteToInt(vChannel.map[0]), 8); + + bitOutputStream.storeBits(byteToInt(yChannel.map[0]), 8); + bitOutputStream.storeBits(byteToInt(uChannel.map[0]), 8); + bitOutputStream.storeBits(byteToInt(vChannel.map[0]), 8); // detect initial step int largestDimension; @@ -118,364 +131,413 @@ public class ImageEncoder { } else { largestDimension = height; } - - while (initialStep < largestDimension){ + + while (initialStep < largestDimension) { initialStep = initialStep * 2; } - + rangeGrid(initialStep); rangeRoundGrid(2); saveGrid(initialStep); } - public void printStatistics(){ + private void encodeChannel(final Table table, final Channel channel, + final int averageDecodedValue, final int index, final int value, + final int range, final int parentIndex) throws IOException { + + final byte[] decodedRangeMap = channel.decodedRangeMap; + final byte[] decodedMap = channel.decodedMap; + + final int inheritedRange = byteToInt(decodedRangeMap[parentIndex]); + + final int inheritedBitCount = table + .proposeBitcountForRange(inheritedRange); + + if (inheritedBitCount > 0) { + int computedRange; + computedRange = table.proposeRangeForRange(range, inheritedRange); + decodedRangeMap[index] = (byte) computedRange; + + channel.bitCount++; + if (computedRange != inheritedRange) { + // brightness range shrinked + bitOutputStream.storeBits(1, 1); + } else { + // brightness range stayed the same + bitOutputStream.storeBits(0, 1); + } + + // encode brightness into available amount of bits + final int computedBitCount = table + .proposeBitcountForRange(computedRange); + + if (computedBitCount > 0) { + + final int differenceToEncode = -(value - averageDecodedValue); + final int bitEncodedDifference = encodeValueIntoGivenBits( + differenceToEncode, computedRange, computedBitCount); + + channel.bitCount = channel.bitCount + computedBitCount; + bitOutputStream.storeBits(bitEncodedDifference, + computedBitCount); + + final int decodedDifference = decodeValueFromGivenBits( + bitEncodedDifference, computedRange, computedBitCount); + int decodedValue = averageDecodedValue - decodedDifference; + if (decodedValue > 255) + decodedValue = 255; + if (decodedValue < 0) + decodedValue = 0; + + decodedMap[index] = (byte) decodedValue; + } else { + decodedMap[index] = (byte) averageDecodedValue; + } + + } else { + decodedRangeMap[index] = (byte) inheritedRange; + decodedMap[index] = (byte) averageDecodedValue; + } + } + + public void printStatistics() { System.out.println("Y channel:"); yChannel.printStatistics(); System.out.println("U channel:"); uChannel.printStatistics(); - + System.out.println("V channel:"); - vChannel.printStatistics(); + vChannel.printStatistics(); } - - public void rangeGrid(int step){ - //gridSquare(step / 2, step / 2, step, pixels); + public void rangeGrid(final int step) { + + // gridSquare(step / 2, step / 2, step, pixels); rangeGridDiagonal(step / 2, step / 2, step); rangeGridSquare(step / 2, 0, step); rangeGridSquare(0, step / 2, step); - if (step > 2) rangeGrid(step / 2); + if (step > 2) + rangeGrid(step / 2); } + public void rangeGridDiagonal(final int offsetX, final int offsetY, + final int step) { + for (int y = offsetY; y < height; y = y + step) { + for (int x = offsetX; x < width; x = x + step) { - public void rangeRoundGrid(int step){ - - rangeRoundGridDiagonal(step / 2, step / 2, step); - rangeRoundGridSquare(step / 2, 0, step); - rangeRoundGridSquare(0, step / 2, step); - - if (step < 1024) rangeRoundGrid(step * 2); - } + final int index = (y * width) + x; + final int halfStep = step / 2; - public void saveGrid(int step) throws IOException { + context.initialize(image, yChannel.map, uChannel.map, + vChannel.map); - saveGridDiagonal(step / 2, step / 2, step); - saveGridSquare(step / 2, 0, step); - saveGridSquare(0, step / 2, step); + context.measureNeighborEncode(x - halfStep, y - halfStep); + context.measureNeighborEncode(x + halfStep, y - halfStep); + context.measureNeighborEncode(x - halfStep, y + halfStep); + context.measureNeighborEncode(x + halfStep, y + halfStep); - if (step > 2) saveGrid(step / 2); + yChannel.rangeMap[index] = (byte) context.getYRange(index); + uChannel.rangeMap[index] = (byte) context.getURange(index); + vChannel.rangeMap[index] = (byte) context.getVRange(index); + } + } } + public void rangeGridSquare(final int offsetX, final int offsetY, + final int step) { + for (int y = offsetY; y < height; y = y + step) { + for (int x = offsetX; x < width; x = x + step) { - public void rangeGridSquare(int offsetX, int offsetY, int step){ - for (int y = offsetY; y < height; y = y + step){ - for (int x = offsetX; x < width; x = x + step){ + final int index = (y * width) + x; + final int halfStep = step / 2; - int index = (y * width) + x; - int halfStep = step / 2; + context.initialize(image, yChannel.map, uChannel.map, + vChannel.map); - context.initialize(image, yChannel.map, uChannel.map, vChannel.map); - context.measureNeighborEncode(x - halfStep, y); context.measureNeighborEncode(x + halfStep, y); context.measureNeighborEncode(x, y - halfStep); context.measureNeighborEncode(x, y + halfStep); - yChannel.rangeMap[index] = (byte)context.getYRange(index); - uChannel.rangeMap[index] = (byte)context.getURange(index); - vChannel.rangeMap[index] = (byte)context.getVRange(index); - } - } + yChannel.rangeMap[index] = (byte) context.getYRange(index); + uChannel.rangeMap[index] = (byte) context.getURange(index); + vChannel.rangeMap[index] = (byte) context.getVRange(index); + } + } } - public void rangeGridDiagonal(int offsetX, int offsetY, int step){ - for (int y = offsetY; y < height; y = y + step){ - for (int x = offsetX; x < width; x = x + step){ + public void rangeRoundGrid(final int step) { - int index = (y * width) + x; - int halfStep = step / 2; - - context.initialize(image, yChannel.map, uChannel.map, vChannel.map); - - context.measureNeighborEncode(x - halfStep, y - halfStep); - context.measureNeighborEncode(x + halfStep, y - halfStep); - context.measureNeighborEncode(x - halfStep, y + halfStep); - context.measureNeighborEncode(x + halfStep, y + halfStep); + rangeRoundGridDiagonal(step / 2, step / 2, step); + rangeRoundGridSquare(step / 2, 0, step); + rangeRoundGridSquare(0, step / 2, step); - yChannel.rangeMap[index] = (byte)context.getYRange(index); - uChannel.rangeMap[index] = (byte)context.getURange(index); - vChannel.rangeMap[index] = (byte)context.getVRange(index); - } - } + if (step < 1024) + rangeRoundGrid(step * 2); } - public void rangeRoundGridDiagonal(int offsetX, int offsetY, int step){ - for (int y = offsetY; y < height; y = y + step){ - for (int x = offsetX; x < width; x = x + step){ + public void rangeRoundGridDiagonal(final int offsetX, final int offsetY, + final int step) { + for (int y = offsetY; y < height; y = y + step) { + for (int x = offsetX; x < width; x = x + step) { - int index = (y * width) + x; + final int index = (y * width) + x; - int yRange = byteToInt(yChannel.rangeMap[index]); - int uRange = byteToInt(uChannel.rangeMap[index]); - int vRange = byteToInt(vChannel.rangeMap[index]); + final int yRange = byteToInt(yChannel.rangeMap[index]); + final int uRange = byteToInt(uChannel.rangeMap[index]); + final int vRange = byteToInt(vChannel.rangeMap[index]); - int halfStep = step / 2; + final int halfStep = step / 2; - int parentIndex = ((y - halfStep) * width) + (x - halfStep); + final int parentIndex = ((y - halfStep) * width) + + (x - halfStep); - int parentYRange = byteToInt(yChannel.rangeMap[parentIndex]); + int parentYRange = byteToInt(yChannel.rangeMap[parentIndex]); - if (parentYRange < yRange){ + if (parentYRange < yRange) { parentYRange = yRange; - yChannel.rangeMap[parentIndex] = (byte)parentYRange; + yChannel.rangeMap[parentIndex] = (byte) parentYRange; } int parentURange = byteToInt(uChannel.rangeMap[parentIndex]); - if (parentURange < uRange){ + if (parentURange < uRange) { parentURange = uRange; - uChannel.rangeMap[parentIndex] = (byte)parentURange; + uChannel.rangeMap[parentIndex] = (byte) parentURange; } int parentVRange = byteToInt(vChannel.rangeMap[parentIndex]); - if (parentVRange < vRange){ + if (parentVRange < vRange) { parentVRange = vRange; - vChannel.rangeMap[parentIndex] = (byte)parentVRange; + vChannel.rangeMap[parentIndex] = (byte) parentVRange; } - } - } + } + } } - public void rangeRoundGridSquare(int offsetX, int offsetY, int step){ - for (int y = offsetY; y < height; y = y + step){ - for (int x = offsetX; x < width; x = x + step){ + public void rangeRoundGridSquare(final int offsetX, final int offsetY, + final int step) { + for (int y = offsetY; y < height; y = y + step) { + for (int x = offsetX; x < width; x = x + step) { - int index = (y * width) + x; - - int yRange = byteToInt(yChannel.rangeMap[index]); - int uRange = byteToInt(uChannel.rangeMap[index]); - int vRange = byteToInt(vChannel.rangeMap[index]); + final int index = (y * width) + x; - int halfStep = step / 2; + final int yRange = byteToInt(yChannel.rangeMap[index]); + final int uRange = byteToInt(uChannel.rangeMap[index]); + final int vRange = byteToInt(vChannel.rangeMap[index]); + + final int halfStep = step / 2; int parentIndex; - if (offsetX > 0){ - parentIndex = (y * width) + (x - halfStep); + if (offsetX > 0) { + parentIndex = (y * width) + (x - halfStep); } else { - parentIndex = ((y - halfStep) * width) + x; + parentIndex = ((y - halfStep) * width) + x; } - int parentYRange = byteToInt(yChannel.rangeMap[parentIndex]); + int parentYRange = byteToInt(yChannel.rangeMap[parentIndex]); - if (parentYRange < yRange){ + if (parentYRange < yRange) { parentYRange = yRange; - yChannel.rangeMap[parentIndex] = (byte)parentYRange; + yChannel.rangeMap[parentIndex] = (byte) parentYRange; } int parentURange = byteToInt(uChannel.rangeMap[parentIndex]); - if (parentURange < uRange){ + if (parentURange < uRange) { parentURange = uRange; - uChannel.rangeMap[parentIndex] = (byte)parentURange; + uChannel.rangeMap[parentIndex] = (byte) parentURange; } int parentVRange = byteToInt(vChannel.rangeMap[parentIndex]); - if (parentVRange < vRange){ + if (parentVRange < vRange) { parentVRange = vRange; - vChannel.rangeMap[parentIndex] = (byte)parentVRange; + vChannel.rangeMap[parentIndex] = (byte) parentVRange; } - } - } + } + } } - public void saveGridSquare(int offsetX, int offsetY, int step) throws IOException{ - for (int y = offsetY; y < height; y = y + step){ - for (int x = offsetX; x < width; x = x + step){ - - int halfStep = step / 2; + public void saveGrid(final int step) throws IOException { - context2.initialize(image, yChannel.decodedMap, uChannel.decodedMap, vChannel.decodedMap); - context2.measureNeighborEncode(x - halfStep, y); - context2.measureNeighborEncode(x + halfStep, y); - context2.measureNeighborEncode(x, y - halfStep); - context2.measureNeighborEncode(x, y + halfStep); - - - savePixel(step, offsetX, offsetY, x, y, - context2.colorStats.getAverageY(), - context2.colorStats.getAverageU(), - context2.colorStats.getAverageV()); + saveGridDiagonal(step / 2, step / 2, step); + saveGridSquare(step / 2, 0, step); + saveGridSquare(0, step / 2, step); - } - } + if (step > 2) + saveGrid(step / 2); } - public void saveGridDiagonal(int offsetX, int offsetY, int step) throws IOException { - for (int y = offsetY; y < height; y = y + step){ - for (int x = offsetX; x < width; x = x + step){ - - int halfStep = step / 2; + public void saveGridDiagonal(final int offsetX, final int offsetY, + final int step) throws IOException { + for (int y = offsetY; y < height; y = y + step) { + for (int x = offsetX; x < width; x = x + step) { - context2.initialize(image, yChannel.decodedMap, uChannel.decodedMap, vChannel.decodedMap); + final int halfStep = step / 2; + + context2.initialize(image, yChannel.decodedMap, + uChannel.decodedMap, vChannel.decodedMap); context2.measureNeighborEncode(x - halfStep, y - halfStep); context2.measureNeighborEncode(x + halfStep, y - halfStep); context2.measureNeighborEncode(x - halfStep, y + halfStep); context2.measureNeighborEncode(x + halfStep, y + halfStep); - - + savePixel(step, offsetX, offsetY, x, y, context2.colorStats.getAverageY(), context2.colorStats.getAverageU(), - context2.colorStats.getAverageV()); - - } - } + context2.colorStats.getAverageV()); + + } + } } - public void savePixel(int step, int offsetX, int offsetY, int x, int y, int averageDecodedY, int averageDecodedU, int averageDecodedV) throws IOException { + public void saveGridSquare(final int offsetX, final int offsetY, + final int step) throws IOException { + for (int y = offsetY; y < height; y = y + step) { + for (int x = offsetX; x < width; x = x + step) { - int index = (y * width) + x; + final int halfStep = step / 2; - int py = byteToInt(yChannel.map[index]); - int pu = byteToInt(uChannel.map[index]); - int pv = byteToInt(vChannel.map[index]); - - int yRange = byteToInt(yChannel.rangeMap[index]); - int uRange = byteToInt(uChannel.rangeMap[index]); - int vRange = byteToInt(vChannel.rangeMap[index]); + context2.initialize(image, yChannel.decodedMap, + uChannel.decodedMap, vChannel.decodedMap); + context2.measureNeighborEncode(x - halfStep, y); + context2.measureNeighborEncode(x + halfStep, y); + context2.measureNeighborEncode(x, y - halfStep); + context2.measureNeighborEncode(x, y + halfStep); - int halfStep = step / 2; + savePixel(step, offsetX, offsetY, x, y, + context2.colorStats.getAverageY(), + context2.colorStats.getAverageU(), + context2.colorStats.getAverageV()); + + } + } + } + + public void savePixel(final int step, final int offsetX, final int offsetY, + final int x, final int y, final int averageDecodedY, + final int averageDecodedU, final int averageDecodedV) + throws IOException { + + final int index = (y * width) + x; + + final int py = byteToInt(yChannel.map[index]); + final int pu = byteToInt(uChannel.map[index]); + final int pv = byteToInt(vChannel.map[index]); + + final int yRange = byteToInt(yChannel.rangeMap[index]); + final int uRange = byteToInt(uChannel.rangeMap[index]); + final int vRange = byteToInt(vChannel.rangeMap[index]); + + final int halfStep = step / 2; int parentIndex; - if (offsetX > 0){ - if (offsetY > 0){ + if (offsetX > 0) { + if (offsetY > 0) { // diagonal approach - parentIndex = ((y - halfStep) * width) + (x - halfStep); + parentIndex = ((y - halfStep) * width) + (x - halfStep); } else { // take left pixel - parentIndex = (y * width) + (x - halfStep); - } + parentIndex = (y * width) + (x - halfStep); + } } else { // take upper pixel - parentIndex = ((y - halfStep) * width) + x; + parentIndex = ((y - halfStep) * width) + x; } - encodeChannel( - approximator.yTable, - yChannel, - averageDecodedY, - index, - py, - yRange, - parentIndex); - - encodeChannel( - approximator.uTable, - uChannel, - averageDecodedU, - index, - pu, - uRange, - parentIndex); - - encodeChannel( - approximator.vTable, - vChannel, - averageDecodedV, - index, - pv, - vRange, - parentIndex); + encodeChannel(approximator.yTable, yChannel, averageDecodedY, index, + py, yRange, parentIndex); + + encodeChannel(approximator.uTable, uChannel, averageDecodedU, index, + pu, uRange, parentIndex); + + encodeChannel(approximator.vTable, vChannel, averageDecodedV, index, + pv, vRange, parentIndex); } + public static int byteToInt(final byte input) { + int result = input; + if (result < 0) + result = result + 256; + return result; + } - private void encodeChannel(Table table, Channel channel, int averageDecodedValue, int index, - int value, int range, int parentIndex) - throws IOException { - - byte[] decodedRangeMap = channel.decodedRangeMap; - byte[] decodedMap = channel.decodedMap; - - int inheritedRange = byteToInt(decodedRangeMap[parentIndex]); + public static int decodeValueFromGivenBits(final int encodedBits, + final int range, final int bitCount) { + final int negativeBit = encodedBits & 1; - int inheritedBitCount = table.proposeBitcountForRange(inheritedRange); + final int remainingBitCount = bitCount - 1; - if (inheritedBitCount > 0){ - int computedRange; - computedRange = table.proposeRangeForRange(range, inheritedRange); - decodedRangeMap[index] = (byte)computedRange; + if (remainingBitCount == 0) { + // no more bits remaining to encode actual value - channel.bitCount++; - if (computedRange != inheritedRange){ - // brightness range shrinked - bitOutputStream.storeBits(1, 1); + if (negativeBit == 0) { + return range; } else { - // brightness range stayed the same - bitOutputStream.storeBits(0, 1); + return -range; } + } else { + // still one or more bits left, encode value as precisely as + // possible - // encode brightness into available amount of bits - int computedBitCount = table.proposeBitcountForRange(computedRange); - - if (computedBitCount > 0){ + final int encodedValue = (encodedBits >>> 1) + 1; - int differenceToEncode = -(value - averageDecodedValue); - int bitEncodedDifference = encodeValueIntoGivenBits(differenceToEncode, computedRange, computedBitCount); + final int realvalueForThisBitcount = 1 << remainingBitCount; - channel.bitCount = channel.bitCount + computedBitCount; - bitOutputStream.storeBits(bitEncodedDifference, computedBitCount); + // int valueMultiplier = range / realvalueForThisBitcount; + int decodedValue = (range * encodedValue) + / realvalueForThisBitcount; - int decodedDifference = decodeValueFromGivenBits(bitEncodedDifference, computedRange, computedBitCount); - int decodedValue = averageDecodedValue - decodedDifference; - if (decodedValue > 255) decodedValue = 255; - if (decodedValue < 0) decodedValue = 0; + if (decodedValue > range) + decodedValue = range; - decodedMap[index] = (byte)decodedValue; - } else { - decodedMap[index] = (byte)averageDecodedValue; - } + if (negativeBit == 0) { + return decodedValue; + } else { + return -decodedValue; + } - } else { - decodedRangeMap[index] = (byte)inheritedRange; - decodedMap[index] = (byte)averageDecodedValue; } } - public static int encodeValueIntoGivenBits(int value, int range, int bitCount){ + public static int encodeValueIntoGivenBits(int value, final int range, + final int bitCount) { int negativeBit = 0; - if (value <0){ + if (value < 0) { negativeBit = 1; value = -value; } - int remainingBitCount = bitCount - 1; + final int remainingBitCount = bitCount - 1; - if (remainingBitCount == 0){ + if (remainingBitCount == 0) { // no more bits remaining to encode actual value return negativeBit; } else { - // still one or more bits left, encode value as precisely as possible - - if (value > range) value = range; + // still one or more bits left, encode value as precisely as + // possible + if (value > range) + value = range; - int realvalueForThisBitcount = 1 << remainingBitCount; + final int realvalueForThisBitcount = 1 << remainingBitCount; // int valueMultiplier = range / realvalueForThisBitcount; - int encodedValue = value * realvalueForThisBitcount / range; + int encodedValue = (value * realvalueForThisBitcount) / range; - if (encodedValue >= realvalueForThisBitcount) encodedValue = realvalueForThisBitcount - 1; + if (encodedValue >= realvalueForThisBitcount) + encodedValue = realvalueForThisBitcount - 1; encodedValue = (encodedValue << 1) + negativeBit; @@ -483,47 +545,4 @@ public class ImageEncoder { } } - - public static int decodeValueFromGivenBits(int encodedBits, int range, int bitCount){ - int negativeBit = encodedBits & 1; - - int remainingBitCount = bitCount - 1; - - if (remainingBitCount == 0){ - // no more bits remaining to encode actual value - - if (negativeBit == 0){ - return range; - } else { - return -range; - } - - } else { - // still one or more bits left, encode value as precisely as possible - - int encodedValue = (encodedBits >>> 1) + 1; - - int realvalueForThisBitcount = 1 << remainingBitCount; - - // int valueMultiplier = range / realvalueForThisBitcount; - int decodedValue = range * encodedValue / realvalueForThisBitcount; - - - if (decodedValue > range) decodedValue = range; - - if (negativeBit == 0){ - return decodedValue; - } else { - return -decodedValue; - } - - } - } - - public static int byteToInt(byte input){ - int result = input; - if (result < 0) result = result + 256; - return result; - } - } diff --git a/src/main/java/eu/svjatoslav/imagesqueeze/codec/ImageMetaData.java b/src/main/java/eu/svjatoslav/imagesqueeze/codec/ImageMetaData.java index 68b3bea..4b21a22 100755 --- a/src/main/java/eu/svjatoslav/imagesqueeze/codec/ImageMetaData.java +++ b/src/main/java/eu/svjatoslav/imagesqueeze/codec/ImageMetaData.java @@ -1,3 +1,12 @@ +/* + * Imagesqueeze - Image codec optimized for photos. + * Copyright (C) 2012, Svjatoslav Agejenko, svjatoslav@svjatoslav.eu + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public License + * as published by the Free Software Foundation. + */ + package eu.svjatoslav.imagesqueeze.codec; /** @@ -7,6 +16,8 @@ package eu.svjatoslav.imagesqueeze.codec; import java.io.IOException; +import eu.svjatoslav.commons.data.BitInputStream; +import eu.svjatoslav.commons.data.BitOutputStream; public class ImageMetaData { @@ -14,16 +25,15 @@ public class ImageMetaData { int width; int height; + public void load(final BitInputStream inputStream) throws IOException { - public void load(BitInputStream inputStream) throws IOException{ - - version = inputStream.readBits(16); + version = inputStream.readBits(16); width = inputStream.readIntegerCompressed8(); height = inputStream.readIntegerCompressed8(); } - public void save(BitOutputStream outputStream) throws IOException{ + public void save(final BitOutputStream outputStream) throws IOException { outputStream.storeBits(version, 16); outputStream.storeIntegerCompressed8(width); diff --git a/src/main/java/eu/svjatoslav/imagesqueeze/codec/OperatingContext.java b/src/main/java/eu/svjatoslav/imagesqueeze/codec/OperatingContext.java index bac1630..e96d610 100755 --- a/src/main/java/eu/svjatoslav/imagesqueeze/codec/OperatingContext.java +++ b/src/main/java/eu/svjatoslav/imagesqueeze/codec/OperatingContext.java @@ -1,3 +1,12 @@ +/* + * Imagesqueeze - Image codec optimized for photos. + * Copyright (C) 2012, Svjatoslav Agejenko, svjatoslav@svjatoslav.eu + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public License + * as published by the Free Software Foundation. + */ + package eu.svjatoslav.imagesqueeze.codec; public class OperatingContext { @@ -7,45 +16,49 @@ public class OperatingContext { byte[] uMap; byte[] vMap; ColorStats colorStats = new ColorStats(); - - public OperatingContext(){ + + public OperatingContext() { } - public void initialize(Image image, byte [] brightnessMap, byte [] colornessMap, byte [] colorMap){ - this.image = image; - this.yMap = brightnessMap; - this.uMap = colornessMap; - this.vMap = colorMap; - - colorStats.reset(); + public int getURange(final int index) { + final int colorness = ImageEncoder.byteToInt(uMap[index]); + return Math.abs(colorness - colorStats.getAverageU()); } - - public void measureNeighborEncode(int x, int y){ - if ((y >= 0) && (y < image.metaData.height) && (x >= 0) && (x < image.metaData.width)){ - - int neighborIndex = y * image.metaData.width + x; - - colorStats.ySum = colorStats.ySum + ImageEncoder.byteToInt(yMap[neighborIndex]); - colorStats.uSum = colorStats.uSum + ImageEncoder.byteToInt(uMap[neighborIndex]); - colorStats.vSum = colorStats.vSum + ImageEncoder.byteToInt(vMap[neighborIndex]); - colorStats.pixelCount++; - } + + public int getVRange(final int index) { + final int color = ImageEncoder.byteToInt(vMap[index]); + return Math.abs(color - colorStats.getAverageV()); } - public int getYRange(int index){ - int brightness = ImageEncoder.byteToInt(yMap[index]); - return Math.abs(brightness - colorStats.getAverageY()); + public int getYRange(final int index) { + final int brightness = ImageEncoder.byteToInt(yMap[index]); + return Math.abs(brightness - colorStats.getAverageY()); } - public int getURange(int index){ - int colorness = ImageEncoder.byteToInt(uMap[index]); - return Math.abs(colorness - colorStats.getAverageU()); + public void initialize(final Image image, final byte[] brightnessMap, + final byte[] colornessMap, final byte[] colorMap) { + this.image = image; + yMap = brightnessMap; + uMap = colornessMap; + vMap = colorMap; + + colorStats.reset(); } - public int getVRange(int index){ - int color = ImageEncoder.byteToInt(vMap[index]); - return Math.abs(color - colorStats.getAverageV()); + public void measureNeighborEncode(final int x, final int y) { + if ((y >= 0) && (y < image.metaData.height) && (x >= 0) + && (x < image.metaData.width)) { + + final int neighborIndex = (y * image.metaData.width) + x; + + colorStats.ySum = colorStats.ySum + + ImageEncoder.byteToInt(yMap[neighborIndex]); + colorStats.uSum = colorStats.uSum + + ImageEncoder.byteToInt(uMap[neighborIndex]); + colorStats.vSum = colorStats.vSum + + ImageEncoder.byteToInt(vMap[neighborIndex]); + colorStats.pixelCount++; + } } - } diff --git a/src/main/java/eu/svjatoslav/imagesqueeze/codec/Table.java b/src/main/java/eu/svjatoslav/imagesqueeze/codec/Table.java index 4e714f4..33637d7 100755 --- a/src/main/java/eu/svjatoslav/imagesqueeze/codec/Table.java +++ b/src/main/java/eu/svjatoslav/imagesqueeze/codec/Table.java @@ -1,168 +1,194 @@ +/* + * Imagesqueeze - Image codec optimized for photos. + * Copyright (C) 2012, Svjatoslav Agejenko, svjatoslav@svjatoslav.eu + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public License + * as published by the Free Software Foundation. + */ + package eu.svjatoslav.imagesqueeze.codec; import java.io.IOException; +import eu.svjatoslav.commons.data.BitInputStream; +import eu.svjatoslav.commons.data.BitOutputStream; + /** * Quick lookup table. */ -public class Table implements Comparable{ +public class Table implements Comparable
{ - int [] range = new int[100]; - int [] switchTreshold = new int[100]; - int [] bitcount = new int[100]; + int[] range = new int[100]; + int[] switchTreshold = new int[100]; + int[] bitcount = new int[100]; + int[] bitCountForRange = new int[256]; + int[] proposedRangeForActualRange = new int[256]; + int[] proposedRangeForActualRangeLow = new int[256]; + int[] proposedRangeForActualRangeHigh = new int[256]; + byte[] proposedDecreasedRange = new byte[256]; - int [] bitCountForRange = new int[256]; - int [] proposedRangeForActualRange = new int[256]; - int [] proposedRangeForActualRangeLow = new int[256]; - int [] proposedRangeForActualRangeHigh = new int[256]; - byte [] proposedDecreasedRange = new byte[256]; + int usedEntries = 0; + /** + * @param switchTreshold + * - switch to this range when actual range in equal or below + * this treshold + */ + public void addEntry(int range, int switchTreshold, int bitcount) { + if (range < 0) + range = 0; + if (range > 255) + range = 255; + + if (switchTreshold < 0) + switchTreshold = 0; + if (switchTreshold > 255) + switchTreshold = 255; + + if (bitcount < 0) + bitcount = 0; + if (bitcount > 8) + bitcount = 8; - int usedEntries = 0; + this.range[usedEntries] = range; + this.switchTreshold[usedEntries] = switchTreshold; + this.bitcount[usedEntries] = bitcount; + usedEntries++; + } + /** + * Compares two tables. Ignores table initialization. + */ + @Override + public int compareTo(final Table o) { + if (usedEntries < o.usedEntries) + return -1; + if (usedEntries > o.usedEntries) + return 1; + + for (int i = 0; i < usedEntries; i++) { + if (range[i] < o.range[i]) + return -1; + if (range[i] > o.range[i]) + return 1; + + if (switchTreshold[i] < o.switchTreshold[i]) + return -1; + if (switchTreshold[i] > o.switchTreshold[i]) + return 1; + + if (bitcount[i] < o.bitcount[i]) + return -1; + if (bitcount[i] > o.bitcount[i]) + return 1; + } - public void computeLookupTables(){ + return 0; + } + + public void computeLookupTables() { int currentCheckPointer = 0; - for (int i=0; i<256; i++){ + for (int i = 0; i < 256; i++) { - if (range[currentCheckPointer] == i){ + if (range[currentCheckPointer] == i) { currentCheckPointer++; } - if (currentCheckPointer > 0){ - bitCountForRange[i] = bitcount[currentCheckPointer-1]; + if (currentCheckPointer > 0) { + bitCountForRange[i] = bitcount[currentCheckPointer - 1]; } else { bitCountForRange[i] = 0; } } - for (int i=0; i<256; i++){ + for (int i = 0; i < 256; i++) { int seek; - seekLoop:{ - for (seek = 0; seek < usedEntries; seek ++){ + seekLoop: { + for (seek = 0; seek < usedEntries; seek++) { - if (switchTreshold[seek] >= i) break seekLoop; + if (switchTreshold[seek] >= i) + break seekLoop; } } - proposedRangeForActualRange[i] = range[seek]; - if (seek == 0){ - proposedRangeForActualRangeLow[i] = 0; + proposedRangeForActualRange[i] = range[seek]; + if (seek == 0) { + proposedRangeForActualRangeLow[i] = 0; } else { - proposedRangeForActualRangeLow[i] = switchTreshold[seek-1]+1; + proposedRangeForActualRangeLow[i] = switchTreshold[seek - 1] + 1; } - proposedRangeForActualRangeHigh[i] = switchTreshold[seek]; + proposedRangeForActualRangeHigh[i] = switchTreshold[seek]; } - currentCheckPointer = usedEntries - 2; - for (int i=255; i >= 0; i--){ - if (range[currentCheckPointer] == i) currentCheckPointer--; + for (int i = 255; i >= 0; i--) { + if (range[currentCheckPointer] == i) + currentCheckPointer--; - if (currentCheckPointer < 0){ + if (currentCheckPointer < 0) { proposedDecreasedRange[i] = 0; } else { - proposedDecreasedRange[i] = (byte)(range[currentCheckPointer]); + proposedDecreasedRange[i] = (byte) (range[currentCheckPointer]); } } } - /** - * @param switchTreshold - switch to this range when actual range in equal or below this treshold - */ - public void addEntry(int range, int switchTreshold, int bitcount){ - if (range < 0) range = 0; - if (range > 255) range = 255; - - if (switchTreshold < 0) switchTreshold = 0; - if (switchTreshold > 255) switchTreshold = 255; - - if (bitcount < 0) bitcount = 0; - if (bitcount > 8) bitcount = 8; - - - this.range[usedEntries] = range; - this.switchTreshold[usedEntries] = switchTreshold; - this.bitcount[usedEntries] = bitcount; - usedEntries++; - } - - - - - public int proposeRangeForRange(int actualRange, int inheritedRange){ + public void load(final BitInputStream inputStream) throws IOException { + reset(); - if (inheritedRange > 255) inheritedRange = 255; - if (inheritedRange < 0) inheritedRange = 0; + final int availableEntries = inputStream.readIntegerCompressed8(); - if (proposedRangeForActualRangeLow[inheritedRange] <= actualRange){ - return inheritedRange; + for (int i = 0; i < availableEntries; i++) { + addEntry(inputStream.readBits(8), inputStream.readBits(8), + inputStream.readBits(4)); } - - return proposeDecreasedRange(inheritedRange); } - - public int proposeDecreasedRange(int range){ - if (range > 255) range = 255; - if (range < 0) range = 0; - - return ImageEncoder.byteToInt(proposedDecreasedRange[range]); + public int proposeBitcountForRange(int range) { + if (range > 255) + range = 255; + if (range < 0) + range = 0; + final int proposal = bitCountForRange[range]; + return proposal; } + public int proposeDecreasedRange(int range) { + if (range > 255) + range = 255; + if (range < 0) + range = 0; - public int proposeBitcountForRange(int range){ - if (range > 255) range = 255; - if (range < 0) range = 0; - int proposal = bitCountForRange[range]; - return proposal; + return ImageEncoder.byteToInt(proposedDecreasedRange[range]); } - /** - * Compares two tables. - * Ignores table initialization. - */ - public int compareTo(Table o) { - if (usedEntries < o.usedEntries) return -1; - if (usedEntries > o.usedEntries) return 1; - - for (int i=0; i o.range[i]) return 1; + public int proposeRangeForRange(final int actualRange, int inheritedRange) { - if (switchTreshold[i] < o.switchTreshold[i]) return -1; - if (switchTreshold[i] > o.switchTreshold[i]) return 1; + if (inheritedRange > 255) + inheritedRange = 255; + if (inheritedRange < 0) + inheritedRange = 0; - if (bitcount[i] < o.bitcount[i]) return -1; - if (bitcount[i] > o.bitcount[i]) return 1; + if (proposedRangeForActualRangeLow[inheritedRange] <= actualRange) { + return inheritedRange; } - return 0; + return proposeDecreasedRange(inheritedRange); } - public void save(BitOutputStream outputStream) throws IOException { - outputStream.storeIntegerCompressed8(usedEntries); - - for (int i=0; i < usedEntries; i++){ - outputStream.storeBits(this.range[i], 8); - outputStream.storeBits(this.switchTreshold[i], 8); - outputStream.storeBits(this.bitcount[i], 4); - } - } - - public void reset(){ - range = new int[100]; + public void reset() { + range = new int[100]; switchTreshold = new int[100]; bitcount = new int[100]; - bitCountForRange = new int[256]; proposedRangeForActualRange = new int[256]; proposedRangeForActualRangeLow = new int[256]; @@ -171,17 +197,15 @@ public class Table implements Comparable
{ usedEntries = 0; } - - public void load(BitInputStream inputStream) throws IOException { - reset(); - - int availableEntries = inputStream.readIntegerCompressed8(); - - for (int i=0; i < availableEntries; i++){ - addEntry(inputStream.readBits(8), inputStream.readBits(8), inputStream.readBits(4)); - } - } + public void save(final BitOutputStream outputStream) throws IOException { + outputStream.storeIntegerCompressed8(usedEntries); + for (int i = 0; i < usedEntries; i++) { + outputStream.storeBits(range[i], 8); + outputStream.storeBits(switchTreshold[i], 8); + outputStream.storeBits(bitcount[i], 4); + } + } } diff --git a/src/main/java/eu/svjatoslav/imagesqueeze/sampleApplication/ImageFrame.java b/src/main/java/eu/svjatoslav/imagesqueeze/sampleApplication/ImageFrame.java index 3da2f77..2b68ef7 100755 --- a/src/main/java/eu/svjatoslav/imagesqueeze/sampleApplication/ImageFrame.java +++ b/src/main/java/eu/svjatoslav/imagesqueeze/sampleApplication/ImageFrame.java @@ -1,35 +1,35 @@ +/* + * Imagesqueeze - Image codec optimized for photos. + * Copyright (C) 2012, Svjatoslav Agejenko, svjatoslav@svjatoslav.eu + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public License + * as published by the Free Software Foundation. + */ + package eu.svjatoslav.imagesqueeze.sampleApplication; + import java.awt.BorderLayout; -import javax.swing.WindowConstants; import javax.swing.SwingUtilities; - +import javax.swing.WindowConstants; public class ImageFrame extends javax.swing.JFrame { private ImagePanel imagePanel1; - /** - * Auto-generated main method to display this JFrame - */ - public static void main(String[] args) { - SwingUtilities.invokeLater(new Runnable() { - public void run() { - ImageFrame inst = new ImageFrame("test"); - inst.setLocationRelativeTo(null); - inst.setVisible(true); - } - }); - } - - public ImageFrame(String title) { + public ImageFrame(final String title) { super(); setTitle(title); initGUI(); } - + + public ImagePanel getImagePanel() { + return imagePanel1; + } + private void initGUI() { try { - BorderLayout thisLayout = new BorderLayout(); + final BorderLayout thisLayout = new BorderLayout(); setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); getContentPane().setLayout(thisLayout); { @@ -37,13 +37,23 @@ public class ImageFrame extends javax.swing.JFrame { getContentPane().add(getImagePanel(), BorderLayout.CENTER); } pack(); - } catch (Exception e) { + } catch (final Exception e) { e.printStackTrace(); } } - - public ImagePanel getImagePanel() { - return imagePanel1; + + /** + * Auto-generated main method to display this JFrame + */ + public static void main(final String[] args) { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + final ImageFrame inst = new ImageFrame("test"); + inst.setLocationRelativeTo(null); + inst.setVisible(true); + } + }); } } diff --git a/src/main/java/eu/svjatoslav/imagesqueeze/sampleApplication/ImagePanel.java b/src/main/java/eu/svjatoslav/imagesqueeze/sampleApplication/ImagePanel.java index 749d558..c2ccead 100755 --- a/src/main/java/eu/svjatoslav/imagesqueeze/sampleApplication/ImagePanel.java +++ b/src/main/java/eu/svjatoslav/imagesqueeze/sampleApplication/ImagePanel.java @@ -1,126 +1,131 @@ +/* + * Imagesqueeze - Image codec optimized for photos. + * Copyright (C) 2012, Svjatoslav Agejenko, svjatoslav@svjatoslav.eu + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public License + * as published by the Free Software Foundation. + */ + package eu.svjatoslav.imagesqueeze.sampleApplication; import java.awt.BorderLayout; - import java.awt.Dimension; import java.awt.image.BufferedImage; -import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; -import javax.swing.JButton; - import javax.swing.ImageIcon; -import javax.swing.JPanel; -import javax.swing.WindowConstants; import javax.swing.JFrame; import javax.swing.JLabel; +import javax.swing.WindowConstants; import eu.svjatoslav.imagesqueeze.codec.Image; - public class ImagePanel extends javax.swing.JPanel { private JLabel imageLabel; public BufferedImage bufferedImage; - /** - * Auto-generated main method to display this - * JPanel inside a new JFrame. - */ - public static void main(String[] args) { - JFrame frame = new JFrame(); - frame.getContentPane().add(new ImagePanel()); - frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); - frame.pack(); - frame.setVisible(true); - } - public ImagePanel() { super(); initGUI(); } + public void createEmptyImage(final Dimension dimension) { + + bufferedImage = new BufferedImage(dimension.width, dimension.height, + BufferedImage.TYPE_3BYTE_BGR); + + final ImageIcon icon = new ImageIcon(bufferedImage); + + imageLabel.setIcon(icon); + } + + public JLabel getImageLabel() { + return imageLabel; + } + private void initGUI() { try { - BorderLayout thisLayout = new BorderLayout(); - this.setLayout(thisLayout); + final BorderLayout thisLayout = new BorderLayout(); + setLayout(thisLayout); setPreferredSize(new Dimension(660, 500)); { imageLabel = new JLabel(); this.add(getImageLabel(), BorderLayout.CENTER); } - } catch (Exception e) { + } catch (final Exception e) { e.printStackTrace(); } } - public JLabel getImageLabel() { - return imageLabel; - } + public void loadImage(final File inputFile, final boolean isImgSqz) + throws IOException { + final FileInputStream fileInputStream = new FileInputStream(inputFile); - public void loadImage(File inputFile, boolean isImgSqz) throws IOException{ - FileInputStream fileInputStream = new FileInputStream(inputFile); - - loadImage(fileInputStream, isImgSqz); + loadImage(fileInputStream, isImgSqz); } - public void loadImage(InputStream inputStream, boolean isImgSqz) throws IOException{ - if (isImgSqz){ + public void loadImage(final InputStream inputStream, final boolean isImgSqz) + throws IOException { + if (isImgSqz) { // load ImageSqueeze file - Image image = new Image(); + final Image image = new Image(); image.loadImage(inputStream); bufferedImage = image.bufferedImage; - ImageIcon icon = new ImageIcon(bufferedImage); - //ImageIcon icon = new ImageIcon("sample data/original.png"); + final ImageIcon icon = new ImageIcon(bufferedImage); + // ImageIcon icon = new ImageIcon("sample data/original.png"); - imageLabel.setIcon(icon); + imageLabel.setIcon(icon); } else { // load JPEG, PNG, GIF file - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - readLoop:{ - for (;;){ - int b = inputStream.read(); - if (b == -1) break readLoop; + final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + readLoop: { + for (;;) { + final int b = inputStream.read(); + if (b == -1) + break readLoop; outputStream.write(b); } } - - ImageIcon icon = new ImageIcon(outputStream.toByteArray()); - bufferedImage = new BufferedImage(icon.getIconWidth(), icon.getIconHeight(), BufferedImage.TYPE_3BYTE_BGR); + final ImageIcon icon = new ImageIcon(outputStream.toByteArray()); + + bufferedImage = new BufferedImage(icon.getIconWidth(), + icon.getIconHeight(), BufferedImage.TYPE_3BYTE_BGR); bufferedImage.getGraphics().drawImage(icon.getImage(), 0, 0, null); - ImageIcon displayIcon = new ImageIcon(bufferedImage); - imageLabel.setIcon(displayIcon); + final ImageIcon displayIcon = new ImageIcon(bufferedImage); + imageLabel.setIcon(displayIcon); } } - - public void createEmptyImage(Dimension dimension){ - - bufferedImage = new BufferedImage(dimension.width, dimension.height, BufferedImage.TYPE_3BYTE_BGR); - - ImageIcon icon = new ImageIcon(bufferedImage); - - imageLabel.setIcon(icon); - } - - - public void saveImage(File outputFile){ - Image image = new Image(bufferedImage); + public void saveImage(final File outputFile) { + final Image image = new Image(bufferedImage); try { image.saveImage(outputFile); - } catch (Exception e) { + } catch (final Exception e) { System.out.println("Error while saving image: " + e.toString()); } } + /** + * Auto-generated main method to display this JPanel inside a new JFrame. + */ + public static void main(final String[] args) { + final JFrame frame = new JFrame(); + frame.getContentPane().add(new ImagePanel()); + frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + frame.pack(); + frame.setVisible(true); + } + } diff --git a/src/main/java/eu/svjatoslav/imagesqueeze/sampleApplication/Main.java b/src/main/java/eu/svjatoslav/imagesqueeze/sampleApplication/Main.java index f67ff3a..0e2ccce 100755 --- a/src/main/java/eu/svjatoslav/imagesqueeze/sampleApplication/Main.java +++ b/src/main/java/eu/svjatoslav/imagesqueeze/sampleApplication/Main.java @@ -1,45 +1,50 @@ +/* + * Imagesqueeze - Image codec optimized for photos. + * Copyright (C) 2012, Svjatoslav Agejenko, svjatoslav@svjatoslav.eu + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public License + * as published by the Free Software Foundation. + */ + package eu.svjatoslav.imagesqueeze.sampleApplication; import java.io.File; import java.io.IOException; import java.io.InputStream; - - public class Main { - public static void main(String[] args) { + public static void main(final String[] args) { try { - String image = "colorful photo"; - String sourceDirectory = "/eu/svjatoslav/imagesqueeze/sampleApplication/data/"; - + final String image = "colorful photo"; + final String sourceDirectory = "/eu/svjatoslav/imagesqueeze/sampleApplication/data/"; + // create visible frame // load image into frame - InputStream inputStream = Main.class.getResourceAsStream(sourceDirectory + image + ".png"); - - ImageFrame frame = new ImageFrame("Original image"); - frame.getImagePanel().loadImage(inputStream, false); - frame.setVisible(true); + final InputStream inputStream = Main.class + .getResourceAsStream(sourceDirectory + image + ".png"); + final ImageFrame frame = new ImageFrame("Original image"); + frame.getImagePanel().loadImage(inputStream, false); + frame.setVisible(true); // encode image into file frame.getImagePanel().saveImage(new File(image + ".ImgSqz")); - // create second frame for decoded image - ImageFrame frame2 = new ImageFrame("Encoded -> Decoded"); + final ImageFrame frame2 = new ImageFrame("Encoded -> Decoded"); // decode image frame2.getImagePanel().loadImage(new File(image + ".ImgSqz"), true); frame2.setVisible(true); - } catch (IOException exception){ + } catch (final IOException exception) { System.out.println("Error while loading an image: " + exception); } - } } -- 2.20.1