Skip to content
Snippets Groups Projects
ImageDecompressor.java 8.72 KiB
Newer Older
  • Learn to ignore specific revisions
  • Vojtech Moravec's avatar
    Vojtech Moravec committed
    package azgracompress.compression;
    
    Vojtech Moravec's avatar
    Vojtech Moravec committed
    import azgracompress.cli.ParsedCliOptions;
    
    import azgracompress.compression.exception.ImageDecompressionException;
    
    Vojtech Moravec's avatar
    Vojtech Moravec committed
    import azgracompress.fileformat.QCMPFileHeader;
    
    import azgracompress.utilities.Stopwatch;
    
    public class ImageDecompressor extends CompressorDecompressorBase {
    
        public ImageDecompressor(ParsedCliOptions options) {
            super(options);
    
        /**
         * Read compressed QCMP file header.
         *
         * @param inputStream Compressed data stream.
         * @return Decompressed file header.
         * @throws IOException when failed to read header.
         */
    
        private QCMPFileHeader readQCMPFileHeader(DataInputStream inputStream) throws IOException {
            QCMPFileHeader header = new QCMPFileHeader();
            if (!header.readHeader(inputStream)) {
    
                // Invalid QCMPFile header.
    
        /**
         * Get image plane decompressor for set quantization type.
         *
         * @return Correct implementation of image decompressor.
         */
    
        private IImageDecompressor getImageDecompressor(final QCMPFileHeader header) {
            switch (header.getQuantizationType()) {
    
                case Scalar:
                    return new SQImageDecompressor(options);
                case Vector1D:
                case Vector2D:
                    return new VQImageDecompressor(options);
                case Vector3D:
                case Invalid:
                default:
                    return null;
            }
        }
    
        /**
         * Inspect the compressed file by returning information contained in its header.
         *
         * @return Information from header.
         * @throws IOException When fails to read the header.
         */
    
        public String inspectCompressedFile() throws IOException {
            StringBuilder logBuilder = new StringBuilder();
    
            boolean validFile = true;
    
    Vojtech Moravec's avatar
    Vojtech Moravec committed
            QCMPFileHeader header = null;
            try (FileInputStream fileInputStream = new FileInputStream(options.getInputFile());
                 DataInputStream dataInputStream = new DataInputStream(fileInputStream)) {
                header = readQCMPFileHeader(dataInputStream);
    
    Vojtech Moravec's avatar
    Vojtech Moravec committed
                ioEx.printStackTrace();
                return "";
            }
    
            if (header == null) {
                logBuilder.append("Input file is not valid QCMPFile\n");
    
            } else {
    
    
                final boolean validHeader = header.validateHeader();
                logBuilder.append("Header is:\t\t").append(validHeader ? "valid" : "invalid").append('\n');
    
                logBuilder.append("Magic value:\t\t").append(header.getMagicValue()).append('\n');
                logBuilder.append("Quantization type\t");
                switch (header.getQuantizationType()) {
                    case Scalar:
                        logBuilder.append("Scalar\n");
                        break;
                    case Vector1D:
                        logBuilder.append("Vector1D\n");
                        break;
                    case Vector2D:
                        logBuilder.append("Vector2D\n");
                        break;
                    case Vector3D:
                        logBuilder.append("Vector3D\n");
                        break;
                    case Invalid:
                        logBuilder.append("INVALID\n");
                        break;
                }
                logBuilder.append("Bits per pixel:\t\t").append(header.getBitsPerPixel()).append('\n');
    
                logBuilder.append("Codebook:\t\t").append(header.isCodebookPerPlane() ? "one per plane\n" : "one for " +
                        "all\n");
    
                final int codebookSize = (int) Math.pow(2, header.getBitsPerPixel());
    
                logBuilder.append("Codebook size:\t\t").append(codebookSize).append('\n');
    
    
                logBuilder.append("Image size X:\t\t").append(header.getImageSizeX()).append('\n');
                logBuilder.append("Image size Y:\t\t").append(header.getImageSizeY()).append('\n');
                logBuilder.append("Image size Z:\t\t").append(header.getImageSizeZ()).append('\n');
    
                logBuilder.append("Vector size X:\t\t").append(header.getVectorSizeX()).append('\n');
                logBuilder.append("Vector size Y:\t\t").append(header.getVectorSizeY()).append('\n');
                logBuilder.append("Vector size Z:\t\t").append(header.getVectorSizeZ()).append('\n');
    
    
                final long fileSize = new File(options.getInputFile()).length();
    
    Vojtech Moravec's avatar
    Vojtech Moravec committed
                final long headerSize = header.getHeaderSize();
                final long dataSize = fileSize - headerSize;
    
    Vojtech Moravec's avatar
    Vojtech Moravec committed
                final IImageDecompressor decompressor = getImageDecompressor(header);
    
                if (decompressor != null) {
    
                    final long expectedDataSize = decompressor.getExpectedDataSize(header);
    
                    validFile = (dataSize == expectedDataSize);
    
    
                    logBuilder.append("File size:\t\t").append(fileSize).append(" B");
    
                    final long KB = (fileSize / 1000);
                    if (KB > 0) {
                        logBuilder.append(" (").append(KB).append(" KB)");
                        final long MB = (KB / 1000);
                        if (MB > 0) {
                            logBuilder.append(" (").append(MB).append(" MB)");
                        }
                    }
                    logBuilder.append('\n');
    
    Vojtech Moravec's avatar
    Vojtech Moravec committed
    
                    logBuilder.append("Header size:\t\t").append(headerSize).append(" Bytes\n");
    
                    logBuilder.append("Data size:\t\t").append(dataSize).append(" Bytes ")
                            .append(dataSize == expectedDataSize ? "(correct)\n" : "(INVALID)\n");
    
    Vojtech Moravec's avatar
    Vojtech Moravec committed
                    final long pixelCount = header.getImageDims().multiplyTogether();
                    final long uncompressedSize = 2 * pixelCount; // We assert 16 bit (2 byte) pixel.
    
                    final double compressionRatio = (double) fileSize / (double) uncompressedSize;
    
                    logBuilder.append(String.format("Compression ratio:\t%.4f\n", compressionRatio));
    
    Vojtech Moravec's avatar
    Vojtech Moravec committed
    
                    final double BPP = ((double) fileSize * 8.0) / (double) pixelCount;
    
                    logBuilder.append(String.format("Bits Per Pixel (BPP):\t%.4f\n", BPP));
    
    
            logBuilder.append("\n=== Input file is ").append(validFile ? "VALID" : "INVALID").append(" ===\n");
    
    
            if (header != null && options.isVerbose()) {
                final long[] planeDataSizes = header.getPlaneDataSizes();
                long planeIndex = 0;
                for (final long planeDataSize : planeDataSizes) {
                    logBuilder.append(String.format("Plane: %d - %d Bytes\n", planeIndex++, planeDataSize));
                }
            }
    
    
            return logBuilder.toString();
        }
    
        public boolean decompress() {
    
            final Stopwatch decompressionStopwatch = Stopwatch.startNew();
            final long decompressedFileSize;
    
            try (FileInputStream fileInputStream = new FileInputStream(options.getInputFile());
                 DataInputStream dataInputStream = new DataInputStream(fileInputStream)) {
    
                final QCMPFileHeader header = readQCMPFileHeader(dataInputStream);
                if (header == null) {
                    System.err.println("Failed to read QCMPFile header");
                    return false;
    
                if (!header.validateHeader()) {
                    System.err.println("QCMPFile header is invalid");
                    return false;
    
                decompressedFileSize = header.getImageDims().multiplyTogether();
    
                IImageDecompressor imageDecompressor = getImageDecompressor(header);
    
                if (imageDecompressor == null) {
                    System.err.println("Unable to create correct decompressor.");
                    return false;
    
                final long fileSize = new File(options.getInputFile()).length();
    
                final long dataSize = fileSize - header.getHeaderSize();
    
                final long expectedDataSize = imageDecompressor.getExpectedDataSize(header);
                if (dataSize != expectedDataSize) {
                    System.err.println("Invalid file size.");
                    return false;
    
                try (FileOutputStream fos = new FileOutputStream(options.getOutputFile(), false);
                     DataOutputStream decompressStream = new DataOutputStream(fos)) {
    
                    imageDecompressor.decompress(dataInputStream, decompressStream, header);
    
    
                } catch (ImageDecompressionException ex) {
                    System.err.println(ex.getMessage());
    
                    return false;
    
            } catch (IOException ioEx) {
                ioEx.printStackTrace();
                return false;
    
            decompressionStopwatch.stop();
            final double seconds = decompressionStopwatch.totalElapsedSeconds();
            final double MBSize = ((double) decompressedFileSize / 1000.0) / 1000.0;
            final double MBPerSec = MBSize / seconds;
            Log("Decompression speed: %.4f MB/s", MBPerSec);
    
    Vojtech Moravec's avatar
    Vojtech Moravec committed
    }