/*
 * Copyright (c) 1997-2006  Motoyuki Kasahara
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the project nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include "build-pre.h"
#include "eb.h"
#include "error.h"
#include "font.h"
#include "build-post.h"

#include <zlib.h>

/*
 * Unexported functions.
 */
static unsigned long png_crc(const char *buf, size_t len);
static int png_compress(const char *src, int width, int height, char *dest,
  size_t *dest_len);


/*
 * Return required buffer size for a narrow font character converted
 * to XBM image format.
 */
EB_Error_Code
eb_narrow_font_xbm_size(EB_Font_Code height, size_t *size)
{
  EB_Error_Code error_code;

  LOG(("in: eb_narrow_font_xbm_size(height=%d)", (int)height));

  switch (height) {
  case EB_FONT_16:
    *size = EB_SIZE_NARROW_FONT_16_XBM;
    break;
  case EB_FONT_24:
    *size = EB_SIZE_NARROW_FONT_24_XBM;
    break;
  case EB_FONT_30:
    *size = EB_SIZE_NARROW_FONT_30_XBM;
    break;
  case EB_FONT_48:
    *size = EB_SIZE_NARROW_FONT_48_XBM;
    break;
  default:
    error_code = EB_ERR_NO_SUCH_FONT;
    goto failed;
  }

  LOG(("out: eb_narrow_font_xbm_size(size=%ld) = %s", (long)*size,
    eb_error_string(EB_SUCCESS)));

  return EB_SUCCESS;

  /*
   * An error occurs...
   */
  failed:
  *size = 0;
  LOG(("out: eb_narrow_font_xbm_size() = %s", eb_error_string(error_code)));
  return error_code;
}


/*
 * Return required buffer size for a narrow font character converted
 * to XPM image format.
 */
EB_Error_Code
eb_narrow_font_xpm_size(EB_Font_Code height, size_t *size)
{
  EB_Error_Code error_code;

  LOG(("in: eb_narrow_font_xpm_size(height=%d)", (int)height));

  switch (height) {
  case EB_FONT_16:
    *size = EB_SIZE_NARROW_FONT_16_XPM;
    break;
  case EB_FONT_24:
    *size = EB_SIZE_NARROW_FONT_24_XPM;
    break;
  case EB_FONT_30:
    *size = EB_SIZE_NARROW_FONT_30_XPM;
    break;
  case EB_FONT_48:
    *size = EB_SIZE_NARROW_FONT_48_XPM;
    break;
  default:
    error_code = EB_ERR_NO_SUCH_FONT;
    goto failed;
  }

  LOG(("out: eb_narrow_font_xpm_size(size=%ld) = %s", (long)*size,
    eb_error_string(EB_SUCCESS)));

  return EB_SUCCESS;

  /*
   * An error occurs...
   */
  failed:
  *size = 0;
  LOG(("out: eb_narrow_font_xpm_size() = %s", eb_error_string(error_code)));
  return error_code;
}


/*
 * Return required buffer size for a narrow font character converted
 * to GIF image format.
 */
EB_Error_Code
eb_narrow_font_gif_size(EB_Font_Code height, size_t *size)
{
  EB_Error_Code error_code;

  LOG(("in: eb_narrow_font_gif_size(height=%d)", (int)height));

  switch (height) {
  case EB_FONT_16:
    *size = EB_SIZE_NARROW_FONT_16_GIF;
    break;
  case EB_FONT_24:
    *size = EB_SIZE_NARROW_FONT_24_GIF;
    break;
  case EB_FONT_30:
    *size = EB_SIZE_NARROW_FONT_30_GIF;
    break;
  case EB_FONT_48:
    *size = EB_SIZE_NARROW_FONT_48_GIF;
  default:
    error_code = EB_ERR_NO_SUCH_FONT;
    goto failed;
  }

  LOG(("out: eb_narrow_font_gif_size(size=%ld) = %s", (long)*size,
    eb_error_string(EB_SUCCESS)));

  return EB_SUCCESS;

  /*
   * An error occurs...
   */
  failed:
  *size = 0;
  LOG(("out: eb_narrow_font_gif_size() = %s", eb_error_string(error_code)));
  return error_code;
}


/*
 * Return required buffer size for a narrow font character converted
 * to BMP image format.
 */
EB_Error_Code
eb_narrow_font_bmp_size(EB_Font_Code height, size_t *size)
{
  EB_Error_Code error_code;

  LOG(("in: eb_narrow_font_bmp_size(height=%d)", (int)height));

  switch (height) {
  case EB_FONT_16:
    *size = EB_SIZE_NARROW_FONT_16_BMP;
    break;
  case EB_FONT_24:
    *size = EB_SIZE_NARROW_FONT_24_BMP;
    break;
  case EB_FONT_30:
    *size = EB_SIZE_NARROW_FONT_30_BMP;
    break;
  case EB_FONT_48:
    *size = EB_SIZE_NARROW_FONT_48_BMP;
    break;
  default:
    error_code = EB_ERR_NO_SUCH_FONT;
    goto failed;
  }

  LOG(("out: eb_narrow_font_bmp_size(size=%ld) = %s", (long)*size,
    eb_error_string(EB_SUCCESS)));

  return EB_SUCCESS;

  /*
   * An error occurs...
   */
  failed:
  *size = 0;
  LOG(("out: eb_narrow_font_bmp_size() = %s", eb_error_string(error_code)));
  return error_code;
}


/*
 * Return required buffer size for a narrow font character converted
 * to PNG image format.
 */
