view ar/com/hjg/pngj/pixels/CompressorStream.java @ 6:da7f11dcc6fd pngj

move
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 15 Apr 2016 10:36:51 +0200 (2016-04-15)
parents com/hjg/pngj/pixels/CompressorStream.java@3f418d4451d6
children
line wrap: on
line source
package ar.com.hjg.pngj.pixels;

import java.io.OutputStream;

import ar.com.hjg.pngj.IDatChunkWriter;

/**
 * This is an OutputStream that compresses (via Deflater or a deflater-like object), and optionally passes the
 * compressed stream to another output stream.
 * 
 * It allows to compute in/out/ratio stats.
 * 
 * It works as a stream (similar to DeflaterOutputStream), but it's peculiar in that it expects that each writes has a
 * fixed length (other lenghts are accepted, but it's less efficient) and that the total amount of bytes is known (so it
 * can close itself, but it can also be closed on demand) In PNGJ use, the block is typically a row (including filter
 * byte).
 * 
 * We use this to do the real compression (with Deflate) but also to compute tentative estimators
 * 
 * If not closed, it can be recicled via reset()
 * 
 * 
 */
public abstract class CompressorStream extends OutputStream {

  protected IDatChunkWriter idatChunkWriter;
  public final int blockLen;
  public final long totalbytes;

  boolean closed = false;
  protected boolean done = false;
  protected long bytesIn = 0;
  protected long bytesOut = 0;
  protected int block = -1;

  /** optionally stores the first byte of each block (row) */
  private byte[] firstBytes;
  protected boolean storeFirstByte = false;

  /**
   * 
   * @param idatCw Can be null (if we are only interested in compute compression ratio)
   * @param blockLen Estimated maximum block length. If unknown, use -1.
   * @param totalbytes Expected total bytes to be fed. If unknown, use -1.
   */
  public CompressorStream(IDatChunkWriter idatCw, int blockLen, long totalbytes) {
    this.idatChunkWriter = idatCw;
    if (blockLen < 0)
      blockLen = 4096;
    if (totalbytes < 0)
      totalbytes = Long.MAX_VALUE;
    if (blockLen < 1 || totalbytes < 1)
      throw new RuntimeException(" maxBlockLen or totalLen invalid");
    this.blockLen = blockLen;
    this.totalbytes = totalbytes;
  }

  /** Releases resources. Idempotent. */
  @Override
  public void close() {
    done();
    if(idatChunkWriter!=null) idatChunkWriter.close();
    closed = true;
  }

  /**
   * Will be called automatically when the number of bytes reaches the total expected Can be also be called from
   * outside. This should set the flag done=true
   */
  public abstract void done();

  @Override
  public final void write(byte[] data) {
    write(data, 0, data.length);
  }

  @Override
  public final void write(byte[] data, int off, int len) {
    block++;
    if (len <= blockLen) { // normal case
      mywrite(data, off, len);
      if (storeFirstByte && block < firstBytes.length) {
        firstBytes[block] = data[off]; // only makes sense in this case
      }
    } else {
      while (len > 0) {
        mywrite(data, off, blockLen);
        off += blockLen;
        len -= blockLen;
      }
    }
    if (bytesIn >= totalbytes)
      done();

  }

  /**
   * same as write, but guarantedd to not exceed blockLen The implementation should update bytesOut and bytesInt but not
   * check for totalBytes
   */
  public abstract void mywrite(byte[] data, int off, int len);


  /**
   * compressed/raw. This should be called only when done
   */
  public final double getCompressionRatio() {
    return bytesOut == 0 ? 1.0 : bytesOut / (double) bytesIn;
  }

  /**
   * raw (input) bytes. This should be called only when done
   */
  public final long getBytesRaw() {
    return bytesIn;
  }

  /**
   * compressed (out) bytes. This should be called only when done
   */
  public final long getBytesCompressed() {
    return bytesOut;
  }

  public boolean isClosed() {
    return closed;
  }

  public boolean isDone() {
    return done;
  }

  public byte[] getFirstBytes() {
    return firstBytes;
  }

  public void setStoreFirstByte(boolean storeFirstByte, int nblocks) {
    this.storeFirstByte = storeFirstByte;
    if (this.storeFirstByte) {
      if (firstBytes == null || firstBytes.length < nblocks)
        firstBytes = new byte[nblocks];
    } else
      firstBytes = null;
  }

  public void reset() {
    done();
    bytesIn = 0;
    bytesOut = 0;
    block = -1;
    done = false;
  }

  @Override
  public void write(int i) { // should not be used
    write(new byte[] {(byte) i});
  }


}