LoginSignup
8
8

More than 5 years have passed since last update.

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

Last updated at Posted at 2014-07-22

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を呼び出すたびに、JpegDstManagerbufferに、出力したjpegのデータが書き込まれます。
bufferのサイズは、BUFFER_SIZEに制限されるため、このサイズを超えるごとに、empty_output_buffer_callbackが呼び出され、バッファが空にセットされます。

jpeg_finish_compressで、バッファの残りがterm_destination_callbackに渡されて呼び出されます。

libjpeg.png

8
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
8