EB_Error_Code
eb_narrow_font_png_size(EB_Font_Code height, size_t *size)
{
  EB_Error_Code error_code;

  LOG(("in: eb_narrow_font_png_size(height=%d)", (int)height));

  switch (height) {
  case EB_FONT_16:
    *size = EB_SIZE_NARROW_FONT_16_PNG;
    break;
  case EB_FONT_24:
    *size = EB_SIZE_NARROW_FONT_24_PNG;
    break;
  case EB_FONT_30:
    *size = EB_SIZE_NARROW_FONT_30_PNG;
    break;
  case EB_FONT_48:
    *size = EB_SIZE_NARROW_FONT_48_PNG;
  default:
    error_code = EB_ERR_NO_SUCH_FONT;
    goto failed;
  }

  LOG(("out: eb_narrow_font_png_size(size=%ld) = %s", (long)*size,
    eb_error_string(EB_SUCCESS)));

  return EB_SUCCESS;

  /*
   * An error occurs...
   */
  failed:
  *size = 0;
  LOG(("out: eb_narrow_font_png_size() = %s", eb_error_string(error_code)));
  return error_code;
}


/*
 * Return required buffer size for a wide font character converted
 * to XBM image format.
 */
EB_Error_Code
eb_wide_font_xbm_size(EB_Font_Code height, size_t *size)
{
  EB_Error_Code error_code;

  LOG(("in: eb_wide_font_xbm_size(height=%d)", (int)height));

  switch (height) {
  case EB_FONT_16:
    *size = EB_SIZE_WIDE_FONT_16_XBM;
    break;
  case EB_FONT_24:
    *size = EB_SIZE_WIDE_FONT_24_XBM;
    break;
  case EB_FONT_30:
    *size = EB_SIZE_WIDE_FONT_30_XBM;
    break;
  case EB_FONT_48:
    *size = EB_SIZE_WIDE_FONT_48_XBM;
    break;
  default:
    error_code = EB_ERR_NO_SUCH_FONT;
    goto failed;
  }

  LOG(("out: eb_wide_font_xbm_size(size=%ld) = %s", (long)*size,
    eb_error_string(EB_SUCCESS)));

  return EB_SUCCESS;

  /*
   * An error occurs...
   */
  failed:
  *size = 0;
  LOG(("out: eb_wide_font_xbm_size() = %s", eb_error_string(error_code)));
  return error_code;
}


/*
 * Return required buffer size for a wide font character converted
 * to XPM image format.
 */
EB_Error_Code
eb_wide_font_xpm_size(EB_Font_Code height, size_t *size)
{
  EB_Error_Code error_code;

  LOG(("in: eb_wide_font_xpm_size(height=%d)", (int)height));

  switch (height) {
  case EB_FONT_16:
    *size = EB_SIZE_WIDE_FONT_16_XPM;
    break;
  case EB_FONT_24:
    *size = EB_SIZE_WIDE_FONT_24_XPM;
    break;
  case EB_FONT_30:
    *size = EB_SIZE_WIDE_FONT_30_XPM;
    break;
  case EB_FONT_48:
    *size = EB_SIZE_WIDE_FONT_48_XPM;
    break;
  default:
    error_code = EB_ERR_NO_SUCH_FONT;
    goto failed;
  }

  LOG(("out: eb_wide_font_xpm_size(size=%ld) = %s", (long)*size,
    eb_error_string(EB_SUCCESS)));

  return EB_SUCCESS;

  /*
   * An error occurs...
   */
  failed:
  *size = 0;
  LOG(("out: eb_wide_font_xpm_size() = %s", eb_error_string(error_code)));
  return error_code;
}


/*
 * Return required buffer size for a wide font character converted
 * to GIF image format.
 */
EB_Error_Code
eb_wide_font_gif_size(EB_Font_Code height, size_t *size)
{
  EB_Error_Code error_code;

  LOG(("in: eb_wide_font_gif_size(height=%d)", (int)height));

  switch (height) {
  case EB_FONT_16:
    *size = EB_SIZE_WIDE_FONT_16_GIF;
    break;
  case EB_FONT_24:
    *size = EB_SIZE_WIDE_FONT_24_GIF;
    break;
  case EB_FONT_30:
    *size = EB_SIZE_WIDE_FONT_30_GIF;
    break;
  case EB_FONT_48:
    *size = EB_SIZE_WIDE_FONT_48_GIF;
    break;
  default:
    error_code = EB_ERR_NO_SUCH_FONT;
    goto failed;
  }

  LOG(("out: eb_wide_font_gif_size(size=%ld) = %s", (long)*size,
    eb_error_string(EB_SUCCESS)));

  return EB_SUCCESS;

  /*
   * An error occurs...
   */
  failed:
  *size = 0;
  LOG(("out: eb_wide_font_gif_size() = %s", eb_error_string(error_code)));
  return error_code;
}


/*
 * Return required buffer size for a wide font character converted
 * to BMP image format.
 */
EB_Error_Code
eb_wide_font_bmp_size(EB_Font_Code height, size_t *size)
{
  EB_Error_Code error_code;

  LOG(("in: eb_wide_font_bmp_size(height=%d)", (int)height));

  switch (height) {
  case EB_FONT_16:
    *size = EB_SIZE_WIDE_FONT_16_BMP;
    break;
  case EB_FONT_24:
    *size = EB_SIZE_WIDE_FONT_24_BMP;
    break;
  case EB_FONT_30:
    *size = EB_SIZE_WIDE_FONT_30_BMP;
    break;
  case EB_FONT_48:
    *size = EB_SIZE_WIDE_FONT_48_BMP;
    break;
  default:
    error_code = EB_ERR_NO_SUCH_FONT;
    goto failed;
  }

  LOG(("out: eb_wide_font_bmp_size(size=%ld) = %s", (long)*size,
    eb_error_string(EB_SUCCESS)));

  return EB_SUCCESS;

  /*
   * An error occurs...
   */
  failed:
  *size = 0;
  LOG(("out: eb_wide_font_bmp_size() = %s", eb_error_string(error_code)));
  return error_code;
}


