initial commit
authorSvjatoslav Agejenko <n0@big-laptop.(none)>
Sat, 7 Jul 2012 19:41:18 +0000 (22:41 +0300)
committerSvjatoslav Agejenko <n0@big-laptop.(none)>
Sat, 7 Jul 2012 19:41:18 +0000 (22:41 +0300)
30 files changed:
.classpath [new file with mode: 0644]
.gitignore [new file with mode: 0644]
.project [new file with mode: 0755]
.settings/org.eclipse.jdt.core.prefs [new file with mode: 0644]
.settings/org.maven.ide.eclipse.prefs [new file with mode: 0644]
COPYING [new file with mode: 0755]
README.TXT [new file with mode: 0755]
colorful photo.ImgSqz [new file with mode: 0644]
doc/index.html [new file with mode: 0755]
doc/originalAndCompressed.png [new file with mode: 0755]
pom.xml [new file with mode: 0644]
src/main/java/eu/svjatoslav/imagesqueeze/codec/Approximator.java [new file with mode: 0755]
src/main/java/eu/svjatoslav/imagesqueeze/codec/BitInputStream.java [new file with mode: 0755]
src/main/java/eu/svjatoslav/imagesqueeze/codec/BitOutputStream.java [new file with mode: 0755]
src/main/java/eu/svjatoslav/imagesqueeze/codec/Channel.java [new file with mode: 0755]
src/main/java/eu/svjatoslav/imagesqueeze/codec/Color.java [new file with mode: 0755]
src/main/java/eu/svjatoslav/imagesqueeze/codec/ColorStats.java [new file with mode: 0755]
src/main/java/eu/svjatoslav/imagesqueeze/codec/Image.java [new file with mode: 0755]
src/main/java/eu/svjatoslav/imagesqueeze/codec/ImageDecoder.java [new file with mode: 0755]
src/main/java/eu/svjatoslav/imagesqueeze/codec/ImageEncoder.java [new file with mode: 0755]
src/main/java/eu/svjatoslav/imagesqueeze/codec/ImageMetaData.java [new file with mode: 0755]
src/main/java/eu/svjatoslav/imagesqueeze/codec/OperatingContext.java [new file with mode: 0755]
src/main/java/eu/svjatoslav/imagesqueeze/codec/Table.java [new file with mode: 0755]
src/main/java/eu/svjatoslav/imagesqueeze/sampleApplication/ImageFrame.java [new file with mode: 0755]
src/main/java/eu/svjatoslav/imagesqueeze/sampleApplication/ImagePanel.java [new file with mode: 0755]
src/main/java/eu/svjatoslav/imagesqueeze/sampleApplication/Main.java [new file with mode: 0755]
src/main/resources/eu/svjatoslav/imagesqueeze/sampleApplication/data/bw schematics.gif [new file with mode: 0755]
src/main/resources/eu/svjatoslav/imagesqueeze/sampleApplication/data/clouds.png [new file with mode: 0755]
src/main/resources/eu/svjatoslav/imagesqueeze/sampleApplication/data/colorful photo.png [new file with mode: 0755]
src/main/resources/eu/svjatoslav/imagesqueeze/sampleApplication/data/small logo.png [new file with mode: 0755]

