Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- package CompressAnImageToA4kibPreview;
- import java.awt.BasicStroke;
- import java.awt.Color;
- import java.awt.GradientPaint;
- import java.awt.Graphics2D;
- import java.awt.Image;
- import java.awt.RenderingHints;
- import java.awt.RenderingHints.Key;
- import java.awt.geom.Path2D;
- import java.awt.image.BufferedImage;
- import java.awt.image.BufferedImageOp;
- import java.awt.image.ConvolveOp;
- import java.awt.image.Kernel;
- import java.io.BufferedInputStream;
- import java.io.BufferedOutputStream;
- import java.io.ByteArrayOutputStream;
- import java.io.DataInputStream;
- import java.io.DataOutputStream;
- import java.io.EOFException;
- import java.io.File;
- import java.io.FileInputStream;
- import java.io.FileOutputStream;
- import java.io.IOException;
- import java.io.InputStream;
- import java.io.OutputStream;
- import java.util.ArrayList;
- import java.util.Collections;
- import java.util.HashMap;
- import java.util.List;
- import java.util.Map;
- import java.util.Random;
- import java.util.zip.Deflater;
- import java.util.zip.DeflaterOutputStream;
- import java.util.zip.InflaterInputStream;
- import javax.imageio.ImageIO;
- /**
- * Licensed under Revised BSD License:
- *
- * Copyright (c) 2016, Nicholas Klaebe
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- * * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * * Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * * Neither the name of the Nicholas Klaebe nor the
- * names of its contributors may be used to endorse or promote products
- * derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
- * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *
- * @author Nicholas Klaebe
- *
- */
- public class CompressAnImageToA4kibPreview
- {
- /**
- * InputStream that allows 1-63 bitwise writes
- * @author default
- *
- */
- static class BitInputStream
- {
- InputStream fis;
- int count;
- long totalCount;
- byte value;
- byte[] buffer;
- int buffCount;
- int buffSize;
- boolean returnVal;
- private static final int EOF_INT = -1;
- /**
- * Constructor
- *
- * @param fis1
- * The base inputStream
- * @param size
- * size of the input buffer
- */
- public BitInputStream(InputStream fis1, int size)
- {
- count = 0;
- buffer = new byte[size];
- buffCount = 0;
- totalCount = 0;
- fis = fis1;
- }
- /**
- * read in a single bit
- *
- * @return bit read
- * @throws IOException
- */
- public boolean read() throws IOException
- {
- if (count == 0)
- {
- if (buffCount == 0)
- {
- buffSize = 0;
- while (buffSize == 0)
- {
- buffSize = fis.read(buffer, 0, buffer.length);
- }
- if (buffSize == EOF_INT)
- {
- throw new EOFException("END OF FILE");
- }
- }
- value = buffer[buffCount];
- buffCount++;
- if (buffCount == buffSize)
- buffCount = 0;
- }
- if ((value >> (7 - count) & 0x01) > 0)
- returnVal = true;
- else
- returnVal = false;
- count++;
- totalCount++;
- if (count == 8)
- count = 0;
- return returnVal;
- }
- /**
- * reads in the next char, if not on a 8bit boundary then the stream is aligned to the next byte
- *
- * @return byte read
- * @throws IOException
- */
- public char readChar() throws IOException
- {
- byte[] b = new byte[1];
- alignToNextByte();
- fis.read(b);
- totalCount += 8;
- return (char) b[0];
- }
- /**
- * aligns the bit stream to the next byte boundary
- *
- * @throws IOException
- */
- public void alignToNextByte() throws IOException
- {
- while (count != 0)
- {
- read();
- }
- }
- /**
- * read in a specified number of bits
- *
- * @param bits
- * the number of bits to be read (between 1 and 64)
- * @return a long integer containg the bits read
- * @throws IOException
- */
- public long read(int bits) throws IOException
- {
- long val = 0;
- int mask = 0x01;
- boolean[] bitsRead = new boolean[bits];
- for (int i = 0; i < bits; i++)
- {
- bitsRead[i] = read();
- }
- for (int j = bits - 1; j > -1; j--)
- {
- val = val << 1;
- if (bitsRead[j])
- {
- val = val | mask;
- }
- }
- return val;
- }
- /**
- * attempts to read bytes into the array given
- *
- * @param temp
- * the array being populated
- * @param start
- * the starting index
- * @param end
- * the ending index
- * @return the bytes read in
- * @throws IOException
- */
- public int read(byte[] temp, int start, int end) throws IOException
- {
- totalCount += (end - start) * 8;
- return fis.read(temp, start, end);
- }
- public int read(byte[] temp) throws IOException
- {
- totalCount += temp.length * 8;
- return fis.read(temp);
- }
- public int readByte() throws IOException
- {
- totalCount += 8;
- return fis.read();
- }
- public void close() throws IOException
- {
- fis.close();
- }
- }
- /**
- * OutputStream that allows 1-63 bitwise reads
- */
- static public class BitOutputStream
- {
- int value; // The byte in which the encoded bits are firstly stored.
- int count; // The number of bits written into value.
- byte[] buffer; // A byte buffer which is filled with 'value' each time value is full. Used for wirting to file.
- int buffCount; // The current number of 'values' written into the buffer.
- long masterCount; // The overall count of bits that have been written
- OutputStream fos;
- /**
- * constructor
- *
- * @param fos1
- * The outputstream which this bit stream writes to
- */
- public BitOutputStream(OutputStream fos1)
- {
- fos = fos1;
- value = 0;
- count = 0;
- buffer = new byte[4096];
- buffCount = 0;
- masterCount = 0;
- }
- /**
- * Writes the passed value (temp) to the file using the given number of bits
- *
- * @param temp
- * the value to be written
- * @param bits
- * the number if bits to write
- * @throws IOException
- */
- public void write(long temp, int bits) throws IOException
- {
- for (int j = 0, mask = 1; j < bits; j++, mask <<= 1)
- {
- value = value << 1;
- count++;
- if ((temp & mask) > 0)
- {
- value = value | 0x01;
- }
- addToBuffer();
- }
- }
- /**
- * write a single bit to the stream
- *
- * @param bit
- * The bit to write
- * @throws IOException
- */
- public void write(boolean bit) throws IOException
- {
- value = value << 1;
- count++;
- if (bit)
- {
- value = value | 0x01;
- }
- addToBuffer();
- }
- /**
- * writes a single char (converted to a byte) to the output stream aligning with the next byte boundary
- *
- * @param c
- * the char to write
- * @throws IOException
- */
- public void write(char c) throws IOException
- {
- flush();
- byte[] b = new byte[1];
- b[0] = (byte) c;
- fos.write(b);
- masterCount += 8;
- }
- /**
- * 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
- *
- * @throws IOException
- */
- public void addToBuffer() throws IOException
- {
- masterCount++;
- if (count < 8)
- return;
- // byte temp=(byte) (value);
- buffer[buffCount] = (byte) (value);
- ;
- buffCount++;
- if (buffCount == buffer.length)
- {
- fos.write(buffer, 0, buffCount);
- buffCount = 0;
- }
- value = 0;
- count = 0;
- }
- /**
- * writes a single byte to the output stream aligning with the next byte boundary
- *
- * @param b
- * the byte to write
- * @throws IOException
- */
- public void write(byte[] b) throws IOException
- {
- flush();
- fos.write(b);
- }
- /**
- * writes a single byte to the output stream aligning with the next byte boundary
- *
- * @param b
- * the byte to write
- * @throws IOException
- */
- public void write(byte b) throws IOException
- {
- flush();
- fos.write(b);
- }
- public void write(byte[] b, int start, int count) throws IOException
- {
- flush();
- fos.write(b, start, count);
- }
- /**
- * align the output stream with the next byte boundary
- *
- * @throws IOException
- */
- public void flush() throws IOException
- {
- // pad out the last byte if necessary
- if (count > 0)
- {
- masterCount += (8 - count - 1);
- value = value << (8 - count);
- count = 8;
- addToBuffer();
- }
- if (buffCount > 0)
- {
- fos.write(buffer, 0, buffCount);
- buffCount = 0;
- }
- }
- /**
- * close the output stream
- *
- * @throws IOException
- */
- public void close() throws IOException
- {
- flush();
- fos.close();
- }
- }
- static private final Random rand = new Random(0);
- static private final int IMAGE_DIMENSION_BITS = 16;
- static private final int LUMA_BLOCK_SIZE = 3; // 4 //4 //4 //4 //3
- static private final int LUMA_SAMPLED_DIMENSION_BLOCKS = 32 * 2;// 18 //30 //16 //16 //8
- static private final int LUMA_SAMPLED_DIMENSION = LUMA_BLOCK_SIZE * LUMA_SAMPLED_DIMENSION_BLOCKS;
- static private final int MIN_SAMPLED_DIMENSION = LUMA_SAMPLED_DIMENSION_BLOCKS / 4;
- static private final int MAX_SAMPLED_DIMENSION_SCALAR_BITS = 8;
- static private final int MAX_SAMPLED_DIMENSION_SCALAR = (int) Math.pow(2, MAX_SAMPLED_DIMENSION_SCALAR_BITS);
- static private final int LUMA_FILTER_BIT_DEPTH = 10;// 10;//16 //16 //12 //12
- static private final int LUMA_FILTER_COUNT = (int) Math.pow(2, LUMA_FILTER_BIT_DEPTH);
- static private final int LUMA_BLOCK_SAMPLE_SIZE = LUMA_FILTER_COUNT * 10; // 10000
- static private final int LUMA_FILTERS_IMAGE_DIMENSION = (int) (Math.ceil(Math.sqrt(LUMA_FILTER_COUNT)));
- static private final int CHROMA_BLOCK_SIZE = LUMA_BLOCK_SIZE * 2; // 8 //8 //8 //6
- static private final int CHROMA_SAMPLED_DIMENSION_BLOCKS = LUMA_SAMPLED_DIMENSION_BLOCKS / 2; // 15 //8 //8 //4
- static private final int CHROMA_SAMPLED_DIMENSION = CHROMA_BLOCK_SIZE * CHROMA_SAMPLED_DIMENSION_BLOCKS;
- static private final int CHROMA_FILTER_BIT_DEPTH = 10;// 12;//10 //10 //7 //7
- static private final int CHROMA_FILTER_COUNT = (int) Math.pow(2, CHROMA_FILTER_BIT_DEPTH);
- static private final int CHROMA_BLOCK_SAMPLE_SIZE = CHROMA_FILTER_COUNT * 10; // 10000
- static private final int CHROMA_FILTERS_IMAGE_DIMENSION = (int) (Math.ceil(Math.sqrt(CHROMA_FILTER_COUNT)));
- private static final int LUMA_SIMILARITY_BIT_DEPTH = LUMA_FILTER_BIT_DEPTH;// 0;//2;//5;
- private static final int LUMA_SIMILARITY_COUNT = (int) Math.pow(2, LUMA_SIMILARITY_BIT_DEPTH);
- private static final int CHROMA_SIMILARITY_BIT_DEPTH = CHROMA_FILTER_BIT_DEPTH;// 0;//2;//5;
- private static final int CHROMA_SIMILARITY_COUNT = (int) Math.pow(2, CHROMA_SIMILARITY_BIT_DEPTH);
- static private final int LUMA = 0;
- static private final int CHROMA_U = 1;
- static private final int CHROMA_V = 2;
- static private final float EIGHT_BIT_DIVISOR = 1.0F / 256;
- static private final int GENERATE_NONE_ENUM = 0;
- static private final int GENERATE_ONLY_IF_NEEDED_ENUM = 1;
- static private final int GENERATE_ALWAYS_ENUM = 2;
- private static int GENERATE = GENERATE_ONLY_IF_NEEDED_ENUM;
- private static final boolean CLUSTER_LUMA = true;
- private static final boolean CLUSTER_CHROMA = true;
- private static final int DESIRED_COMPRESSED_SIZE_BYTES = 4096;
- private static final boolean OUTPUT_SAMPLE_IMAGES = false;
- /**
- * FilterManager manages the lumenosity and chromenosity filters including creation, writing and reading.
- * @author default
- *
- */
- static final class FilterManager
- {
- private static final int MAX_SAMPLE_IMAGE_SHAPE_SELECTOR = 20;
- private static final int SAMPLE_IMAGE_SUBSHAPE_STROKE_DIVISOR = 20;
- private static final float MIN_SAMPLE_IMAGE_SUBSHAPE_STROKE = 0.5F;
- private static final int MIN_SAMPLE_IMAGE_SUBSHAPE_SIZE = 10;
- private static final int SAMPLE_IMAGE_COUNT = 25;
- Block[] lumaFilters;
- Block[] chromaFilters;
- int[][] lumaFilterSimilarityOrdering;
- int[][] chromaFilterSimilarityOrdering;
- private void loadFilters() throws IOException
- {
- lumaFilters = new Block[LUMA_FILTER_COUNT];
- chromaFilters = new Block[CHROMA_FILTER_COUNT];
- 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"));
- for (int i = 0; i < LUMA_FILTER_COUNT; i++)
- {
- 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));
- lumaFilters[i] = new Block(pixels, 0, 0, LUMA_BLOCK_SIZE);
- lumaFilters[i].closestMatchingFilterId = i;
- }
- filtersImage = ImageIO.read(new File("data/images/working/chromaFilters_" + LUMA_BLOCK_SIZE + "_" + CHROMA_BLOCK_SIZE + "_" + LUMA_FILTER_BIT_DEPTH + "_" + CHROMA_FILTER_BIT_DEPTH + ".png"));
- for (int i = 0; i < CHROMA_FILTER_COUNT; i++)
- {
- 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));
- chromaFilters[i] = new Block(pixels, 0, 0, CHROMA_BLOCK_SIZE);
- chromaFilters[i].closestMatchingFilterId = i;
- }
- }
- private void loadSimilarityOrdering() throws IOException
- {
- 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"))));
- if (LUMA_SIMILARITY_BIT_DEPTH > 0)
- {
- lumaFilterSimilarityOrdering = new int[LUMA_FILTER_COUNT][];
- for (int i = 0; i < lumaFilterSimilarityOrdering.length; i++)
- {
- lumaFilterSimilarityOrdering[i] = new int[LUMA_SIMILARITY_COUNT];
- for (int j = 0; j < LUMA_SIMILARITY_COUNT; j++)
- {
- lumaFilterSimilarityOrdering[i][j] = dis.readInt();
- }
- }
- }
- if (CHROMA_SIMILARITY_BIT_DEPTH > 0)
- {
- chromaFilterSimilarityOrdering = new int[CHROMA_FILTER_COUNT][];
- for (int i = 0; i < chromaFilterSimilarityOrdering.length; i++)
- {
- chromaFilterSimilarityOrdering[i] = new int[CHROMA_SIMILARITY_COUNT];
- for (int j = 0; j < CHROMA_SIMILARITY_COUNT; j++)
- {
- chromaFilterSimilarityOrdering[i][j] = dis.readInt();
- }
- }
- }
- dis.close();
- }
- private void performSimilarityOrdering() throws IOException
- {
- System.out.println("Creating similarity ordered Lists...");
- 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"))));
- if (LUMA_SIMILARITY_BIT_DEPTH > 0)
- {
- lumaFilterSimilarityOrdering = new int[LUMA_FILTER_COUNT][];
- for (int i = 0; i < lumaFilterSimilarityOrdering.length; i++)
- {
- lumaFilterSimilarityOrdering[i] = order(lumaFilters, lumaFilters[i], LumaComparator.comparator, LUMA_SIMILARITY_COUNT);
- }
- for (int i = 0; i < lumaFilterSimilarityOrdering.length; i++)
- {
- for (int j = 0; j < LUMA_SIMILARITY_COUNT; j++)
- {
- dos.writeInt(lumaFilterSimilarityOrdering[i][j]);
- }
- }
- }
- if (CHROMA_SIMILARITY_BIT_DEPTH > 0)
- {
- chromaFilterSimilarityOrdering = new int[CHROMA_FILTER_COUNT][];
- for (int i = 0; i < chromaFilterSimilarityOrdering.length; i++)
- {
- chromaFilterSimilarityOrdering[i] = order(chromaFilters, chromaFilters[i], ChromaComparator.comparator, CHROMA_SIMILARITY_COUNT);
- }
- for (int i = 0; i < chromaFilterSimilarityOrdering.length; i++)
- {
- for (int j = 0; j < CHROMA_SIMILARITY_COUNT; j++)
- {
- dos.writeInt(chromaFilterSimilarityOrdering[i][j]);
- }
- }
- }
- dos.close();
- System.out.println("Creating similarity ordered Lists - Done");
- }
- /**
- * Performs similarity ordering of the given filter type as compared with the given filter.
- *
- * @return array of similarity ordered indices into the filter list.
- */
- static private int[] order(Block[] filters, final Block comparisionFilter, final BlockComparator comparator, final int similarityCount)
- {
- final class Tuple implements Comparable<Tuple>
- {
- public Tuple(float difference, int id)
- {
- this.difference = difference;
- this.id = id;
- }
- float difference;
- int id;
- @Override
- public int compareTo(Tuple other)
- {
- float diff = difference - other.difference;
- return diff < 0 ? -1 : (diff > 0 ? 1 : 0);
- }
- }
- ArrayList<Tuple> tuples = new ArrayList<>();
- for (int i = 0; i < filters.length; i++)
- {
- tuples.add(new Tuple(comparator.calculateDifference(filters[i], comparisionFilter), i));
- }
- Collections.sort(tuples);
- int[] orderedIndecies = new int[similarityCount];
- for (int i = 0; i < similarityCount; i++)
- {
- orderedIndecies[i] = tuples.get(i).id;
- }
- return orderedIndecies;
- }
- private void generateFilters() throws IOException
- {
- System.out.println("First time running (no filters found). Generating Sample Images...");
- List<float[][][]> sampleImages = new ArrayList<float[][][]>();
- BufferedImage sampleImage = new BufferedImage(LUMA_SAMPLED_DIMENSION, LUMA_SAMPLED_DIMENSION, BufferedImage.TYPE_INT_RGB);
- BufferedImage image1 = new BufferedImage(1024, 1024, BufferedImage.TYPE_INT_RGB);
- // generate sample images and convert and down sample them into YUV images
- for (int j = 0; j < SAMPLE_IMAGE_COUNT; j++)
- {
- final int MAX_SHAPE_SIZE = MIN_SAMPLE_IMAGE_SUBSHAPE_SIZE + image1.getWidth() / 5;
- Graphics2D g = (Graphics2D) image1.getGraphics();
- Color colour1 = new Color(Color.HSBtoRGB(rand.nextFloat(), rand.nextFloat(), (float) Math.log(0.001 + rand.nextFloat() * 10)));
- Color colour2 = new Color(Color.HSBtoRGB(rand.nextFloat(), rand.nextFloat(), (float) Math.log(0.001 + rand.nextFloat() * 10)));
- GradientPaint gradientPaint = new GradientPaint(0, 0, colour1, 1024, 1024, colour2);
- g.setPaint(gradientPaint);
- g.fillRect(0, 0, image1.getWidth(), image1.getHeight());
- for (int i = 0; i < 200; i++)
- {
- colour1 = new Color(Color.HSBtoRGB(rand.nextFloat(), rand.nextFloat(), (float) Math.log(0.001 + rand.nextFloat() * 10)));
- colour2 = new Color(Color.HSBtoRGB(rand.nextFloat(), rand.nextFloat(), (float) Math.log(0.001 + rand.nextFloat() * 10)));
- g.setStroke(new BasicStroke(MIN_SAMPLE_IMAGE_SUBSHAPE_STROKE + rand.nextFloat() * image1.getWidth() / SAMPLE_IMAGE_SUBSHAPE_STROKE_DIVISOR));
- int x = rand.nextInt(image1.getWidth() + MAX_SHAPE_SIZE / 2) - MAX_SHAPE_SIZE / 2;
- int y = rand.nextInt(image1.getHeight() - MAX_SHAPE_SIZE) + MAX_SHAPE_SIZE / 2;
- int w = rand.nextInt(MAX_SHAPE_SIZE);
- int h = rand.nextInt(MAX_SHAPE_SIZE);
- gradientPaint = new GradientPaint(x, y, colour1, x + w, y + h, colour2);
- g.setPaint(gradientPaint);
- switch (rand.nextInt(MAX_SAMPLE_IMAGE_SHAPE_SELECTOR))
- {
- case 0:
- g.fillRect(x, y, w, h);
- break;
- case 1:
- g.drawRect(x, y, w, h);
- break;
- case 2:
- g.drawOval(x, y, w, h);
- break;
- case 3:
- g.fillOval(x, y, w, h);
- break;
- case 4:
- g.drawLine(x, y, x + w, y + h);
- break;
- default:
- Path2D prettyPoly = new Path2D.Double();
- boolean isFirst = true;
- for (int points = 0; points < 3 + rand.nextInt(MAX_SHAPE_SIZE); points++)
- {
- double xx = rand.nextDouble() * MAX_SHAPE_SIZE;
- double yy = rand.nextDouble() * MAX_SHAPE_SIZE;
- if (isFirst)
- {
- prettyPoly.moveTo(xx, yy);
- isFirst = false;
- }
- else
- {
- prettyPoly.lineTo(xx, yy);
- }
- }
- prettyPoly.closePath();
- g.translate(x, y);
- gradientPaint = new GradientPaint(0, 0, colour1, MAX_SHAPE_SIZE, MAX_SHAPE_SIZE, colour2);
- g.setPaint(gradientPaint);
- g.fill(prettyPoly);
- g.translate(-x, -y);
- break;
- }
- }
- if (OUTPUT_SAMPLE_IMAGES)
- {
- File sampleFile = new File("data/images/working/sample" + j + ".png");
- sampleFile.getParentFile().mkdirs();
- ImageIO.write(image1, "png", sampleFile);
- }
- sampleImage.getGraphics().drawImage(image1.getScaledInstance(LUMA_SAMPLED_DIMENSION, LUMA_SAMPLED_DIMENSION, BufferedImage.SCALE_AREA_AVERAGING), 0, 0, null);
- float[][][] pixels = convertToYUVImage(sampleImage);
- sampleImages.add(pixels);
- }
- System.out.println("Generating Sample Images Completed.");
- // perform k-clustering for lumenosity and 'chromanosity'
- lumaFilters = clusterLuma(sampleImages);
- chromaFilters = new Block[0];
- if (!(CHROMA_FILTER_COUNT == 0 || CHROMA_SAMPLED_DIMENSION == 0))
- {
- chromaFilters = clusterChroma(sampleImages);
- }
- // construct and save clustered lumenosity filters image
- BufferedImage lumaFiltersImage = new BufferedImage(LUMA_FILTERS_IMAGE_DIMENSION * (LUMA_BLOCK_SIZE + 2), LUMA_FILTERS_IMAGE_DIMENSION * (LUMA_BLOCK_SIZE + 2), BufferedImage.TYPE_INT_RGB);
- BufferedImage lumaFilterImage = new BufferedImage(LUMA_BLOCK_SIZE, LUMA_BLOCK_SIZE, BufferedImage.TYPE_INT_RGB);
- for (int i = 0; i < lumaFilters.length; i++)
- {
- renderYUV(lumaFilterImage, lumaFilters[i].pixels);
- lumaFiltersImage.getGraphics().drawImage(lumaFilterImage, (i % LUMA_FILTERS_IMAGE_DIMENSION) * (LUMA_BLOCK_SIZE + 2), (i / LUMA_FILTERS_IMAGE_DIMENSION) * (LUMA_BLOCK_SIZE + 2), null);
- }
- new File("data/images/working").mkdirs();
- 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"));
- // construct and save clustered 'chromanosity' filters image
- BufferedImage chromafiltersImage = new BufferedImage(CHROMA_FILTERS_IMAGE_DIMENSION * (CHROMA_BLOCK_SIZE + 2), CHROMA_FILTERS_IMAGE_DIMENSION * (CHROMA_BLOCK_SIZE + 2), BufferedImage.TYPE_INT_RGB);
- BufferedImage chromafilterImage = new BufferedImage(CHROMA_BLOCK_SIZE, CHROMA_BLOCK_SIZE, BufferedImage.TYPE_INT_RGB);
- for (int i = 0; i < chromaFilters.length; i++)
- {
- renderYUV(chromafilterImage, chromaFilters[i].pixels);
- chromafiltersImage.getGraphics().drawImage(chromafilterImage, (i % CHROMA_FILTERS_IMAGE_DIMENSION) * (CHROMA_BLOCK_SIZE + 2), (i / CHROMA_FILTERS_IMAGE_DIMENSION) * (CHROMA_BLOCK_SIZE + 2), null);
- }
- 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"));
- }
- /**
- * Performs K-clustering of samples blocks to produce an array of filter blocks.
- *
- * @param sampleImages
- * the collection of YUV down sampled sample images to obtain random blocks from
- * @param sampledDimension
- * the dimension of the down sampled image (width == height)
- * @param blockSize
- * the dimension of the filter block (width == height)
- * @param filterCount
- * the number of filters to cluster for
- * @param blockSampleSize
- * the number of blocks to cluster
- * @param comparator
- * the comparator to use when determining magnitude of difference between two blocks
- * @return array of filters
- */
- static private Block[] cluster(List<float[][][]> sampleImages, final int sampledDimension, final int blockSize, final int filterCount, final int blockSampleSize, BlockComparator comparator, boolean performCluster)
- {
- Block[] filters = new Block[filterCount];
- Block[] samples = new Block[blockSampleSize];
- for (int i = 0; i < blockSampleSize; i++)
- {
- samples[i] = new Block(sampleImages.get(rand.nextInt(sampleImages.size())), rand.nextInt(sampledDimension - blockSize), rand.nextInt(sampledDimension - blockSize), blockSize);
- }
- // select initial filters
- for (int i = 0; i < filterCount; i++)
- {
- filters[i] = new Block(samples[i]);
- filters[i].closestMatchingFilterId = i;
- }
- if (performCluster)
- {
- int iteration = 0;
- boolean converged = false;
- long stopTime = System.currentTimeMillis() + 1000 * 60 * 60;
- // iterate until all blocks have converged into clusters or timeout occurs
- while (!converged && System.currentTimeMillis() < stopTime)
- {
- iteration++;
- System.out.print("iteration: " + iteration);
- converged = true;
- int blockSwapClusterCount = 0;
- // identify closest cluster mid point for each sample block
- for (int i = 0; i < blockSampleSize; i++)
- {
- Block block = samples[i];
- float bestDifference = Float.MAX_VALUE;
- int bestFilterIndex = -1;
- for (int j = 0; j < filterCount; j++)
- {
- float difference = comparator.calculateDifference(block, filters[j]);
- if (difference < bestDifference)
- {
- bestDifference = difference;
- bestFilterIndex = j;
- }
- }
- // sample block has changed to a different cluster or the assigned cluster's collection has been updated
- if (bestDifference != block.closestDifference || bestFilterIndex != block.closestMatchingFilterId)
- {
- blockSwapClusterCount++;
- converged = false;
- block.closestMatchingFilterId = bestFilterIndex;
- block.closestDifference = bestDifference;
- }
- }
- if (!converged)
- {
- int orphanedClusterCount = 0;
- // determine new cluster centre (i.e. filter)
- for (int j = 0; j < filterCount; j++)
- {
- int clusterSize = 0;
- Block filter = filters[j];
- filter.zeroise();
- for (int i = 0; i < blockSampleSize; i++)
- {
- if (samples[i].closestMatchingFilterId == j)
- {
- clusterSize++;
- filter.add(samples[i]);
- }
- }
- // the cluster no longer has any members... lets choose another random sample to be our this cluster centre
- if (clusterSize == 0)
- {
- orphanedClusterCount++;
- filter = new Block(samples[rand.nextInt(blockSampleSize)]);
- filter.closestMatchingFilterId = j;
- filters[j] = filter;
- }
- else
- {
- filter.divide(clusterSize);
- }
- }
- if (orphanedClusterCount > 0)
- {
- System.out.print(" orphanedClusterCount: " + orphanedClusterCount);
- }
- }
- if (blockSwapClusterCount > 0)
- {
- System.out.print(" blockSwapClusterCount: " + blockSwapClusterCount);
- }
- System.out.println();
- }
- System.out.println("Clustering complete");
- }
- return filters;
- }
- /**
- * Perform k-clustering based on pixel lumenosity to derive lumenosity filters
- *
- * @param sampleImages
- * the collection of YUV down sampled sample images to obtain random blocks from
- * @return array of filters
- */
- static private Block[] clusterLuma(List<float[][][]> sampleImages)
- {
- if (CLUSTER_LUMA)
- {
- System.out.println("K-Cluster Luma filters...");
- }
- return cluster(sampleImages, LUMA_SAMPLED_DIMENSION, LUMA_BLOCK_SIZE, LUMA_FILTER_COUNT, LUMA_BLOCK_SAMPLE_SIZE, LumaComparator.comparator, CLUSTER_LUMA);
- }
- /**
- * Perform k-clustering based on pixel 'chromanosity' to derive 'chromanosity' filters
- *
- * @param sampleImages
- * the collection of YUV down sampled sample images to obtain random blocks from
- * @return array of filters
- */
- static private Block[] clusterChroma(List<float[][][]> sampleImages)
- {
- if (CLUSTER_CHROMA)
- {
- System.out.println("K-Cluster Chroma filters...");
- }
- return cluster(sampleImages, CHROMA_SAMPLED_DIMENSION, CHROMA_BLOCK_SIZE, CHROMA_FILTER_COUNT, CHROMA_BLOCK_SAMPLE_SIZE, ChromaComparator.comparator, CLUSTER_CHROMA);
- }
- public static FilterManager create() throws IOException
- {
- FilterManager filterManager = new FilterManager();
- filterManager.generateFilters();
- filterManager.performSimilarityOrdering();
- return filterManager;
- }
- public static FilterManager load() throws IOException
- {
- FilterManager filterManager = new FilterManager();
- filterManager.loadFilters();
- filterManager.loadSimilarityOrdering();
- return filterManager;
- }
- }
- /**
- * A square block holding YUV pixels
- */
- public static final class Block
- {
- float[][][] pixels;
- int closestMatchingFilterId = -1;
- float closestDifference = -1;
- final int blockSize;
- public Block(float[][][] sourceImage, int x, int y, final int blockSize)
- {
- this.blockSize = blockSize;
- pixels = new float[blockSize][blockSize][3];
- for (int i = 0; i < blockSize; i++)
- {
- for (int j = 0; j < blockSize; j++)
- {
- float[] pixel = sourceImage[x + j][y + i];
- pixels[j][i][LUMA] = pixel[LUMA];
- pixels[j][i][CHROMA_U] = pixel[CHROMA_U];
- pixels[j][i][CHROMA_V] = pixel[CHROMA_V];
- }
- }
- }
- /**
- * Copy Constructor
- *
- * @param other
- */
- public Block(Block other)
- {
- this.blockSize = other.blockSize;
- pixels = new float[blockSize][blockSize][3];
- for (int i = 0; i < blockSize; i++)
- {
- for (int j = 0; j < blockSize; j++)
- {
- pixels[j][i][LUMA] = other.pixels[j][i][LUMA];
- pixels[j][i][CHROMA_U] = other.pixels[j][i][CHROMA_U];
- pixels[j][i][CHROMA_V] = other.pixels[j][i][CHROMA_V];
- }
- }
- }
- /**
- * sets all pixel colour components to 0
- */
- public void zeroise()
- {
- pixels = new float[blockSize][blockSize][3];
- }
- /**
- * adds each pixel's colour components to this block's corresponding pixel colour components
- *
- * @param other
- * the other block to add
- */
- public void add(Block other)
- {
- for (int i = 0; i < blockSize; i++)
- {
- for (int j = 0; j < blockSize; j++)
- {
- pixels[j][i][LUMA] += other.pixels[j][i][LUMA];
- pixels[j][i][CHROMA_U] += other.pixels[j][i][CHROMA_U];
- pixels[j][i][CHROMA_V] += other.pixels[j][i][CHROMA_V];
- }
- }
- }
- /**
- * divides all pixels colour components by the provided divisor
- *
- * @param divisor
- */
- public void divide(float divisor)
- {
- for (int i = 0; i < blockSize; i++)
- {
- for (int j = 0; j < blockSize; j++)
- {
- pixels[j][i][LUMA] /= divisor;
- pixels[j][i][CHROMA_U] /= divisor;
- pixels[j][i][CHROMA_V] /= divisor;
- }
- }
- }
- }
- /**
- * Abstract Class providing the method signature to calculate the difference between blocks
- */
- public static abstract class BlockComparator
- {
- /**
- * 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.
- *
- * @param block1
- * @param block2
- * @return the magnitude of difference between the two provided blocks
- */
- abstract public float calculateDifference(Block block1, Block block2);
- }
- /**
- * Concrete implementation of BlockComparator with the difference calculated based on lumenosity
- */
- public static abstract class LumaComparator extends BlockComparator
- {
- public static final LumaComparator comparator = new LumaComparator()
- {
- public float calculateDifference(Block block1, Block block2)
- {
- float diffMetric = 0;
- for (int i = 0; i < LUMA_BLOCK_SIZE; i++)
- {
- for (int j = 0; j < LUMA_BLOCK_SIZE; j++)
- {
- diffMetric += (block1.pixels[j][i][LUMA] - block2.pixels[j][i][LUMA]) * (block1.pixels[j][i][LUMA] - block2.pixels[j][i][LUMA]);
- }
- }
- return diffMetric;
- }
- };
- }
- /**
- * Concrete implementation of BlockComparator with the difference calculated based on 'chromanosity'
- */
- public static abstract class ChromaComparator extends BlockComparator
- {
- public static final ChromaComparator comparator = new ChromaComparator()
- {
- public float calculateDifference(Block block1, Block block2)
- {
- float diff;
- float diffMetric = 0;
- for (int i = 0; i < CHROMA_BLOCK_SIZE; i++)
- {
- for (int j = 0; j < CHROMA_BLOCK_SIZE; j++)
- {
- diff = (block1.pixels[j][i][CHROMA_U] - block2.pixels[j][i][CHROMA_U]);
- diffMetric += diff * diff * diff * diff;
- diff = (block1.pixels[j][i][CHROMA_V] - block2.pixels[j][i][CHROMA_V]);
- diffMetric += diff * diff * diff * diff;
- }
- }
- return diffMetric;
- }
- };
- }
- public static void printCommandLineOptions()
- {
- System.out.println("Usage: \n" +
- " For single image compression: java CompressAnImageToA4kibPreview -c <INPUT IMAGE> [<COMPRESSED IMAGE>]\n" +
- " For multiple image compression: java CompressAnImageToA4kibPreview -c <INPUT IMAGES DIR> [<COMPRESSED IMAGE DIR>]\n" +
- " For single image decompression: java CompressAnImageToA4kibPreview -d <COMPRESSED IMAGE> [<DECOMPRESSED IMAGE>\n" +
- " For multiple image decompression: java CompressAnImageToA4kibPreview -d <COMPRESSED IMAGE DIR> [<DECOMPRESSED IMAGES DIR>]\n" +
- "\n" +
- "If optional parameters are not set then defaults will be used:\n" +
- " For single image compression, compressed image will be created in same directory are input image and have '.compressed' file extension.\n" +
- " 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" +
- " For single image decompression, decompressed image will be created in same directory are input image and have '.out.png' file extension.\n" +
- " For multiple image decompression, decompressed images will be created a new 'out' sub directory of <COMPRESSED IMAGE DIR> and have '.png' file extensions.\n" +
- "\n" +
- "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.");
- }
- public static void main(String[] args) throws Exception
- {
- if (args.length < 2 || args.length > 3)
- {
- printCommandLineOptions();
- return;
- }
- File filterFile = new File("data/images/working/lumaFilters_" + LUMA_BLOCK_SIZE + "_" + CHROMA_BLOCK_SIZE + "_" + LUMA_FILTER_BIT_DEPTH + "_" + CHROMA_FILTER_BIT_DEPTH + ".png");
- FilterManager filters = (GENERATE == GENERATE_ALWAYS_ENUM || (GENERATE == GENERATE_ONLY_IF_NEEDED_ENUM && !filterFile.exists())) ? FilterManager.create() : FilterManager.load();
- File outputFile;
- File inputFile = new File(args[1]);
- boolean compress = args[0].equals("-c");
- if (compress)
- {
- if (inputFile.exists())
- {
- if (inputFile.isDirectory())
- {
- if (args.length == 3)
- {
- outputFile = new File(args[2]);
- if (outputFile.exists() && outputFile.isFile())
- {
- System.out.println("ERROR: " + outputFile + " is not a directory.\n\n");
- printCommandLineOptions();
- return;
- }
- }
- else
- {
- outputFile = new File(args[1] + "/out");
- if (outputFile.exists() && outputFile.isFile())
- {
- System.out.println("ERROR: " + outputFile + " is not a directory.\n\n");
- printCommandLineOptions();
- return;
- }
- }
- outputFile.mkdirs();
- }
- else
- {
- if (args.length == 3)
- {
- outputFile = new File(args[2]);
- if (outputFile.exists() && outputFile.isDirectory())
- {
- System.out.println("ERROR: " + outputFile + " is a directory.\n\n");
- printCommandLineOptions();
- return;
- }
- }
- else
- {
- outputFile = new File(args[1] + ".out.compressed");
- if (outputFile.exists() && outputFile.isDirectory())
- {
- System.out.println("ERROR: " + outputFile + " is a directory.\n\n");
- printCommandLineOptions();
- return;
- }
- }
- }
- }
- else
- {
- System.out.println("ERROR: " + inputFile + " does not exist.\n\n");
- printCommandLineOptions();
- return;
- }
- compress(inputFile, outputFile, filters);
- }
- else
- {
- if (inputFile.exists())
- {
- if (inputFile.isDirectory())
- {
- if (args.length == 3)
- {
- outputFile = new File(args[2]);
- if (outputFile.exists() && outputFile.isFile())
- {
- System.out.println("ERROR: " + outputFile + " is not a directory.\n\n");
- printCommandLineOptions();
- return;
- }
- }
- else
- {
- outputFile = new File(args[1] + "/out");
- if (outputFile.exists() && outputFile.isFile())
- {
- System.out.println("ERROR: " + outputFile + " is not a directory.\n\n");
- printCommandLineOptions();
- return;
- }
- }
- outputFile.mkdirs();
- }
- else
- {
- if (args.length == 3)
- {
- outputFile = new File(args[2]);
- if (outputFile.exists() && outputFile.isDirectory())
- {
- System.out.println("ERROR: " + outputFile + " is a directory.\n\n");
- printCommandLineOptions();
- return;
- }
- }
- else
- {
- outputFile = new File(args[1] + ".out.png");
- if (outputFile.exists() && outputFile.isDirectory())
- {
- System.out.println("ERROR: " + outputFile + " is a directory.\n\n");
- printCommandLineOptions();
- return;
- }
- }
- }
- }
- else
- {
- System.out.println("ERROR: " + inputFile + " does not exist.\n\n");
- printCommandLineOptions();
- return;
- }
- decompress(inputFile, outputFile, filters);
- }
- }
- /**
- * Compresses the input image(s) and writes to origOutputFile
- * @param origInputFile
- * @param origOutputFile
- * @param filters
- * @throws Exception
- */
- private static void compress(File origInputFile, File origOutputFile, FilterManager filters) throws Exception
- {
- String[] files = new String[] { origInputFile.getAbsolutePath() };
- if (origInputFile.isDirectory())
- {
- List<String> list = new ArrayList<String>();
- for (File file : origInputFile.listFiles())
- {
- list.add(file.getAbsolutePath());
- }
- files = list.toArray(new String[0]);
- }
- String outputDir = origOutputFile.getAbsolutePath();
- for (String inputFileString : files)
- {
- ByteArrayOutputStream defBaos = new ByteArrayOutputStream();
- int compressedSize = 0;
- int sampledDimensionScalar = MIN_SAMPLED_DIMENSION;
- File inputFile = new File(inputFileString);
- String outputFileString = (origInputFile.isFile() ? origOutputFile : new File(outputDir + File.separator + inputFile.getName())).getAbsolutePath() + ".compressed";
- File outputFile = new File(outputFileString);
- if (inputFile.isFile())
- {
- System.out.println("Compressing "+inputFile.getAbsolutePath()+"...");
- BufferedImage inputImage = ImageIO.read(inputFile);
- outputFile.getParentFile().mkdirs();
- while (compressedSize < DESIRED_COMPRESSED_SIZE_BYTES && sampledDimensionScalar < MAX_SAMPLED_DIMENSION_SCALAR)
- {
- sampledDimensionScalar += 1;
- if (compressedSize > 0)
- {
- System.out.println("Compressed Size: " + compressedSize + " Scalar: " + (sampledDimensionScalar - 1));
- byte[] data = defBaos.toByteArray();
- defBaos.reset();
- FileOutputStream fos = new FileOutputStream(outputFile);
- fos.write(data);
- fos.close();
- }
- int previousFrameChromaFilter = 0;
- int previousFrameLumaFilter = 0;
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- BitOutputStream bos = new BitOutputStream(baos);
- // Down sample input image
- int sampledDimension = CHROMA_BLOCK_SIZE * sampledDimensionScalar;
- BufferedImage scaledImage = new BufferedImage(sampledDimension, sampledDimension, BufferedImage.TYPE_INT_RGB);
- scaledImage.getGraphics().drawImage(inputImage.getScaledInstance(sampledDimension, sampledDimension, BufferedImage.SCALE_AREA_AVERAGING), 0, 0, null);
- float[][][] pixels = convertToYUVImage(scaledImage);
- // save image dimensions
- bos.write(inputImage.getWidth(), IMAGE_DIMENSION_BITS);
- bos.write(inputImage.getHeight(), IMAGE_DIMENSION_BITS);
- bos.write(sampledDimensionScalar, MAX_SAMPLED_DIMENSION_SCALAR_BITS);
- // match sampled image blocks' lumenosity to filters...
- float[][][] categorisedPixels = new float[sampledDimension][sampledDimension][3];
- for (int i = 0; i < sampledDimension; i += LUMA_BLOCK_SIZE)
- {
- for (int j = 0; j < sampledDimension; j += LUMA_BLOCK_SIZE)
- {
- Block block = new Block(pixels, j, i, LUMA_BLOCK_SIZE);
- Block leastDifferenceLumaFilter = null;
- float leastDifference = Float.MAX_VALUE;
- float difference;
- for (Block filter : filters.lumaFilters)
- {
- difference = LumaComparator.comparator.calculateDifference(filter, block);
- if (difference < leastDifference)
- {
- leastDifference = difference;
- leastDifferenceLumaFilter = filter;
- }
- }
- // save luma block
- if (LUMA_SIMILARITY_BIT_DEPTH == 0)
- {
- bos.write(leastDifferenceLumaFilter.closestMatchingFilterId, LUMA_FILTER_BIT_DEPTH);
- }
- else
- out:
- {
- int[] lumaFilterSimilarityOrdering = filters.lumaFilterSimilarityOrdering[previousFrameLumaFilter];
- for (int k = 0; k < LUMA_SIMILARITY_COUNT; k++)
- if (lumaFilterSimilarityOrdering[k] == leastDifferenceLumaFilter.closestMatchingFilterId)
- {
- if (LUMA_FILTER_COUNT > LUMA_SIMILARITY_COUNT)
- bos.write(true);
- bos.write(k, LUMA_SIMILARITY_BIT_DEPTH);
- break out;
- }
- if (LUMA_FILTER_COUNT > LUMA_SIMILARITY_COUNT)
- bos.write(false);
- bos.write(leastDifferenceLumaFilter.closestMatchingFilterId, LUMA_FILTER_BIT_DEPTH);
- }
- previousFrameLumaFilter = leastDifferenceLumaFilter.closestMatchingFilterId;
- // copy matched filter's lumenosity into corresponding output image's block
- for (int y = 0; y < LUMA_BLOCK_SIZE; y++)
- {
- for (int x = 0; x < LUMA_BLOCK_SIZE; x++)
- {
- categorisedPixels[j + x][i + y][LUMA] = leastDifferenceLumaFilter.pixels[x][y][LUMA];
- }
- }
- }
- }
- // match sampled image blocks' 'chromanosity' to filters...
- for (int i = 0; i < sampledDimension; i += CHROMA_BLOCK_SIZE)
- {
- for (int j = 0; j < sampledDimension; j += CHROMA_BLOCK_SIZE)
- {
- Block block = new Block(pixels, j, i, CHROMA_BLOCK_SIZE);
- Block leastDifferenceChromaFilter = null;
- float leastDifference = Float.MAX_VALUE;
- float difference;
- for (Block filter : filters.chromaFilters)
- {
- difference = ChromaComparator.comparator.calculateDifference(filter, block);
- if (difference < leastDifference)
- {
- leastDifference = difference;
- leastDifferenceChromaFilter = filter;
- }
- }
- // save chroma block
- if (CHROMA_SIMILARITY_BIT_DEPTH == 0)
- {
- bos.write(leastDifferenceChromaFilter.closestMatchingFilterId, CHROMA_FILTER_BIT_DEPTH);
- }
- else
- out:
- {
- int[] chromaFilterSimilarityOrdering = filters.chromaFilterSimilarityOrdering[previousFrameChromaFilter];
- for (int k = 0; k < CHROMA_SIMILARITY_COUNT; k++)
- if (chromaFilterSimilarityOrdering[k] == leastDifferenceChromaFilter.closestMatchingFilterId)
- {
- if (CHROMA_FILTER_COUNT > CHROMA_SIMILARITY_COUNT)
- bos.write(true);
- bos.write(k, CHROMA_SIMILARITY_BIT_DEPTH);
- break out;
- }
- if (CHROMA_FILTER_COUNT > CHROMA_SIMILARITY_COUNT)
- bos.write(false);
- bos.write(leastDifferenceChromaFilter.closestMatchingFilterId, CHROMA_FILTER_BIT_DEPTH);
- }
- previousFrameChromaFilter = leastDifferenceChromaFilter.closestMatchingFilterId;
- // copy matched filter's 'chromanosity' into corresponding output image's block
- for (int y = 0; y < CHROMA_BLOCK_SIZE; y++)
- {
- for (int x = 0; x < CHROMA_BLOCK_SIZE; x++)
- {
- categorisedPixels[j + x][i + y][CHROMA_U] = leastDifferenceChromaFilter.pixels[x][y][CHROMA_U];
- categorisedPixels[j + x][i + y][CHROMA_V] = leastDifferenceChromaFilter.pixels[x][y][CHROMA_V];
- }
- }
- }
- }
- bos.close();
- DeflaterOutputStream deflater = new DeflaterOutputStream(defBaos, new Deflater(Deflater.BEST_COMPRESSION));
- deflater.write(baos.toByteArray());
- deflater.close();
- compressedSize = defBaos.size();
- }
- System.out.println("Compressed "+inputFile.getAbsolutePath()+".");
- }
- }
- }
- /**
- * Decompresses the input image(s) and writes to origOutputFile
- */
- private static void decompress(File origInputFile, File origOutputFile, FilterManager filters) throws Exception
- {
- String[] files = new String[] { origInputFile.getAbsolutePath() };
- if (origInputFile.isDirectory())
- {
- List<String> list = new ArrayList<String>();
- for (File file : origInputFile.listFiles())
- {
- list.add(file.getAbsolutePath());
- }
- files = list.toArray(new String[0]);
- }
- String outputDir = origOutputFile.getAbsolutePath();
- for (String inputFileString : files)
- {
- File inputFile = new File(inputFileString);
- if (inputFile.isFile())
- {
- File decompressedFile = origInputFile.isFile() ? origOutputFile : new File(outputDir + File.separator + inputFile.getName()+".png");
- filters = FilterManager.load();
- decodeFrame(decompressedFile, filters, new FileInputStream(inputFile));
- }
- }
- }
- static void decodeFrame(File outputFile, FilterManager filters, InputStream is) throws IOException
- {
- InflaterInputStream inflater = new InflaterInputStream(is);
- BitInputStream bis = new BitInputStream(inflater, 8);
- int previousFrameLumaFilter = 0;
- int previousFrameChromaFilter = 0;
- int width = (int) bis.read(IMAGE_DIMENSION_BITS);
- int height = (int) bis.read(IMAGE_DIMENSION_BITS);
- int sampledDimensionScalar = (int) bis.read(MAX_SAMPLED_DIMENSION_SCALAR_BITS);
- int sampledDimension = CHROMA_BLOCK_SIZE * sampledDimensionScalar;
- float[][][] pixels = new float[sampledDimension][sampledDimension][3];
- for (int i = 0; i < sampledDimension; i += LUMA_BLOCK_SIZE)
- {
- for (int j = 0; j < sampledDimension; j += LUMA_BLOCK_SIZE)
- {
- int filterId;
- if (LUMA_SIMILARITY_BIT_DEPTH == 0 || (LUMA_FILTER_COUNT > LUMA_SIMILARITY_COUNT && !bis.read()))
- {
- filterId = (int) bis.read(LUMA_FILTER_BIT_DEPTH);
- }
- else
- {
- filterId = filters.lumaFilterSimilarityOrdering[previousFrameLumaFilter][(int) bis.read(LUMA_SIMILARITY_BIT_DEPTH)];
- }
- previousFrameLumaFilter = filterId;
- Block filter = filters.lumaFilters[filterId];
- // copy matched filter's lumenosity into corresponding output image's block
- for (int y = 0; y < LUMA_BLOCK_SIZE; y++)
- {
- for (int x = 0; x < LUMA_BLOCK_SIZE; x++)
- {
- pixels[j + x][i + y][LUMA] = filter.pixels[x][y][LUMA];
- }
- }
- }
- }
- // match sampled image blocks' 'chromanosity' to filters...
- for (int i = 0; i < sampledDimension; i += CHROMA_BLOCK_SIZE)
- {
- for (int j = 0; j < sampledDimension; j += CHROMA_BLOCK_SIZE)
- {
- int filterId;
- if (CHROMA_SIMILARITY_BIT_DEPTH == 0 || (CHROMA_FILTER_COUNT > CHROMA_SIMILARITY_COUNT && !bis.read()))
- {
- filterId = (int) bis.read(CHROMA_FILTER_BIT_DEPTH);
- }
- else
- {
- filterId = filters.chromaFilterSimilarityOrdering[previousFrameChromaFilter][(int) bis.read(CHROMA_SIMILARITY_BIT_DEPTH)];
- }
- previousFrameChromaFilter = filterId;
- Block filter = filters.chromaFilters[filterId];
- // copy matched filter's 'chromanosity' into corresponding output image's block
- for (int y = 0; y < CHROMA_BLOCK_SIZE; y++)
- {
- for (int x = 0; x < CHROMA_BLOCK_SIZE; x++)
- {
- pixels[j + x][i + y][CHROMA_U] = filter.pixels[x][y][CHROMA_U];
- pixels[j + x][i + y][CHROMA_V] = filter.pixels[x][y][CHROMA_V];
- }
- }
- }
- }
- // render the scaled output image
- BufferedImage yuvImage = new BufferedImage(sampledDimension, sampledDimension, BufferedImage.TYPE_INT_RGB);
- renderYUV(yuvImage, pixels);
- // save the output image, resized to the input image's dimensons
- ImageIO.write(blurResize(yuvImage, width, height), "png", outputFile);
- // BufferedImage scaledImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
- // scaledImage.getGraphics().drawImage(yuvImage.getScaledInstance(width, height, Image.SCALE_FAST), 0, 0, null);
- // ImageIO.write(scaledImage, "png", new File(outputFile.getAbsolutePath() + ".nonBlurred.png"));
- System.out.println("Decompressed "+outputFile.getAbsolutePath());
- }
- /**
- * Converts the given buffered image into a 3-dimensional array of YUV pixels
- *
- * @param image
- * the buffered image to convert
- * @return 3-dimensional array of YUV pixels
- */
- static private float[][][] convertToYUVImage(BufferedImage image)
- {
- final int width = image.getWidth();
- final int height = image.getHeight();
- float[][][] yuv = new float[width][height][3];
- for (int y = 0; y < height; y++)
- {
- for (int x = 0; x < width; x++)
- {
- int rgb = image.getRGB(x, y);
- yuv[x][y] = rgb2yuv(rgb);
- }
- }
- return yuv;
- }
- /**
- * Renders the given YUV image into the given buffered image.
- *
- * @param image
- * the buffered image to render to
- * @param pixels
- * the YUV image to render.
- * @param dimension
- * the
- */
- static private void renderYUV(BufferedImage image, float[][][] pixels)
- {
- final int height = pixels.length;
- final int width = pixels[0].length;
- int rgb;
- for (int y = 0; y < height; y++)
- {
- for (int x = 0; x < width; x++)
- {
- rgb = yuv2rgb(pixels[x][y]);
- image.setRGB(x, y, rgb);
- }
- }
- }
- /**
- * Converts a RGB pixel into a YUV pixel
- *
- * @param rgb
- * a pixel encoded as 24 bit RGB
- * @return array representing a pixel. Consisting of Y,U and V components
- */
- static float[] rgb2yuv(int rgb)
- {
- float red = EIGHT_BIT_DIVISOR * ((rgb >> 16) & 0xFF);
- float green = EIGHT_BIT_DIVISOR * ((rgb >> 8) & 0xFF);
- float blue = EIGHT_BIT_DIVISOR * (rgb & 0xFF);
- float Y = 0.299F * red + 0.587F * green + 0.114F * blue;
- float U = (blue - Y) * 0.565F;
- float V = (red - Y) * 0.713F;
- return new float[] { Y, U, V };
- }
- /**
- * Converts a YUV pixel into a RGB pixel
- *
- * @param yuv
- * array representing a pixel. Consisting of Y,U and V components
- * @return a pixel encoded as 24 bit RGB
- */
- static int yuv2rgb(float[] yuv)
- {
- int red = (int) ((yuv[0] + 1.403 * yuv[2]) * 256);
- int green = (int) ((yuv[0] - 0.344 * yuv[1] - 0.714 * yuv[2]) * 256);
- int blue = (int) ((yuv[0] + 1.77 * yuv[1]) * 256);
- // clamp to 0-255 range
- red = red < 0 ? 0 : red > 255 ? 255 : red;
- green = green < 0 ? 0 : green > 255 ? 255 : green;
- blue = blue < 0 ? 0 : blue > 255 ? 255 : blue;
- return (red << 16) | (green << 8) | blue;
- }
- /**
- * Blurs an image using a 3x3 weighted kernel.
- *
- * @param image
- * the image to blur from
- * @return the blurred image
- */
- public static BufferedImage blurImage(BufferedImage image)
- {
- float[] blurKernel = { 0.0375f, 0.0625f, 0.0375f, 0.0625f, 0.6f, 0.0625f, 0.0375f, 0.0625f, 0.0375f };
- Map<Key, Object> map = new HashMap<Key, Object>();
- map.put(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
- map.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
- map.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
- RenderingHints hints = new RenderingHints(map);
- BufferedImageOp op = new ConvolveOp(new Kernel(3, 3, blurKernel), ConvolveOp.EDGE_NO_OP, hints);
- return op.filter(image, null);
- }
- /**
- * Resizes the input image to the given width and height.
- *
- * 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
- *
- * @param image
- * the image to resize
- * @param newWidth
- * the desired width of the resized image
- * @param newHeight
- * the desired height of the resized image
- * @return a new resized image
- */
- public static BufferedImage blurResize(BufferedImage image, int newWidth, int newHeight)
- {
- int width = image.getWidth();
- int height = image.getHeight();
- while (width * 2 <= newWidth && height * 2 <= newHeight)
- {
- BufferedImage newImage = new BufferedImage(width * 2, height * 2, BufferedImage.TYPE_INT_RGB);
- newImage.getGraphics().drawImage(image.getScaledInstance(width * 2, height * 2, Image.SCALE_AREA_AVERAGING), 0, 0, null);
- image = blurImage(newImage);
- width = image.getWidth();
- height = image.getHeight();
- }
- BufferedImage newImage = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_RGB);
- newImage.getGraphics().drawImage(image.getScaledInstance(newWidth, newHeight, Image.SCALE_AREA_AVERAGING), 0, 0, null);
- return newImage;
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement