JPEG画像の入出力を行う時、libjpegを使うのが一般的かと思います。
libjpegは、デフォルトでは標準出力にjpegを吐く凶悪な仕様となっているため、通常は出力先を切り替えることになります。
ここで、コールバック関数を指定することで、出力先をFILEポインタに限らず、ソケットやパイプなど、任意の出力先にすることができます。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <jpeglib.h>
enum {
BUFFER_SIZE = 4096,
WIDTH = 256,
HEIGHT = 256,
};
typedef struct {
struct jpeg_destination_mgr pub; /* public fields */
FILE *fp; /* ファイル出力以外を行いたい場合はここを変える */
char *buffer;
} JpegDstManager;
static void init_destination_callback(j_compress_ptr cinfo)
{
}
static boolean empty_output_buffer_callback(j_compress_ptr cinfo)
{
JpegDstManager *dst = (JpegDstManager *)cinfo->dest;
/* ファイル出力以外を行いたい場合はここを変える */
fwrite(dst->buffer, 1, BUFFER_SIZE, dst->fp);
dst->pub.next_output_byte = (uint8_t *)dst->buffer;
dst->pub.free_in_buffer = BUFFER_SIZE;
return TRUE;
}
static void term_destination_callback(j_compress_ptr cinfo)
{
JpegDstManager *dst = (JpegDstManager *)cinfo->dest;
int write_size = BUFFER_SIZE - dst->pub.free_in_buffer;
if (write_size > 0) {
/* ファイル出力以外を行いたい場合はここを変える */
fwrite(dst->buffer, 1, write_size, dst->fp);
}
}
static void jpeg_init_destination(j_compress_ptr cinfo, FILE *fp)
{
JpegDstManager *dst = (JpegDstManager *)cinfo->dest;
if (dst == NULL) {
dst = (*cinfo->mem->alloc_small)((j_common_ptr) cinfo, JPOOL_PERMANENT, sizeof(JpegDstManager));
dst->buffer = cinfo->mem->alloc_small((j_common_ptr) cinfo, JPOOL_IMAGE, BUFFER_SIZE);
dst->pub.next_output_byte = (uint8_t *)dst->buffer;
dst->pub.free_in_buffer = BUFFER_SIZE;
cinfo->dest = (struct jpeg_destination_mgr *)dst;
}
/* 出力先のコールバック関数を指定する */
dst->pub.init_destination = init_destination_callback;
dst->pub.empty_output_buffer = empty_output_buffer_callback;
dst->pub.term_destination = term_destination_callback;
/* 出力先を設定 */
dst->fp = fp;
}
static void throw_jpeg_error(j_common_ptr cinfo)
{
char cbuf[JMSG_LENGTH_MAX];
cinfo->err->format_message(cinfo, cbuf);
fprintf(stderr, "%s\n", cbuf);
}
int output_jpeg(int width, int height, uint8_t *data, int quality, const char *filename)
{
struct jpeg_compress_struct cinfo;
struct jpeg_error_mgr jerr;
FILE *fp;
int y;
/* この部分は共通 */
memset(&cinfo, 0, sizeof(cinfo));
memset(&jerr, 0, sizeof(jerr));
cinfo.err = jpeg_std_error(&jerr);
jerr.error_exit = throw_jpeg_error;
jerr.output_message = throw_jpeg_error;
jpeg_create_compress(&cinfo);
cinfo.input_components = 3; /* カラーは3,モノクロは1 */
cinfo.in_color_space = JCS_RGB; /* JCS_CMYKやJCS_GRAYSCALEも */
cinfo.image_width = width;
cinfo.image_height = height;
jpeg_set_defaults(&cinfo);
jpeg_set_quality(&cinfo, quality, TRUE); /* 品質値は0-100 数字が大きいほうが画質がいい */
fp = fopen(filename, "wb");
if (fp == NULL) {
return FALSE;
}
jpeg_init_destination(&cinfo, fp);
jpeg_start_compress(&cinfo, TRUE);
for (y = 0; y < height; y++) {
JSAMPROW rows[1];
rows[0] = data + width * y * 3;
/* 1行(X軸方向)ずつ書き込む */
if (jpeg_write_scanlines(&cinfo, rows, 1) != 1) {
goto ERROR_END;
}
}
jpeg_finish_compress(&cinfo);
jpeg_destroy_compress(&cinfo);
fclose(fp);
return TRUE;
ERROR_END:
jpeg_destroy_compress(&cinfo);
fclose(fp);
return FALSE;
}
int main(void)
{
char *data = malloc(WIDTH * HEIGHT * 3);
int x, y;
/* グラデーション画像を作成する */
/* dataは、RGBRGB...の順で並んでいる */
for (y = 0; y < HEIGHT; y++) {
for (x = 0; x < WIDTH; x++) {
uint8_t *p = data + (x + y * WIDTH) * 3;
p[0] = 128;
p[1] = x;
p[2] = y;
}
}
output_jpeg(WIDTH, HEIGHT, data, 100, "test.jpg");
return 0;
}
jpeg_write_scanlines
を呼び出すたびに、JpegDstManager
のbuffer
に、出力したjpegのデータが書き込まれます。
buffer
のサイズは、BUFFER_SIZE
に制限されるため、このサイズを超えるごとに、empty_output_buffer_callback
が呼び出され、バッファが空にセットされます。
jpeg_finish_compress
で、バッファの残りがterm_destination_callback
に渡されて呼び出されます。