/*
 * Return required buffer size for a wide font character converted
 * to PNG image format.
 */
EB_Error_Code
eb_wide_font_png_size(EB_Font_Code height, size_t *size)
{
  EB_Error_Code error_code;

  LOG(("in: eb_wide_font_png_size(height=%d)", (int)height));

  switch (height) {
  case EB_FONT_16:
    *size = EB_SIZE_WIDE_FONT_16_PNG;
    break;
  case EB_FONT_24:
    *size = EB_SIZE_WIDE_FONT_24_PNG;
    break;
  case EB_FONT_30:
    *size = EB_SIZE_WIDE_FONT_30_PNG;
    break;
  case EB_FONT_48:
    *size = EB_SIZE_WIDE_FONT_48_PNG;
    break;
  default:
    error_code = EB_ERR_NO_SUCH_FONT;
    goto failed;
  }

  LOG(("out: eb_wide_font_png_size(size=%ld) = %s", (long)*size,
    eb_error_string(EB_SUCCESS)));

  return EB_SUCCESS;

  /*
   * An error occurs...
   */
  failed:
  *size = 0;
  LOG(("out: eb_wide_font_png_size() = %s", eb_error_string(error_code)));
  return error_code;
}


/*
 * The maximum number of octets in a line in a XBM file.
 */
#define XBM_MAX_OCTETS_A_LINE        12

/*
 * The base name of a XBM file.
 */
#define XBM_BASE_NAME            "default"

/*
 * Convert a bitmap image to XBM format.
 *
 * It requires four arguements.  `xbm' is buffer to store the XBM
 * image data.  `bitmap', `width', and `height' are bitmap data,
 * width, and height of the bitmap image.
 */
EB_Error_Code
eb_bitmap_to_xbm(const char *bitmap, int width, int height, char *xbm,
  size_t *xbm_length)
{
  char *xbm_p = xbm;
  const unsigned char *bitmap_p = (const unsigned char *)bitmap;
  int bitmap_size = (width + 7) / 8 * height;
  int hex;
  int i;

  LOG(("in: eb_bitmap_to_xbm(width=%d, height=%d)", width, height));

  /*
   * Output a header.
   */
  sprintf(xbm_p, "#define %s_width %4d\n", XBM_BASE_NAME, width);
  xbm_p = strchr(xbm_p, '\n') + 1;
  sprintf(xbm_p, "#define %s_height %4d\n", XBM_BASE_NAME, height);
  xbm_p = strchr(xbm_p, '\n') + 1;
  sprintf(xbm_p, "static unsigned char %s_bits[] = {\n", XBM_BASE_NAME);
  xbm_p = strchr(xbm_p, '\n') + 1;

  /*
   * Output image data.
   */
  for (i = 0; i < bitmap_size; i++) {
    hex = 0;
    hex |= (*bitmap_p & 0x80) ? 0x01 : 0x00;
    hex |= (*bitmap_p & 0x40) ? 0x02 : 0x00;
    hex |= (*bitmap_p & 0x20) ? 0x04 : 0x00;
    hex |= (*bitmap_p & 0x10) ? 0x08 : 0x00;
    hex |= (*bitmap_p & 0x08) ? 0x10 : 0x00;
    hex |= (*bitmap_p & 0x04) ? 0x20 : 0x00;
    hex |= (*bitmap_p & 0x02) ? 0x40 : 0x00;
    hex |= (*bitmap_p & 0x01) ? 0x80 : 0x00;
    bitmap_p++;

    if (i % XBM_MAX_OCTETS_A_LINE != 0) {
      sprintf(xbm_p, ", 0x%02x", hex);
      xbm_p += 6;
    } else if (i == 0) {
      sprintf(xbm_p, "   0x%02x", hex);
      xbm_p += 7;
    } else {
      sprintf(xbm_p, ",\n   0x%02x", hex);
      xbm_p += 9;
    }
  }

  /*
   * Output a footer.
   */
  memcpy(xbm_p, "};\n", 3);
  xbm_p += 3;

  *xbm_length = xbm_p - xbm;

  LOG(("out: eb_bitmap_to_xbm(xbm_length=%ld) = %s",
    (long)(xbm_p - xbm), eb_error_string(EB_SUCCESS)));

  return EB_SUCCESS;
}


/*
 * The base name of a XPM file.
 */
#define XPM_BASE_NAME        "default"

/*
 * The foreground and background colors of XPM image.
 */
#define XPM_FOREGROUND_COLOR        "Black"
#define XPM_BACKGROUND_COLOR        "None"

/*
 * Convert a bitmap image to XPM format.
 *
 * It requires four arguements.  `xpm' is buffer to store the XPM
 * image data.  `bitmap', `width', and `height' are bitmap data,
 * width, and height of the bitmap image.
 */
