libjpegで、出力先をコールバック関数で指定する方法

JPEG画像の入出力を行う時、libjpegを使うのが一般的かと思います。
libjpegは、デフォルトでは標準出力にjpegを吐く凶悪な仕様となっているため、通常は出力先を切り替えることになります。
ここで、コールバック関数を指定することで、出力先をFILEポインタに限らず、ソケットやパイプなど、任意の出力先にすることができます。

```c
#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`に渡されて呼び出されます。

![libjpeg.png](https://qiita-image-store.s3.amazonaws.com/0/48925/edace531-0636-d4ab-a1cd-70db383bf8d0.png)