Skip to content

Instantly share code, notes, and snippets.

@imaya
Created March 1, 2013 12:49
Show Gist options
  • Save imaya/5064438 to your computer and use it in GitHub Desktop.
Save imaya/5064438 to your computer and use it in GitHub Desktop.
Zopfli を使って PNG の IDAT チャンクを圧縮し直す。検証用。 使い方: Zopfli を clone して zopfli.c を消すか拡張子変えて、以下のファイルをぶちこんで make すれば良いと思います
make:
gcc *.c -O2 -W -Wall -Wextra -ansi -pedantic -lm -lz -o zopfli_png
debug:
gcc *.c -g3 -lm -lz -o zopfli_png
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <zlib.h>
#include "deflate.h"
#include "zlib_container.h"
/* file to buffer */
static void
load_file(const char* filename, unsigned char** out, size_t* outsize) {
FILE* file;
*out = 0;
*outsize = 0;
file = fopen(filename, "rb");
if (file == NULL) {
return;
}
fseek(file, 0, SEEK_END);
*outsize = ftell(file);
rewind(file);
*out = malloc(*outsize);
if (*outsize && (*out)) {
size_t testsize = fread(*out, 1, *outsize, file);
if (testsize != *outsize) {
/* It could be a directory */
free(*out);
*out = 0;
*outsize = 0;
}
}
assert(!(*outsize) || out); /* If size is not zero, out must be allocated. */
fclose(file);
}
/* buffer to file */
static void
save_file(const char* filename, const unsigned char* in, size_t insize) {
FILE* file = fopen(filename, "wb" );
assert(file);
fwrite((char*)in, 1, insize, file);
fclose(file);
}
/* read uint32_t number */
uint32_t
read_uint32(const unsigned char* in, uint32_t *ipos) {
uint32_t num =
(in[(*ipos)+0] << 24) | (in[(*ipos)+1] << 16) |
(in[(*ipos)+2] << 8) | (in[(*ipos)+3] );
*ipos += 4;
return num;
}
/* recompress png file */
void
recompress_file(const Options* options, const char* infilename,
const char* outfilename) {
/* buffer */
unsigned char* in = NULL;
size_t insize;
unsigned char* out = NULL;
size_t outsize = 0;
uint32_t ipos = 0;
uint32_t opos = 0;
/* chunk */
uint32_t length;
char chunk_type[5];
uint32_t width;
uint32_t height;
uint8_t depth;
uint8_t type;
uint8_t compress;
uint8_t filter;
uint8_t interlace;
unsigned char* plain;
/* idat */
unsigned char* idat_buffer = NULL;
uint32_t idat_pos = 0;
uLongf plain_size;
/* zlib */
unsigned char* compressed = NULL;
int retval;
uLong crc;
/* load file */
load_file(infilename, &in, &insize);
if (insize == 0) {
fprintf(stderr, "Invalid filename: %s\n", infilename);
return;
}
/* allocate idat buffer */
idat_buffer = malloc(insize);
if (idat_buffer == NULL) {
fprintf(stderr, "no enough memory\n");
goto free;
}
/* copy buffer */
out = malloc(insize * 2);
if (out == NULL) {
fprintf(stderr, "no enough memory\n");
goto free;
}
/* check signature */
if (strncmp((char *)in, "\x89\x50\x4e\x47\xd\xa\x1a\xa", 8) != 0) {
fprintf(stderr, "Invalid PNG Signature\n");
goto free;
return;
}
memcpy(&out[opos], &in[ipos], 8);
ipos += 8;
opos += 8;
/* parse chunks */
do {
/* Length */
length = read_uint32(in, &ipos);
/* Type */
strncpy(&chunk_type[0], (char *)&in[ipos], 4);
chunk_type[4] = 0;
if (strncmp((char *)&in[ipos], "IHDR", 4) == 0) {
memcpy(&out[opos], &in[ipos - 4], length + 12);
ipos += 4;
/* image information */
width = read_uint32(in, &ipos);
height = read_uint32(in, &ipos);
depth = in[ipos++];
type = in[ipos++];
compress = in[ipos++];
filter = in[ipos++];
interlace = in[ipos++];
/* decide image buffer size */
plain_size = width * height *
/* bitdepth */
(depth == 16 ? 2 : 1) *
/* alpha */
((type & 0x4) != 0 ? 4 : 3) +
/* filter byter */
height;
ipos += 4;
opos += length + 12;
} else if (strncmp((char *)&in[ipos], "IDAT", 4) == 0) {
ipos += 4;
/* concat idat */
memcpy(&idat_buffer[idat_pos], &in[ipos], length);
ipos += length + 4;
idat_pos += length;
} else if (strncmp((char *)&in[ipos], "IEND", 4) == 0) {
/* inflate image data */
plain = malloc(plain_size);
retval = uncompress(plain, &plain_size, idat_buffer, idat_pos);
switch (retval) {
case Z_OK:
break;
case Z_MEM_ERROR:
fprintf(stderr, "Inflate Error (no enough memory)\n");
goto free;
/* NOTREACHED */
case Z_BUF_ERROR:
fprintf(stderr, "Inflate Error (no enough output buffer)\n");
goto free;
/* NOTREACHED */
case Z_DATA_ERROR:
fprintf(stderr, "Inflate Error (invalid data)\n");
goto free;
/* NOTREACHED */
default:
fprintf(stderr, "Inflate Error (unknown return value: %d)\n", retval);
goto free;
/* NOTREACHED */
}
/* recompression */
compressed = malloc(plain_size * 2);
if (out == NULL) {
fprintf(stderr, "no enough memory\n");
goto free;
}
ZlibCompress(options, plain, plain_size, &compressed, &outsize);
/* output idat chunk length */
out[opos++] = outsize >> 24;
out[opos++] = outsize >> 16;
out[opos++] = outsize >> 8;
out[opos++] = outsize;
/* output idat chunk type */
strncpy((char *)&out[opos], "IDAT", 4);
opos += 4;
/* output idat chunk data */
memcpy(&out[opos], compressed, outsize);
opos += outsize;
/* output idat chunk crc */
crc = crc32(0L, (Bytef *)"IDAT", 4);
crc = crc32(crc, compressed, outsize);
out[opos++] = crc >> 24;
out[opos++] = crc >> 16;
out[opos++] = crc >> 8;
out[opos++] = crc;
/* output iend */
memcpy(&out[opos], &in[ipos - 4], length + 12);
ipos += length + 8;
opos += length + 12;
} else {
/* skip current chunk */
memcpy(&out[opos], &in[ipos - 4], length + 12);
ipos += length + 8;
opos += length + 12;
}
} while (ipos < insize);
save_file(outfilename, out, opos);
free:
if (idat_buffer != NULL) {
free(idat_buffer);
}
if (compressed != NULL) {
free(compressed);
}
if (out != NULL) {
free(out);
}
if (in != NULL) {
free(in);
}
}
/* concat strings */
static char*
add_strings(const char* str1, const char* str2) {
size_t len = strlen(str1) + strlen(str2);
char* result = (char*)malloc(len + 1);
if (!result) exit(-1); /* Allocation failed. */
strcpy(result, str1);
strcat(result, str2);
return result;
}
/* compare strings */
static bool
strings_equal(const char* str1, const char* str2) {
return strcmp(str1, str2) == 0 ? true : false;
}
int
main(int argc, char* argv[]) {
Options options;
const char* filename = 0;
int i;
InitOptions(&options);
for (i = 1; i < argc; i++) {
if (strings_equal(argv[i], "-v")) options.verbose = 1;
else if (strings_equal(argv[i], "--i5")) options.numiterations = 5;
else if (strings_equal(argv[i], "--i10")) options.numiterations = 10;
else if (strings_equal(argv[i], "--i15")) options.numiterations = 15;
else if (strings_equal(argv[i], "--i25")) options.numiterations = 25;
else if (strings_equal(argv[i], "--i50")) options.numiterations = 50;
else if (strings_equal(argv[i], "--i100")) options.numiterations = 100;
else if (strings_equal(argv[i], "--i250")) options.numiterations = 250;
else if (strings_equal(argv[i], "--i500")) options.numiterations = 500;
else if (strings_equal(argv[i], "--i1000")) options.numiterations = 1000;
else if (strings_equal(argv[i], "-h")) {
fprintf(stderr, "Usage: zopfli [OPTION]... FILE\n"
" -h gives this help\n"
" -v verbose mode\n");
fprintf(stderr, " --i5 less compression, but faster\n"
" --i10 less compression, but faster\n"
" --i15 default compression, 15 iterations\n"
" --i25 more compression, but slower\n"
" --i50 more compression, but slower\n"
" --i100 more compression, but slower\n"
" --i250 more compression, but slower\n"
" --i500 more compression, but slower\n"
" --i1000 more compression, but slower\n");
return 0;
}
}
for (i = 1; i < argc; ++i) {
if (argv[i][0] != '-') {
char* outfilename;
filename = argv[i];
outfilename = add_strings(filename, ".zopfli.png");
if (options.verbose && outfilename) {
fprintf(stderr, "Saving to: %s\n", outfilename);
}
recompress_file(&options, filename, outfilename);
free(outfilename);
}
}
if (!filename) {
fprintf(stderr,
"Please provide filename\nFor help, type: %s -h\n", argv[0]);
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment