From 5b16f45b72a6c8bb5d8f434319950252d66622cd Mon Sep 17 00:00:00 2001
From: Vojtech Moravec <vojtech.moravec.st@vsb.cz>
Date: Sat, 21 Mar 2020 19:55:03 +0100
Subject: [PATCH] Write compressed plane data sizes to the header.

---
 FileFormat.docx                               | Bin 14523 -> 14857 bytes
 .../compression/IImageCompressor.java         |   2 +-
 .../compression/ImageCompressor.java          |  36 +++++++++++++++---
 .../compression/SQImageCompressor.java        |   5 ++-
 .../compression/SQImageDecompressor.java      |  28 ++++++++++----
 .../compression/VQImageCompressor.java        |   5 ++-
 .../fileformat/QCMPFileHeader.java            |   6 +++
 7 files changed, 67 insertions(+), 15 deletions(-)

diff --git a/FileFormat.docx b/FileFormat.docx
index a31fd8ffb333168cc1e291afa13d6bbfc421675e..a3ae876fcd1f9627bbf2425eb4651a0fe69d1586 100644
GIT binary patch
delta 4766
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
TV3@3FEH`<ng#cT;8OUG&WgP`O

delta 4455
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
SQozT+$1sJNfkDs|!~+2G|4~5z

diff --git a/src/main/java/azgracompress/compression/IImageCompressor.java b/src/main/java/azgracompress/compression/IImageCompressor.java
index d5bb08f..be9d499 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 0239a1b..75ed35d 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 be249be..7c4258e 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 34adc34..ead979a 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 695cd16..2941305 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 80da098..0912f7e 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 {
-- 
GitLab