EB_Error_Code
eb_bitmap_to_xpm(const char *bitmap, int width, int height, char *xpm,
  size_t *xpm_length)
{
  char *xpm_p = xpm;
  const unsigned char *bitmap_p = (const unsigned char *)bitmap;
  int i, j;

  LOG(("in: eb_bitmap_to_xpm(width=%d, height=%d)", width, height));

  /*
   * Output a header.
   */
  sprintf(xpm_p, "/* XPM */\n");
  xpm_p = strchr(xpm_p, '\n') + 1;

  sprintf(xpm_p, "static char * %s[] = {\n", XPM_BASE_NAME);
  xpm_p = strchr(xpm_p, '\n') + 1;

  sprintf(xpm_p, "\"%d %d 2 1\",\n", width, height);
  xpm_p = strchr(xpm_p, '\n') + 1;

  sprintf(xpm_p, "\"     c %s\",\n", XPM_BACKGROUND_COLOR);
  xpm_p = strchr(xpm_p, '\n') + 1;

  sprintf(xpm_p, "\".     c %s\",\n", XPM_FOREGROUND_COLOR);
  xpm_p = strchr(xpm_p, '\n') + 1;

  /*
   * Output image data.
   */
  for (i = 0; i < height; i++) {
    if (0 < i) {
      strcpy(xpm_p, "\",\n\"");
      xpm_p += 4;
    } else {
      *xpm_p++ = '\"';
    }

    for (j = 0; j + 7 < width; j += 8, bitmap_p++) {
      *xpm_p++ = (*bitmap_p & 0x80) ? '.' : ' ';
      *xpm_p++ = (*bitmap_p & 0x40) ? '.' : ' ';
      *xpm_p++ = (*bitmap_p & 0x20) ? '.' : ' ';
      *xpm_p++ = (*bitmap_p & 0x10) ? '.' : ' ';
      *xpm_p++ = (*bitmap_p & 0x08) ? '.' : ' ';
      *xpm_p++ = (*bitmap_p & 0x04) ? '.' : ' ';
      *xpm_p++ = (*bitmap_p & 0x02) ? '.' : ' ';
      *xpm_p++ = (*bitmap_p & 0x01) ? '.' : ' ';
    }

    if (j < width) {
      if (j++ < width)
        *xpm_p++ = (*bitmap_p & 0x80) ? '.' : ' ';
      if (j++ < width)
        *xpm_p++ = (*bitmap_p & 0x40) ? '.' : ' ';
      if (j++ < width)
        *xpm_p++ = (*bitmap_p & 0x20) ? '.' : ' ';
      if (j++ < width)
        *xpm_p++ = (*bitmap_p & 0x10) ? '.' : ' ';
      if (j++ < width)
        *xpm_p++ = (*bitmap_p & 0x08) ? '.' : ' ';
      if (j++ < width)
        *xpm_p++ = (*bitmap_p & 0x04) ? '.' : ' ';
      if (j++ < width)
        *xpm_p++ = (*bitmap_p & 0x02) ? '.' : ' ';
      if (j++ < width)
        *xpm_p++ = (*bitmap_p & 0x01) ? '.' : ' ';
      bitmap_p++;
    }
  }

  /*
   * Output a footer.
   */
  memcpy(xpm_p, "\"};\n", 4);
  xpm_p += 4;

  if (xpm_length != NULL)
    *xpm_length = xpm_p - xpm;

  LOG(("out: eb_bitmap_to_xpm(xpm_length=%ld) = %s",
    (long)(xpm_p - xpm), eb_error_string(EB_SUCCESS)));

  return EB_SUCCESS;
}


/*
 * The Foreground and background colors of GIF image.
 */
#define GIF_FOREGROUND_COLOR        0x000000
#define GIF_BACKGROUND_COLOR        0xffffff

/*
 * The preamble of GIF image.
 */
#define GIF_PREAMBLE_LENGTH    38

static const unsigned char gif_preamble[GIF_PREAMBLE_LENGTH] = {
  /*
   * Header. (6 bytes)
   */
  'G', 'I', 'F', '8', '9', 'a',

  /*
   * Logical Screen Descriptor. (7 bytes)
   *   global color table flag = 1.
   *   color resolution = 1 - 1 = 0.
   *   sort flag = 0.
   *   size of global color table = 1 - 1 = 0.
   *   background color index = 0.
   *   the pixel aspect ratio = 0 (unused)
   * Logical screen width and height are set at run time.
   */
  0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,

  /*
   * Global Color Table. (6 bytes)
   * These are set at run time.
   */
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

  /*
   * Graphic Control Extension. (8 bytes)
   *   disposal method = 0.
   *   user input flag = 0.
   *   transparency flag = 1.
   *   delay time = 0.
   *   transparent color index = 0.
   */
  0x21, 0xf9, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00,

  /*
   * Image Descriptor. (10 bytes)
   *   image left position = 0.
   *   image top position = 0.
   *   local color table flag = 0.
   *   interlace flag = 0.
   *   sort flag = 0.
   *   size of local color table = 0.
   * Image width and height are set at run time.
   */
  0x2c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

  /*
   * Code size. (1byte)
   */
  0x03
};


/*
 * Convert a bitmap image to GIF format.
 *
 * It requires four arguements.  `gif' is buffer to store the GIF
 * image data.  `bitmap', `width', and `height' are bitmap data,
 * width, and height of the bitmap image.
 *
 * Note: This GIF image doesn't use LZW because of patent.
 */
