(JPEG 2000とかいう無劣化にもできる形式もあるらしいですが、知りません)
そこで、JPGのエンコードの特徴を考え、破壊ポイントをすり抜けることで、情報を壊さずにJPGでエンコードしてみます。
※これはネタです。普通は画像を壊されたくなければPNGとかの可逆圧縮形式を使え。
まず、JPGのエンコードのしかたをおさらいします。
- 画像をYCbCrに変換する
- 画像を8x8ピクセルのブロックに分割する
- それぞれのブロックを離散コサイン変換する
- 量子化テーブルに従って値を割り算する
- ジグザグな順番でエンコードする
- 画像をYCbCrに変換する → ピクセルのR, G, Bの値を統一してきれいに変換できるようにし、R, G, Bの情報をを別々のピクセルで保存する
- 画像を8x8ピクセルのブロックに分割する → (対策なし)
- それぞれのブロックを離散コサイン変換する → ブロック内全てを同じ色にする
- 量子化テーブルに従って値を割り算する → 割る数を十分小さくする。実験の結果、8なら劣化しなそうであることがわかった
- ジグザグな順番でエンコードする → (対策なし)
実装コード(libjpeg、libpngおよびzlibを使用しています)
► スポイラーを表示
#include
#include
#include
#include
#include
#include
#include
#include
struct color_t {
uint8_t r, g, b;
};
struct image_t {
uint32_t width, height;
int is_color;
union {
struct color_t* color;
uint8_t* gray;
} data;
};
/* ラップしないようにチェックを入れてからメモリを確保する */
void* malloc_image(size_t elem_size, size_t width, size_t height) {
size_t num;
errno = 0;
if (width > SIZE_MAX / height) return NULL;
num = width * height;
if (num > SIZE_MAX / elem_size) return NULL;
return malloc(elem_size * num);
}
int read_image(struct image_t* ret, const char* file_name) {
FILE* fp;
uint32_t i, j;
fp = fopen(file_name, "rb");
if (fp == NULL) {
perror("input_file open failed");
return 0;
}
if (fgetc(fp) == 0xff) {
/* JPEGじゃね? (適当) */
struct jpeg_decompress_struct cinfo;
struct jpeg_error_mgr jerr;
JSAMPROW samprow;
JSAMPARRAY samparray = &samprow;
rewind(fp);
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_decompress(&cinfo);
jpeg_stdio_src(&cinfo, fp);
jpeg_read_header(&cinfo, TRUE);
if (cinfo.jpeg_color_space != JCS_RGB && cinfo.jpeg_color_space != JCS_GRAYSCALE) {
cinfo.out_color_space = JCS_RGB;
}
jpeg_start_decompress(&cinfo);
if (cinfo.output_components != cinfo.out_color_components ||
(cinfo.out_color_components != 1 && cinfo.out_color_components != 3)) {
fputs("invalid jpeg output format!\n", stderr);
jpeg_destroy_decompress(&cinfo);
fclose(fp);
return 0;
}
samprow = malloc_image(sizeof(JSAMPLE), cinfo.output_components, cinfo.output_width);
if (samprow == NULL) {
perror("malloc");
jpeg_destroy_decompress(&cinfo);
fclose(fp);
return 0;
}
ret->width = cinfo.output_width;
ret->height = cinfo.output_height;
ret->is_color = 1;
ret->data.color = malloc_image(sizeof(struct color_t), ret->width, ret->height);
if (ret->data.color == NULL) {
perror("malloc");
jpeg_destroy_decompress(&cinfo);
fclose(fp);
free(samprow);
return 0;
}
for (i = 0; i data.color[ret->width * i + j];
if (cinfo.out_color_components == 1) {
/* グレースケール */
color->r = color->g = color->b = samprow[j];
} else {
/* カラー */
color->r = samprow[3 * j];
color->g = samprow[3 * j + 1];
color->b = samprow[3 * j + 2];
}
}
}
jpeg_finish_decompress(&cinfo);
jpeg_destroy_decompress(&cinfo);
free(samprow);
fclose(fp);
return 1;
} else {
/* JPEGじゃなければPNGじゃね? (超適当) */
png_structp png_ptr;
png_infop info_ptr;
png_bytepp row_data;
png_byte color_type;
rewind(fp);
png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (!png_ptr) {
fputs("png_create_read_struct failed\n", stderr);
fclose(fp);
return 0;
}
info_ptr = png_create_info_struct(png_ptr);
if (!info_ptr) {
fputs("png_create_info_struct failed\n", stderr);
png_destroy_read_struct(&png_ptr, NULL, NULL);
fclose(fp);
return 0;
}
if (setjmp(png_jmpbuf(png_ptr))) {
fputs("something wrong happened while reading PNG file\n", stderr);
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
fclose(fp);
return 0;
}
png_init_io(png_ptr, fp);
png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_SCALE_16 |
PNG_TRANSFORM_EXPAND | PNG_TRANSFORM_GRAY_TO_RGB, NULL);
ret->width = png_get_image_width(png_ptr, info_ptr);
ret->height = png_get_image_height(png_ptr, info_ptr);
ret->is_color = 1;
color_type = png_get_color_type(png_ptr, info_ptr);
ret->data.color = malloc_image(sizeof(struct color_t), ret->width, ret->height);
if (ret->data.color == NULL) {
perror("malloc");
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
fclose(fp);
return 0;
}
row_data = png_get_rows(png_ptr, info_ptr);
for (i = 0; i height; i++) {
png_bytep samprow = row_data[i];
for (j = 0; j width; j++) {
struct color_t* color = &ret->data.color[ret->width * i + j];
if (color_type & PNG_COLOR_MASK_ALPHA) {
int32_t alpha = samprow[4 * j + 3];
int32_t noalpha = 255 - alpha;
color->r = (samprow[4 * j] * alpha + 255 * noalpha) / 255;
color->g = (samprow[4 * j + 1] * alpha + 255 * noalpha) / 255;
color->b = (samprow[4 * j + 2] * alpha + 255 * noalpha) / 255;
} else {
color->r = samprow[3 * j];
color->g = samprow[3 * j + 1];
color->b = samprow[3 * j + 2];
}
}
}
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
fclose(fp);
return 1;
}
}
int write_image(const struct image_t* img, const char* file_name, int is_decode) {
FILE* fp;
size_t fname_len;
uint32_t i, j;
fp = fopen(file_name, "wb");
if (fp == NULL) {
perror("output_file open failed");
return 0;
}
/* 拡張子がjpgかjpegならJPEGで出力、その他はPNGで出力 */
fname_len = strlen(file_name);
if ((fname_len >= 4 && file_name[fname_len - 4] == '.' &&
tolower((unsigned char)file_name[fname_len - 3]) == 'j' &&
tolower((unsigned char)file_name[fname_len - 2]) == 'p' &&
tolower((unsigned char)file_name[fname_len - 1]) == 'g') ||
(fname_len >= 5 && file_name[fname_len - 5] == '.' &&
tolower((unsigned char)file_name[fname_len - 4]) == 'j' &&
tolower((unsigned char)file_name[fname_len - 3]) == 'p' &&
tolower((unsigned char)file_name[fname_len - 2]) == 'e' &&
tolower((unsigned char)file_name[fname_len - 1]) == 'g')) {
/* JPEGで出力する */
struct jpeg_compress_struct cinfo;
struct jpeg_error_mgr jerr;
JSAMPROW row_pointer;
JSAMPARRAY row_array = &row_pointer;
row_pointer = malloc_image(sizeof(JSAMPLE), img->is_color ? 3 : 1, img->width);
if (row_pointer == NULL) {
perror("malloc");
fclose(fp);
return 0;
}
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_compress(&cinfo);
jpeg_stdio_dest(&cinfo, fp);
cinfo.image_width = img->width;
cinfo.image_height = img->height;
if (img->is_color) {
cinfo.input_components = 3;
cinfo.in_color_space = JCS_RGB;
} else {
cinfo.input_components = 1;
cinfo.in_color_space = JCS_GRAYSCALE;
}
jpeg_set_defaults(&cinfo);
if (is_decode) {
jpeg_set_quality(&cinfo, 100, TRUE);
} else {
unsigned int tbl[64];
int k;
tbl[0] = 8;
for (k = 1; k height; i++) {
for (j = 0; j width; j++) {
if (img->is_color) {
row_pointer[j * 3] = img->data.color[img->width * i + j].r;
row_pointer[j * 3 + 1] = img->data.color[img->width * i + j].g;
row_pointer[j * 3 + 2] = img->data.color[img->width * i + j].b;
} else {
row_pointer[j] = img->data.gray[img->width * i + j];
}
}
jpeg_write_scanlines(&cinfo, row_array, 1);
}
jpeg_finish_compress(&cinfo);
jpeg_destroy_compress(&cinfo);
free(row_pointer);
} else {
/* PNGで出力する */
png_structp png_ptr;
png_infop info_ptr;
png_bytep row_pointer;
row_pointer = malloc_image(sizeof(png_byte), img->is_color ? 3 : 1, img->width);
if (row_pointer == NULL) {
perror("malloc");
fclose(fp);
return 0;
}
png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (!png_ptr) {
fputs("png_create_write_struct failed\n", stderr);
free(row_pointer);
fclose(fp);
return 0;
}
info_ptr = png_create_info_struct(png_ptr);
if (!info_ptr) {
png_destroy_write_struct(&png_ptr, NULL);
fputs("png_create_info_struct failed\n", stderr);
free(row_pointer);
fclose(fp);
return 0;
}
if (setjmp(png_jmpbuf(png_ptr))) {
png_destroy_write_struct(&png_ptr, &info_ptr);
fputs("something bad happened in writing PNG file.\n", stderr);
free(row_pointer);
fclose(fp);
return 0;
}
png_init_io(png_ptr, fp);
png_set_IHDR(png_ptr, info_ptr, img->width, img->height, 8,
img->is_color ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_GRAY,
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
png_write_info(png_ptr, info_ptr);
for (i = 0; i height; i++) {
for (j = 0; j width; j++) {
if (img->is_color) {
row_pointer[j * 3] = img->data.color[img->width * i + j].r;
row_pointer[j * 3 + 1] = img->data.color[img->width * i + j].g;
row_pointer[j * 3 + 2] = img->data.color[img->width * i + j].b;
} else {
row_pointer[j] = img->data.gray[img->width * i + j];
}
}
png_write_row(png_ptr, row_pointer);
}
png_write_end(png_ptr, info_ptr);
png_destroy_write_struct(&png_ptr, &info_ptr);
free(row_pointer);
}
fclose(fp);
return 1;
}
int main(int argc, char* argv[]) {
struct image_t input_image, output_image;
int out_success;
if (argc != 4 || (argv[3][1] != '\0' || (argv[3][0] != 'e' && argv[3][0] != 'd'))) {
fprintf(stderr, "Usage: %s input_file output_file e/d\n",
argc > 0 ? argv[0] : "jpeg-enc");
return 1;
}
if (!read_image(&input_image, argv[1])) {
return 1;
}
if (argv[3][0] == 'e') {
/* エンコード */
output_image.width = input_image.width * 8;
output_image.height = input_image.height * 8 * 3;
output_image.is_color = 0;
output_image.data.gray = malloc_image(sizeof(uint8_t),
output_image.width, output_image.height);
if (output_image.data.gray == NULL) {
perror("malloc");
free(input_image.data.color);
return 1;
}
for (uint32_t i = 0; i < input_image.height; i++) {
for (uint32_t j = 0; j < input_image.width; j++) {
for (int di = 0; di < 8; di++) {
for (int dj = 0; dj < 8; dj++) {
output_image.data.gray[output_image.width * (i * 8 + di) +
(j * 8 + dj)] = input_image.data.color[input_image.width * i + j].r;
output_image.data.gray[output_image.width * ((i + input_image.height) * 8 + di) +
(j * 8 + dj)] = input_image.data.color[input_image.width * i + j].g;
output_image.data.gray[output_image.width * ((i + input_image.height * 2) * 8 + di) +
(j * 8 + dj)] = input_image.data.color[input_image.width * i + j].b;
}
}
}
}
} else {
/* デコード */
if (input_image.width % 8 != 0 || input_image.height % (8 * 3) != 0) {
fputs("input is not encoded image!\n", stderr);
free(input_image.data.color);
return 1;
}
output_image.width = input_image.width / 8;
output_image.height = input_image.height / (8 * 3);
output_image.is_color = 1;
output_image.data.color = malloc_image(sizeof(struct color_t),
output_image.width, output_image.height);
if (output_image.data.color == NULL) {
perror("malloc");
free(input_image.data.color);
return 1;
}
for (uint32_t i = 0; i < output_image.height; i++) {
for (uint32_t j = 0; j < output_image.width; j++) {
output_image.data.color[output_image.width * i + j].r =
input_image.data.color[input_image.width * (i * 8 + 4) + (j * 8 + 4)].r;
output_image.data.color[output_image.width * i + j].g =
input_image.data.color[input_image.width * ((output_image.height + i) * 8 + 4) + (j * 8 + 4)].g;
output_image.data.color[output_image.width * i + j].b =
input_image.data.color[input_image.width * ((output_image.height * 2 + i) * 8 + 4) + (j * 8 + 4)].b;
}
}
}
out_success = write_image(&output_image, argv[2], argv[3][0] != 'e');
free(input_image.data.color);
free(output_image.is_color ? (void*)output_image.data.color : (void*)output_image.data.gray);
return out_success ? 0 : 1;
}