diff --git a/FileFormat.docx b/FileFormat.docx index a31fd8ffb333168cc1e291afa13d6bbfc421675e..a3ae876fcd1f9627bbf2425eb4651a0fe69d1586 100644 Binary files a/FileFormat.docx and b/FileFormat.docx differ diff --git a/src/main/java/azgracompress/compression/IImageCompressor.java b/src/main/java/azgracompress/compression/IImageCompressor.java index d5bb08f1aa054f26df2accb3dca80415bdfc1e91..be9d499576b1de60a8e24bfd86f0580a1d82d8bb 100644 --- a/src/main/java/azgracompress/compression/IImageCompressor.java +++ b/src/main/java/azgracompress/compression/IImageCompressor.java @@ -12,7 +12,7 @@ public interface IImageCompressor { * @param compressStream Compressed data stream. * @throws ImageCompressionException when compression fails. */ - void compress(DataOutputStream compressStream) throws ImageCompressionException; + long[] compress(DataOutputStream compressStream) throws ImageCompressionException; /** * Train codebook from selected frames and save the learned codebook to cache file. diff --git a/src/main/java/azgracompress/compression/ImageCompressor.java b/src/main/java/azgracompress/compression/ImageCompressor.java index 0239a1b3488f9e9fa88f6c251bc0424be5d640ed..75ed35d8171b8e4a442aa4cd1bff784223736dcd 100644 --- a/src/main/java/azgracompress/compression/ImageCompressor.java +++ b/src/main/java/azgracompress/compression/ImageCompressor.java @@ -4,12 +4,11 @@ import azgracompress.cli.ParsedCliOptions; import azgracompress.compression.exception.ImageCompressionException; import azgracompress.fileformat.QCMPFileHeader; -import java.io.BufferedOutputStream; -import java.io.DataOutputStream; -import java.io.FileOutputStream; +import java.io.*; public class ImageCompressor extends CompressorDecompressorBase { + final int PLANE_DATA_SIZES_OFFSET = 23; private final int codebookSize; public ImageCompressor(ParsedCliOptions options) { @@ -67,18 +66,19 @@ public class ImageCompressor extends CompressorDecompressorBase { return false; } + long[] planeDataSizes = null; + try (FileOutputStream fos = new FileOutputStream(options.getOutputFile(), false); DataOutputStream compressStream = new DataOutputStream(new BufferedOutputStream(fos, 8192))) { final QCMPFileHeader header = createHeader(); header.writeHeader(compressStream); - imageCompressor.compress(compressStream); + planeDataSizes = imageCompressor.compress(compressStream); if (options.isVerbose()) { reportCompressionRatio(header, compressStream.size()); } - } catch (ImageCompressionException ex) { System.err.println(ex.getMessage()); return false; @@ -86,9 +86,35 @@ public class ImageCompressor extends CompressorDecompressorBase { e.printStackTrace(); return false; } + + if (planeDataSizes == null) { + System.err.println("Plane data sizes are unknown!"); + return false; + } + + try (RandomAccessFile raf = new RandomAccessFile(options.getOutputFile(), "rw")) { + raf.seek(PLANE_DATA_SIZES_OFFSET); + writePlaneDataSizes(raf, planeDataSizes); + } catch (IOException ex) { + ex.printStackTrace(); + return false; + } + return true; } + /** + * Write plane data size to compressed file. + * + * @param outStream Compressed file stream. + * @param planeDataSizes Written compressed plane sizes. + * @throws IOException when fails to write plane data size. + */ + private void writePlaneDataSizes(RandomAccessFile outStream, final long[] planeDataSizes) throws IOException { + for (final long planeDataSize : planeDataSizes) { + outStream.writeInt((int) planeDataSize); + } + } /** * Create QCMPFile header for compressed file. diff --git a/src/main/java/azgracompress/compression/SQImageCompressor.java b/src/main/java/azgracompress/compression/SQImageCompressor.java index be249be3c668f042cbd0520e5f17133b04cdaca3..7c4258ec4a1c322517cba0dd415d1c8860af4da0 100644 --- a/src/main/java/azgracompress/compression/SQImageCompressor.java +++ b/src/main/java/azgracompress/compression/SQImageCompressor.java @@ -88,8 +88,9 @@ public class SQImageCompressor extends CompressorDecompressorBase implements IIm * @param compressStream Stream to which compressed data will be written. * @throws ImageCompressionException When compress process fails. */ - public void compress(DataOutputStream compressStream) throws ImageCompressionException { + public long[] compress(DataOutputStream compressStream) throws ImageCompressionException { Stopwatch stopwatch = new Stopwatch(); + long[] planeDataSizes = new long[options.getImageDimension().getZ()]; final boolean hasGeneralQuantizer = options.hasCodebookCacheFolder() || options.hasReferencePlaneIndex(); ScalarQuantizer quantizer = null; @@ -161,10 +162,12 @@ public class SQImageCompressor extends CompressorDecompressorBase implements IIm } catch (Exception ex) { throw new ImageCompressionException("Unable to write indices to OutBitStream.", ex); } + // TODO: Fill plane data size stopwatch.stop(); Log("Plane time: " + stopwatch.getElapsedTimeString()); Log(String.format("Finished processing of plane %d", planeIndex)); } + return planeDataSizes; } private int[] loadConfiguredPlanesData() throws ImageCompressionException { diff --git a/src/main/java/azgracompress/compression/SQImageDecompressor.java b/src/main/java/azgracompress/compression/SQImageDecompressor.java index 34adc34d9ca1f9e9e8761dba01661baa1265adc5..ead979a372e7c91939eacbe385fda805ed4655a5 100644 --- a/src/main/java/azgracompress/compression/SQImageDecompressor.java +++ b/src/main/java/azgracompress/compression/SQImageDecompressor.java @@ -3,7 +3,9 @@ package azgracompress.compression; import azgracompress.cli.ParsedCliOptions; import azgracompress.compression.exception.ImageDecompressionException; import azgracompress.fileformat.QCMPFileHeader; +import azgracompress.huffman.Huffman; import azgracompress.io.InBitStream; +import azgracompress.quantization.scalar.ScalarQuantizationCodebook; import azgracompress.utilities.Stopwatch; import azgracompress.utilities.TypeConverter; @@ -16,17 +18,20 @@ public class SQImageDecompressor extends CompressorDecompressorBase implements I super(options); } - private int[] readScalarQuantizationValues(DataInputStream compressedStream, - final int n) throws ImageDecompressionException { - int[] quantizationValues = new int[n]; + private ScalarQuantizationCodebook readScalarQuantizationValues(DataInputStream compressedStream) throws ImageDecompressionException { + int[] quantizationValues = new int[codebookSize]; + long[] symbolFrequencies = new long[codebookSize]; try { - for (int i = 0; i < n; i++) { + for (int i = 0; i < codebookSize; i++) { quantizationValues[i] = compressedStream.readUnsignedShort(); } + for (int i = 0; i < codebookSize; i++) { + symbolFrequencies[i] = compressedStream.readLong(); + } } catch (IOException ioEx) { throw new ImageDecompressionException("Unable to read quantization values from compressed stream.", ioEx); } - return quantizationValues; + return new ScalarQuantizationCodebook(quantizationValues, symbolFrequencies); } @Override @@ -51,6 +56,8 @@ public class SQImageDecompressor extends CompressorDecompressorBase implements I public void decompress(DataInputStream compressedStream, DataOutputStream decompressStream, QCMPFileHeader header) throws ImageDecompressionException { + + final int[] huffmanSymbols = createHuffmanSymbols(); final int codebookSize = (int) Math.pow(2, header.getBitsPerPixel()); final int planeCountForDecompression = header.getImageSizeZ(); @@ -58,10 +65,13 @@ public class SQImageDecompressor extends CompressorDecompressorBase implements I final int planeIndicesDataSize = (int) Math.ceil((planePixelCount * header.getBitsPerPixel()) / 8.0); int[] quantizationValues = null; + Huffman huffman = null; if (!header.isCodebookPerPlane()) { // There is only one codebook. Log("Loading reference codebook..."); - quantizationValues = readScalarQuantizationValues(compressedStream, codebookSize); + huffman = null; + // TODO(Moravec): Handle loading of Huffman. + //quantizationValues = readScalarQuantizationValues(compressedStream, codebookSize); } Stopwatch stopwatch = new Stopwatch(); @@ -69,9 +79,13 @@ public class SQImageDecompressor extends CompressorDecompressorBase implements I stopwatch.restart(); if (header.isCodebookPerPlane()) { Log("Loading plane codebook..."); - quantizationValues = readScalarQuantizationValues(compressedStream, codebookSize); + ScalarQuantizationCodebook codebook = readScalarQuantizationValues(compressedStream); + quantizationValues = codebook.getCentroids(); + huffman = new Huffman(huffmanSymbols, codebook.getSymbolFrequencies()); + huffman.buildHuffmanTree(); } assert (quantizationValues != null); + assert (huffman != null); Log(String.format("Decompressing plane %d...", planeIndex)); byte[] decompressedPlaneData = null; diff --git a/src/main/java/azgracompress/compression/VQImageCompressor.java b/src/main/java/azgracompress/compression/VQImageCompressor.java index 695cd163a97122fd81733fec4f6e240919bdcf40..29413055f6933f39765df19ca311d16cf5f05cc5 100644 --- a/src/main/java/azgracompress/compression/VQImageCompressor.java +++ b/src/main/java/azgracompress/compression/VQImageCompressor.java @@ -85,7 +85,8 @@ public class VQImageCompressor extends CompressorDecompressorBase implements IIm * @param compressStream Stream to which compressed data will be written. * @throws ImageCompressionException When compress process fails. */ - public void compress(DataOutputStream compressStream) throws ImageCompressionException { + public long[] compress(DataOutputStream compressStream) throws ImageCompressionException { + long[] planeDataSizes = new long[options.getImageDimension().getZ()]; Stopwatch stopwatch = new Stopwatch(); final boolean hasGeneralQuantizer = options.hasCodebookCacheFolder() || options.hasReferencePlaneIndex(); VectorQuantizer quantizer = null; @@ -149,10 +150,12 @@ public class VQImageCompressor extends CompressorDecompressorBase implements IIm } catch (Exception ex) { throw new ImageCompressionException("Unable to write indices to OutBitStream.", ex); } + // TODO: Fill plane data size stopwatch.stop(); Log("Plane time: " + stopwatch.getElapsedTimeString()); Log(String.format("Finished processing of plane %d.", planeIndex)); } + return planeDataSizes; } diff --git a/src/main/java/azgracompress/fileformat/QCMPFileHeader.java b/src/main/java/azgracompress/fileformat/QCMPFileHeader.java index 80da0987432d8d2a7843661262d48c208aea97d7..0912f7e5dd85b4fc89e0f27dad29663c1d487bdd 100644 --- a/src/main/java/azgracompress/fileformat/QCMPFileHeader.java +++ b/src/main/java/azgracompress/fileformat/QCMPFileHeader.java @@ -69,6 +69,12 @@ public class QCMPFileHeader { outputStream.writeShort(vectorSizeX); outputStream.writeShort(vectorSizeY); outputStream.writeShort(vectorSizeZ); + + + // NOTE(Moravec): Allocate space for plane data sizes. Offset: 23. + for (int i = 0; i < imageSizeZ; i++) { + outputStream.writeInt(0x0); + } } public boolean readHeader(DataInputStream inputStream) throws IOException {