EB_Error_Code
eb_bitmap_to_gif(const char *bitmap, int width, int height, char *gif,
  size_t *gif_length)
{
  unsigned char *gif_p = (unsigned char *)gif;
  const unsigned char *bitmap_p = (const unsigned char *)bitmap;
  int i, j;

  LOG(("in: eb_bitmap_to_gif(width=%d, height=%d)", width, height));

  /*
   * Copy the default preamble.
   */
  memcpy(gif_p, gif_preamble, GIF_PREAMBLE_LENGTH);

  /*
   * Set logical screen width and height.
   */
  gif_p[6] = width & 0xff;
  gif_p[7] = (width >> 8) & 0xff;
  gif_p[8] = height & 0xff;
  gif_p[9] = (height >> 8) & 0xff;

  /*
   * Set global colors.
   */
  gif_p[13] = (GIF_BACKGROUND_COLOR >> 16) & 0xff;
  gif_p[14] = (GIF_BACKGROUND_COLOR >> 8) & 0xff;
  gif_p[15] = GIF_BACKGROUND_COLOR & 0xff;
  gif_p[16] = (GIF_FOREGROUND_COLOR >> 16) & 0xff;
  gif_p[17] = (GIF_FOREGROUND_COLOR >> 8) & 0xff;
  gif_p[18] = GIF_FOREGROUND_COLOR & 0xff;

  /*
   * Set image width and height.
   */
  gif_p[32] = width & 0xff;
  gif_p[33] = (width >> 8) & 0xff;
  gif_p[34] = height & 0xff;
  gif_p[35] = (height >> 8) & 0xff;

  gif_p += GIF_PREAMBLE_LENGTH;

  /*
   * Output image data.
   */
  for (i = 0;  i < height; i++) {
    *gif_p++ = (unsigned char)width;
    for (j = 0; j + 7 < width; j += 8, bitmap_p++) {
      *gif_p++ = (*bitmap_p & 0x80) ? 0x81 : 0x80;
      *gif_p++ = (*bitmap_p & 0x40) ? 0x81 : 0x80;
      *gif_p++ = (*bitmap_p & 0x20) ? 0x81 : 0x80;
      *gif_p++ = (*bitmap_p & 0x10) ? 0x81 : 0x80;
      *gif_p++ = (*bitmap_p & 0x08) ? 0x81 : 0x80;
      *gif_p++ = (*bitmap_p & 0x04) ? 0x81 : 0x80;
      *gif_p++ = (*bitmap_p & 0x02) ? 0x81 : 0x80;
      *gif_p++ = (*bitmap_p & 0x01) ? 0x81 : 0x80;
    }

    if (j < width) {
      if (j++ < width)
        *gif_p++ = (*bitmap_p & 0x80) ? 0x81 : 0x80;
      if (j++ < width)
        *gif_p++ = (*bitmap_p & 0x40) ? 0x81 : 0x80;
      if (j++ < width)
        *gif_p++ = (*bitmap_p & 0x20) ? 0x81 : 0x80;
      if (j++ < width)
        *gif_p++ = (*bitmap_p & 0x10) ? 0x81 : 0x80;
      if (j++ < width)
        *gif_p++ = (*bitmap_p & 0x08) ? 0x81 : 0x80;
      if (j++ < width)
        *gif_p++ = (*bitmap_p & 0x04) ? 0x81 : 0x80;
      if (j++ < width)
        *gif_p++ = (*bitmap_p & 0x02) ? 0x81 : 0x80;
      if (j++ < width)
        *gif_p++ = (*bitmap_p & 0x01) ? 0x81 : 0x80;
      bitmap_p++;
    }
  }

  /*
   * Output a trailer.
   */
  memcpy(gif_p, "\001\011\000\073", 4);
  gif_p += 4;

  if (gif_length != NULL)
    *gif_length = ((char *)gif_p - gif);

  LOG(("out: eb_bitmap_to_gif(gif_length=%ld) = %s",
    (long)((char *)gif_p - gif), eb_error_string(EB_SUCCESS)));

  return EB_SUCCESS;
}


/*
 * The preamble of BMP image.
 */
#define BMP_PREAMBLE_LENGTH    62

static const unsigned char bmp_preamble[] = {
  /* Type. */
  'B', 'M',

  /* File size. (set at run time) */
  0x00, 0x00, 0x00, 0x00,

  /* Reserved. */
  0x00, 0x00, 0x00, 0x00,

  /* Offset of bitmap bits part. */
  0x3e, 0x00, 0x00, 0x00,

  /* Size of bitmap info part. */
  0x28, 0x00, 0x00, 0x00,

  /* Width. (set at run time) */
  0x00, 0x00, 0x00, 0x00,

  /* Height. (set at run time) */
  0x00, 0x00, 0x00, 0x00,

  /* Planes. */
  0x01, 0x00,

  /* Bits per pixels. */
  0x01, 0x00,

  /* Compression mode. */
  0x00, 0x00, 0x00, 0x00,

  /* Size of bitmap bits part. (set at run time) */
  0x00, 0x00, 0x00, 0x00,

  /* X Pixels per meter. */
  0x6d, 0x0b, 0x00, 0x00,

  /* Y Pixels per meter. */
  0x6d, 0x0b, 0x00, 0x00,

  /* Colors */
  0x02, 0x00, 0x00, 0x00,

  /* Important colors */
  0x02, 0x00, 0x00, 0x00,

  /* RGB quad of color 0   RGB quad of color 1 */
  0xff, 0xff, 0xff, 0x00,  0x00, 0x00, 0x00, 0x00,
};

/*
 * Convert a bitmap image to BMP format.
 *
 * It requires four arguements.  `bmp' is buffer to store the BMP
 * image data.  `bitmap', `width', and `height' are bitmap data,
 * width, and height of the bitmap image.
 */
EB_Error_Code
eb_bitmap_to_bmp(const char *bitmap, int width, int height, char *bmp,
  size_t *bmp_length)
{
  unsigned char *bmp_p = (unsigned char *)bmp;
  size_t data_size;
  size_t file_size;
  size_t line_pad_length;
  size_t bitmap_line_length;
  int i, j;

  LOG(("in: eb_bitmap_to_bmp(width=%d, height=%d)", width, height));

  if (width % 32 == 0)
    line_pad_length = 0;
  else if (width % 32 <= 8)
    line_pad_length = 3;
  else if (width % 32 <= 16)
    line_pad_length = 2;
  else if (width % 32 <= 24)
    line_pad_length = 1;
  else
    line_pad_length = 0;

  data_size = (width / 2 + line_pad_length) * height;
  file_size = data_size + BMP_PREAMBLE_LENGTH;

  /*
   * Set BMP preamble.
   */
  memcpy(bmp_p, bmp_preamble, BMP_PREAMBLE_LENGTH);

  bmp_p[2] =  file_size    & 0xff;
  bmp_p[3] = (file_size >> 8)  & 0xff;
  bmp_p[4] = (file_size >> 16) & 0xff;
  bmp_p[5] = (file_size >> 24) & 0xff;

  bmp_p[18] =  width    & 0xff;
  bmp_p[19] = (width >> 8)  & 0xff;
  bmp_p[20] = (width >> 16) & 0xff;
  bmp_p[21] = (width >> 24) & 0xff;

  bmp_p[22] =  height    & 0xff;
  bmp_p[23] = (height >> 8)  & 0xff;
  bmp_p[24] = (height >> 16) & 0xff;
  bmp_p[25] = (height >> 24) & 0xff;

  bmp_p[34] =  data_size    & 0xff;
  bmp_p[35] = (data_size >> 8)  & 0xff;
  bmp_p[36] = (data_size >> 16) & 0xff;
  bmp_p[37] = (data_size >> 24) & 0xff;

  bmp_p += BMP_PREAMBLE_LENGTH;
  bitmap_line_length = (width + 7) / 8;

  for (i = height - 1; 0 <= i; i--) {
    memcpy(bmp_p, bitmap + bitmap_line_length * i, bitmap_line_length);
    bmp_p += bitmap_line_length;
    for (j = 0; j < line_pad_length; j++, bmp_p++)
      *bmp_p = 0x00;
  }

  if (bmp_length != NULL)
    *bmp_length = ((char *)bmp_p - bmp);

  LOG(("out: eb_bitmap_to_bmp(bmp_length=%ld) = %s",
    (long)((char *)bmp_p - bmp), eb_error_string(EB_SUCCESS)));

  return EB_SUCCESS;
}


/*
 * The Foreground and background colors of PNG image.
 */
#define PNG_FOREGROUND_COLOR        0x000000
#define PNG_BACKGROUND_COLOR        0xffffff

/*
 * The preamble of PNG image.
 */
static const unsigned char png_preamble[] = {
  /*
   * PNG file signature (8 bytes)
   */
  0x89, 'P', 'N', 'G', '\r', '\n', 0x1a, '\n',
  /*
   * IHDR(Image Header) Chunk (25 bytes)
   */
  /* Size of IHDR. */
  0x00, 0x00, 0x00, 0x0d,
  'I', 'H', 'D', 'R',
  /* Width. (set at run time) */
  0x00, 0x00, 0x00, 0x00,
  /* Height. (set at run time) */
  0x00, 0x00, 0x00, 0x00,
  /* misc. */
  0x01, 0x03, 0x00, 0x00, 0x00,
  /* CRC (set at run time) */
  0x00, 0x00, 0x00, 0x00,

  /*
   * PLTE(Palette) Chunk (18 bytes)
   */
  /* Size of PLTE */
  0x00, 0x00, 0x00, 0x06,
  'P', 'L', 'T', 'E',
  /* RGB for palette index 0 */
  0xff, 0xff, 0xff,
  /* RGB for palette index 1 */
  0x00, 0x00, 0x00,
  /* CRC (set at run time) */
  0x00, 0x00, 0x00, 0x00,

  /*
   * tRNS(Transparency) Chunk (13 bytes)
   */
  /* Size of tRNS */
  0x00, 0x00, 0x00, 0x01,
  't', 'R', 'N', 'S',
  /* Alpha for palette index 0 */
  0x00,
  /* CRC */
  0x40, 0xe6, 0xd8, 0x66,

  /*
   * IDAT(Image Data) Chunk (12+ bytes)
   */
  /* Size of IDAT (set at run time) */
  0x00, 0x00, 0x00, 0x00,
  'I', 'D', 'A', 'T',
};

static const unsigned char png_trailer[] = {
  /* CRC (set at run time) */
  0x00, 0x00, 0x00, 0x00,
  /*
   * IEND(Image End) Chunk (12 bytes)
   */
  /* Size of IEND */
  0x00, 0x00, 0x00, 0x00,
  'I', 'E', 'N', 'D',
  /* CRC */
  0xae, 0x42, 0x60, 0x82,
};

/*
 * Table of CRCs of all 8-bit messages.
 */
static const unsigned long png_crc_table[256] = {
  0x00000000, 0x77073096, 0xee0e612c, 0x990951ba,   /* 0x00 - 0x03 */
  0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,   /* 0x04 - 0x07 */
  0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,   /* 0x08 - 0x0b */
  0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,   /* 0x0c - 0x0f */
  0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de,   /* 0x10 - 0x13 */
  0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,   /* 0x14 - 0x17 */
  0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec,   /* 0x18 - 0x1b */
  0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,   /* 0x1c - 0x1f */
  0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,   /* 0x20 - 0x23 */
  0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,   /* 0x24 - 0x27 */
  0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940,   /* 0x28 - 0x2b */
  0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,   /* 0x2c - 0x2f */
  0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116,   /* 0x30 - 0x33 */
  0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,   /* 0x34 - 0x37 */
  0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,   /* 0x38 - 0x3b */
  0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,   /* 0x3c - 0x3f */
  0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a,   /* 0x40 - 0x43 */
  0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,   /* 0x44 - 0x47 */
  0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818,   /* 0x48 - 0x4b */
  0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,   /* 0x4c - 0x4f */
  0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,   /* 0x50 - 0x53 */
  0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,   /* 0x54 - 0x57 */
  0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c,   /* 0x58 - 0x5b */
  0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,   /* 0x5c - 0x5f */
  0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2,   /* 0x60 - 0x63 */
  0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,   /* 0x64 - 0x67 */
  0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,   /* 0x68 - 0x6b */
  0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,   /* 0x6c - 0x6f */
  0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086,   /* 0x70 - 0x73 */
  0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,   /* 0x74 - 0x77 */
  0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4,   /* 0x78 - 0x7b */
  0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,   /* 0x7c - 0x7f */
  0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,   /* 0x80 - 0x83 */
  0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,   /* 0x84 - 0x87 */
  0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,   /* 0x88 - 0x8b */
  0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,   /* 0x8c - 0x8f */
  0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe,   /* 0x90 - 0x93 */
  0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,   /* 0x94 - 0x97 */
  0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,   /* 0x98 - 0x9b */
  0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,   /* 0x9c - 0x9f */
  0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252,   /* 0xa0 - 0xa3 */
  0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,   /* 0xa4 - 0xa7 */
  0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60,   /* 0xa8 - 0xab */
  0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,   /* 0xac - 0xaf */
  0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,   /* 0xb0 - 0xb3 */
  0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,   /* 0xb4 - 0xb7 */
  0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04,   /* 0xb8 - 0xbb */
  0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,   /* 0xbc - 0xbf */
  0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a,   /* 0xc0 - 0xc3 */
  0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,   /* 0xc4 - 0xc7 */
  0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,   /* 0xc8 - 0xcb */
  0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,   /* 0xcc - 0xcf */
  0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e,   /* 0xd0 - 0xd3 */
  0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,   /* 0xd4 - 0xd7 */
  0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c,   /* 0xd8 - 0xdb */
  0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,   /* 0xdc - 0xdf */
  0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,   /* 0xe0 - 0xe3 */
  0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,   /* 0xe4 - 0xe7 */
  0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0,   /* 0xe8 - 0xeb */
  0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,   /* 0xec - 0xef */
  0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6,   /* 0xf0 - 0xf3 */
  0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,   /* 0xf4 - 0xf7 */
  0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,   /* 0xf8 - 0xfb */
  0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d  /* 0xfc - 0xff */
};

static unsigned long
png_crc(const char *buf, size_t len)
{
  unsigned long c = 0xffffffffL;
  int n;

  for (n = 0; n < len; n++)
    c = png_crc_table[(c ^ *((unsigned char *)buf + n)) & 0xff] ^ (c >> 8);
  return c ^ 0xffffffffL;
}


static int
png_compress(const char *src, int width, int height, char *dest,
  size_t *dest_len)
{
  int line_size = (width  + 7) / 8;
  z_stream z;
  int z_result;
  unsigned char byte_zero = 0x00;
  int i;

  z.zalloc = Z_NULL;
  z.zfree = Z_NULL;
  z.opaque = Z_NULL;
  z_result = deflateInit(&z, Z_NO_COMPRESSION);
  if (z_result != Z_OK)
    return z_result;

  /*
   * Exactly to say, `z.avail_out' must be:
   *   avail_out > (sizeof(src) + 12) * 1.001
   * but we use an approximation here.
   */
  z.next_out = (unsigned char *)dest;
  z.avail_out = (line_size + 1) * height + 12 + 256;
  for (i = 0; i < height - 1; i++) {
    z.next_in = &byte_zero;
    z.avail_in = 1;
    z_result = deflate(&z, Z_NO_FLUSH);
    if (z_result != Z_OK || z.avail_in != 0)
      goto failed;

    z.next_in = (unsigned char *)src + (line_size * i);
    z.avail_in = line_size;
    z_result = deflate(&z, Z_NO_FLUSH);
    if (z_result != Z_OK || z.avail_in != 0)
      goto failed;
  }

  z.next_in = &byte_zero;
  z.avail_in = 1;
  z_result = deflate(&z, Z_NO_FLUSH);
  if (z_result != Z_OK || z.avail_in != 0)
    goto failed;

  z.next_in = (unsigned char *)src + (line_size * i);
  z.avail_in = line_size;
  if (deflate(&z, Z_FINISH) != Z_STREAM_END)
    goto failed;

  z_result = deflateEnd(&z);
  if (z_result != Z_OK)
    return z_result;

  *dest_len = (z.next_out - (unsigned char *)dest);
  return Z_STREAM_END;

  /*
   * An error occurs...
   */
  failed:
  deflateEnd(&z);
  return z_result;
}


#define INT2CHARS(p, i) do { \
   *(unsigned char *)(p) = ((i) >> 24) & 0xff; \
   *((unsigned char *)(p) + 1) = ((i) >> 16) & 0xff; \
   *((unsigned char *)(p) + 2) = ((i) >> 8) & 0xff; \
   *((unsigned char *)(p) + 3) = (i) & 0xff; \
} while (0);

