kusano fc6ab3
/* fitblk.c: example of fitting compressed output to a specified size
kusano fc6ab3
   Not copyrighted -- provided to the public domain
kusano fc6ab3
   Version 1.1  25 November 2004  Mark Adler */
kusano fc6ab3
kusano fc6ab3
/* Version history:
kusano fc6ab3
   1.0  24 Nov 2004  First version
kusano fc6ab3
   1.1  25 Nov 2004  Change deflateInit2() to deflateInit()
kusano fc6ab3
                     Use fixed-size, stack-allocated raw buffers
kusano fc6ab3
                     Simplify code moving compression to subroutines
kusano fc6ab3
                     Use assert() for internal errors
kusano fc6ab3
                     Add detailed description of approach
kusano fc6ab3
 */
kusano fc6ab3
kusano fc6ab3
/* Approach to just fitting a requested compressed size:
kusano fc6ab3
kusano fc6ab3
   fitblk performs three compression passes on a portion of the input
kusano fc6ab3
   data in order to determine how much of that input will compress to
kusano fc6ab3
   nearly the requested output block size.  The first pass generates
kusano fc6ab3
   enough deflate blocks to produce output to fill the requested
kusano fc6ab3
   output size plus a specfied excess amount (see the EXCESS define
kusano fc6ab3
   below).  The last deflate block may go quite a bit past that, but
kusano fc6ab3
   is discarded.  The second pass decompresses and recompresses just
kusano fc6ab3
   the compressed data that fit in the requested plus excess sized
kusano fc6ab3
   buffer.  The deflate process is terminated after that amount of
kusano fc6ab3
   input, which is less than the amount consumed on the first pass.
kusano fc6ab3
   The last deflate block of the result will be of a comparable size
kusano fc6ab3
   to the final product, so that the header for that deflate block and
kusano fc6ab3
   the compression ratio for that block will be about the same as in
kusano fc6ab3
   the final product.  The third compression pass decompresses the
kusano fc6ab3
   result of the second step, but only the compressed data up to the
kusano fc6ab3
   requested size minus an amount to allow the compressed stream to
kusano fc6ab3
   complete (see the MARGIN define below).  That will result in a
kusano fc6ab3
   final compressed stream whose length is less than or equal to the
kusano fc6ab3
   requested size.  Assuming sufficient input and a requested size
kusano fc6ab3
   greater than a few hundred bytes, the shortfall will typically be
kusano fc6ab3
   less than ten bytes.
kusano fc6ab3
kusano fc6ab3
   If the input is short enough that the first compression completes
kusano fc6ab3
   before filling the requested output size, then that compressed
kusano fc6ab3
   stream is return with no recompression.
kusano fc6ab3
kusano fc6ab3
   EXCESS is chosen to be just greater than the shortfall seen in a
kusano fc6ab3
   two pass approach similar to the above.  That shortfall is due to
kusano fc6ab3
   the last deflate block compressing more efficiently with a smaller
kusano fc6ab3
   header on the second pass.  EXCESS is set to be large enough so
kusano fc6ab3
   that there is enough uncompressed data for the second pass to fill
kusano fc6ab3
   out the requested size, and small enough so that the final deflate
kusano fc6ab3
   block of the second pass will be close in size to the final deflate
kusano fc6ab3
   block of the third and final pass.  MARGIN is chosen to be just
kusano fc6ab3
   large enough to assure that the final compression has enough room
kusano fc6ab3
   to complete in all cases.
kusano fc6ab3
 */
kusano fc6ab3
kusano fc6ab3
#include <stdio.h></stdio.h>
kusano fc6ab3
#include <stdlib.h></stdlib.h>
kusano fc6ab3
#include <assert.h></assert.h>
kusano fc6ab3
#include "zlib.h"
kusano fc6ab3
kusano fc6ab3
#define local static
kusano fc6ab3
kusano fc6ab3
/* print nastygram and leave */
kusano fc6ab3
local void quit(char *why)
kusano fc6ab3
{
kusano fc6ab3
    fprintf(stderr, "fitblk abort: %s\n", why);
kusano fc6ab3
    exit(1);
kusano fc6ab3
}
kusano fc6ab3
kusano fc6ab3
#define RAWLEN 4096    /* intermediate uncompressed buffer size */
kusano fc6ab3
kusano fc6ab3
/* compress from file to def until provided buffer is full or end of
kusano fc6ab3
   input reached; return last deflate() return value, or Z_ERRNO if
kusano fc6ab3
   there was read error on the file */
