Advertisement
Guest User

Untitled

a guest
Feb 13th, 2016
64
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Java 5 54.50 KB | None | 0 0
  1. package CompressAnImageToA4kibPreview;
  2.  
  3. import java.awt.BasicStroke;
  4. import java.awt.Color;
  5. import java.awt.GradientPaint;
  6. import java.awt.Graphics2D;
  7. import java.awt.Image;
  8. import java.awt.RenderingHints;
  9. import java.awt.RenderingHints.Key;
  10. import java.awt.geom.Path2D;
  11. import java.awt.image.BufferedImage;
  12. import java.awt.image.BufferedImageOp;
  13. import java.awt.image.ConvolveOp;
  14. import java.awt.image.Kernel;
  15. import java.io.BufferedInputStream;
  16. import java.io.BufferedOutputStream;
  17. import java.io.ByteArrayOutputStream;
  18. import java.io.DataInputStream;
  19. import java.io.DataOutputStream;
  20. import java.io.EOFException;
  21. import java.io.File;
  22. import java.io.FileInputStream;
  23. import java.io.FileOutputStream;
  24. import java.io.IOException;
  25. import java.io.InputStream;
  26. import java.io.OutputStream;
  27. import java.util.ArrayList;
  28. import java.util.Collections;
  29. import java.util.HashMap;
  30. import java.util.List;
  31. import java.util.Map;
  32. import java.util.Random;
  33. import java.util.zip.Deflater;
  34. import java.util.zip.DeflaterOutputStream;
  35. import java.util.zip.InflaterInputStream;
  36.  
  37. import javax.imageio.ImageIO;
  38.  
  39. /**
  40.  * Licensed under Revised BSD License:
  41.  *
  42.  * Copyright (c) 2016, Nicholas Klaebe
  43.  * All rights reserved.
  44.  *
  45.  * Redistribution and use in source and binary forms, with or without
  46.  * modification, are permitted provided that the following conditions are met:
  47.  *     * Redistributions of source code must retain the above copyright
  48.  *       notice, this list of conditions and the following disclaimer.
  49.  *     * Redistributions in binary form must reproduce the above copyright
  50.  *       notice, this list of conditions and the following disclaimer in the
  51.  *       documentation and/or other materials provided with the distribution.
  52.  *     * Neither the name of the Nicholas Klaebe nor the
  53.  *       names of its contributors may be used to endorse or promote products
  54.  *       derived from this software without specific prior written permission.
  55.  *
  56.  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  57.  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  58.  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  59.  * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
  60.  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  61.  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  62.  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  63.  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  64.  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  65.  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  66.  *
  67.  *  @author Nicholas Klaebe
  68.  *
  69.  */
  70. public class CompressAnImageToA4kibPreview
  71. {
  72.     /**
  73.      * InputStream that allows 1-63 bitwise writes
  74.      * @author default
  75.      *
  76.      */
  77.     static class BitInputStream
  78.     {
  79.         InputStream fis;
  80.         int count;
  81.         long totalCount;
  82.         byte value;
  83.         byte[] buffer;
  84.         int buffCount;
  85.         int buffSize;
  86.         boolean returnVal;
  87.         private static final int EOF_INT = -1;
  88.  
  89.         /**
  90.          * Constructor
  91.          *
  92.          * @param fis1
  93.          *            The base inputStream
  94.          * @param size
  95.          *            size of the input buffer
  96.          */
  97.         public BitInputStream(InputStream fis1, int size)
  98.         {
  99.             count = 0;
  100.             buffer = new byte[size];
  101.             buffCount = 0;
  102.             totalCount = 0;
  103.             fis = fis1;
  104.         }
  105.  
  106.         /**
  107.          * read in a single bit
  108.          *
  109.          * @return bit read
  110.          * @throws IOException
  111.          */
  112.         public boolean read() throws IOException
  113.         {
  114.             if (count == 0)
  115.             {
  116.                 if (buffCount == 0)
  117.                 {
  118.                     buffSize = 0;
  119.                     while (buffSize == 0)
  120.                     {
  121.                         buffSize = fis.read(buffer, 0, buffer.length);
  122.                     }
  123.                     if (buffSize == EOF_INT)
  124.                     {
  125.                         throw new EOFException("END OF FILE");
  126.                     }
  127.  
  128.                 }
  129.                 value = buffer[buffCount];
  130.                 buffCount++;
  131.                 if (buffCount == buffSize)
  132.                     buffCount = 0;
  133.             }
  134.  
  135.             if ((value >> (7 - count) & 0x01) > 0)
  136.                 returnVal = true;
  137.             else
  138.                 returnVal = false;
  139.  
  140.             count++;
  141.             totalCount++;
  142.             if (count == 8)
  143.                 count = 0;
  144.  
  145.             return returnVal;
  146.         }
  147.  
  148.         /**
  149.          * reads in the next char, if not on a 8bit boundary then the stream is aligned to the next byte
  150.          *
  151.          * @return byte read
  152.          * @throws IOException
  153.          */
  154.         public char readChar() throws IOException
  155.         {
  156.             byte[] b = new byte[1];
  157.             alignToNextByte();
  158.             fis.read(b);
  159.             totalCount += 8;
  160.             return (char) b[0];
  161.         }
  162.  
  163.         /**
  164.          * aligns the bit stream to the next byte boundary
  165.          *
  166.          * @throws IOException
  167.          */
  168.         public void alignToNextByte() throws IOException
  169.         {
  170.             while (count != 0)
  171.             {
  172.                 read();
  173.             }
  174.         }
  175.  
  176.         /**
  177.          * read in a specified number of bits
  178.          *
  179.          * @param bits
  180.          *            the number of bits to be read (between 1 and 64)
  181.          * @return a long integer containg the bits read
  182.          * @throws IOException
  183.          */
  184.  
  185.         public long read(int bits) throws IOException
  186.         {
  187.             long val = 0;
  188.             int mask = 0x01;
  189.             boolean[] bitsRead = new boolean[bits];
  190.  
  191.             for (int i = 0; i < bits; i++)
  192.             {
  193.                 bitsRead[i] = read();
  194.             }
  195.             for (int j = bits - 1; j > -1; j--)
  196.             {
  197.  
  198.                 val = val << 1;
  199.                 if (bitsRead[j])
  200.                 {
  201.                     val = val | mask;
  202.                 }
  203.             }
  204.             return val;
  205.         }
  206.  
  207.         /**
  208.          * attempts to read bytes into the array given
  209.          *
  210.          * @param temp
  211.          *            the array being populated
  212.          * @param start
  213.          *            the starting index
  214.          * @param end
  215.          *            the ending index
  216.          * @return the bytes read in
  217.          * @throws IOException
  218.          */
  219.         public int read(byte[] temp, int start, int end) throws IOException
  220.         {
  221.             totalCount += (end - start) * 8;
  222.             return fis.read(temp, start, end);
  223.         }
  224.  
  225.         public int read(byte[] temp) throws IOException
  226.         {
  227.             totalCount += temp.length * 8;
  228.             return fis.read(temp);
  229.         }
  230.  
  231.         public int readByte() throws IOException
  232.         {
  233.             totalCount += 8;
  234.             return fis.read();
  235.         }
  236.  
  237.         public void close() throws IOException
  238.         {
  239.             fis.close();
  240.         }
  241.     }
  242.  
  243.     /**
  244.      * OutputStream that allows 1-63 bitwise reads
  245.      */
  246.     static public class BitOutputStream
  247.     {
  248.         int value; // The byte in which the encoded bits are firstly stored.
  249.         int count; // The number of bits written into value.
  250.         byte[] buffer; // A byte buffer which is filled with 'value' each time value is full. Used for wirting to file.
  251.         int buffCount; // The current number of 'values' written into the buffer.
  252.         long masterCount; // The overall count of bits that have been written
  253.         OutputStream fos;
  254.  
  255.         /**
  256.          * constructor
  257.          *
  258.          * @param fos1
  259.          *            The outputstream which this bit stream writes to
  260.          */
  261.         public BitOutputStream(OutputStream fos1)
  262.         {
  263.             fos = fos1;
  264.             value = 0;
  265.             count = 0;
  266.             buffer = new byte[4096];
  267.             buffCount = 0;
  268.             masterCount = 0;
  269.         }
  270.  
  271.         /**
  272.          * Writes the passed value (temp) to the file using the given number of bits
  273.          *
  274.          * @param temp
  275.          *            the value to be written
  276.          * @param bits
  277.          *            the number if bits to write
  278.          * @throws IOException
  279.          */
  280.         public void write(long temp, int bits) throws IOException
  281.         {
  282.             for (int j = 0, mask = 1; j < bits; j++, mask <<= 1)
  283.             {
  284.                 value = value << 1;
  285.                 count++;
  286.                 if ((temp & mask) > 0)
  287.                 {
  288.                     value = value | 0x01;
  289.                 }
  290.                 addToBuffer();
  291.             }
  292.         }
  293.  
  294.         /**
  295.          * write a single bit to the stream
  296.          *
  297.          * @param bit
  298.          *            The bit to write
  299.          * @throws IOException
  300.          */
  301.         public void write(boolean bit) throws IOException
  302.         {
  303.             value = value << 1;
  304.             count++;
  305.             if (bit)
  306.             {
  307.                 value = value | 0x01;
  308.             }
  309.             addToBuffer();
  310.  
  311.         }
  312.  
  313.         /**
  314.          * writes a single char (converted to a byte) to the output stream aligning with the next byte boundary
  315.          *
  316.          * @param c
  317.          *            the char to write
  318.          * @throws IOException
  319.          */
  320.  
  321.         public void write(char c) throws IOException
  322.         {
  323.             flush();
  324.             byte[] b = new byte[1];
  325.             b[0] = (byte) c;
  326.             fos.write(b);
  327.             masterCount += 8;
  328.  
  329.         }
  330.  
  331.         /**
  332.          * adds bits stored in 'value' to a buffer which will be saved to a file if the current bit count since last storing into the buffer is less than 8 then return without adding it to the buffer
  333.          *
  334.          * @throws IOException
  335.          */
  336.  
  337.         public void addToBuffer() throws IOException
  338.         {
  339.             masterCount++;
  340.  
  341.             if (count < 8)
  342.                 return;
  343.  
  344.             // byte temp=(byte) (value);
  345.             buffer[buffCount] = (byte) (value);
  346.             ;
  347.             buffCount++;
  348.  
  349.             if (buffCount == buffer.length)
  350.             {
  351.                 fos.write(buffer, 0, buffCount);
  352.                 buffCount = 0;
  353.             }
  354.             value = 0;
  355.             count = 0;
  356.         }
  357.  
  358.         /**
  359.          * writes a single byte to the output stream aligning with the next byte boundary
  360.          *
  361.          * @param b
  362.          *            the byte to write
  363.          * @throws IOException
  364.          */
  365.         public void write(byte[] b) throws IOException
  366.         {
  367.             flush();
  368.             fos.write(b);
  369.         }
  370.  
  371.         /**
  372.          * writes a single byte to the output stream aligning with the next byte boundary
  373.          *
  374.          * @param b
  375.          *            the byte to write
  376.          * @throws IOException
  377.          */
  378.         public void write(byte b) throws IOException
  379.         {
  380.             flush();
  381.             fos.write(b);
  382.         }
  383.  
  384.         public void write(byte[] b, int start, int count) throws IOException
  385.         {
  386.             flush();
  387.  
  388.             fos.write(b, start, count);
  389.         }
  390.  
  391.         /**
  392.          * align the output stream with the next byte boundary
  393.          *
  394.          * @throws IOException
  395.          */
  396.  
  397.         public void flush() throws IOException
  398.         {
  399.             // pad out the last byte if necessary
  400.  
  401.             if (count > 0)
  402.             {
  403.                 masterCount += (8 - count - 1);
  404.                 value = value << (8 - count);
  405.                 count = 8;
  406.                 addToBuffer();
  407.             }
  408.             if (buffCount > 0)
  409.             {
  410.  
  411.                 fos.write(buffer, 0, buffCount);
  412.                 buffCount = 0;
  413.             }
  414.  
  415.         }
  416.  
  417.         /**
  418.          * close the output stream
  419.          *
  420.          * @throws IOException
  421.          */
  422.  
  423.         public void close() throws IOException
  424.         {
  425.             flush();
  426.             fos.close();
  427.         }
  428.     }
  429.  
  430.     static private final Random rand = new Random(0);
  431.  
  432.     static private final int IMAGE_DIMENSION_BITS = 16;
  433.     static private final int LUMA_BLOCK_SIZE = 3; // 4 //4 //4 //4 //3
  434.     static private final int LUMA_SAMPLED_DIMENSION_BLOCKS = 32 * 2;// 18 //30 //16 //16 //8
  435.     static private final int LUMA_SAMPLED_DIMENSION = LUMA_BLOCK_SIZE * LUMA_SAMPLED_DIMENSION_BLOCKS;
  436.     static private final int MIN_SAMPLED_DIMENSION = LUMA_SAMPLED_DIMENSION_BLOCKS / 4;
  437.     static private final int MAX_SAMPLED_DIMENSION_SCALAR_BITS = 8;
  438.     static private final int MAX_SAMPLED_DIMENSION_SCALAR = (int) Math.pow(2, MAX_SAMPLED_DIMENSION_SCALAR_BITS);
  439.  
  440.     static private final int LUMA_FILTER_BIT_DEPTH = 10;// 10;//16 //16 //12 //12
  441.     static private final int LUMA_FILTER_COUNT = (int) Math.pow(2, LUMA_FILTER_BIT_DEPTH);
  442.     static private final int LUMA_BLOCK_SAMPLE_SIZE = LUMA_FILTER_COUNT * 10; // 10000
  443.     static private final int LUMA_FILTERS_IMAGE_DIMENSION = (int) (Math.ceil(Math.sqrt(LUMA_FILTER_COUNT)));
  444.  
  445.     static private final int CHROMA_BLOCK_SIZE = LUMA_BLOCK_SIZE * 2; // 8 //8 //8 //6
  446.     static private final int CHROMA_SAMPLED_DIMENSION_BLOCKS = LUMA_SAMPLED_DIMENSION_BLOCKS / 2; // 15 //8 //8 //4
  447.     static private final int CHROMA_SAMPLED_DIMENSION = CHROMA_BLOCK_SIZE * CHROMA_SAMPLED_DIMENSION_BLOCKS;
  448.     static private final int CHROMA_FILTER_BIT_DEPTH = 10;// 12;//10 //10 //7 //7
  449.     static private final int CHROMA_FILTER_COUNT = (int) Math.pow(2, CHROMA_FILTER_BIT_DEPTH);
  450.     static private final int CHROMA_BLOCK_SAMPLE_SIZE = CHROMA_FILTER_COUNT * 10; // 10000
  451.     static private final int CHROMA_FILTERS_IMAGE_DIMENSION = (int) (Math.ceil(Math.sqrt(CHROMA_FILTER_COUNT)));
  452.  
  453.     private static final int LUMA_SIMILARITY_BIT_DEPTH = LUMA_FILTER_BIT_DEPTH;// 0;//2;//5;
  454.     private static final int LUMA_SIMILARITY_COUNT = (int) Math.pow(2, LUMA_SIMILARITY_BIT_DEPTH);
  455.  
  456.     private static final int CHROMA_SIMILARITY_BIT_DEPTH = CHROMA_FILTER_BIT_DEPTH;// 0;//2;//5;
  457.     private static final int CHROMA_SIMILARITY_COUNT = (int) Math.pow(2, CHROMA_SIMILARITY_BIT_DEPTH);
  458.  
  459.     static private final int LUMA = 0;
  460.     static private final int CHROMA_U = 1;
  461.     static private final int CHROMA_V = 2;
  462.  
  463.     static private final float EIGHT_BIT_DIVISOR = 1.0F / 256;
  464.  
  465.     static private final int GENERATE_NONE_ENUM = 0;
  466.     static private final int GENERATE_ONLY_IF_NEEDED_ENUM = 1;
  467.     static private final int GENERATE_ALWAYS_ENUM = 2;
  468.     private static int GENERATE = GENERATE_ONLY_IF_NEEDED_ENUM;
  469.     private static final boolean CLUSTER_LUMA = true;
  470.     private static final boolean CLUSTER_CHROMA = true;
  471.  
  472.     private static final int DESIRED_COMPRESSED_SIZE_BYTES = 4096;
  473.  
  474.     private static final boolean OUTPUT_SAMPLE_IMAGES = false;
  475.  
  476.     /**
  477.      * FilterManager manages the lumenosity and chromenosity filters including creation, writing and reading.
  478.      * @author default
  479.      *
  480.      */
  481.     static final class FilterManager
  482.     {
  483.         private static final int MAX_SAMPLE_IMAGE_SHAPE_SELECTOR = 20;
  484.         private static final int SAMPLE_IMAGE_SUBSHAPE_STROKE_DIVISOR = 20;
  485.         private static final float MIN_SAMPLE_IMAGE_SUBSHAPE_STROKE = 0.5F;
  486.         private static final int MIN_SAMPLE_IMAGE_SUBSHAPE_SIZE = 10;
  487.         private static final int SAMPLE_IMAGE_COUNT = 25;
  488.         Block[] lumaFilters;
  489.         Block[] chromaFilters;
  490.         int[][] lumaFilterSimilarityOrdering;
  491.         int[][] chromaFilterSimilarityOrdering;
  492.  
  493.         private void loadFilters() throws IOException
  494.         {
  495.             lumaFilters = new Block[LUMA_FILTER_COUNT];
  496.             chromaFilters = new Block[CHROMA_FILTER_COUNT];
  497.             BufferedImage filtersImage = ImageIO.read(new File("data/images/working/lumaFilters_" + LUMA_BLOCK_SIZE + "_" + CHROMA_BLOCK_SIZE + "_" + LUMA_FILTER_BIT_DEPTH + "_" + CHROMA_FILTER_BIT_DEPTH + ".png"));
  498.             for (int i = 0; i < LUMA_FILTER_COUNT; i++)
  499.             {
  500.                 float[][][] pixels = convertToYUVImage(filtersImage.getSubimage((i % LUMA_FILTERS_IMAGE_DIMENSION) * (LUMA_BLOCK_SIZE + 2), (i / LUMA_FILTERS_IMAGE_DIMENSION) * (LUMA_BLOCK_SIZE + 2), LUMA_BLOCK_SIZE, LUMA_BLOCK_SIZE));
  501.                 lumaFilters[i] = new Block(pixels, 0, 0, LUMA_BLOCK_SIZE);
  502.                 lumaFilters[i].closestMatchingFilterId = i;
  503.             }
  504.  
  505.             filtersImage = ImageIO.read(new File("data/images/working/chromaFilters_" + LUMA_BLOCK_SIZE + "_" + CHROMA_BLOCK_SIZE + "_" + LUMA_FILTER_BIT_DEPTH + "_" + CHROMA_FILTER_BIT_DEPTH + ".png"));
  506.             for (int i = 0; i < CHROMA_FILTER_COUNT; i++)
  507.             {
  508.                 float[][][] pixels = convertToYUVImage(filtersImage.getSubimage((i % CHROMA_FILTERS_IMAGE_DIMENSION) * (CHROMA_BLOCK_SIZE + 2), (i / CHROMA_FILTERS_IMAGE_DIMENSION) * (CHROMA_BLOCK_SIZE + 2), CHROMA_BLOCK_SIZE, CHROMA_BLOCK_SIZE));
  509.                 chromaFilters[i] = new Block(pixels, 0, 0, CHROMA_BLOCK_SIZE);
  510.                 chromaFilters[i].closestMatchingFilterId = i;
  511.             }
  512.  
  513.         }
  514.  
  515.         private void loadSimilarityOrdering() throws IOException
  516.         {
  517.             DataInputStream dis = new DataInputStream(new BufferedInputStream(new FileInputStream(new File("data/images/working/ordering_" + LUMA_BLOCK_SIZE + "_" + CHROMA_BLOCK_SIZE + "_" + LUMA_FILTER_BIT_DEPTH + "_" + CHROMA_FILTER_BIT_DEPTH + ".dat"))));
  518.             if (LUMA_SIMILARITY_BIT_DEPTH > 0)
  519.             {
  520.                 lumaFilterSimilarityOrdering = new int[LUMA_FILTER_COUNT][];
  521.                 for (int i = 0; i < lumaFilterSimilarityOrdering.length; i++)
  522.                 {
  523.                     lumaFilterSimilarityOrdering[i] = new int[LUMA_SIMILARITY_COUNT];
  524.                     for (int j = 0; j < LUMA_SIMILARITY_COUNT; j++)
  525.                     {
  526.                         lumaFilterSimilarityOrdering[i][j] = dis.readInt();
  527.                     }
  528.                 }
  529.             }
  530.  
  531.             if (CHROMA_SIMILARITY_BIT_DEPTH > 0)
  532.             {
  533.                 chromaFilterSimilarityOrdering = new int[CHROMA_FILTER_COUNT][];
  534.                 for (int i = 0; i < chromaFilterSimilarityOrdering.length; i++)
  535.                 {
  536.                     chromaFilterSimilarityOrdering[i] = new int[CHROMA_SIMILARITY_COUNT];
  537.                     for (int j = 0; j < CHROMA_SIMILARITY_COUNT; j++)
  538.                     {
  539.                         chromaFilterSimilarityOrdering[i][j] = dis.readInt();
  540.                     }
  541.                 }
  542.             }
  543.             dis.close();
  544.  
  545.         }
  546.  
  547.         private void performSimilarityOrdering() throws IOException
  548.         {
  549.  
  550.             System.out.println("Creating similarity ordered Lists...");
  551.  
  552.             DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(new File("data/images/working/ordering_" + LUMA_BLOCK_SIZE + "_" + CHROMA_BLOCK_SIZE + "_" + LUMA_FILTER_BIT_DEPTH + "_" + CHROMA_FILTER_BIT_DEPTH + ".dat"))));
  553.             if (LUMA_SIMILARITY_BIT_DEPTH > 0)
  554.             {
  555.                 lumaFilterSimilarityOrdering = new int[LUMA_FILTER_COUNT][];
  556.  
  557.                 for (int i = 0; i < lumaFilterSimilarityOrdering.length; i++)
  558.                 {
  559.                     lumaFilterSimilarityOrdering[i] = order(lumaFilters, lumaFilters[i], LumaComparator.comparator, LUMA_SIMILARITY_COUNT);
  560.                 }
  561.  
  562.                 for (int i = 0; i < lumaFilterSimilarityOrdering.length; i++)
  563.                 {
  564.                     for (int j = 0; j < LUMA_SIMILARITY_COUNT; j++)
  565.                     {
  566.                         dos.writeInt(lumaFilterSimilarityOrdering[i][j]);
  567.                     }
  568.                 }
  569.             }
  570.  
  571.             if (CHROMA_SIMILARITY_BIT_DEPTH > 0)
  572.             {
  573.  
  574.                 chromaFilterSimilarityOrdering = new int[CHROMA_FILTER_COUNT][];
  575.  
  576.                 for (int i = 0; i < chromaFilterSimilarityOrdering.length; i++)
  577.                 {
  578.                     chromaFilterSimilarityOrdering[i] = order(chromaFilters, chromaFilters[i], ChromaComparator.comparator, CHROMA_SIMILARITY_COUNT);
  579.                 }
  580.  
  581.                 for (int i = 0; i < chromaFilterSimilarityOrdering.length; i++)
  582.                 {
  583.                     for (int j = 0; j < CHROMA_SIMILARITY_COUNT; j++)
  584.                     {
  585.                         dos.writeInt(chromaFilterSimilarityOrdering[i][j]);
  586.                     }
  587.                 }
  588.             }
  589.             dos.close();
  590.             System.out.println("Creating similarity ordered Lists - Done");
  591.  
  592.         }
  593.  
  594.         /**
  595.          * Performs similarity ordering of the given filter type as compared with the given filter.
  596.          *
  597.          * @return array of similarity ordered indices into the filter list.
  598.          */
  599.         static private int[] order(Block[] filters, final Block comparisionFilter, final BlockComparator comparator, final int similarityCount)
  600.         {
  601.             final class Tuple implements Comparable<Tuple>
  602.             {
  603.                 public Tuple(float difference, int id)
  604.                 {
  605.                     this.difference = difference;
  606.                     this.id = id;
  607.                 }
  608.  
  609.                 float difference;
  610.                 int id;
  611.  
  612.                 @Override
  613.                 public int compareTo(Tuple other)
  614.                 {
  615.                     float diff = difference - other.difference;
  616.                     return diff < 0 ? -1 : (diff > 0 ? 1 : 0);
  617.                 }
  618.  
  619.             }
  620.  
  621.             ArrayList<Tuple> tuples = new ArrayList<>();
  622.             for (int i = 0; i < filters.length; i++)
  623.             {
  624.                 tuples.add(new Tuple(comparator.calculateDifference(filters[i], comparisionFilter), i));
  625.             }
  626.             Collections.sort(tuples);
  627.  
  628.             int[] orderedIndecies = new int[similarityCount];
  629.  
  630.             for (int i = 0; i < similarityCount; i++)
  631.             {
  632.                 orderedIndecies[i] = tuples.get(i).id;
  633.             }
  634.  
  635.             return orderedIndecies;
  636.         }
  637.  
  638.         private void generateFilters() throws IOException
  639.         {
  640.             System.out.println("First time running (no filters found). Generating Sample Images...");
  641.  
  642.             List<float[][][]> sampleImages = new ArrayList<float[][][]>();
  643.             BufferedImage sampleImage = new BufferedImage(LUMA_SAMPLED_DIMENSION, LUMA_SAMPLED_DIMENSION, BufferedImage.TYPE_INT_RGB);
  644.             BufferedImage image1 = new BufferedImage(1024, 1024, BufferedImage.TYPE_INT_RGB);
  645.  
  646.             // generate sample images and convert and down sample them into YUV images
  647.             for (int j = 0; j < SAMPLE_IMAGE_COUNT; j++)
  648.             {
  649.                 final int MAX_SHAPE_SIZE = MIN_SAMPLE_IMAGE_SUBSHAPE_SIZE + image1.getWidth() / 5;
  650.                 Graphics2D g = (Graphics2D) image1.getGraphics();
  651.  
  652.                 Color colour1 = new Color(Color.HSBtoRGB(rand.nextFloat(), rand.nextFloat(), (float) Math.log(0.001 + rand.nextFloat() * 10)));
  653.                 Color colour2 = new Color(Color.HSBtoRGB(rand.nextFloat(), rand.nextFloat(), (float) Math.log(0.001 + rand.nextFloat() * 10)));
  654.  
  655.                 GradientPaint gradientPaint = new GradientPaint(0, 0, colour1, 1024, 1024, colour2);
  656.                 g.setPaint(gradientPaint);
  657.  
  658.                 g.fillRect(0, 0, image1.getWidth(), image1.getHeight());
  659.  
  660.                 for (int i = 0; i < 200; i++)
  661.                 {
  662.                     colour1 = new Color(Color.HSBtoRGB(rand.nextFloat(), rand.nextFloat(), (float) Math.log(0.001 + rand.nextFloat() * 10)));
  663.                     colour2 = new Color(Color.HSBtoRGB(rand.nextFloat(), rand.nextFloat(), (float) Math.log(0.001 + rand.nextFloat() * 10)));
  664.  
  665.                     g.setStroke(new BasicStroke(MIN_SAMPLE_IMAGE_SUBSHAPE_STROKE + rand.nextFloat() * image1.getWidth() / SAMPLE_IMAGE_SUBSHAPE_STROKE_DIVISOR));
  666.                     int x = rand.nextInt(image1.getWidth() + MAX_SHAPE_SIZE / 2) - MAX_SHAPE_SIZE / 2;
  667.                     int y = rand.nextInt(image1.getHeight() - MAX_SHAPE_SIZE) + MAX_SHAPE_SIZE / 2;
  668.                     int w = rand.nextInt(MAX_SHAPE_SIZE);
  669.                     int h = rand.nextInt(MAX_SHAPE_SIZE);
  670.  
  671.                     gradientPaint = new GradientPaint(x, y, colour1, x + w, y + h, colour2);
  672.                     g.setPaint(gradientPaint);
  673.  
  674.                     switch (rand.nextInt(MAX_SAMPLE_IMAGE_SHAPE_SELECTOR))
  675.                     {
  676.                     case 0:
  677.                         g.fillRect(x, y, w, h);
  678.                         break;
  679.                     case 1:
  680.                         g.drawRect(x, y, w, h);
  681.                         break;
  682.                     case 2:
  683.                         g.drawOval(x, y, w, h);
  684.                         break;
  685.                     case 3:
  686.                         g.fillOval(x, y, w, h);
  687.                         break;
  688.                     case 4:
  689.                         g.drawLine(x, y, x + w, y + h);
  690.                         break;
  691.                     default:
  692.                         Path2D prettyPoly = new Path2D.Double();
  693.                         boolean isFirst = true;
  694.                         for (int points = 0; points < 3 + rand.nextInt(MAX_SHAPE_SIZE); points++)
  695.                         {
  696.                             double xx = rand.nextDouble() * MAX_SHAPE_SIZE;
  697.                             double yy = rand.nextDouble() * MAX_SHAPE_SIZE;
  698.  
  699.                             if (isFirst)
  700.                             {
  701.                                 prettyPoly.moveTo(xx, yy);
  702.                                 isFirst = false;
  703.                             }
  704.                             else
  705.                             {
  706.                                 prettyPoly.lineTo(xx, yy);
  707.                             }
  708.                         }
  709.  
  710.                         prettyPoly.closePath();
  711.                         g.translate(x, y);
  712.                         gradientPaint = new GradientPaint(0, 0, colour1, MAX_SHAPE_SIZE, MAX_SHAPE_SIZE, colour2);
  713.                         g.setPaint(gradientPaint);
  714.                         g.fill(prettyPoly);
  715.                         g.translate(-x, -y);
  716.                         break;
  717.                     }
  718.                 }
  719.  
  720.                 if (OUTPUT_SAMPLE_IMAGES)
  721.                 {
  722.                     File sampleFile = new File("data/images/working/sample" + j + ".png");
  723.                     sampleFile.getParentFile().mkdirs();
  724.                     ImageIO.write(image1, "png", sampleFile);
  725.                 }
  726.  
  727.                 sampleImage.getGraphics().drawImage(image1.getScaledInstance(LUMA_SAMPLED_DIMENSION, LUMA_SAMPLED_DIMENSION, BufferedImage.SCALE_AREA_AVERAGING), 0, 0, null);
  728.                 float[][][] pixels = convertToYUVImage(sampleImage);
  729.                 sampleImages.add(pixels);
  730.             }
  731.  
  732.             System.out.println("Generating Sample Images Completed.");
  733.  
  734.             // perform k-clustering for lumenosity and 'chromanosity'
  735.             lumaFilters = clusterLuma(sampleImages);
  736.             chromaFilters = new Block[0];
  737.  
  738.             if (!(CHROMA_FILTER_COUNT == 0 || CHROMA_SAMPLED_DIMENSION == 0))
  739.             {
  740.                 chromaFilters = clusterChroma(sampleImages);
  741.             }
  742.  
  743.             // construct and save clustered lumenosity filters image
  744.             BufferedImage lumaFiltersImage = new BufferedImage(LUMA_FILTERS_IMAGE_DIMENSION * (LUMA_BLOCK_SIZE + 2), LUMA_FILTERS_IMAGE_DIMENSION * (LUMA_BLOCK_SIZE + 2), BufferedImage.TYPE_INT_RGB);
  745.             BufferedImage lumaFilterImage = new BufferedImage(LUMA_BLOCK_SIZE, LUMA_BLOCK_SIZE, BufferedImage.TYPE_INT_RGB);
  746.  
  747.             for (int i = 0; i < lumaFilters.length; i++)
  748.             {
  749.                 renderYUV(lumaFilterImage, lumaFilters[i].pixels);
  750.                 lumaFiltersImage.getGraphics().drawImage(lumaFilterImage, (i % LUMA_FILTERS_IMAGE_DIMENSION) * (LUMA_BLOCK_SIZE + 2), (i / LUMA_FILTERS_IMAGE_DIMENSION) * (LUMA_BLOCK_SIZE + 2), null);
  751.             }
  752.             new File("data/images/working").mkdirs();
  753.             ImageIO.write(lumaFiltersImage, "png", new File("data/images/working/lumaFilters_" + LUMA_BLOCK_SIZE + "_" + CHROMA_BLOCK_SIZE + "_" + LUMA_FILTER_BIT_DEPTH + "_" + CHROMA_FILTER_BIT_DEPTH + ".png"));
  754.  
  755.             // construct and save clustered 'chromanosity' filters image
  756.             BufferedImage chromafiltersImage = new BufferedImage(CHROMA_FILTERS_IMAGE_DIMENSION * (CHROMA_BLOCK_SIZE + 2), CHROMA_FILTERS_IMAGE_DIMENSION * (CHROMA_BLOCK_SIZE + 2), BufferedImage.TYPE_INT_RGB);
  757.             BufferedImage chromafilterImage = new BufferedImage(CHROMA_BLOCK_SIZE, CHROMA_BLOCK_SIZE, BufferedImage.TYPE_INT_RGB);
  758.  
  759.             for (int i = 0; i < chromaFilters.length; i++)
  760.             {
  761.                 renderYUV(chromafilterImage, chromaFilters[i].pixels);
  762.                 chromafiltersImage.getGraphics().drawImage(chromafilterImage, (i % CHROMA_FILTERS_IMAGE_DIMENSION) * (CHROMA_BLOCK_SIZE + 2), (i / CHROMA_FILTERS_IMAGE_DIMENSION) * (CHROMA_BLOCK_SIZE + 2), null);
  763.             }
  764.             ImageIO.write(chromafiltersImage, "png", new File("data/images/working/chromaFilters_" + LUMA_BLOCK_SIZE + "_" + CHROMA_BLOCK_SIZE + "_" + LUMA_FILTER_BIT_DEPTH + "_" + CHROMA_FILTER_BIT_DEPTH + ".png"));
  765.  
  766.         }
  767.  
  768.         /**
  769.          * Performs K-clustering of samples blocks to produce an array of filter blocks.
  770.          *
  771.          * @param sampleImages
  772.          *            the collection of YUV down sampled sample images to obtain random blocks from
  773.          * @param sampledDimension
  774.          *            the dimension of the down sampled image (width == height)
  775.          * @param blockSize
  776.          *            the dimension of the filter block (width == height)
  777.          * @param filterCount
  778.          *            the number of filters to cluster for
  779.          * @param blockSampleSize
  780.          *            the number of blocks to cluster
  781.          * @param comparator
  782.          *            the comparator to use when determining magnitude of difference between two blocks
  783.          * @return array of filters
  784.          */
  785.         static private Block[] cluster(List<float[][][]> sampleImages, final int sampledDimension, final int blockSize, final int filterCount, final int blockSampleSize, BlockComparator comparator, boolean performCluster)
  786.         {
  787.             Block[] filters = new Block[filterCount];
  788.  
  789.             Block[] samples = new Block[blockSampleSize];
  790.             for (int i = 0; i < blockSampleSize; i++)
  791.             {
  792.                 samples[i] = new Block(sampleImages.get(rand.nextInt(sampleImages.size())), rand.nextInt(sampledDimension - blockSize), rand.nextInt(sampledDimension - blockSize), blockSize);
  793.             }
  794.  
  795.             // select initial filters
  796.             for (int i = 0; i < filterCount; i++)
  797.             {
  798.                 filters[i] = new Block(samples[i]);
  799.                 filters[i].closestMatchingFilterId = i;
  800.             }
  801.  
  802.             if (performCluster)
  803.             {
  804.                 int iteration = 0;
  805.                 boolean converged = false;
  806.                 long stopTime = System.currentTimeMillis() + 1000 * 60 * 60;
  807.  
  808.                 // iterate until all blocks have converged into clusters or timeout occurs
  809.                 while (!converged && System.currentTimeMillis() < stopTime)
  810.                 {
  811.                     iteration++;
  812.                     System.out.print("iteration: " + iteration);
  813.  
  814.                     converged = true;
  815.                     int blockSwapClusterCount = 0;
  816.  
  817.                     // identify closest cluster mid point for each sample block
  818.                     for (int i = 0; i < blockSampleSize; i++)
  819.                     {
  820.                         Block block = samples[i];
  821.                         float bestDifference = Float.MAX_VALUE;
  822.                         int bestFilterIndex = -1;
  823.  
  824.                         for (int j = 0; j < filterCount; j++)
  825.                         {
  826.                             float difference = comparator.calculateDifference(block, filters[j]);
  827.  
  828.                             if (difference < bestDifference)
  829.                             {
  830.                                 bestDifference = difference;
  831.                                 bestFilterIndex = j;
  832.                             }
  833.                         }
  834.  
  835.                         // sample block has changed to a different cluster or the assigned cluster's collection has been updated
  836.                         if (bestDifference != block.closestDifference || bestFilterIndex != block.closestMatchingFilterId)
  837.                         {
  838.                             blockSwapClusterCount++;
  839.                             converged = false;
  840.                             block.closestMatchingFilterId = bestFilterIndex;
  841.                             block.closestDifference = bestDifference;
  842.                         }
  843.                     }
  844.  
  845.                     if (!converged)
  846.                     {
  847.                         int orphanedClusterCount = 0;
  848.                         // determine new cluster centre (i.e. filter)
  849.                         for (int j = 0; j < filterCount; j++)
  850.                         {
  851.                             int clusterSize = 0;
  852.                             Block filter = filters[j];
  853.                             filter.zeroise();
  854.                             for (int i = 0; i < blockSampleSize; i++)
  855.                             {
  856.                                 if (samples[i].closestMatchingFilterId == j)
  857.                                 {
  858.                                     clusterSize++;
  859.                                     filter.add(samples[i]);
  860.                                 }
  861.                             }
  862.  
  863.                             // the cluster no longer has any members... lets choose another random sample to be our this cluster centre
  864.                             if (clusterSize == 0)
  865.                             {
  866.                                 orphanedClusterCount++;
  867.                                 filter = new Block(samples[rand.nextInt(blockSampleSize)]);
  868.                                 filter.closestMatchingFilterId = j;
  869.                                 filters[j] = filter;
  870.                             }
  871.                             else
  872.                             {
  873.                                 filter.divide(clusterSize);
  874.                             }
  875.  
  876.                         }
  877.  
  878.                         if (orphanedClusterCount > 0)
  879.                         {
  880.                             System.out.print(" orphanedClusterCount: " + orphanedClusterCount);
  881.                         }
  882.                     }
  883.  
  884.                     if (blockSwapClusterCount > 0)
  885.                     {
  886.                         System.out.print(" blockSwapClusterCount: " + blockSwapClusterCount);
  887.                     }
  888.                     System.out.println();
  889.                 }
  890.                 System.out.println("Clustering complete");
  891.             }
  892.             return filters;
  893.         }
  894.  
  895.         /**
  896.          * Perform k-clustering based on pixel lumenosity to derive lumenosity filters
  897.          *
  898.          * @param sampleImages
  899.          *            the collection of YUV down sampled sample images to obtain random blocks from
  900.          * @return array of filters
  901.          */
  902.         static private Block[] clusterLuma(List<float[][][]> sampleImages)
  903.         {
  904.             if (CLUSTER_LUMA)
  905.             {
  906.                 System.out.println("K-Cluster Luma filters...");
  907.             }
  908.             return cluster(sampleImages, LUMA_SAMPLED_DIMENSION, LUMA_BLOCK_SIZE, LUMA_FILTER_COUNT, LUMA_BLOCK_SAMPLE_SIZE, LumaComparator.comparator, CLUSTER_LUMA);
  909.         }
  910.  
  911.         /**
  912.          * Perform k-clustering based on pixel 'chromanosity' to derive 'chromanosity' filters
  913.          *
  914.          * @param sampleImages
  915.          *            the collection of YUV down sampled sample images to obtain random blocks from
  916.          * @return array of filters
  917.          */
  918.         static private Block[] clusterChroma(List<float[][][]> sampleImages)
  919.         {
  920.             if (CLUSTER_CHROMA)
  921.             {
  922.                 System.out.println("K-Cluster Chroma filters...");
  923.             }
  924.  
  925.             return cluster(sampleImages, CHROMA_SAMPLED_DIMENSION, CHROMA_BLOCK_SIZE, CHROMA_FILTER_COUNT, CHROMA_BLOCK_SAMPLE_SIZE, ChromaComparator.comparator, CLUSTER_CHROMA);
  926.         }
  927.  
  928.         public static FilterManager create() throws IOException
  929.         {
  930.             FilterManager filterManager = new FilterManager();
  931.             filterManager.generateFilters();
  932.             filterManager.performSimilarityOrdering();
  933.             return filterManager;
  934.         }
  935.  
  936.         public static FilterManager load() throws IOException
  937.         {
  938.             FilterManager filterManager = new FilterManager();
  939.             filterManager.loadFilters();
  940.             filterManager.loadSimilarityOrdering();
  941.             return filterManager;
  942.         }
  943.  
  944.     }
  945.  
  946.     /**
  947.      * A square block holding YUV pixels
  948.      */
  949.     public static final class Block
  950.     {
  951.         float[][][] pixels;
  952.  
  953.         int closestMatchingFilterId = -1;
  954.         float closestDifference = -1;
  955.         final int blockSize;
  956.  
  957.         public Block(float[][][] sourceImage, int x, int y, final int blockSize)
  958.         {
  959.             this.blockSize = blockSize;
  960.             pixels = new float[blockSize][blockSize][3];
  961.             for (int i = 0; i < blockSize; i++)
  962.             {
  963.                 for (int j = 0; j < blockSize; j++)
  964.                 {
  965.                     float[] pixel = sourceImage[x + j][y + i];
  966.                     pixels[j][i][LUMA] = pixel[LUMA];
  967.                     pixels[j][i][CHROMA_U] = pixel[CHROMA_U];
  968.                     pixels[j][i][CHROMA_V] = pixel[CHROMA_V];
  969.                 }
  970.             }
  971.         }
  972.  
  973.         /**
  974.          * Copy Constructor
  975.          *
  976.          * @param other
  977.          */
  978.         public Block(Block other)
  979.         {
  980.             this.blockSize = other.blockSize;
  981.             pixels = new float[blockSize][blockSize][3];
  982.             for (int i = 0; i < blockSize; i++)
  983.             {
  984.                 for (int j = 0; j < blockSize; j++)
  985.                 {
  986.                     pixels[j][i][LUMA] = other.pixels[j][i][LUMA];
  987.                     pixels[j][i][CHROMA_U] = other.pixels[j][i][CHROMA_U];
  988.                     pixels[j][i][CHROMA_V] = other.pixels[j][i][CHROMA_V];
  989.                 }
  990.             }
  991.         }
  992.  
  993.         /**
  994.          * sets all pixel colour components to 0
  995.          */
  996.         public void zeroise()
  997.         {
  998.             pixels = new float[blockSize][blockSize][3];
  999.         }
  1000.  
  1001.         /**
  1002.          * adds each pixel's colour components to this block's corresponding pixel colour components
  1003.          *
  1004.          * @param other
  1005.          *            the other block to add
  1006.          */
  1007.         public void add(Block other)
  1008.         {
  1009.             for (int i = 0; i < blockSize; i++)
  1010.             {
  1011.                 for (int j = 0; j < blockSize; j++)
  1012.                 {
  1013.                     pixels[j][i][LUMA] += other.pixels[j][i][LUMA];
  1014.                     pixels[j][i][CHROMA_U] += other.pixels[j][i][CHROMA_U];
  1015.                     pixels[j][i][CHROMA_V] += other.pixels[j][i][CHROMA_V];
  1016.                 }
  1017.             }
  1018.         }
  1019.  
  1020.         /**
  1021.          * divides all pixels colour components by the provided divisor
  1022.          *
  1023.          * @param divisor
  1024.          */
  1025.         public void divide(float divisor)
  1026.         {
  1027.             for (int i = 0; i < blockSize; i++)
  1028.             {
  1029.                 for (int j = 0; j < blockSize; j++)
  1030.                 {
  1031.                     pixels[j][i][LUMA] /= divisor;
  1032.                     pixels[j][i][CHROMA_U] /= divisor;
  1033.                     pixels[j][i][CHROMA_V] /= divisor;
  1034.                 }
  1035.             }
  1036.         }
  1037.  
  1038.     }
  1039.  
  1040.     /**
  1041.      * Abstract Class providing the method signature to calculate the difference between blocks
  1042.      */
  1043.     public static abstract class BlockComparator
  1044.     {
  1045.         /**
  1046.          * Calculates and returns the magnitude of difference between the two provided blocks. The result has only has meaning when comparing against other results obtained by calling this method.
  1047.          *
  1048.          * @param block1
  1049.          * @param block2
  1050.          * @return the magnitude of difference between the two provided blocks
  1051.          */
  1052.         abstract public float calculateDifference(Block block1, Block block2);
  1053.     }
  1054.  
  1055.     /**
  1056.      * Concrete implementation of BlockComparator with the difference calculated based on lumenosity
  1057.      */
  1058.     public static abstract class LumaComparator extends BlockComparator
  1059.     {
  1060.         public static final LumaComparator comparator = new LumaComparator()
  1061.         {
  1062.             public float calculateDifference(Block block1, Block block2)
  1063.             {
  1064.                 float diffMetric = 0;
  1065.                 for (int i = 0; i < LUMA_BLOCK_SIZE; i++)
  1066.                 {
  1067.                     for (int j = 0; j < LUMA_BLOCK_SIZE; j++)
  1068.                     {
  1069.                         diffMetric += (block1.pixels[j][i][LUMA] - block2.pixels[j][i][LUMA]) * (block1.pixels[j][i][LUMA] - block2.pixels[j][i][LUMA]);
  1070.                     }
  1071.                 }
  1072.                 return diffMetric;
  1073.             }
  1074.         };
  1075.     }
  1076.  
  1077.     /**
  1078.      * Concrete implementation of BlockComparator with the difference calculated based on 'chromanosity'
  1079.      */
  1080.     public static abstract class ChromaComparator extends BlockComparator
  1081.     {
  1082.         public static final ChromaComparator comparator = new ChromaComparator()
  1083.         {
  1084.             public float calculateDifference(Block block1, Block block2)
  1085.             {
  1086.                 float diff;
  1087.                 float diffMetric = 0;
  1088.                 for (int i = 0; i < CHROMA_BLOCK_SIZE; i++)
  1089.                 {
  1090.                     for (int j = 0; j < CHROMA_BLOCK_SIZE; j++)
  1091.                     {
  1092.                         diff = (block1.pixels[j][i][CHROMA_U] - block2.pixels[j][i][CHROMA_U]);
  1093.                         diffMetric += diff * diff * diff * diff;
  1094.                         diff = (block1.pixels[j][i][CHROMA_V] - block2.pixels[j][i][CHROMA_V]);
  1095.                         diffMetric += diff * diff * diff * diff;
  1096.                     }
  1097.                 }
  1098.                 return diffMetric;
  1099.             }
  1100.         };
  1101.     }
  1102.  
  1103.     public static void printCommandLineOptions()
  1104.     {
  1105.         System.out.println("Usage: \n" +
  1106.                 "       For single image compression: java CompressAnImageToA4kibPreview -c <INPUT IMAGE> [<COMPRESSED IMAGE>]\n" +
  1107.                 "       For multiple image compression: java CompressAnImageToA4kibPreview -c <INPUT IMAGES DIR> [<COMPRESSED IMAGE DIR>]\n" +
  1108.                 "       For single image decompression: java CompressAnImageToA4kibPreview -d <COMPRESSED IMAGE> [<DECOMPRESSED IMAGE>\n" +
  1109.                 "       For multiple image decompression: java CompressAnImageToA4kibPreview -d <COMPRESSED IMAGE DIR> [<DECOMPRESSED IMAGES DIR>]\n" +
  1110.                 "\n" +
  1111.                 "If optional parameters are not set then defaults will be used:\n" +
  1112.                 "       For single image compression, compressed image will be created in same directory are input image and have '.compressed' file extension.\n"  +
  1113.                 "       For multiple image compression, compressed images will be created in a new 'out' sub directory of <INPUT IMAGES DIR> and have '.compressed' file extensions.\n" +
  1114.                 "       For single image decompression, decompressed image will be created in same directory are input image and have '.out.png' file extension.\n" +
  1115.                 "       For multiple image decompression, decompressed images will be created a new 'out' sub directory of <COMPRESSED IMAGE DIR> and have '.png' file extensions.\n" +
  1116.                 "\n" +
  1117.                 "The first time this application is run, required files will be generated and saved in a directory relative to execution working dir. This may take a few minutes.");
  1118.     }
  1119.  
  1120.     public static void main(String[] args) throws Exception
  1121.     {
  1122.         if (args.length < 2 || args.length > 3)
  1123.         {
  1124.             printCommandLineOptions();
  1125.             return;
  1126.         }
  1127.  
  1128.         File filterFile = new File("data/images/working/lumaFilters_" + LUMA_BLOCK_SIZE + "_" + CHROMA_BLOCK_SIZE + "_" + LUMA_FILTER_BIT_DEPTH + "_" + CHROMA_FILTER_BIT_DEPTH + ".png");
  1129.         FilterManager filters = (GENERATE == GENERATE_ALWAYS_ENUM || (GENERATE == GENERATE_ONLY_IF_NEEDED_ENUM && !filterFile.exists())) ? FilterManager.create() : FilterManager.load();
  1130.  
  1131.         File outputFile;
  1132.         File inputFile = new File(args[1]);
  1133.  
  1134.         boolean compress = args[0].equals("-c");
  1135.  
  1136.         if (compress)
  1137.         {
  1138.             if (inputFile.exists())
  1139.             {
  1140.                 if (inputFile.isDirectory())
  1141.                 {
  1142.                     if (args.length == 3)
  1143.                     {
  1144.                         outputFile = new File(args[2]);
  1145.                         if (outputFile.exists() && outputFile.isFile())
  1146.                         {
  1147.                             System.out.println("ERROR: " + outputFile + " is not a directory.\n\n");
  1148.                             printCommandLineOptions();
  1149.                             return;
  1150.                         }
  1151.                     }
  1152.                     else
  1153.                     {
  1154.                         outputFile = new File(args[1] + "/out");
  1155.                         if (outputFile.exists() && outputFile.isFile())
  1156.                         {
  1157.                             System.out.println("ERROR: " + outputFile + " is not a directory.\n\n");
  1158.                             printCommandLineOptions();
  1159.                             return;
  1160.                         }
  1161.                     }
  1162.  
  1163.                     outputFile.mkdirs();
  1164.                 }
  1165.                 else
  1166.                 {
  1167.                     if (args.length == 3)
  1168.                     {
  1169.                         outputFile = new File(args[2]);
  1170.                         if (outputFile.exists() && outputFile.isDirectory())
  1171.                         {
  1172.                             System.out.println("ERROR: " + outputFile + " is a directory.\n\n");
  1173.                             printCommandLineOptions();
  1174.                             return;
  1175.                         }
  1176.                     }
  1177.                     else
  1178.                     {
  1179.                         outputFile = new File(args[1] + ".out.compressed");
  1180.                         if (outputFile.exists() && outputFile.isDirectory())
  1181.                         {
  1182.                             System.out.println("ERROR: " + outputFile + " is a directory.\n\n");
  1183.                             printCommandLineOptions();
  1184.                             return;
  1185.                         }
  1186.                     }
  1187.                 }
  1188.             }
  1189.             else
  1190.             {
  1191.                 System.out.println("ERROR: " + inputFile + " does not exist.\n\n");
  1192.                 printCommandLineOptions();
  1193.                 return;
  1194.             }
  1195.  
  1196.             compress(inputFile, outputFile, filters);
  1197.         }
  1198.         else
  1199.         {
  1200.             if (inputFile.exists())
  1201.             {
  1202.                 if (inputFile.isDirectory())
  1203.                 {
  1204.                     if (args.length == 3)
  1205.                     {
  1206.                         outputFile = new File(args[2]);
  1207.                         if (outputFile.exists() && outputFile.isFile())
  1208.                         {
  1209.                             System.out.println("ERROR: " + outputFile + " is not a directory.\n\n");
  1210.                             printCommandLineOptions();
  1211.                             return;
  1212.                         }
  1213.                     }
  1214.                     else
  1215.                     {
  1216.                         outputFile = new File(args[1] + "/out");
  1217.                         if (outputFile.exists() && outputFile.isFile())
  1218.                         {
  1219.                             System.out.println("ERROR: " + outputFile + " is not a directory.\n\n");
  1220.                             printCommandLineOptions();
  1221.                             return;
  1222.                         }
  1223.                     }
  1224.                     outputFile.mkdirs();
  1225.                 }
  1226.                 else
  1227.                 {
  1228.                     if (args.length == 3)
  1229.                     {
  1230.                         outputFile = new File(args[2]);
  1231.                         if (outputFile.exists() && outputFile.isDirectory())
  1232.                         {
  1233.                             System.out.println("ERROR: " + outputFile + " is a directory.\n\n");
  1234.                             printCommandLineOptions();
  1235.                             return;
  1236.                         }
  1237.                     }
  1238.                     else
  1239.                     {
  1240.                         outputFile = new File(args[1] + ".out.png");
  1241.                         if (outputFile.exists() && outputFile.isDirectory())
  1242.                         {
  1243.                             System.out.println("ERROR: " + outputFile + " is a directory.\n\n");
  1244.                             printCommandLineOptions();
  1245.                             return;
  1246.                         }
  1247.                     }
  1248.                 }
  1249.             }
  1250.             else
  1251.             {
  1252.                 System.out.println("ERROR: " + inputFile + " does not exist.\n\n");
  1253.                 printCommandLineOptions();
  1254.                 return;
  1255.             }
  1256.  
  1257.             decompress(inputFile, outputFile, filters);
  1258.         }
  1259.     }
  1260.  
  1261.     /**
  1262.      * Compresses the input image(s) and writes to origOutputFile
  1263.      * @param origInputFile
  1264.      * @param origOutputFile
  1265.      * @param filters
  1266.      * @throws Exception
  1267.      */
  1268.     private static void compress(File origInputFile, File origOutputFile, FilterManager filters) throws Exception
  1269.     {
  1270.         String[] files = new String[] { origInputFile.getAbsolutePath() };
  1271.         if (origInputFile.isDirectory())
  1272.         {
  1273.             List<String> list = new ArrayList<String>();
  1274.             for (File file : origInputFile.listFiles())
  1275.             {
  1276.                 list.add(file.getAbsolutePath());
  1277.             }
  1278.             files = list.toArray(new String[0]);
  1279.         }
  1280.  
  1281.         String outputDir = origOutputFile.getAbsolutePath();
  1282.  
  1283.         for (String inputFileString : files)
  1284.         {
  1285.             ByteArrayOutputStream defBaos = new ByteArrayOutputStream();
  1286.             int compressedSize = 0;
  1287.             int sampledDimensionScalar = MIN_SAMPLED_DIMENSION;
  1288.  
  1289.             File inputFile = new File(inputFileString);
  1290.            
  1291.             String outputFileString = (origInputFile.isFile() ? origOutputFile : new File(outputDir + File.separator + inputFile.getName())).getAbsolutePath() + ".compressed";
  1292.             File outputFile = new File(outputFileString);
  1293.             if (inputFile.isFile())
  1294.             {
  1295.                 System.out.println("Compressing "+inputFile.getAbsolutePath()+"...");
  1296.                 BufferedImage inputImage = ImageIO.read(inputFile);
  1297.  
  1298.                 outputFile.getParentFile().mkdirs();
  1299.  
  1300.                 while (compressedSize < DESIRED_COMPRESSED_SIZE_BYTES && sampledDimensionScalar < MAX_SAMPLED_DIMENSION_SCALAR)
  1301.                 {
  1302.                     sampledDimensionScalar += 1;
  1303.                     if (compressedSize > 0)
  1304.                     {
  1305.  
  1306.                         System.out.println("Compressed Size: " + compressedSize + " Scalar: " + (sampledDimensionScalar - 1));
  1307.  
  1308.                         byte[] data = defBaos.toByteArray();
  1309.                         defBaos.reset();
  1310.                         FileOutputStream fos = new FileOutputStream(outputFile);
  1311.                         fos.write(data);
  1312.                         fos.close();
  1313.                     }
  1314.  
  1315.                     int previousFrameChromaFilter = 0;
  1316.                     int previousFrameLumaFilter = 0;
  1317.  
  1318.                     ByteArrayOutputStream baos = new ByteArrayOutputStream();
  1319.                     BitOutputStream bos = new BitOutputStream(baos);
  1320.  
  1321.                     // Down sample input image
  1322.  
  1323.                     int sampledDimension = CHROMA_BLOCK_SIZE * sampledDimensionScalar;
  1324.                     BufferedImage scaledImage = new BufferedImage(sampledDimension, sampledDimension, BufferedImage.TYPE_INT_RGB);
  1325.                     scaledImage.getGraphics().drawImage(inputImage.getScaledInstance(sampledDimension, sampledDimension, BufferedImage.SCALE_AREA_AVERAGING), 0, 0, null);
  1326.                     float[][][] pixels = convertToYUVImage(scaledImage);
  1327.  
  1328.                     // save image dimensions
  1329.                     bos.write(inputImage.getWidth(), IMAGE_DIMENSION_BITS);
  1330.                     bos.write(inputImage.getHeight(), IMAGE_DIMENSION_BITS);
  1331.                     bos.write(sampledDimensionScalar, MAX_SAMPLED_DIMENSION_SCALAR_BITS);
  1332.  
  1333.                     // match sampled image blocks' lumenosity to filters...
  1334.                     float[][][] categorisedPixels = new float[sampledDimension][sampledDimension][3];
  1335.  
  1336.                     for (int i = 0; i < sampledDimension; i += LUMA_BLOCK_SIZE)
  1337.                     {
  1338.                         for (int j = 0; j < sampledDimension; j += LUMA_BLOCK_SIZE)
  1339.                         {
  1340.                             Block block = new Block(pixels, j, i, LUMA_BLOCK_SIZE);
  1341.                             Block leastDifferenceLumaFilter = null;
  1342.                             float leastDifference = Float.MAX_VALUE;
  1343.                             float difference;
  1344.                             for (Block filter : filters.lumaFilters)
  1345.                             {
  1346.                                 difference = LumaComparator.comparator.calculateDifference(filter, block);
  1347.  
  1348.                                 if (difference < leastDifference)
  1349.                                 {
  1350.                                     leastDifference = difference;
  1351.                                     leastDifferenceLumaFilter = filter;
  1352.                                 }
  1353.                             }
  1354.  
  1355.                             // save luma block
  1356.                             if (LUMA_SIMILARITY_BIT_DEPTH == 0)
  1357.                             {
  1358.                                 bos.write(leastDifferenceLumaFilter.closestMatchingFilterId, LUMA_FILTER_BIT_DEPTH);
  1359.                             }
  1360.                             else
  1361.                                 out:
  1362.                                 {
  1363.                                     int[] lumaFilterSimilarityOrdering = filters.lumaFilterSimilarityOrdering[previousFrameLumaFilter];
  1364.                                     for (int k = 0; k < LUMA_SIMILARITY_COUNT; k++)
  1365.                                         if (lumaFilterSimilarityOrdering[k] == leastDifferenceLumaFilter.closestMatchingFilterId)
  1366.                                         {
  1367.                                             if (LUMA_FILTER_COUNT > LUMA_SIMILARITY_COUNT)
  1368.                                                 bos.write(true);
  1369.                                             bos.write(k, LUMA_SIMILARITY_BIT_DEPTH);
  1370.                                             break out;
  1371.                                         }
  1372.                                     if (LUMA_FILTER_COUNT > LUMA_SIMILARITY_COUNT)
  1373.                                         bos.write(false);
  1374.                                     bos.write(leastDifferenceLumaFilter.closestMatchingFilterId, LUMA_FILTER_BIT_DEPTH);
  1375.                                 }
  1376.                             previousFrameLumaFilter = leastDifferenceLumaFilter.closestMatchingFilterId;
  1377.  
  1378.                             // copy matched filter's lumenosity into corresponding output image's block
  1379.                             for (int y = 0; y < LUMA_BLOCK_SIZE; y++)
  1380.                             {
  1381.                                 for (int x = 0; x < LUMA_BLOCK_SIZE; x++)
  1382.                                 {
  1383.                                     categorisedPixels[j + x][i + y][LUMA] = leastDifferenceLumaFilter.pixels[x][y][LUMA];
  1384.  
  1385.                                 }
  1386.                             }
  1387.                         }
  1388.                     }
  1389.  
  1390.                     // match sampled image blocks' 'chromanosity' to filters...
  1391.                     for (int i = 0; i < sampledDimension; i += CHROMA_BLOCK_SIZE)
  1392.                     {
  1393.                         for (int j = 0; j < sampledDimension; j += CHROMA_BLOCK_SIZE)
  1394.                         {
  1395.                             Block block = new Block(pixels, j, i, CHROMA_BLOCK_SIZE);
  1396.  
  1397.                             Block leastDifferenceChromaFilter = null;
  1398.                             float leastDifference = Float.MAX_VALUE;
  1399.                             float difference;
  1400.                             for (Block filter : filters.chromaFilters)
  1401.                             {
  1402.                                 difference = ChromaComparator.comparator.calculateDifference(filter, block);
  1403.  
  1404.                                 if (difference < leastDifference)
  1405.                                 {
  1406.                                     leastDifference = difference;
  1407.                                     leastDifferenceChromaFilter = filter;
  1408.                                 }
  1409.                             }
  1410.  
  1411.                             // save chroma block
  1412.                             if (CHROMA_SIMILARITY_BIT_DEPTH == 0)
  1413.                             {
  1414.                                 bos.write(leastDifferenceChromaFilter.closestMatchingFilterId, CHROMA_FILTER_BIT_DEPTH);
  1415.                             }
  1416.                             else
  1417.                                 out:
  1418.                                 {
  1419.                                     int[] chromaFilterSimilarityOrdering = filters.chromaFilterSimilarityOrdering[previousFrameChromaFilter];
  1420.                                     for (int k = 0; k < CHROMA_SIMILARITY_COUNT; k++)
  1421.                                         if (chromaFilterSimilarityOrdering[k] == leastDifferenceChromaFilter.closestMatchingFilterId)
  1422.                                         {
  1423.                                             if (CHROMA_FILTER_COUNT > CHROMA_SIMILARITY_COUNT)
  1424.                                                 bos.write(true);
  1425.                                             bos.write(k, CHROMA_SIMILARITY_BIT_DEPTH);
  1426.                                             break out;
  1427.                                         }
  1428.                                     if (CHROMA_FILTER_COUNT > CHROMA_SIMILARITY_COUNT)
  1429.                                         bos.write(false);
  1430.                                     bos.write(leastDifferenceChromaFilter.closestMatchingFilterId, CHROMA_FILTER_BIT_DEPTH);
  1431.                                 }
  1432.                             previousFrameChromaFilter = leastDifferenceChromaFilter.closestMatchingFilterId;
  1433.  
  1434.                             // copy matched filter's 'chromanosity' into corresponding output image's block
  1435.                             for (int y = 0; y < CHROMA_BLOCK_SIZE; y++)
  1436.                             {
  1437.                                 for (int x = 0; x < CHROMA_BLOCK_SIZE; x++)
  1438.                                 {
  1439.                                     categorisedPixels[j + x][i + y][CHROMA_U] = leastDifferenceChromaFilter.pixels[x][y][CHROMA_U];
  1440.                                     categorisedPixels[j + x][i + y][CHROMA_V] = leastDifferenceChromaFilter.pixels[x][y][CHROMA_V];
  1441.                                 }
  1442.                             }
  1443.                         }
  1444.                     }
  1445.  
  1446.                     bos.close();
  1447.  
  1448.                     DeflaterOutputStream deflater = new DeflaterOutputStream(defBaos, new Deflater(Deflater.BEST_COMPRESSION));
  1449.                     deflater.write(baos.toByteArray());
  1450.                     deflater.close();
  1451.                     compressedSize = defBaos.size();
  1452.  
  1453.                 }
  1454.                 System.out.println("Compressed "+inputFile.getAbsolutePath()+".");
  1455.             }
  1456.         }
  1457.     }
  1458.  
  1459.     /**
  1460.      * Decompresses the input image(s) and writes to origOutputFile
  1461.      */
  1462.     private static void decompress(File origInputFile, File origOutputFile, FilterManager filters) throws Exception
  1463.     {
  1464.         String[] files = new String[] { origInputFile.getAbsolutePath() };
  1465.         if (origInputFile.isDirectory())
  1466.         {
  1467.             List<String> list = new ArrayList<String>();
  1468.             for (File file : origInputFile.listFiles())
  1469.             {
  1470.                 list.add(file.getAbsolutePath());
  1471.             }
  1472.             files = list.toArray(new String[0]);
  1473.         }
  1474.  
  1475.         String outputDir = origOutputFile.getAbsolutePath();
  1476.  
  1477.         for (String inputFileString : files)
  1478.         {
  1479.  
  1480.             File inputFile = new File(inputFileString);
  1481.             if (inputFile.isFile())
  1482.             {
  1483.                 File decompressedFile = origInputFile.isFile() ? origOutputFile : new File(outputDir + File.separator + inputFile.getName()+".png");
  1484.                 filters = FilterManager.load();
  1485.                 decodeFrame(decompressedFile, filters, new FileInputStream(inputFile));
  1486.             }
  1487.         }
  1488.     }
  1489.  
  1490.     static void decodeFrame(File outputFile, FilterManager filters, InputStream is) throws IOException
  1491.     {
  1492.  
  1493.         InflaterInputStream inflater = new InflaterInputStream(is);
  1494.         BitInputStream bis = new BitInputStream(inflater, 8);
  1495.  
  1496.         int previousFrameLumaFilter = 0;
  1497.         int previousFrameChromaFilter = 0;
  1498.  
  1499.         int width = (int) bis.read(IMAGE_DIMENSION_BITS);
  1500.         int height = (int) bis.read(IMAGE_DIMENSION_BITS);
  1501.         int sampledDimensionScalar = (int) bis.read(MAX_SAMPLED_DIMENSION_SCALAR_BITS);
  1502.         int sampledDimension = CHROMA_BLOCK_SIZE * sampledDimensionScalar;
  1503.  
  1504.         float[][][] pixels = new float[sampledDimension][sampledDimension][3];
  1505.  
  1506.         for (int i = 0; i < sampledDimension; i += LUMA_BLOCK_SIZE)
  1507.         {
  1508.             for (int j = 0; j < sampledDimension; j += LUMA_BLOCK_SIZE)
  1509.             {
  1510.                 int filterId;
  1511.                 if (LUMA_SIMILARITY_BIT_DEPTH == 0 || (LUMA_FILTER_COUNT > LUMA_SIMILARITY_COUNT && !bis.read()))
  1512.                 {
  1513.                     filterId = (int) bis.read(LUMA_FILTER_BIT_DEPTH);
  1514.                 }
  1515.                 else
  1516.                 {
  1517.                     filterId = filters.lumaFilterSimilarityOrdering[previousFrameLumaFilter][(int) bis.read(LUMA_SIMILARITY_BIT_DEPTH)];
  1518.                 }
  1519.                 previousFrameLumaFilter = filterId;
  1520.                 Block filter = filters.lumaFilters[filterId];
  1521.  
  1522.                 // copy matched filter's lumenosity into corresponding output image's block
  1523.                 for (int y = 0; y < LUMA_BLOCK_SIZE; y++)
  1524.                 {
  1525.                     for (int x = 0; x < LUMA_BLOCK_SIZE; x++)
  1526.                     {
  1527.                         pixels[j + x][i + y][LUMA] = filter.pixels[x][y][LUMA];
  1528.                     }
  1529.                 }
  1530.             }
  1531.         }
  1532.  
  1533.         // match sampled image blocks' 'chromanosity' to filters...
  1534.         for (int i = 0; i < sampledDimension; i += CHROMA_BLOCK_SIZE)
  1535.         {
  1536.             for (int j = 0; j < sampledDimension; j += CHROMA_BLOCK_SIZE)
  1537.             {
  1538.                 int filterId;
  1539.                 if (CHROMA_SIMILARITY_BIT_DEPTH == 0 || (CHROMA_FILTER_COUNT > CHROMA_SIMILARITY_COUNT && !bis.read()))
  1540.                 {
  1541.                     filterId = (int) bis.read(CHROMA_FILTER_BIT_DEPTH);
  1542.                 }
  1543.                 else
  1544.                 {
  1545.                     filterId = filters.chromaFilterSimilarityOrdering[previousFrameChromaFilter][(int) bis.read(CHROMA_SIMILARITY_BIT_DEPTH)];
  1546.                 }
  1547.                 previousFrameChromaFilter = filterId;
  1548.                 Block filter = filters.chromaFilters[filterId];
  1549.  
  1550.                 // copy matched filter's 'chromanosity' into corresponding output image's block
  1551.                 for (int y = 0; y < CHROMA_BLOCK_SIZE; y++)
  1552.                 {
  1553.                     for (int x = 0; x < CHROMA_BLOCK_SIZE; x++)
  1554.                     {
  1555.                         pixels[j + x][i + y][CHROMA_U] = filter.pixels[x][y][CHROMA_U];
  1556.                         pixels[j + x][i + y][CHROMA_V] = filter.pixels[x][y][CHROMA_V];
  1557.                     }
  1558.                 }
  1559.             }
  1560.         }
  1561.  
  1562.         // render the scaled output image
  1563.         BufferedImage yuvImage = new BufferedImage(sampledDimension, sampledDimension, BufferedImage.TYPE_INT_RGB);
  1564.         renderYUV(yuvImage, pixels);
  1565.  
  1566.         // save the output image, resized to the input image's dimensons
  1567.         ImageIO.write(blurResize(yuvImage, width, height), "png", outputFile);
  1568. //      BufferedImage scaledImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
  1569. //      scaledImage.getGraphics().drawImage(yuvImage.getScaledInstance(width, height, Image.SCALE_FAST), 0, 0, null);
  1570. //      ImageIO.write(scaledImage, "png", new File(outputFile.getAbsolutePath() + ".nonBlurred.png"));
  1571.         System.out.println("Decompressed "+outputFile.getAbsolutePath());
  1572.     }
  1573.  
  1574.     /**
  1575.      * Converts the given buffered image into a 3-dimensional array of YUV pixels
  1576.      *
  1577.      * @param image
  1578.      *            the buffered image to convert
  1579.      * @return 3-dimensional array of YUV pixels
  1580.      */
  1581.     static private float[][][] convertToYUVImage(BufferedImage image)
  1582.     {
  1583.         final int width = image.getWidth();
  1584.         final int height = image.getHeight();
  1585.         float[][][] yuv = new float[width][height][3];
  1586.         for (int y = 0; y < height; y++)
  1587.         {
  1588.             for (int x = 0; x < width; x++)
  1589.             {
  1590.                 int rgb = image.getRGB(x, y);
  1591.                 yuv[x][y] = rgb2yuv(rgb);
  1592.             }
  1593.         }
  1594.         return yuv;
  1595.     }
  1596.  
  1597.     /**
  1598.      * Renders the given YUV image into the given buffered image.
  1599.      *
  1600.      * @param image
  1601.      *            the buffered image to render to
  1602.      * @param pixels
  1603.      *            the YUV image to render.
  1604.      * @param dimension
  1605.      *            the
  1606.      */
  1607.     static private void renderYUV(BufferedImage image, float[][][] pixels)
  1608.     {
  1609.         final int height = pixels.length;
  1610.         final int width = pixels[0].length;
  1611.         int rgb;
  1612.  
  1613.         for (int y = 0; y < height; y++)
  1614.         {
  1615.             for (int x = 0; x < width; x++)
  1616.             {
  1617.  
  1618.                 rgb = yuv2rgb(pixels[x][y]);
  1619.                 image.setRGB(x, y, rgb);
  1620.             }
  1621.         }
  1622.     }
  1623.  
  1624.     /**
  1625.      * Converts a RGB pixel into a YUV pixel
  1626.      *
  1627.      * @param rgb
  1628.      *            a pixel encoded as 24 bit RGB
  1629.      * @return array representing a pixel. Consisting of Y,U and V components
  1630.      */
  1631.     static float[] rgb2yuv(int rgb)
  1632.     {
  1633.         float red = EIGHT_BIT_DIVISOR * ((rgb >> 16) & 0xFF);
  1634.         float green = EIGHT_BIT_DIVISOR * ((rgb >> 8) & 0xFF);
  1635.         float blue = EIGHT_BIT_DIVISOR * (rgb & 0xFF);
  1636.  
  1637.         float Y = 0.299F * red + 0.587F * green + 0.114F * blue;
  1638.         float U = (blue - Y) * 0.565F;
  1639.         float V = (red - Y) * 0.713F;
  1640.  
  1641.         return new float[] { Y, U, V };
  1642.     }
  1643.  
  1644.     /**
  1645.      * Converts a YUV pixel into a RGB pixel
  1646.      *
  1647.      * @param yuv
  1648.      *            array representing a pixel. Consisting of Y,U and V components
  1649.      * @return a pixel encoded as 24 bit RGB
  1650.      */
  1651.     static int yuv2rgb(float[] yuv)
  1652.     {
  1653.         int red = (int) ((yuv[0] + 1.403 * yuv[2]) * 256);
  1654.         int green = (int) ((yuv[0] - 0.344 * yuv[1] - 0.714 * yuv[2]) * 256);
  1655.         int blue = (int) ((yuv[0] + 1.77 * yuv[1]) * 256);
  1656.  
  1657.         // clamp to 0-255 range
  1658.         red = red < 0 ? 0 : red > 255 ? 255 : red;
  1659.         green = green < 0 ? 0 : green > 255 ? 255 : green;
  1660.         blue = blue < 0 ? 0 : blue > 255 ? 255 : blue;
  1661.  
  1662.         return (red << 16) | (green << 8) | blue;
  1663.     }
  1664.  
  1665.     /**
  1666.      * Blurs an image using a 3x3 weighted kernel.
  1667.      *
  1668.      * @param image
  1669.      *            the image to blur from
  1670.      * @return the blurred image
  1671.      */
  1672.     public static BufferedImage blurImage(BufferedImage image)
  1673.     {
  1674.         float[] blurKernel = { 0.0375f, 0.0625f, 0.0375f, 0.0625f, 0.6f, 0.0625f, 0.0375f, 0.0625f, 0.0375f };
  1675.  
  1676.         Map<Key, Object> map = new HashMap<Key, Object>();
  1677.  
  1678.         map.put(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
  1679.  
  1680.         map.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
  1681.         map.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
  1682.  
  1683.         RenderingHints hints = new RenderingHints(map);
  1684.         BufferedImageOp op = new ConvolveOp(new Kernel(3, 3, blurKernel), ConvolveOp.EDGE_NO_OP, hints);
  1685.         return op.filter(image, null);
  1686.     }
  1687.  
  1688.     /**
  1689.      * Resizes the input image to the given width and height.
  1690.      *
  1691.      * Algorithm: 1. check to see if doubling image will exceed desired width or height, if so then goto 5 2. resize the image so that it is double the width and height it was before. 3. blur the image 4. goto 1 5. resize image to desired with and height
  1692.      *
  1693.      * @param image
  1694.      *            the image to resize
  1695.      * @param newWidth
  1696.      *            the desired width of the resized image
  1697.      * @param newHeight
  1698.      *            the desired height of the resized image
  1699.      * @return a new resized image
  1700.      */
  1701.     public static BufferedImage blurResize(BufferedImage image, int newWidth, int newHeight)
  1702.     {
  1703.  
  1704.         int width = image.getWidth();
  1705.         int height = image.getHeight();
  1706.  
  1707.         while (width * 2 <= newWidth && height * 2 <= newHeight)
  1708.         {
  1709.             BufferedImage newImage = new BufferedImage(width * 2, height * 2, BufferedImage.TYPE_INT_RGB);
  1710.             newImage.getGraphics().drawImage(image.getScaledInstance(width * 2, height * 2, Image.SCALE_AREA_AVERAGING), 0, 0, null);
  1711.  
  1712.             image = blurImage(newImage);
  1713.             width = image.getWidth();
  1714.             height = image.getHeight();
  1715.         }
  1716.  
  1717.         BufferedImage newImage = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_RGB);
  1718.         newImage.getGraphics().drawImage(image.getScaledInstance(newWidth, newHeight, Image.SCALE_AREA_AVERAGING), 0, 0, null);
  1719.         return newImage;
  1720.     }
  1721. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement