Created
March 1, 2013 12:49
-
-
Save imaya/5064438 to your computer and use it in GitHub Desktop.
Zopfli を使って PNG の IDAT チャンクを圧縮し直す。検証用。
使い方: Zopfli を clone して zopfli.c を消すか拡張子変えて、以下のファイルをぶちこんで make すれば良いと思います
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
make: | |
gcc *.c -O2 -W -Wall -Wextra -ansi -pedantic -lm -lz -o zopfli_png | |
debug: | |
gcc *.c -g3 -lm -lz -o zopfli_png |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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