kusano fc6ab3
local int partcompress(FILE *in, z_streamp def)
kusano fc6ab3
{
kusano fc6ab3
    int ret, flush;
kusano fc6ab3
    unsigned char raw[RAWLEN];
kusano fc6ab3
kusano fc6ab3
    flush = Z_NO_FLUSH;
kusano fc6ab3
    do {
kusano fc6ab3
        def->avail_in = fread(raw, 1, RAWLEN, in);
kusano fc6ab3
        if (ferror(in))
kusano fc6ab3
            return Z_ERRNO;
kusano fc6ab3
        def->next_in = raw;
kusano fc6ab3
        if (feof(in))
kusano fc6ab3
            flush = Z_FINISH;
kusano fc6ab3
        ret = deflate(def, flush);
kusano fc6ab3
        assert(ret != Z_STREAM_ERROR);
kusano fc6ab3
    } while (def->avail_out != 0 && flush == Z_NO_FLUSH);
kusano fc6ab3
    return ret;
kusano fc6ab3
}
kusano fc6ab3
kusano fc6ab3
/* recompress from inf's input to def's output; the input for inf and
kusano fc6ab3
   the output for def are set in those structures before calling;
kusano fc6ab3
   return last deflate() return value, or Z_MEM_ERROR if inflate()
kusano fc6ab3
   was not able to allocate enough memory when it needed to */
kusano fc6ab3
local int recompress(z_streamp inf, z_streamp def)
kusano fc6ab3
{
kusano fc6ab3
    int ret, flush;
kusano fc6ab3
    unsigned char raw[RAWLEN];
kusano fc6ab3
kusano fc6ab3
    flush = Z_NO_FLUSH;
kusano fc6ab3
    do {
kusano fc6ab3
        /* decompress */
kusano fc6ab3
        inf->avail_out = RAWLEN;
kusano fc6ab3
        inf->next_out = raw;
kusano fc6ab3
        ret = inflate(inf, Z_NO_FLUSH);
kusano fc6ab3
        assert(ret != Z_STREAM_ERROR && ret != Z_DATA_ERROR &&
kusano fc6ab3
               ret != Z_NEED_DICT);
kusano fc6ab3
        if (ret == Z_MEM_ERROR)
kusano fc6ab3
            return ret;
kusano fc6ab3
kusano fc6ab3
        /* compress what was decompresed until done or no room */
kusano fc6ab3
        def->avail_in = RAWLEN - inf->avail_out;
kusano fc6ab3
        def->next_in = raw;
kusano fc6ab3
        if (inf->avail_out != 0)
kusano fc6ab3
            flush = Z_FINISH;
kusano fc6ab3
        ret = deflate(def, flush);
kusano fc6ab3
        assert(ret != Z_STREAM_ERROR);
kusano fc6ab3
    } while (ret != Z_STREAM_END && def->avail_out != 0);
kusano fc6ab3
    return ret;
kusano fc6ab3
}
kusano fc6ab3
kusano fc6ab3
#define EXCESS 256      /* empirically determined stream overage */
kusano fc6ab3
#define MARGIN 8        /* amount to back off for completion */
kusano fc6ab3
kusano fc6ab3
/* compress from stdin to fixed-size block on stdout */
kusano fc6ab3
int main(int argc, char **argv)
kusano fc6ab3
{
kusano fc6ab3
    int ret;                /* return code */
kusano fc6ab3
    unsigned size;          /* requested fixed output block size */
kusano fc6ab3
    unsigned have;          /* bytes written by deflate() call */
kusano fc6ab3
    unsigned char *blk;     /* intermediate and final stream */
kusano fc6ab3
    unsigned char *tmp;     /* close to desired size stream */
kusano fc6ab3
    z_stream def, inf;      /* zlib deflate and inflate states */
kusano fc6ab3
kusano fc6ab3
    /* get requested output size */
kusano fc6ab3
    if (argc != 2)
kusano fc6ab3
        quit("need one argument: size of output block");
kusano fc6ab3
    ret = strtol(argv[1], argv + 1, 10);
kusano fc6ab3
    if (argv[1][0] != 0)
kusano fc6ab3
        quit("argument must be a number");
kusano fc6ab3
    if (ret < 8)            /* 8 is minimum zlib stream size */
kusano fc6ab3
        quit("need positive size of 8 or greater");
kusano fc6ab3
    size = (unsigned)ret;
kusano fc6ab3
kusano fc6ab3
    /* allocate memory for buffers and compression engine */
kusano fc6ab3
    blk = malloc(size + EXCESS);
kusano fc6ab3
    def.zalloc = Z_NULL;
kusano fc6ab3
    def.zfree = Z_NULL;
kusano fc6ab3
    def.opaque = Z_NULL;
kusano fc6ab3
    ret = deflateInit(&def, Z_DEFAULT_COMPRESSION);
kusano fc6ab3
    if (ret != Z_OK || blk == NULL)
kusano fc6ab3
        quit("out of memory");
kusano fc6ab3
kusano fc6ab3
    /* compress from stdin until output full, or no more input */
kusano fc6ab3
    def.avail_out = size + EXCESS;
kusano fc6ab3
    def.next_out = blk;
kusano fc6ab3
    ret = partcompress(stdin, &def);
kusano fc6ab3
    if (ret == Z_ERRNO)
kusano fc6ab3
        quit("error reading input");
kusano fc6ab3
kusano fc6ab3
    /* if it all fit, then size was undersubscribed -- done! */
kusano fc6ab3
    if (ret == Z_STREAM_END && def.avail_out >= EXCESS) {
kusano fc6ab3
        /* write block to stdout */
kusano fc6ab3
        have = size + EXCESS - def.avail_out;
kusano fc6ab3
        if (fwrite(blk, 1, have, stdout) != have || ferror(stdout))
kusano fc6ab3
            quit("error writing output");
kusano fc6ab3
kusano fc6ab3
        /* clean up and print results to stderr */
kusano fc6ab3
        ret = deflateEnd(&def);
kusano fc6ab3
        assert(ret != Z_STREAM_ERROR);
kusano fc6ab3
        free(blk);
kusano fc6ab3
        fprintf(stderr,
kusano fc6ab3
                "%u bytes unused out of %u requested (all input)\n",
kusano fc6ab3
                size - have, size);
kusano fc6ab3
        return 0;
kusano fc6ab3
    }
kusano fc6ab3
kusano fc6ab3
    /* it didn't all fit -- set up for recompression */
kusano fc6ab3
    inf.zalloc = Z_NULL;
kusano fc6ab3
    inf.zfree = Z_NULL;
kusano fc6ab3
    inf.opaque = Z_NULL;
kusano fc6ab3
    inf.avail_in = 0;
kusano fc6ab3
    inf.next_in = Z_NULL;
kusano fc6ab3
    ret = inflateInit(&inf);
kusano fc6ab3
    tmp = malloc(size + EXCESS);
kusano fc6ab3
    if (ret != Z_OK || tmp == NULL)
kusano fc6ab3
        quit("out of memory");
kusano fc6ab3
    ret = deflateReset(&def);
kusano fc6ab3
    assert(ret != Z_STREAM_ERROR);
kusano fc6ab3
kusano fc6ab3
    /* do first recompression close to the right amount */
kusano fc6ab3
    inf.avail_in = size + EXCESS;
kusano fc6ab3
    inf.next_in = blk;
kusano fc6ab3
    def.avail_out = size + EXCESS;
kusano fc6ab3
    def.next_out = tmp;
kusano fc6ab3
    ret = recompress(&inf, &def);
kusano fc6ab3
    if (ret == Z_MEM_ERROR)
kusano fc6ab3
        quit("out of memory");
kusano fc6ab3
kusano fc6ab3
    /* set up for next reocmpression */
kusano fc6ab3
    ret = inflateReset(&inf);
kusano fc6ab3
    assert(ret != Z_STREAM_ERROR);
kusano fc6ab3
    ret = deflateReset(&def);
kusano fc6ab3
    assert(ret != Z_STREAM_ERROR);
kusano fc6ab3
kusano fc6ab3
    /* do second and final recompression (third compression) */
kusano fc6ab3
    inf.avail_in = size - MARGIN;   /* assure stream will complete */
kusano fc6ab3
    inf.next_in = tmp;
kusano fc6ab3
    def.avail_out = size;
kusano fc6ab3
    def.next_out = blk;
kusano fc6ab3
    ret = recompress(&inf, &def);
kusano fc6ab3
    if (ret == Z_MEM_ERROR)
kusano fc6ab3
        quit("out of memory");
kusano fc6ab3
    assert(ret == Z_STREAM_END);    /* otherwise MARGIN too small */
kusano fc6ab3
kusano fc6ab3
    /* done -- write block to stdout */
kusano fc6ab3
    have = size - def.avail_out;
kusano fc6ab3
    if (fwrite(blk, 1, have, stdout) != have || ferror(stdout))
kusano fc6ab3
        quit("error writing output");
kusano fc6ab3
kusano fc6ab3
    /* clean up and print results to stderr */
kusano fc6ab3
    free(tmp);
kusano fc6ab3
    ret = inflateEnd(&inf);
kusano fc6ab3
    assert(ret != Z_STREAM_ERROR);
kusano fc6ab3
    ret = deflateEnd(&def);
kusano fc6ab3
    assert(ret != Z_STREAM_ERROR);
kusano fc6ab3
    free(blk);
kusano fc6ab3
    fprintf(stderr,
kusano fc6ab3
            "%u bytes unused out of %u requested (%lu input)\n",
kusano fc6ab3
            size - have, size, def.total_in);
kusano fc6ab3
    return 0;
kusano fc6ab3
}