#define RGB2CHARS(p, i) do { \
   *(unsigned char *)(p) = ((i) >> 16) & 0xff; \
   *((unsigned char *)(p) + 1) = ((i) >> 8) & 0xff; \
   *((unsigned char *)(p) + 2) = (i) & 0xff; \
} while (0);

/*
 * Convert a bitmap image to PNG format.
 *
 * It requires four arguements.  `png' is buffer to store the PNG
 * image data.  `bitmap', `width', and `height' are bitmap data,
 * width, and height of the bitmap image.
 */
EB_Error_Code
eb_bitmap_to_png(const char *bitmap, int width, int height, char *png,
  size_t *png_length)
{
  EB_Error_Code error_code;
  char *png_p = png;
  char *idat_start;
  size_t idat_len;
  unsigned long crc;
  int z_result;

  LOG(("in: eb_bitmap_to_png(width=%d, height=%d)", width, height));

  /*
   * Copy the default preamble.
   */
  memcpy(png_p, png_preamble, sizeof(png_preamble));

  /*
   * Set image width and height.
   */
  INT2CHARS(png_p + 16, width);
  INT2CHARS(png_p + 20, height);

  crc = png_crc(png_p + 12, 17);
  INT2CHARS(png_p + 29, crc);

  /*
   * Set global colors.
   */
  RGB2CHARS(png_p + 41, PNG_BACKGROUND_COLOR);
  RGB2CHARS(png_p + 44, PNG_FOREGROUND_COLOR);
  crc = png_crc(png_p + 37, 10);
  INT2CHARS(png_p + 47, crc);

  /*
   * Output `bitmap'.
   * We assume memory allocation error occurs if png_compress() doesn't
   * return Z_STREAM_END.
   */
  idat_start = png_p + sizeof(png_preamble);
  z_result = png_compress(bitmap, width, height, idat_start, &idat_len);
  if (z_result != Z_STREAM_END) {
    error_code = EB_ERR_MEMORY_EXHAUSTED;
    goto failed;
  }
  INT2CHARS(png_p + 64, idat_len);
  crc = png_crc(idat_start - 4, idat_len + 4);
  png_p = idat_start + idat_len;

  /*
   * Output a trailer.
   */
  memcpy(png_p, png_trailer, sizeof(png_trailer));
  INT2CHARS(png_p, crc);
  png_p += sizeof(png_trailer);
  if (png_length != NULL)
    *png_length = ((char *)png_p - png);

  LOG(("out: eb_bitmap_to_png(png_length=%ld) = %s",
    (long)((char *)png_p - png), eb_error_string(EB_SUCCESS)));

  return EB_SUCCESS;

  /*
   * An error occurs...
   */
  failed:
  LOG(("out: eb_bitmap_to_png(png_length=%ld) = %s",
    (long)((char *)png_p - png), eb_error_string(error_code)));
  return error_code;
}

#undef INT2CHARS
#undef RGB2CHARS


#ifdef TEST

#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

#define test_width 32
#define test_height 16
static unsigned char test_bitmap[] = {
   0xff, 0xff, 0xff, 0xff, 0x80, 0x81, 0x83, 0x01, 0x80, 0x81, 0x01, 0x01,
   0x80, 0x81, 0x01, 0x01, 0xe3, 0x8f, 0x11, 0xc7, 0xe3, 0x8f, 0x0f, 0xc7,
   0xe3, 0x81, 0x87, 0xc7, 0xe3, 0x81, 0xc3, 0xc7, 0xe3, 0x81, 0xe1, 0xc7,
   0xe3, 0x8f, 0x11, 0xc7, 0xe3, 0x8f, 0x11, 0xc7, 0xe3, 0x81, 0x01, 0xc7,
   0xe3, 0x81, 0x01, 0xc7, 0xe3, 0x81, 0x83, 0xc7, 0xff, 0xff, 0xff, 0xff,
   0xff, 0xff, 0xff, 0xff
};

int
main(int argc, char *argv[])
{
  char image[EB_SIZE_FONT_IMAGE];
  size_t image_size;
  int file;

  eb_bitmap_to_xbm(test_bitmap, test_width, test_height, image, &image_size);
  file = creat("test.xbm", 0644);
  if (file < 0)
    exit(1);
  if (write(file, image, image_size) != image_size) {
    close(file);
    exit(1);
  }

  eb_bitmap_to_xpm(test_bitmap, test_width, test_height, image, &image_size);
  file = creat("test.xpm", 0644);
  if (file < 0)
    exit(1);
  if (write(file, image, image_size) != image_size) {
    close(file);
    exit(1);
  }

  eb_bitmap_to_gif(test_bitmap, test_width, test_height, image, &image_size);
  file = creat("test.gif", 0644);
  if (file < 0)
    exit(1);
  if (write(file, image, image_size) != image_size) {
    close(file);
    exit(1);
  }

  eb_bitmap_to_bmp(test_bitmap, test_width, test_height, image, &image_size);
  file = creat("test.bmp", 0644);
  if (file < 0)
    exit(1);
  if (write(file, image, image_size) != image_size) {
    close(file);
    exit(1);
  }

  eb_bitmap_to_png(test_bitmap, test_width, test_height, image, &image_size);
  file = creat("test.png", 0644);
  if (file < 0)
    exit(1);
  if (write(file, image, image_size) != image_size) {
    close(file);
    exit(1);
  }

  return 0;
}

#endif /* TEST */