diff --git a/.classpath b/.classpath
new file mode 100644 (file)
index 0000000..3b25ab3
--- /dev/null
@@ -0,0 +1,5 @@
+<classpath>
+  <classpathentry kind="src" path="src/main/java" including="**/*.java"/>
+  <classpathentry kind="output" path="target/classes"/>
+  <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
+</classpath>
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..fb60f67
--- /dev/null
@@ -0,0 +1,2 @@
+bin/
+target/
diff --git a/.project b/.project
new file mode 100755 (executable)
index 0000000..cf45093
--- /dev/null
+++ b/.project
@@ -0,0 +1,17 @@
+<projectDescription>
+  <name>imagesqueeze</name>
+  <comment>ImageSqueeze - image codec optimized for photos. NO_M2ECLIPSE_SUPPORT: Project files created with the maven-eclipse-plugin are not supported in M2Eclipse.</comment>
+  <projects/>
+  <buildSpec>
+    <buildCommand>
+      <name>org.eclipse.jdt.core.javabuilder</name>
+    </buildCommand>
+    <buildCommand>
+      <name>org.maven.ide.eclipse.maven2Builder</name>
+    </buildCommand>
+  </buildSpec>
+  <natures>
+    <nature>org.maven.ide.eclipse.maven2Nature</nature>
+    <nature>org.eclipse.jdt.core.javanature</nature>
+  </natures>
+</projectDescription>
\ No newline at end of file
diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs
new file mode 100644 (file)
index 0000000..beb3db6
--- /dev/null
@@ -0,0 +1,10 @@
+#Sun Aug 28 02:19:45 EEST 2011
+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
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.source=1.6
diff --git a/.settings/org.maven.ide.eclipse.prefs b/.settings/org.maven.ide.eclipse.prefs
new file mode 100644 (file)
index 0000000..f3597ed
--- /dev/null
@@ -0,0 +1,8 @@
+#Sun Aug 28 01:59:39 EEST 2011
+activeProfiles=
+eclipse.preferences.version=1
+fullBuildGoals=process-test-resources
+resolveWorkspaceProjects=true
+resourceFilterGoals=process-resources resources\:testResources
+skipCompilerPlugin=true
+version=1
diff --git a/COPYING b/COPYING
new file mode 100755 (executable)
index 0000000..10828e0
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,341 @@
+
+                   GNU GENERAL PUBLIC LICENSE
+                      Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+                       51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                           Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+\f
+                   GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+\f
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+\f
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+\f
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                           NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                    END OF TERMS AND CONDITIONS
+\f
+           How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/README.TXT b/README.TXT
new file mode 100755 (executable)
index 0000000..5e92643
--- /dev/null
@@ -0,0 +1,12 @@
+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/colorful photo.ImgSqz b/colorful photo.ImgSqz
new file mode 100644 (file)
index 0000000..6c358b6
Binary files /dev/null and b/colorful photo.ImgSqz differ
diff --git a/doc/index.html b/doc/index.html
new file mode 100755 (executable)
index 0000000..7ccf9f6
--- /dev/null
@@ -0,0 +1,47 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<head>
+<meta
+    http-equiv="Content-Type"
+    content="text/html; charset=UTF-8">
+<title>ImageSqueeze</title>
+</head>
+<body>
+    <h1>ImageSqueeze - lossy image codec</h1>
+    <a href="http://php.svjatoslav.eu/gitweb/?p=imagesqueeze.git;a=snapshot;h=HEAD;sf=tgz">Download</a>
+    &nbsp;&nbsp;
+    <a href="http://svjatoslav.eu/static/gitbrowse/imagesqueeze/doc/index.html">Online homepage</a>
+    &nbsp;&nbsp;
+    <a href="http://svjatoslav.eu/programs.jsp">Other applications hosted on svjatoslav.eu</a>
+    <pre>
+<b>Program author:</b>
+    Svjatoslav Agejenko
+    Homepage: <a href="http://svjatoslav.eu">http://svjatoslav.eu</a>
+    Email: <a href="mailto:svjatoslav@svjatoslav.eu">svjatoslav@svjatoslav.eu</a>
+
+This software is distributed under <a href="http://www.gnu.org/licenses/gpl-2.0.html">GNU GENERAL PUBLIC LICENSE Version 2</a>.
+
+
+Lossy image codec. Optimized for photos.
+I developed it to test out an image compression ideas.
+
+I believe my algorithm has following advantages:
+    * Fast. Relatively few computations per pixel. 
+    * Easy to add support for progressive image loading. (Saving is already progressive)
+
+Current limitations / to do:
+    * Documentation describing idea behind this algorithm is still missing (lack of time)
+    * Code documentation is weak.
+    * Better sample applications needed: Commandline image conversion utility. Image viewer.
+
+
+Below are original photo and the same image being compressed down to ~93 Kb and then decompressed.
+<img src="originalAndCompressed.png" />
+
+When looking very closely, slight grainyness, loss of color precision and
+blurriness (loss of detail) could be noticed as a compression artifacts.
+Still sharp edges are always preserved. Also no blocks typical to JPEG are ever seen.
+I think that is awesome result for just ~ 2.5 bits per pixel on that color photo. 
+</pre>
+</body>
+</html>
\ No newline at end of file
diff --git a/doc/originalAndCompressed.png b/doc/originalAndCompressed.png
new file mode 100755 (executable)
index 0000000..f45ce73
Binary files /dev/null and b/doc/originalAndCompressed.png differ
diff --git a/pom.xml b/pom.xml
new file mode 100644 (file)
index 0000000..c73ed5d
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,82 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+       <modelVersion>4.0.0</modelVersion>
+       <groupId>eu.svjatoslav</groupId>
+       <artifactId>imagesqueeze</artifactId>
+       <version>1.0-SNAPSHOT</version>
+       <name>imagesqueeze</name>
+       <packaging>jar</packaging>
+
+       <description>ImageSqueeze - image codec optimized for photos</description>
+
+       <build>
+               <finalName>imagesqueeze</finalName>
+               <resources>
+                       <resource>
+                               <filtering>false</filtering>
+                               <directory>src/main/resources</directory>
+                       </resource>
+                       <resource>
+                               <filtering>true</filtering>
+                               <directory>src/main/resources</directory>
+                               <includes>
+                                       <include>*.xml</include>
+                               </includes>
+                       </resource>
+               </resources>
+               <testResources>
+                       <testResource>
+                               <filtering>true</filtering>
+                               <directory>src/test/resources</directory>
+                       </testResource>
+               </testResources>
+               <plugins>
+                       <plugin>
+                               <groupId>org.apache.maven.plugins</groupId>
+                               <artifactId>maven-compiler-plugin</artifactId>
+                               <version>2.3.1</version>
+                               <configuration>
+                                       <source>1.6</source>
+                                       <target>1.6</target>
+                                       <optimize>true</optimize>
+                                       <encoding>UTF-8</encoding>
+                               </configuration>
+                       </plugin>
+                       <plugin>
+                               <groupId>org.apache.maven.plugins</groupId>
+                               <artifactId>maven-resources-plugin</artifactId>
+                               <version>2.4.3</version>
+                               <configuration>
+                                       <encoding>UTF-8</encoding>
+                               </configuration>
+                       </plugin>
+               </plugins>
+       </build>
+
+
+
+       <repositories>
+
+               <repository>
+                       <id>svjatoslav.eu</id>
+                       <name>Svjatoslav repository</name>
+                       <url>http://svjatoslav.eu/maven/</url>
+               </repository>
+
+       </repositories>
+
+
+
+       <distributionManagement>
+               <!-- use the following if you're not using a snapshot version. <repository> <id>repo</id> <name>Repository Name</name> 
+                       <url>scp://host/path/to/repo</url> </repository> -->
+
+               <!-- use the following if you ARE using a snapshot version. -->
+               <snapshotRepository>
+                       <id>svjatoslav.eu</id>
+                       <name>svjatoslav.eu</name>
+                       <url>scp://svjatoslav.eu/opt/svjatoslav.eu/webcontent/maven</url>
+               </snapshotRepository>
+       </distributionManagement>
+
+</project>
diff --git a/src/main/java/eu/svjatoslav/imagesqueeze/codec/Approximator.java b/src/main/java/eu/svjatoslav/imagesqueeze/codec/Approximator.java
new file mode 100755 (executable)
index 0000000..622ae80
--- /dev/null
@@ -0,0 +1,69 @@
+package eu.svjatoslav.imagesqueeze.codec;
+
+import java.io.IOException;
+
+/**
+ * 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<Approximator> {
+
+       public Table yTable = new Table();
+       public Table uTable = new Table();
+       public Table vTable = new Table();
+
+       public Approximator(){
+       }
+
+       public int compareTo(Approximator o) {
+               int result = yTable.compareTo(o.yTable);
+               if (result != 0) return result;
+
+               result = uTable.compareTo(o.uTable);
+               if (result != 0) return result;
+
+               result = vTable.compareTo(o.vTable);
+               return result;
+       }
+       
+       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);
+
+               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 {
+               yTable.load(inputStream);
+               uTable.load(inputStream);
+               vTable.load(inputStream);                               
+       }
+
+       public void computeLookupTables(){
+               yTable.computeLookupTables();
+               uTable.computeLookupTables();
+               vTable.computeLookupTables();           
+       }
+       
+}
diff --git a/src/main/java/eu/svjatoslav/imagesqueeze/codec/BitInputStream.java b/src/main/java/eu/svjatoslav/imagesqueeze/codec/BitInputStream.java
new file mode 100755 (executable)
index 0000000..b5ba69a
--- /dev/null
@@ -0,0 +1,56 @@
+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
new file mode 100755 (executable)
index 0000000..469bb7b
--- /dev/null
@@ -0,0 +1,63 @@
+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
new file mode 100755 (executable)
index 0000000..879326e
--- /dev/null
@@ -0,0 +1,52 @@
+package eu.svjatoslav.imagesqueeze.codec;
+
+public class Channel {
+
+       byte [] rangeMap;       
+       byte [] map;
+
+       byte [] decodedRangeMap;
+       byte [] decodedMap;
+
+       int bitCount;
+       
+       public Channel(int width, int height) {
+               rangeMap = new byte[width * height];
+
+               map = new byte[width * height];         
+
+               decodedRangeMap = new byte[width * height];
+               decodedRangeMap[0] = (byte)255;
+
+               decodedMap = new byte[width * height];
+       };
+
+       
+       public void reset(){
+               
+               for (int i=0; i < decodedMap.length; i++){
+                       decodedMap[i] = 0;
+               }
+
+               for (int i=0; i < decodedRangeMap.length; i++){
+                       decodedRangeMap[i] = 0;
+               }
+               decodedRangeMap[0] = (byte)255;
+               
+               for (int i=0; i < map.length; i++){
+                       map[i] = 0;
+               }
+
+               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
new file mode 100755 (executable)
index 0000000..1d8b7a6
--- /dev/null
@@ -0,0 +1,49 @@
+package eu.svjatoslav.imagesqueeze.codec;
+
+/**
+ * Helper class to convert between RGB and YUV
+ */
+public class Color {
+
+       int r;
+       int g;
+       int b;
+
+       int y;
+       int u;
+       int v;
+
+       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));
+
+               if (r < 0) r = 0;
+               if (g < 0) g = 0;
+               if (b < 0) b = 0;
+
+               if (r > 255) r = 255;
+               if (g > 255) g = 255;
+               if (b > 255) b = 255;
+
+       }
+
+       public void RGB2YUV(){
+
+               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 (y < 0) y = 0;
+               if (u < 0) u = 0;
+               if (v < 0) v = 0;
+
+               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
new file mode 100755 (executable)
index 0000000..b856fe5
--- /dev/null
@@ -0,0 +1,37 @@
+package eu.svjatoslav.imagesqueeze.codec;
+
+public class ColorStats {
+
+       int ySum;
+       int uSum;
+       int vSum;
+       
+       int pixelCount;
+       
+       
+       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 getAverageV(){
+               return vSum / pixelCount;               
+       }
+       
+}
+
+
diff --git a/src/main/java/eu/svjatoslav/imagesqueeze/codec/Image.java b/src/main/java/eu/svjatoslav/imagesqueeze/codec/Image.java
new file mode 100755 (executable)
index 0000000..c644eaa
--- /dev/null
@@ -0,0 +1,120 @@
+package eu.svjatoslav.imagesqueeze.codec;
+
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Main class representing compressed image.
+ */
+
+public class Image {
+
+       public ImageMetaData metaData;
+
+       public BufferedImage bufferedImage;
+
+       ImageEncoder encoder;
+       
+       public Image(){};
+
+       /**
+        * Initialize imagesqueeze image based on {@link BufferedImage}. {@link BufferedImage} must be of type BufferedImage.TYPE_3BYTE_BGR .
+        */
+       public Image(BufferedImage image){
+
+               this.bufferedImage = image;
+               metaData = new ImageMetaData();
+
+               metaData.version = 1;
+               metaData.width = image.getWidth();
+               metaData.height = image.getHeight();            
+       }
+
+       /**
+        * Initialize empty imagesqueeze image.
+        */
+       public Image(int width, int height){
+
+               this.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();
+       }
+
+       /**
+        * Load ImgSqz image from {@link File}.
+        */
+       public void loadImage(File source) throws IOException{
+
+               byte [] fileContent = new byte[(int)source.length()];
+
+               FileInputStream fileInputStream = new FileInputStream(source);
+
+               fileInputStream.read(fileContent);
+
+               fileInputStream.close();
+
+               ByteArrayInputStream inputStream = new ByteArrayInputStream(fileContent);
+
+               loadImage(inputStream);
+       }
+
+       /**
+        * Save image into ImgSqz file format.
+        */
+       public void saveImage(OutputStream outputStream) throws IOException{
+
+               BitOutputStream bitOutputStream = new BitOutputStream(outputStream);
+
+               metaData.save(bitOutputStream);
+               
+               if (encoder == null){
+                       encoder = new ImageEncoder(this);                       
+               }
+
+               encoder.encode(bitOutputStream);
+
+               bitOutputStream.finishByte();
+       }
+
+
+       /**
+        * Save image into ImgSqz file format.
+        */
+       public void saveImage(File file) throws IOException{
+               ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+               saveImage(outputStream);
+
+               byte [] buffer = outputStream.toByteArray();
+               FileOutputStream fileOutputStream = new FileOutputStream(file);
+               fileOutputStream.write(buffer);
+               fileOutputStream.close();
+       }
+
+
+}
diff --git a/src/main/java/eu/svjatoslav/imagesqueeze/codec/ImageDecoder.java b/src/main/java/eu/svjatoslav/imagesqueeze/codec/ImageDecoder.java
new file mode 100755 (executable)
index 0000000..37f3c0e
--- /dev/null
@@ -0,0 +1,232 @@
+package eu.svjatoslav.imagesqueeze.codec;
+
+/**
+ * Compressed image pixels decoder.
+ */
+
+import java.awt.image.DataBufferByte;
+import java.awt.image.WritableRaster;
+import java.io.IOException;
+
+public class ImageDecoder {
+
+       int width, height;
+       Image image;
+
+       byte [] decodedYRangeMap;
+       byte [] decodedYMap;
+
+       byte [] decodedURangeMap;
+       byte [] decodedUMap;
+
+       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) {
+               approximator = new Approximator();
+
+               this.image = image;     
+               this.bitInputStream = bitInputStream;
+
+               width = image.metaData.width;
+               height = image.metaData.height;
+
+               decodedYRangeMap = new byte[width * height];
+               decodedYRangeMap[0] = (byte)(255);
+               decodedYMap = new byte[width * height];
+
+               decodedURangeMap = new byte[width * height];
+               decodedURangeMap[0] = (byte)(255);
+               decodedUMap = new byte[width * height];
+
+               decodedVRangeMap = new byte[width * height];
+               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();
+               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;                            
+
+                               
+               // detect initial step
+               int largestDimension;
+               int initialStep = 2;
+               if (width > height) {
+                       largestDimension = width;
+               } else {
+                       largestDimension = height;
+               }
+               
+               while (initialStep < largestDimension){
+                       initialStep = initialStep * 2;
+               }
+
+               grid(initialStep, pixels);
+       }
+
+
+       public void grid(int step, 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());
+
+                       }                       
+               }               
+       }
+
+
+       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){
+
+                               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,
+                                               context.colorStats.getAverageY(),
+                                               context.colorStats.getAverageU(),
+                                               context.colorStats.getAverageV());
+
+                       }                       
+               }               
+       }
+
+
+       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;                                                                             
+               }
+
+
+
+               int colorBufferIndex = index * 3;
+
+               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;                             
+
+       }
+
+
+       private int loadChannel(byte [] decodedRangeMap, byte [] decodedMap, Table table, int averageDecodedValue, int index,
+                       int parentIndex) throws IOException {
+               int decodedValue = averageDecodedValue;
+
+               int inheritedRange = ImageEncoder.byteToInt(decodedRangeMap[parentIndex]);
+               int computedRange = inheritedRange;
+
+               int bitCount = table.proposeBitcountForRange(inheritedRange);
+               int computedRangeBitCount = 0;
+               if ( bitCount > 0){
+
+                       int rangeDecreases = bitInputStream.readBits(1);
+                       if (rangeDecreases != 0){
+                               computedRange = table.proposeDecreasedRange(inheritedRange);
+                       }                               
+
+                       decodedRangeMap[index] = (byte)computedRange;
+                       computedRangeBitCount = table.proposeBitcountForRange(computedRange);
+
+                       if (computedRangeBitCount > 0){
+
+                               int encodedDifference = bitInputStream.readBits(computedRangeBitCount);
+
+                               int decodedDifference = ImageEncoder.decodeValueFromGivenBits(encodedDifference, computedRange, computedRangeBitCount);
+
+                               decodedValue = averageDecodedValue - decodedDifference;
+                               if (decodedValue > 255) decodedValue = 255;
+                               if (decodedValue < 0) decodedValue = 0;
+                       }
+               } else {
+                       decodedRangeMap[index] = (byte)inheritedRange;
+               }
+               decodedMap[index] = (byte)decodedValue;
+               return decodedValue;
+       }
+
+}
diff --git a/src/main/java/eu/svjatoslav/imagesqueeze/codec/ImageEncoder.java b/src/main/java/eu/svjatoslav/imagesqueeze/codec/ImageEncoder.java
new file mode 100755 (executable)
index 0000000..46c2135
--- /dev/null
@@ -0,0 +1,529 @@
+package eu.svjatoslav.imagesqueeze.codec;
+
+/**
+ * Compressed image pixels encoder.
+ */
+
+import java.awt.image.DataBufferByte;
+import java.awt.image.WritableRaster;
+import java.io.IOException;
+
+
+public class ImageEncoder {
+
+       Image image;
+       int width, height;
+
+       Channel yChannel;
+       Channel uChannel;
+       Channel vChannel;
+       
+       Approximator approximator;
+
+       int bitsForY;
+       int bitsForU;
+       int bitsForV;
+       
+       //ColorStats colorStats = new ColorStats();
+       OperatingContext context = new OperatingContext();
+       OperatingContext context2 = new OperatingContext();
+
+       BitOutputStream bitOutputStream;
+
+       public ImageEncoder(Image image){
+               approximator = new Approximator();
+
+               //bitOutputStream = outputStream;
+
+               this.image = image;
+
+       }
+
+
+       public void encode(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();
+
+               if (yChannel == null){
+                       yChannel = new Channel(width, height);                  
+               } else {
+                       yChannel.reset();
+               }
+               
+               if (uChannel == null){
+                       uChannel = new Channel(width, height);                  
+               } else {
+                       uChannel.reset();
+               }
+               
+               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++){
+
+                               int index = (y * width) + x;
+                               int colorBufferIndex = index * 3;
+
+                               int blue = pixels[colorBufferIndex];                            
+                               if (blue < 0) blue = blue + 256;
+
+                               int green = pixels[colorBufferIndex+1];
+                               if (green < 0) green = green + 256;
+
+                               int red = pixels[colorBufferIndex+2];
+                               if (red < 0) red = red + 256;
+
+                               color.r = red;
+                               color.g = green;
+                               color.b = blue;
+
+                               color.RGB2YUV();
+
+                               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);                                                               
+
+               // detect initial step
+               int largestDimension;
+               int initialStep = 2;
+               if (width > height) {
+                       largestDimension = width;
+               } else {
+                       largestDimension = height;
+               }
+               
+               while (initialStep < largestDimension){
+                       initialStep = initialStep * 2;
+               }
+                               
+               rangeGrid(initialStep);
+               rangeRoundGrid(2);
+               saveGrid(initialStep);
+       }
+
+       public void printStatistics(){
+               System.out.println("Y channel:");
+               yChannel.printStatistics();
+
+               System.out.println("U channel:");
+               uChannel.printStatistics();
+               
+               System.out.println("V channel:");
+               vChannel.printStatistics();             
+       }
+       
+       public void rangeGrid(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);
+       }
+
+
+       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);
+       }
+
+       public void saveGrid(int step) throws IOException {
+
+               saveGridDiagonal(step / 2, step / 2, step);
+               saveGridSquare(step / 2, 0, step);
+               saveGridSquare(0, step / 2, step);
+
+               if (step > 2) saveGrid(step / 2);
+       }
+
+
+       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){
+
+                               int index = (y * width) + x;
+                               int halfStep = step / 2;
+
+                               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);
+                       }                       
+               }               
+       }
+
+       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){
+
+                               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);
+
+                               yChannel.rangeMap[index] = (byte)context.getYRange(index);
+                               uChannel.rangeMap[index] = (byte)context.getURange(index);
+                               vChannel.rangeMap[index] = (byte)context.getVRange(index);
+                       }                       
+               }               
+       }
+
+       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){
+
+                               int index = (y * width) + x;
+
+                               int yRange = byteToInt(yChannel.rangeMap[index]);
+                               int uRange = byteToInt(uChannel.rangeMap[index]);
+                               int vRange = byteToInt(vChannel.rangeMap[index]);
+
+                               int halfStep = step / 2;
+
+                               int parentIndex = ((y - halfStep) * width) + (x - halfStep);
+
+                               int parentYRange =  byteToInt(yChannel.rangeMap[parentIndex]);
+
+                               if (parentYRange < yRange){
+                                       parentYRange = yRange;
+                                       yChannel.rangeMap[parentIndex] = (byte)parentYRange;
+                               }
+
+                               int parentURange = byteToInt(uChannel.rangeMap[parentIndex]);
+
+                               if (parentURange < uRange){
+                                       parentURange = uRange;
+                                       uChannel.rangeMap[parentIndex] = (byte)parentURange;
+                               }
+
+                               int parentVRange = byteToInt(vChannel.rangeMap[parentIndex]);
+
+                               if (parentVRange < vRange){
+                                       parentVRange = vRange;
+                                       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){
+
+                               int index = (y * width) + x;
+                               
+                               int yRange = byteToInt(yChannel.rangeMap[index]);
+                               int uRange = byteToInt(uChannel.rangeMap[index]);
+                               int vRange = byteToInt(vChannel.rangeMap[index]);
+
+                               int halfStep = step / 2;
+
+                               int parentIndex;
+                               if (offsetX > 0){
+                                       parentIndex = (y * width) + (x - halfStep);                                     
+                               } else {
+                                       parentIndex = ((y - halfStep) * width) + x;                                                                             
+                               }
+
+                               int parentYRange =  byteToInt(yChannel.rangeMap[parentIndex]);
+
+                               if (parentYRange < yRange){
+                                       parentYRange = yRange;
+                                       yChannel.rangeMap[parentIndex] = (byte)parentYRange;
+                               }
+
+                               int parentURange = byteToInt(uChannel.rangeMap[parentIndex]);
+
+                               if (parentURange < uRange){
+                                       parentURange = uRange;
+                                       uChannel.rangeMap[parentIndex] = (byte)parentURange;
+                               }
+
+                               int parentVRange = byteToInt(vChannel.rangeMap[parentIndex]);
+
+                               if (parentVRange < vRange){
+                                       parentVRange = vRange;
+                                       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;
+
+                               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());                             
+
+                       }                       
+               }               
+       }
+
+       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;
+
+                               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());                             
+                               
+                       }                       
+               }               
+       }
+
+       public void savePixel(int step, int offsetX, int offsetY, int x, int y, int averageDecodedY, int averageDecodedU, int averageDecodedV) throws IOException {
+
+               int index = (y * width) + x;
+
+               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]);
+
+               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;                                                                             
+               }
+
+               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);
+
+       }
+
+
+       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]);
+
+               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
+                       int computedBitCount = table.proposeBitcountForRange(computedRange);
+
+                       if (computedBitCount > 0){
+
+                               int differenceToEncode = -(value - averageDecodedValue);
+                               int bitEncodedDifference = encodeValueIntoGivenBits(differenceToEncode, computedRange, computedBitCount);
+
+                               channel.bitCount = channel.bitCount + computedBitCount;
+                               bitOutputStream.storeBits(bitEncodedDifference, computedBitCount);                                                              
+
+                               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 static int encodeValueIntoGivenBits(int value, int range, int bitCount){
+
+               int negativeBit = 0;
+
+               if (value <0){
+                       negativeBit = 1;
+                       value = -value;
+               }
+
+               int remainingBitCount = bitCount - 1;
+
+               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;
+
+
+                       int realvalueForThisBitcount = 1 << remainingBitCount;
+                       // int valueMultiplier = range / realvalueForThisBitcount;
+                       int encodedValue = value * realvalueForThisBitcount / range;
+
+                       if (encodedValue >= realvalueForThisBitcount) encodedValue = realvalueForThisBitcount - 1;
+
+                       encodedValue = (encodedValue << 1) + negativeBit;
+
+                       return encodedValue;
+               }
+       }
+
+
+       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
new file mode 100755 (executable)
index 0000000..68b3bea
--- /dev/null
@@ -0,0 +1,34 @@
+package eu.svjatoslav.imagesqueeze.codec;
+
+/**
+ * Class to hold image metadata.
+ * Like image dimensions, header version, compression quality, etc.. 
+ */
+
+import java.io.IOException;
+
+
+public class ImageMetaData {
+
+       int version;
+       int width;
+       int height;
+
+
+       public void load(BitInputStream inputStream) throws IOException{
+
+               version = inputStream.readBits(16);
+               width = inputStream.readIntegerCompressed8();
+               height = inputStream.readIntegerCompressed8();
+
+       }
+
+       public void save(BitOutputStream outputStream) throws IOException{
+
+               outputStream.storeBits(version, 16);
+               outputStream.storeIntegerCompressed8(width);
+               outputStream.storeIntegerCompressed8(height);
+
+       }
+
+}
diff --git a/src/main/java/eu/svjatoslav/imagesqueeze/codec/OperatingContext.java b/src/main/java/eu/svjatoslav/imagesqueeze/codec/OperatingContext.java
new file mode 100755 (executable)
index 0000000..bac1630
--- /dev/null
@@ -0,0 +1,51 @@
+package eu.svjatoslav.imagesqueeze.codec;
+
+public class OperatingContext {
+
+       Image image;
+       byte[] yMap;
+       byte[] uMap;
+       byte[] vMap;
+       ColorStats colorStats = new ColorStats();
+       
+       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 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 getYRange(int index){
+               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 int getVRange(int index){
+               int color = ImageEncoder.byteToInt(vMap[index]);                                
+               return Math.abs(color - colorStats.getAverageV());              
+       }
+
+               
+}
diff --git a/src/main/java/eu/svjatoslav/imagesqueeze/codec/Table.java b/src/main/java/eu/svjatoslav/imagesqueeze/codec/Table.java
new file mode 100755 (executable)
index 0000000..4e714f4
--- /dev/null
@@ -0,0 +1,187 @@
+package eu.svjatoslav.imagesqueeze.codec;
+
+import java.io.IOException;
+
+/**
+ * Quick lookup table.
+ */
+
+public class Table implements Comparable<Table>{
+
+       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 usedEntries = 0;
+
+
+       public void computeLookupTables(){
+               int currentCheckPointer = 0;
+
+               for (int i=0; i<256; i++){
+
+                       if (range[currentCheckPointer] == i){
+                               currentCheckPointer++;
+                       }
+
+                       if (currentCheckPointer > 0){
+                               bitCountForRange[i] = bitcount[currentCheckPointer-1];                          
+                       } else {
+                               bitCountForRange[i] = 0;
+                       }
+
+               }
+
+               for (int i=0; i<256; i++){
+
+                       int seek;
+                       seekLoop:{
+                               for (seek = 0; seek < usedEntries; seek ++){
+
+                                       if (switchTreshold[seek] >= i) break seekLoop;
+
+                               }
+                       }
+
+                       proposedRangeForActualRange[i] = range[seek];           
+                       if (seek == 0){
+                               proposedRangeForActualRangeLow[i] = 0;          
+                       } else {
+                               proposedRangeForActualRangeLow[i] = switchTreshold[seek-1]+1;                                           
+                       }
+                       proposedRangeForActualRangeHigh[i] = switchTreshold[seek];                              
+               }
+
+
+               currentCheckPointer = usedEntries - 2;
+               for (int i=255; i >= 0; i--){           
+                       if (range[currentCheckPointer] == i) currentCheckPointer--;
+
+                       if (currentCheckPointer < 0){
+                               proposedDecreasedRange[i] = 0;
+                       } else {
+                               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){
+
+               if (inheritedRange > 255) inheritedRange = 255;
+               if (inheritedRange < 0) inheritedRange = 0;
+
+               if (proposedRangeForActualRangeLow[inheritedRange] <= actualRange){
+                       return inheritedRange;
+               }
+
+               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;
+               int proposal = bitCountForRange[range];
+               return proposal;
+       }
+
+       /**
+        * 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<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;
+               }
+
+               return 0;
+       }
+
+       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];   
+               switchTreshold = new int[100];
+               bitcount = new int[100];
+
+
+               bitCountForRange = new int[256];
+               proposedRangeForActualRange = new int[256];
+               proposedRangeForActualRangeLow = new int[256];
+               proposedRangeForActualRangeHigh = new int[256];
+               proposedDecreasedRange = new byte[256];
+
+               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));                    
+               }
+       }
+
+
+
+}
diff --git a/src/main/java/eu/svjatoslav/imagesqueeze/sampleApplication/ImageFrame.java b/src/main/java/eu/svjatoslav/imagesqueeze/sampleApplication/ImageFrame.java
new file mode 100755 (executable)
index 0000000..3da2f77
--- /dev/null
@@ -0,0 +1,49 @@
+package eu.svjatoslav.imagesqueeze.sampleApplication;
+import java.awt.BorderLayout;
+
+import javax.swing.WindowConstants;
+import javax.swing.SwingUtilities;
+
+
+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) {
+               super();
+               setTitle(title);
+               initGUI();
+       }
+       
+       private void initGUI() {
+               try {
+                       BorderLayout thisLayout = new BorderLayout();
+                       setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
+                       getContentPane().setLayout(thisLayout);
+                       {
+                               imagePanel1 = new ImagePanel();
+                               getContentPane().add(getImagePanel(), BorderLayout.CENTER);
+                       }
+                       pack();
+               } catch (Exception e) {
+                       e.printStackTrace();
+               }
+       }
+       
+       public ImagePanel getImagePanel() {
+               return imagePanel1;
+       }
+
+}
diff --git a/src/main/java/eu/svjatoslav/imagesqueeze/sampleApplication/ImagePanel.java b/src/main/java/eu/svjatoslav/imagesqueeze/sampleApplication/ImagePanel.java
new file mode 100755 (executable)
index 0000000..749d558
--- /dev/null
@@ -0,0 +1,126 @@
+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 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();
+       }
+
+       private void initGUI() {
+               try {
+                       BorderLayout thisLayout = new BorderLayout();
+                       this.setLayout(thisLayout);
+                       setPreferredSize(new Dimension(660, 500));
+                       {
+                               imageLabel = new JLabel();
+                               this.add(getImageLabel(), BorderLayout.CENTER);
+                       }
+               } catch (Exception e) {
+                       e.printStackTrace();
+               }
+       }
+
+       public JLabel getImageLabel() {
+               return imageLabel;
+       }
+
+       public void loadImage(File inputFile, boolean isImgSqz) throws IOException{
+               FileInputStream fileInputStream = new FileInputStream(inputFile);
+       
+               loadImage(fileInputStream, isImgSqz);           
+       }
+
+       public void loadImage(InputStream inputStream, boolean isImgSqz) throws IOException{
+               if (isImgSqz){
+                       // load ImageSqueeze file
+
+                       Image image = new Image();
+                       image.loadImage(inputStream);
+
+                       bufferedImage = image.bufferedImage;
+
+                       ImageIcon icon = new ImageIcon(bufferedImage);
+                       //ImageIcon icon = new ImageIcon("sample data/original.png");
+
+                       imageLabel.setIcon(icon);                       
+
+               } else {
+                       // load JPEG, PNG, GIF file
+                       ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+                       readLoop:{
+                               for (;;){                               
+                                       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);
+                       bufferedImage.getGraphics().drawImage(icon.getImage(), 0, 0, null);
+
+                       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);
+               try {
+                       image.saveImage(outputFile);
+               } catch (Exception e) {
+                       System.out.println("Error while saving image: " + e.toString());
+               }
+       }
+
+}
diff --git a/src/main/java/eu/svjatoslav/imagesqueeze/sampleApplication/Main.java b/src/main/java/eu/svjatoslav/imagesqueeze/sampleApplication/Main.java
new file mode 100755 (executable)
index 0000000..f67ff3a
--- /dev/null
@@ -0,0 +1,45 @@
+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) {
+
+               try {
+
+                       String image = "colorful photo";
+                       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);
+
+
+                       // encode image into file
+                       frame.getImagePanel().saveImage(new File(image + ".ImgSqz"));
+
+
+                       // create second frame for decoded image
+                       ImageFrame frame2 = new ImageFrame("Encoded -> Decoded");
+
+                       // decode image
+                       frame2.getImagePanel().loadImage(new File(image + ".ImgSqz"), true);
+                       frame2.setVisible(true);
+
+               } catch (IOException exception){
+                       System.out.println("Error while loading an image: " + exception);
+               }
+
+
+       }
+
+}
diff --git a/src/main/resources/eu/svjatoslav/imagesqueeze/sampleApplication/data/bw schematics.gif b/src/main/resources/eu/svjatoslav/imagesqueeze/sampleApplication/data/bw schematics.gif
new file mode 100755 (executable)
index 0000000..aab67ed
Binary files /dev/null and b/src/main/resources/eu/svjatoslav/imagesqueeze/sampleApplication/data/bw schematics.gif differ
diff --git a/src/main/resources/eu/svjatoslav/imagesqueeze/sampleApplication/data/clouds.png b/src/main/resources/eu/svjatoslav/imagesqueeze/sampleApplication/data/clouds.png
new file mode 100755 (executable)
index 0000000..42a0145
Binary files /dev/null and b/src/main/resources/eu/svjatoslav/imagesqueeze/sampleApplication/data/clouds.png differ
diff --git a/src/main/resources/eu/svjatoslav/imagesqueeze/sampleApplication/data/colorful photo.png b/src/main/resources/eu/svjatoslav/imagesqueeze/sampleApplication/data/colorful photo.png
new file mode 100755 (executable)
index 0000000..9a063d9
Binary files /dev/null and b/src/main/resources/eu/svjatoslav/imagesqueeze/sampleApplication/data/colorful photo.png differ
diff --git a/src/main/resources/eu/svjatoslav/imagesqueeze/sampleApplication/data/small logo.png b/src/main/resources/eu/svjatoslav/imagesqueeze/sampleApplication/data/small logo.png
new file mode 100755 (executable)
index 0000000..5914597
Binary files /dev/null and b/src/main/resources/eu/svjatoslav/imagesqueeze/sampleApplication/data/small logo.png differ