/**
 * Graphics::PNG is PNG library
 *
 * @author DATE Ken (as Itacchi) / ge6537@i.bekkoame.ne.jp
 * @code-name Aoi
 * @see http://www.isc.meiji.ac.jp/~ee77038/ruby/
 * $Id: writer.c,v 1.1 2000/09/27 17:55:25 date Exp date $
 */

#include "libpng.h"

#define PNG_WROTE_INFO_BEFORE_PLTE  0x400
#define WRITE_STATUS_FN "write_status_fn"

VALUE cWriter;



/*
 * -----------------------
 * utility function for
 *   Graphics::PNG::Writer
 * -----------------------
 */
static void
writer_free(png_obj)
  png_object *png_obj;
{
  if (png_obj->fp)
    fclose(png_obj->fp);
  if (png_obj->info->valid & PNG_INFO_PLTE)
    free(png_obj->info->palette);
  if (png_obj->info->valid & PNG_INFO_tRNS)
    free(png_obj->info->trans);

  png_destroy_write_struct(&png_obj->obj, &png_obj->info);
  free(png_obj);
}



static void
png_default_error(png_ptr, message)
  png_structp png_ptr;
  png_const_charp message;
{
  rb_raise(ePngError, "Ruby/libpng error: %s\n", message);
}



static void
png_default_warning(png_ptr, message)
  png_structp png_ptr;
  png_const_charp message;
{
  rb_warning("Ruby/libpng warning: %s\n", message);
}



static void
write_row_callback(png_ptr, row_number, pass)
  png_structp png_ptr;
  png_uint_32 row_number;
  int pass;
{
  VALUE proc;

  if(png_ptr == NULL || row_number > PNG_MAX_UINT || pass > 7) return;

  proc = rb_iv_get(cReader, WRITE_STATUS_FN);

  if (NIL_P(proc))
    return;

  rb_funcall(proc, rb_intern("call"), 2, INT2NUM(row_number), INT2FIX(pass));
}




/*
 * -----------------------
 * class method of
 *   Graphics::PNG::Writer
 * -----------------------
 */
static VALUE
writer_new(klass, file)
  VALUE klass, file;
{
  FILE *fp;
  OpenFile *fptr;
  VALUE new_obj;
  png_object *png_obj;


  Check_Type(file, T_STRING);

  if ((fp = fopen(STR2CSTR(file), "wb")) == NULL){
    rb_raise(rb_eException, "can't open: %s", STR2CSTR(file));
    return Qnil;
  }

  /* create PNG object */

  new_obj = Data_Make_Struct(klass, png_object, NULL, writer_free, png_obj);

  png_obj->fp = fp;

  png_obj->obj = png_create_write_struct(PNG_LIBPNG_VER_STRING,
    (png_voidp)NULL, png_default_error, png_default_warning);

  if (png_obj->obj == NULL){
    fclose(fp);
    rb_raise(rb_eException, "can't create PNG object (failer to create write struct)");
    return Qnil;
  }

  png_obj->info = png_create_info_struct(png_obj->obj);
  if (png_obj->info == NULL){
    fclose(fp);
    png_destroy_write_struct(&png_obj->obj, (png_infopp)NULL);
    rb_raise(rb_eException, "can't create PNG object (failer to create info struct)");
    return Qnil;
  }

  png_obj->end_info = png_create_info_struct(png_obj->obj);
  if (png_obj->end_info == NULL){
    fclose(fp);
    png_destroy_write_struct(&png_obj->obj, &png_obj->info);
    rb_raise(rb_eException, "can't create PNG object (failer to create info struct)");
    return Qnil;
  }

  if (setjmp(png_jmpbuf(png_obj->obj))){
     fclose(fp);
     png_destroy_info_struct(png_obj->obj, &png_obj->end_info);
     png_destroy_write_struct(&png_obj->obj, &png_obj->info);
     rb_raise(rb_eException, "can't create PNG object (failer to create write struct)");
     return Qnil;
   }

  png_init_io(png_obj->obj, png_obj->fp);

  rb_obj_call_init(new_obj, 1, &file);

  return new_obj;
}




/*
 * -----------------------
 * instance method of
 *   Graphics::PNG::Writer
 * -----------------------
 */
static VALUE
writer_initialize(obj)
  VALUE obj;
{
  return Qnil;
}



#if defined(PNG_READ_BACKGROUND_SUPPORTED)
#ifdef PNG_FLOATING_POINT_SUPPORTED
static VALUE
libpng_writer_set_background(obj, background_color, 
                          background_gamma_code, need_expand, background_gamma)
  VALUE obj, background_color,
        background_gamma_code, need_expand, background_gamma;
{
  png_object *png_obj;
  png_color_16p bg_color;

  IS_COLOR_16_P(background_color);
  FIXNUM_P(background_gamma_code);
  FIXNUM_P(need_expand);
  Check_Type(background_gamma, T_FLOAT);
 
  GET_PNG_VAL(obj, png_obj);
  Data_Get_Struct(background_color, png_color_16, bg_color);

  png_set_background(png_obj->obj, bg_color,
                     NUM2INT(background_gamma_code), NUM2INT(need_expand),
                     RFLOAT(background_gamma)->value);

  return Qnil;
}
#endif /* PNG_FLOATING_POINT_SUPPORTED */
#endif /* PNG_READ_BACKGROUND_SUPPORTED */


#if defined(PNG_WRITE_BGR_SUPPORTED)
static VALUE
libpng_writer_set_bgr(obj)
  VALUE obj;
{
  png_object *png_obj;

  GET_PNG_VAL(obj, png_obj);

  png_set_bgr(png_obj->obj);

  return Qnil;
}
#endif /* PNG_WRITE_BGR_SUPPORTED */


#if defined(PNG_bKGD_SUPPORTED)
static VALUE
libpng_writer_set_bKGD(obj, background)
  VALUE obj, background;
{
  png_object *png_obj;
  png_color_16p bg_color;

  IS_COLOR_16_P(background);

  GET_PNG_VAL(obj, png_obj);
  Data_Get_Struct(background, png_color_16, bg_color);

  png_set_bKGD(png_obj->obj, png_obj->info, bg_color);

  return Qnil;
}
#endif /* PNG_bKGD_SUPPORTED */



#if defined(PNG_cHRM_SUPPORTED)
#ifdef PNG_FLOATING_POINT_SUPPORTED
static VALUE
libpng_writer_set_cHRM(obj, white_x, white_y, red_x, red_y,
                            green_x, green_y, blue_x, blue_y)
  VALUE obj, white_x, white_y, red_x, red_y,
             green_x, green_y, blue_x, blue_y;
{
  png_object *png_obj;

  Check_Type(white_x, T_FLOAT);
  Check_Type(white_y, T_FLOAT);
  Check_Type(red_x, T_FLOAT);
  Check_Type(red_y, T_FLOAT);
  Check_Type(green_x, T_FLOAT);
  Check_Type(green_y, T_FLOAT);
  Check_Type(blue_x, T_FLOAT);
  Check_Type(blue_y, T_FLOAT);

  GET_PNG_VAL(obj, png_obj);

  png_set_cHRM(png_obj->obj, png_obj->info,
               RFLOAT(white_x)->value, RFLOAT(white_y)->value,
               RFLOAT(red_x)->value, RFLOAT(red_y)->value,
               RFLOAT(green_x)->value, RFLOAT(green_y)->value,
               RFLOAT(blue_x)->value, RFLOAT(blue_y)->value);

  return Qnil;
}
#endif /* PNG_FLOATING_POINT_SUPPORTED */


#ifdef PNG_FIXED_POINT_SUPPORTED
static VALUE
libpng_writer_set_cHRM_fixed(obj, white_x, white_y, red_x, red_y,
                                  green_x, green_y, blue_x, blue_y)
  VALUE obj, white_x, white_y, red_x, red_y,
             green_x, green_y, blue_x, blue_y;
{
  png_object *png_obj;

  FIXNUM_P(white_x);
  FIXNUM_P(white_y);
  FIXNUM_P(red_x);
  FIXNUM_P(red_y);
  FIXNUM_P(green_x);
  FIXNUM_P(green_y);
  FIXNUM_P(blue_x);
  FIXNUM_P(blue_y);

  GET_PNG_VAL(obj, png_obj);

  png_set_cHRM_fixed(png_obj->obj, png_obj->info,
               NUM2LONG(white_x), NUM2LONG(white_y),
               NUM2LONG(red_x), NUM2LONG(red_y),
               NUM2LONG(green_x), NUM2LONG(green_y),
               NUM2LONG(blue_x), NUM2LONG(blue_y));

  return Qnil;
}
#endif /* PNG_FIXED_POINT_SUPPORTED */
#endif /* PNG_cHRM_SUPPORTED */


static VALUE
libpng_writer_set_compression_level(obj, level)
  VALUE obj, level;
{
  png_object *png_obj;

  FIXNUM_P(level);

  GET_PNG_VAL(obj, png_obj);

  png_set_compression_level(png_obj->obj, FIX2INT(level));

  return Qnil;
}


static VALUE
libpng_writer_set_compression_mem_level(obj, mem_level)
  VALUE obj, mem_level;
{
  png_object *png_obj;

  FIXNUM_P(mem_level);

  GET_PNG_VAL(obj, png_obj);

  png_set_compression_mem_level(png_obj->obj, FIX2INT(mem_level));

  return Qnil;
}


static VALUE
libpng_writer_set_compression_method(obj, method)
  VALUE obj, method;
{
  png_object *png_obj;

  FIXNUM_P(method);

  GET_PNG_VAL(obj, png_obj);

  png_set_compression_method(png_obj->obj, FIX2INT(method));

  return Qnil;
}


static VALUE
libpng_writer_set_compression_strategy(obj, strategy)
  VALUE obj, strategy;
{
  png_object *png_obj;

  FIXNUM_P(strategy);

  GET_PNG_VAL(obj, png_obj);

  png_set_compression_strategy(png_obj->obj, FIX2INT(strategy));

  return Qnil;
}


static VALUE
libpng_writer_set_compression_window_bits(obj, window_bits)
  VALUE obj, window_bits;
{
  png_object *png_obj;

  FIXNUM_P(window_bits);

  GET_PNG_VAL(obj, png_obj);

  png_set_compression_window_bits(png_obj->obj, FIX2INT(window_bits));

  return Qnil;
}


static VALUE
libpng_writer_set_crc_action(obj, crit_action, ancil_action)
  VALUE obj, crit_action, ancil_action;
{
  png_object *png_obj;

  FIXNUM_P(crit_action);
  FIXNUM_P(ancil_action);

  GET_PNG_VAL(obj, png_obj);

  png_set_crc_action(png_obj->obj, FIX2INT(crit_action), FIX2INT(ancil_action));

  return Qnil;
}


#if defined(PNG_READ_DITHER_SUPPORTED)
static VALUE
libpng_writer_set_dither(obj, palettes, maximum_colors, histogram, full_dither)
  VALUE obj, palettes, maximum_colors, histogram, full_dither;
{
  png_object *png_obj;
  png_colorp dither_palette;
  png_color *color;
  png_uint_16p dither_histogram;
  int num_palette, i;

  Check_Type(palettes, T_ARRAY);
  FIXNUM_P(maximum_colors);
  Check_Type(histogram, T_ARRAY);
  FIXNUM_P(full_dither);

  GET_PNG_VAL(obj, png_obj);

  num_palette = RARRAY(palettes)->len;
  dither_palette = ALLOCA_N(png_color, num_palette);
  for (i=0; i<num_palette; i++){
    Data_Get_Struct(RARRAY(palettes)->ptr[i], png_color, color);
    dither_palette[i] = *color;
  }

  dither_histogram = ALLOCA_N(png_uint_16, RARRAY(histogram)->len);
  for (i=0; i<RARRAY(histogram)->len; i++){
    dither_histogram[i] = FIX2INT(RARRAY(histogram)->ptr[i]);
  }

  png_set_dither(png_obj->obj, dither_palette, num_palette,
                 FIX2INT(maximum_colors), dither_histogram,
                 FIX2INT(full_dither));

  return Qnil;
}
#endif


#if defined(PNG_WRITE_FILLER_SUPPORTED)
static VALUE
libpng_writer_set_filler(obj, filler, flags)
  VALUE obj, filler, flags;
{
  png_object *png_obj;

  FIXNUM_P(filler);
  FIXNUM_P(flags);

  GET_PNG_VAL(obj, png_obj);

  png_set_filler(png_obj->obj, NUM2LONG(filler), FIX2INT(flags));

  return Qnil;
}
#endif /* PNG_WRITE_FILLER_SUPPORTED */


static VALUE
libpng_writer_set_filter(obj, method, filters)
  VALUE obj, method, filters;
{
  png_object *png_obj;

  FIXNUM_P(method);
  FIXNUM_P(filters);

  GET_PNG_VAL(obj, png_obj);

  png_set_filter(png_obj->obj, FIX2INT(method), FIX2INT(filters));

  return Qnil;
}


#if defined(PNG_WRITE_WEIGHTED_FILTER_SUPPORTED)
#ifdef PNG_FLOATING_POINT_SUPPORTED
static VALUE
libpng_writer_set_filter_heuristics(obj, heuristic_method, filter_weights, filter_costs)
  VALUE obj, heuristic_method, filter_weights, filter_costs;
{
  png_object *png_obj;
  int num_weights, i;
  png_doublep weights, costs;

  FIXNUM_P(heuristic_method);
  Check_Type(filter_weights, T_ARRAY);

  GET_PNG_VAL(obj, png_obj);

  num_weights = RARRAY(filter_weights)->len;

  weights = ALLOCA_N(double, num_weights);
  for (i=0; i<num_weights; i++){
    Check_Type(RARRAY(filter_weights)->ptr[i], T_FLOAT);
    weights[i] = NUM2DBL(RARRAY(filter_weights)->ptr[i]);
  }

  switch (TYPE(filter_costs)){
    case T_NIL:
      png_set_filter_heuristics(png_obj->obj, FIX2INT(heuristic_method),
              num_weights, weights, NULL);
      break;
    case T_ARRAY:
      costs = ALLOCA_N(double, num_weights);
      for (i=0; i<num_weights; i++){
        Check_Type(RARRAY(filter_costs)->ptr[i], T_FLOAT);
        costs[i] = NUM2DBL(RARRAY(filter_costs)->ptr[i]);
      }
      png_set_filter_heuristics(png_obj->obj, FIX2INT(heuristic_method),
              num_weights, weights, costs);
      break;
    default:
      rb_raise(rb_eTypeError, "wrong argument type %s (expected ARRAY/nil)",
               rb_class2name(CLASS_OF(filter_costs)));
  }

  return Qnil;
}
#endif /* PNG_FLOATING_POINT_SUPPORTED */
#endif /* PNG_WRITE_WEIGHTED_FILTER_SUPPORTED */


#if defined(PNG_WRITE_FLUSH_SUPPORTED)
static VALUE
libpng_writer_set_flush(obj, nrows)
  VALUE obj, nrows;
{
  png_object *png_obj;

  FIXNUM_P(nrows);

  GET_PNG_VAL(obj, png_obj);

  png_set_flush(png_obj->obj, FIX2INT(nrows));

  return Qnil;
}
#endif /* PNG_WRITE_FLUSH_SUPPORTED */


#if defined(PNG_READ_GAMMA_SUPPORTED)
#ifdef PNG_FLOATING_POINT_SUPPORTED
static VALUE
libpng_writer_set_gamma(obj, screen_gamma, default_file_gamma)
  VALUE obj, screen_gamma, default_file_gamma;
{
  png_object *png_obj;

  Check_Type(screen_gamma, T_FLOAT);
  Check_Type(default_file_gamma, T_FLOAT);

  GET_PNG_VAL(obj, png_obj);

  png_set_gamma(png_obj->obj, RFLOAT(screen_gamma)->value,
                              RFLOAT(default_file_gamma)->value);

  return Qnil;
}
#endif /* PNG_FLOATING_POINT_SUPPORTED */
#endif /* PNG_READ_GAMMA_SUPPORTED */


#if defined(PNG_gAMA_SUPPORTED)
#ifdef PNG_FLOATING_POINT_SUPPORTED
static VALUE
libpng_writer_set_gAMA(obj, file_gamma)
  VALUE obj, file_gamma;
{
  png_object *png_obj;

  GET_PNG_VAL(obj, png_obj);

  switch (TYPE(file_gamma)){
    case T_FIXNUM:
      png_set_gAMA_fixed(png_obj->obj, png_obj->info, NUM2LONG(file_gamma));
      break;
    case T_FLOAT:
      png_set_gAMA(png_obj->obj, png_obj->info, RFLOAT(file_gamma)->value);
      break;
    default:
      rb_raise(rb_eTypeError, "wrong argument type %s (expected FIXNUM/FLOAT)",
               rb_class2name(CLASS_OF(file_gamma)));
      break;
  }

  return Qnil;
}
#endif /* PNG_FLOATING_POINT_SUPPORTED */


static VALUE
libpng_writer_set_gAMA_fixed(obj, file_gamma)
  VALUE obj, file_gamma;
{
  png_object *png_obj;

  FIXNUM_P(file_gamma);

  GET_PNG_VAL(obj, png_obj);

  png_set_gAMA_fixed(png_obj->obj, png_obj->info, NUM2LONG(file_gamma));

  return Qnil;
}
#endif /* PNG_gAMA_SUPPORTED */


#if defined(PNG_hIST_SUPPORTED)
static VALUE
libpng_writer_set_hIST(obj, hist)
  VALUE obj, hist;
{
  png_object *png_obj;
  png_colorp palette;
  int i, num_palette;
  png_uint_16p histogram;
  VALUE tmp;

  Check_Type(hist, T_ARRAY);

  GET_PNG_VAL(obj, png_obj);
  
  if (png_get_PLTE(png_obj->obj, png_obj->info, &palette, &num_palette)){
    histogram = ALLOCA_N(png_uint_16, num_palette);
    for (i = 0; i < num_palette; i++){
      tmp = rb_ary_shift(hist);
      FIXNUM_P(tmp);
      histogram[i] = FIX2INT(tmp);
    }
    png_set_hIST(png_obj->obj, png_obj->info, histogram);
  }

  return Qnil;
}
#endif /* PNG_hIST_SUPPORTED */


#if defined(PNG_iCCP_SUPPORTED)
static VALUE
libpng_writer_set_iCCP(obj, name, compression_type, profile)
  VALUE obj, name, compression_type, profile;
{
  png_object *png_obj;
  int com_type;
  VALUE profile_name;
  int name_size;
  
  Check_Type(name, T_STRING);
  Check_Type(profile, T_STRING);
  
  GET_PNG_VAL(obj, png_obj);

  name_size = RSTRING(name)->len;
  if (name_size > 80) name_size = 80;
    
  profile_name = rb_str_new(STR2CSTR(name), name_size);
  if (TYPE(compression_type) == T_FIXNUM){
    com_type = FIX2INT(compression_type);
    if (com_type != PNG_COMPRESSION_TYPE_BASE){
      rb_raise(ePngError, "wrong compression type %d", com_type);
      return Qnil;
    }
    png_set_iCCP(png_obj->obj, png_obj->info,
                 STR2CSTR(profile_name), com_type,
                 STR2CSTR(profile), RSTRING(profile)->len);
  }
  else if (TYPE(compression_type) == T_NIL){
    png_set_iCCP(png_obj->obj, png_obj->info,
                 STR2CSTR(profile_name), (int)NULL,
                 STR2CSTR(profile), RSTRING(profile)->len);
  }
  else {
    rb_raise(rb_eTypeError, "wrong argument type %s (expected STRING/NIL)",
             rb_class2name(CLASS_OF(compression_type)));
  }
png_read_update_info(png_obj->obj, png_obj->info);
  
  return Qnil;
}
#endif /* PNG_iCCP_SUPPORTED */


#if defined(PNG_WRITE_INTERLACING_SUPPORTED)
static VALUE
libpng_writer_set_interlace_handling(obj)
  VALUE obj;
{
  png_object *png_obj;

  GET_PNG_VAL(obj, png_obj);

  png_set_interlace_handling(png_obj->obj);

  return Qnil;
}
#endif /* PNG_WRITE_INTERLACING_SUPPORTED */


#if defined(PNG_WRITE_INVERT_ALPHA_SUPPORTED)
static VALUE
libpng_writer_set_invert_alpha(obj)
  VALUE obj;
{
  png_object *png_obj;

  GET_PNG_VAL(obj, png_obj);

  png_set_invert_alpha(png_obj->obj);

  return Qnil;
}
#endif /* PNG_WRITE_INVERT_ALPHA_SUPPORTED */


#if defined(PNG_WRITE_INVERT_SUPPORTED)
static VALUE
libpng_writer_set_invert_mono(obj)
  VALUE obj;
{
  png_object *png_obj;

  GET_PNG_VAL(obj, png_obj);

  png_set_invert_mono(png_obj->obj);

  return Qnil;
}
#endif /* PNG_WRITE_INVERT_SUPPORTED */


static VALUE
libpng_writer_set_IHDR(obj, width, height, bit_depth, color_type,
                       interlace_type, compression_type, filter_type)
  VALUE obj, width, height, bit_depth, color_type,
     interlace_type, compression_type, filter_type;
{
  png_object *png_obj;

  FIXNUM_P(width);
  FIXNUM_P(height);
  FIXNUM_P(bit_depth);
  FIXNUM_P(color_type);
  FIXNUM_P(interlace_type);
  FIXNUM_P(compression_type);
  FIXNUM_P(filter_type);

  GET_PNG_VAL(obj, png_obj);

  png_set_IHDR(png_obj->obj, png_obj->info,
         NUM2INT(width), NUM2INT(height),
         FIX2INT(bit_depth), FIX2INT(color_type), 
         FIX2INT(interlace_type), FIX2INT(compression_type),
         FIX2INT(filter_type));

  return Qnil;
}


#if defined(PNG_UNKNOWN_CHUNKS_SUPPORTED)
static VALUE
libpng_writer_set_keep_unknown_chunks(obj, keep, chunk_list)
  VALUE obj, keep, chunk_list;
{
  png_object *png_obj;
  int keep_condition, num_chunks, i;
  png_bytep affect_chunk_list;

  FIXNUM_P(keep);

  keep_condition = FIX2INT(keep);

  if (keep_condition < HANDLE_CHUNK_AS_DEFAULT ||
      keep_condition > HANDLE_CHUNK_ALWAYS)
    rb_raise(ePngError,
             "invalid \"keep\" directive for unknown chunks: %d",
             keep_condition);

  GET_PNG_VAL(obj, png_obj);

  switch(TYPE(chunk_list)){
    case T_NIL:
      png_set_keep_unknown_chunks(png_obj->obj, keep_condition, NULL, 0);
      break;

    case T_ARRAY:
      num_chunks = RARRAY(chunk_list)->len;
      affect_chunk_list = ALLOCA_N(char, 5 * num_chunks);
      for (i=0; i<num_chunks; i++){
        Check_Type(RARRAY(chunk_list)->ptr[i], T_STRING);
        memcpy(affect_chunk_list, STR2CSTR(RARRAY(chunk_list)->ptr[i]), 4);
        memcpy(affect_chunk_list, '\0', 1);
      }
      png_set_keep_unknown_chunks(png_obj->obj, keep_condition,
                                  affect_chunk_list, num_chunks);
      break;

    default:
      rb_raise(rb_eTypeError, "wrong argument type %s (expected Array/nil)",
               rb_class2name(CLASS_OF(chunk_list)));
  }

  return Qnil;
}
#endif /* PNG_UNKNOWN_CHUNKS_SUPPORTED */


#if defined(PNG_oFFs_SUPPORTED)
static VALUE
libpng_writer_set_oFFs(obj, offset_x, offset_y, unit_type)
  VALUE obj, offset_x, offset_y, unit_type;
{
  png_object *png_obj;

  FIXNUM_P(offset_x);
  FIXNUM_P(offset_y);
  FIXNUM_P(unit_type);

  GET_PNG_VAL(obj, png_obj);

  png_set_oFFs(png_obj->obj, png_obj->info,
               NUM2LONG(offset_x), NUM2LONG(offset_y), FIX2INT(unit_type));

  return Qnil;
}
#endif /* PNG_oFFs_SUPPORTED */


#if defined(PNG_WRITE_PACK_SUPPORTED)
static VALUE
libpng_writer_set_packing(obj)
  VALUE obj;
{
  png_object *png_obj;

  GET_PNG_VAL(obj, png_obj);

  png_set_packing(png_obj->obj);

  return Qnil;
}
#endif /* PNG_WRITE_PACK_SUPPORTED */


#if defined(PNG_WRITE_PACKSWAP_SUPPORTED)
static VALUE
libpng_writer_set_packswap(obj)
  VALUE obj;
{
  png_object *png_obj;

  GET_PNG_VAL(obj, png_obj);

  png_set_packswap(png_obj->obj);

  return Qnil;
}
#endif /* PNG_WRITE_PACKSWAP_SUPPORTED */


#if defined(PNG_pCAL_SUPPORTED)
static VALUE
libpng_writer_set_pCAL(obj, purpose, X0, X1, type, unit, params)
  VALUE obj, purpose, X0, X1, type, unit, params;
{
  png_object *png_obj;
  png_charpp pcal_params;
  int nparams, i;
  VALUE str;

  Check_Type(purpose, T_STRING);
  FIXNUM_P(X0);
  FIXNUM_P(X1);
  FIXNUM_P(type);
  Check_Type(unit, T_STRING);
  Check_Type(params, T_ARRAY);

  GET_PNG_VAL(obj, png_obj);

  nparams = RARRAY(params)->len;
  pcal_params = ALLOCA_N(png_charp, RARRAY(params)->len);
  for (i=0; i<RARRAY(params)->len; i++){
    Check_Type(RARRAY(params)->ptr[i], T_STRING);
    pcal_params[i] = ALLOCA_N(char, nparams);
    strcpy(pcal_params[i], STR2CSTR(RARRAY(params)->ptr[i]));
  }

  png_set_pCAL(png_obj->obj, png_obj->info, STR2CSTR(purpose),
               NUM2LONG(X0), NUM2LONG(X1), FIX2INT(type),
               nparams, STR2CSTR(unit), pcal_params);

  return Qnil;
}
#endif /* PNG_pCAL_SUPPORTED */


#if defined(PNG_pHYs_SUPPORTED)
static VALUE
libpng_writer_set_pHYs(obj, res_x, res_y, unit_type)
  VALUE obj, res_x, res_y, unit_type;
{
  png_object *png_obj;

  FIXNUM_P(res_x);
  FIXNUM_P(res_y);
  FIXNUM_P(unit_type);

  GET_PNG_VAL(obj, png_obj);

  png_set_pHYs(png_obj->obj, png_obj->info,
      NUM2LONG(res_x), NUM2LONG(res_y), FIX2INT(unit_type));

  return Qnil;
}
#endif /* PNG_pHYs_SUPPORTED */


static VALUE
libpng_writer_set_PLTE(obj, palettes)
  VALUE obj, palettes;
{
  png_object *png_obj;
  png_colorp pal;
  png_color *color;
  int num_palette, i;

  Check_Type(palettes, T_ARRAY);

  GET_PNG_VAL(obj, png_obj);

  num_palette = RARRAY(palettes)->len;

  pal = ALLOC_N(png_color, num_palette);

  for (i=0; i<num_palette; i++){
    IS_COLOR_P(RARRAY(palettes)->ptr[i]);
    Data_Get_Struct(RARRAY(palettes)->ptr[i], png_color, color);
    pal[i] = *color;

/* print processing palette
    printf("RGB: %x %x %x\n", pal[i].red, pal[i].green, pal[i].blue);
*/
  }

  png_set_PLTE(png_obj->obj, png_obj->info, pal, num_palette);

  return Qnil;
}


#if defined(PNG_sBIT_SUPPORTED)
static VALUE
libpng_writer_set_sBIT(obj, sig_bit)
  VALUE obj, sig_bit;
{
  png_object *png_obj;
  png_color_8p color;

  IS_COLOR_8_P(sig_bit);

  GET_PNG_VAL(obj, png_obj);
  Data_Get_Struct(sig_bit, png_color_8, color);

  png_set_sBIT(png_obj->obj, png_obj->info, color);
  
  return Qnil;
}
#endif /* PNG_sBIT_SUPPORTED */


#if defined(PNG_sCAL_SUPPORTED)
#ifdef PNG_FLOATING_POINT_SUPPORTED
static VALUE
libpng_writer_set_sCAL(obj, unit, width, height)
  VALUE obj, unit, width, height;
{
  png_object *png_obj;

  FIXNUM_P(unit);
  Check_Type(width, T_FLOAT);
  Check_Type(height, T_FLOAT);

  GET_PNG_VAL(obj, png_obj);

  png_set_sCAL(png_obj->obj, png_obj->info,
      FIX2INT(unit), RFLOAT(width)->value, RFLOAT(height)->value);

  return Qnil;
}
#endif /* PNG_FLOATING_POINT_SUPPORTED */
#endif /* PNG_sCAL_SUPPORTED */


#if defined(PNG_WRITE_SHIFT_SUPPORTED)
static VALUE
libpng_writer_set_shift(obj, true_bits)
  VALUE obj, true_bits;
{
  png_object *png_obj;
  png_color_8p sig_bit;
  VALUE cobj;
  int i;

  IS_COLOR_8_P(cobj);

  GET_PNG_VAL(obj, png_obj);

  Data_Get_Struct(cobj, png_color_8, sig_bit);

  png_set_shift(png_obj->obj, sig_bit);

  return Qnil;
}
#endif


#if defined(PNG_sPLT_SUPPORTED)
static VALUE
libpng_writer_set_sPLT(obj, entries)
  VALUE obj, entries;
{
  png_object *png_obj;
  png_sPLT_tp splt_ptr;
  png_sPLT_t *splt_entry;
  int nentries, i;

  Check_Type(entries, T_ARRAY);

  GET_PNG_VAL(obj, png_obj);

  nentries = RARRAY(entries)->len;  
  splt_ptr = ALLOCA_N(png_sPLT_t, nentries);

  for (i=0; i<nentries; i++){
    IS_sPLT_ENTRY_P(RARRAY(entries)->ptr[i]);
    Data_Get_Struct(RARRAY(entries)->ptr[i], png_sPLT_t, splt_entry);
    splt_ptr[i] = *splt_entry;
  }

  png_set_sPLT(png_obj->obj, png_obj->info, splt_ptr, entries);

  return Qnil;
}
#endif /* PNG_sPLT_SUPPORTED */


#if defined(PNG_sRGB_SUPPORTED)
static VALUE
libpng_writer_set_sRGB(obj, intent)
  VALUE obj, intent;
{
  png_object *png_obj;
  int srgb_intent;

  FIXNUM_P(intent);

  GET_PNG_VAL(obj, png_obj);

  srgb_intent = FIX2INT(intent);

  if (srgb_intent < PNG_sRGB_INTENT_PERCEPTUAL ||
      srgb_intent >= PNG_sRGB_INTENT_LAST)
    rb_raise(ePngError,
             "invalid value for sRGB rendering intent type: %d",
             srgb_intent);

  png_set_sRGB(png_obj->obj, png_obj->info, srgb_intent);

  return Qnil;
}


static VALUE
libpng_writer_set_sRGB_gAMA_and_cHRM(obj, intent)
  VALUE obj, intent;
{
  png_object *png_obj;
  int srgb_intent;

  FIXNUM_P(intent);

  GET_PNG_VAL(obj, png_obj);

  srgb_intent = FIX2INT(intent);

  if (srgb_intent < PNG_sRGB_INTENT_PERCEPTUAL ||
      srgb_intent >= PNG_sRGB_INTENT_LAST)
    rb_raise(ePngError,
             "invalid value for sRGB rendering intent type: %d",
             srgb_intent);

  png_set_sRGB_gAMA_and_cHRM(png_obj->obj, png_obj->info, srgb_intent);

  return Qnil;
}
#endif /* PNG_sRGB_SUPPORTED */


#if defined(PNG_WRITE_SWAP_SUPPORTED)
static VALUE
libpng_writer_set_swap(obj)
  VALUE obj;
{
  png_object *png_obj;

  GET_PNG_VAL(obj, png_obj);

  png_set_swap(png_obj->obj);

  return Qnil;
}
#endif /* PNG_WRITE_SWAP_SUPPORTED */


#if defined(PNG_WRITE_SWAP_ALPHA_SUPPORTED)
static VALUE
libpng_writer_set_swap_alpha(obj)
  VALUE obj;
{
  png_object *png_obj;

  GET_PNG_VAL(obj, png_obj);

  png_set_swap_alpha(png_obj->obj);

  return Qnil;
}
#endif /* PNG_WRITE_SWAP_ALPHA_SUPPORTED */


#if defined(PNG_TEXT_SUPPORTED)
static VALUE
libpng_writer_set_text(obj, texts)
  VALUE obj, texts;
{
  png_object *png_obj;
  png_textp text_ptr;
  png_text *text;
  png_infop info_ptr;
  int num_text, i;

  Check_Type(texts, T_ARRAY);

  GET_PNG_VAL(obj, png_obj);
  
  num_text = RARRAY(texts)->len;
  text_ptr = ALLOC_N(png_text, num_text);

  for (i=0; i<num_text; i++){
    IS_TEXT_P(RARRAY(texts)->ptr[i]);
    Data_Get_Struct(RARRAY(texts)->ptr[i], png_text, text);
    text_ptr[i] = *text;
  }

  if (png_obj->obj->mode & PNG_WROTE_INFO_BEFORE_PLTE)
    info_ptr = png_obj->end_info;
  else
    info_ptr = png_obj->info;

  png_set_text(png_obj->obj, info_ptr, text_ptr, num_text);

  free(text_ptr);

  return Qnil;
}
#endif /* PNG_TEXT_SUPPORTED */


#if defined(PNG_tIME_SUPPORTED)
static VALUE
libpng_writer_set_tIME(obj, mod_time)
  VALUE obj, mod_time;
{
  png_object *png_obj;
  png_time time;
  png_infop info_ptr;
  VALUE ary;

  IS_TIME_P(mod_time);

  GET_PNG_VAL(obj, png_obj);

  ary = rb_funcall(mod_time, rb_intern("to_a"), 0);

  time.year = FIX2INT(RARRAY(ary)->ptr[5]);
  time.month = FIX2INT(RARRAY(ary)->ptr[4]);
  time.day = FIX2INT(RARRAY(ary)->ptr[3]);
  time.hour = FIX2INT(RARRAY(ary)->ptr[2]);
  time.minute = FIX2INT(RARRAY(ary)->ptr[1]);
  time.second = FIX2INT(RARRAY(ary)->ptr[0]);

  if (png_obj->obj->mode & PNG_WROTE_INFO_BEFORE_PLTE)
    info_ptr = png_obj->end_info;
  else
    info_ptr = png_obj->info;

  png_set_tIME(png_obj->obj, info_ptr, &time);

  return Qnil;
}


static VALUE
libpng_writer_set_tIME_end(obj, mod_time)
  VALUE obj, mod_time;
{
  png_object *png_obj;
  png_time time;
  VALUE ary;

  IS_TIME_P(mod_time);

  GET_PNG_VAL(obj, png_obj);

  ary = rb_funcall(mod_time, rb_intern("to_a"), 0);

  time.year = FIX2INT(RARRAY(ary)->ptr[5]);
  time.month = FIX2INT(RARRAY(ary)->ptr[4]);
  time.day = FIX2INT(RARRAY(ary)->ptr[3]);
  time.hour = FIX2INT(RARRAY(ary)->ptr[2]);
  time.minute = FIX2INT(RARRAY(ary)->ptr[1]);
  time.second = FIX2INT(RARRAY(ary)->ptr[0]);

  png_set_tIME(png_obj->obj, png_obj->end_info, &time);
  
  return Qnil;
}
#endif /* PNG_tIME_SUPPORTED */


#if defined(PNG_tRNS_SUPPORTED)
static VALUE
libpng_writer_set_tRNS(obj, trans)
  VALUE obj, trans;
{
  png_object *png_obj;
  png_bytep trans_entry;
  png_color_16p trans_value;
  int i, num_trans;

  GET_PNG_VAL(obj, png_obj);

  if (TYPE(trans) == T_ARRAY){
    /* palette image */
    num_trans = RARRAY(trans)->len;
    trans_entry = ALLOC_N(png_byte, num_trans);
    for (i=0; i<num_trans; i++){
      FIXNUM_P(RARRAY(trans)->ptr[i]);
      trans_entry[i] = FIX2INT(RARRAY(trans)->ptr[i]);
    }
    png_set_tRNS(png_obj->obj, png_obj->info, trans_entry, num_trans, NULL);
  }
  else if (rb_class_of(trans) == cPngColor16){
    /* non-palette image */
    Data_Get_Struct(trans, png_color_16, trans_value);
    png_set_tRNS(png_obj->obj, png_obj->info, NULL, 0, trans_value);
  }
  else {
    rb_raise(rb_eTypeError,
             "wrong argument type %s (expected ARRAY/Graphics::PNG::COLOR16)",
             rb_class2name(CLASS_OF(trans)));
  }

  return Qnil;
}
#endif /* PNG_tRNS_SUPPORTED */


#if defined(PNG_UNKNOWN_CHUNKS_SUPPORTED)
static VALUE
libpng_writer_set_unknown_chunks(obj, unknowns)
  VALUE obj, unknowns;
{
  png_object *png_obj;
  png_unknown_chunkp unknown_chunks;
  png_unknown_chunk *unknown;
  int num_unknowns, i;

  Check_Type(unknowns, T_ARRAY);

  GET_PNG_VAL(obj, png_obj);

  num_unknowns = RARRAY(unknowns)->len;
  unknown_chunks = ALLOC_N(png_unknown_chunk, num_unknowns);

  for (i=0; i<num_unknowns; i++){
    IS_UNKNOWN_CHUNK_P(RARRAY(unknowns)->ptr[i]);
    Data_Get_Struct(RARRAY(unknowns)->ptr[i], png_unknown_chunk, unknown);
    unknown_chunks[i] = *unknown;
  }

  png_set_unknown_chunks(png_obj->obj, png_obj->end_info,
                         unknown_chunks, num_unknowns);

  free(unknown_chunks);

  return Qnil;
}
#endif



static VALUE
libpng_writer_set_write_status_fn(obj, proc)
  VALUE obj, proc;
{
  if (!rb_respond_to(proc, rb_intern("call")))
    rb_raise(rb_eArgError, "argument have to respond to `call'");
  return rb_iv_set(cWriter, WRITE_STATUS_FN, proc);
}



static VALUE
libpng_writer_write_end(obj)
  VALUE obj;
{
  png_object *png_obj;

  GET_PNG_VAL(obj, png_obj);

  png_write_end(png_obj->obj, png_obj->end_info);
  
  return Qnil;
}


static VALUE
libpng_writer_write_image(obj, image)
  VALUE obj, image;
{
  png_object *png_obj;
  png_bytepp image_data;
  int height, row;
  int num_passes, pass;
int i;

  Check_Type(image, T_ARRAY);

  GET_PNG_VAL(obj, png_obj);

  image_data = ALLOC_N(png_bytep, RARRAY(image)->len);
  height = RARRAY(image)->len;

  for (row=0; row<height; row++){
    image_data[row] = ALLOC_N(png_byte, RSTRING(RARRAY(image)->ptr[row])->len);
    png_memcpy(image_data[row], STR2CSTR(RARRAY(image)->ptr[row]),
               RSTRING(RARRAY(image)->ptr[row])->len);
/*
for (i=0;i<RSTRING(RARRAY(image)->ptr[row])->len;i++)
  printf("%2x ", image_data[row][i]);
printf("\n");
*/
  }

/*  png_write_image(png_obj->obj, image_data); */

  num_passes =png_set_interlace_handling(png_obj->obj);

  for (pass=0; pass<num_passes; pass++){
    for (row=0; row<height; row++){
      png_write_rows(png_obj->obj, &image_data[row], 1);
    }
  }

  for (row=0; row<height; row++)
    free(image_data[row]);
  free(image_data);

  return Qnil;
}


static VALUE
libpng_writer_write_info(obj)
  VALUE obj;
{
  png_object *png_obj;

  GET_PNG_VAL(obj, png_obj);

  png_write_info(png_obj->obj, png_obj->info);
  
  return Qnil;
}




void
Init_writer()
{
  /*
   * ---------------------
   * Graphics::PNG::Writer
   * ---------------------
   */

  cWriter = rb_define_class_under(mPng, "Writer", rb_cObject);


  rb_define_singleton_method(cWriter, "new", writer_new, 1);


  rb_define_method(cWriter, "initialize", writer_initialize, -1);

#if defined(PNG_READ_BACKGROUND_SUPPORTED)
#ifdef PNG_FLOATING_POINT_SUPPORTED
  rb_define_method(cWriter, "set_background", libpng_writer_set_background, 4);
#endif
#endif

#if defined(PNG_WRITE_BGR_SUPPORTED)
  rb_define_method(cWriter, "set_bgr", libpng_writer_set_bgr, 0);
#endif

#if defined(PNG_bKGD_SUPPORTED)
  rb_define_method(cWriter, "set_bKGD", libpng_writer_set_bKGD, 1);
#endif

#if defined(PNG_cHRM_SUPPORTED)
#ifdef PNG_FLOATING_POINT_SUPPORTED
  rb_define_method(cWriter, "set_cHRM", libpng_writer_set_cHRM, 8);
#endif
#ifdef PNG_FIXED_POINT_SUPPORTED
  rb_define_method(cWriter, "set_cHRM_fixed", libpng_writer_set_cHRM_fixed, 8);
#endif
#endif

  rb_define_method(cWriter, "set_compression_level", libpng_writer_set_compression_level, 1);
  rb_define_method(cWriter, "set_compression_mem_level", libpng_writer_set_compression_mem_level, 1);
  rb_define_method(cWriter, "set_compression_method", libpng_writer_set_compression_method, 1);
  rb_define_method(cWriter, "set_compression_strategy", libpng_writer_set_compression_strategy, 1);
  rb_define_method(cWriter, "set_compression_windows_bits", libpng_writer_set_compression_window_bits, 1);
  rb_define_method(cWriter, "set_crc_action", libpng_writer_set_crc_action, 2);

#if defined(PNG_READ_DITHER_SUPPORTED)
  rb_define_method(cWriter, "set_dither", libpng_writer_set_dither, 5);
#endif

#if defined(PNG_WRITE_FILLER_SUPPORTED)
  rb_define_method(cWriter, "set_filler", libpng_writer_set_filler, 2);
#endif

  rb_define_method(cWriter, "set_filter", libpng_writer_set_filter, 2);

#if defined(PNG_WRITE_WEIGHTED_FILTER_SUPPORTED)
#ifdef PNG_FLOATING_POINT_SUPPORTED
  rb_define_method(cWriter, "set_filter_heuristics", libpng_writer_set_filter_heuristics, 4);
#endif
#endif

#if defined(PNG_WRITE_FLUSH_SUPPORTED)
  rb_define_method(cWriter, "set_flush", libpng_writer_set_flush, 1);
#endif

#if defined(PNG_READ_GAMMA_SUPPORTED)
#ifdef PNG_FLOATING_POINT_SUPPORTED
  rb_define_method(cWriter, "set_gamma", libpng_writer_set_gamma, 2);
#endif
#endif

#if defined(PNG_gAMA_SUPPORTED)
#ifdef PNG_FLOATING_POINT_SUPPORTED
  rb_define_method(cWriter, "set_gAMA", libpng_writer_set_gAMA, 1);
#endif
  rb_define_method(cWriter, "set_gAMA_fixed", libpng_writer_set_gAMA_fixed, 1);
#endif

#if defined(PNG_hIST_SUPPORTED)
  rb_define_method(cWriter, "set_hIST", libpng_writer_set_hIST, 1);
#endif

#if defined(PNG_iCCP_SUPPORTED)
  rb_define_method(cWriter, "set_iCCP", libpng_writer_set_iCCP, 3);
#endif

#if defined(PNG_WRITE_INTERLACING_SUPPORTED)
  rb_define_method(cWriter, "set_interlace_handling", libpng_writer_set_interlace_handling, 0);
#endif

#if defined(PNG_WRITE_INVERT_ALPHA_SUPPORTED)
  rb_define_method(cWriter, "set_invert_alpha", libpng_writer_set_invert_alpha, 0);
#endif

#if defined(PNG_WRITE_INVERT_SUPPORTED)
  rb_define_method(cWriter, "set_invert_mono", libpng_writer_set_invert_mono, 0);
#endif

  rb_define_method(cWriter, "set_IHDR", libpng_writer_set_IHDR, 7);

#if defined(PNG_UNKNOWN_CHUNKS_SUPPORTED)
  rb_define_method(cWriter, "set_keep_unknown_chunks",
                   libpng_writer_set_keep_unknown_chunks, 2);
#endif

#if defined(PNG_oFFs_SUPPORTED)
  rb_define_method(cWriter, "set_oFFs", libpng_writer_set_oFFs, 3);
#endif

#if defined(PNG_WRITE_PACK_SUPPORTED)
  rb_define_method(cWriter, "set_packing", libpng_writer_set_packing, 0);
#endif

#if defined(PNG_WRITE_PACKSWAP_SUPPORTED)
  rb_define_method(cWriter, "set_packswap", libpng_writer_set_packswap, 0);
#endif

#if defined(PNG_pCAL_SUPPORTED)
  rb_define_method(cWriter, "set_pCAL", libpng_writer_set_pCAL, 6);
#endif

#if defined(PNG_pHYs_SUPPORTED)
  rb_define_method(cWriter, "set_pHYs", libpng_writer_set_pHYs, 3);
#endif

  rb_define_method(cWriter, "set_PLTE", libpng_writer_set_PLTE, 1);

/*
  rb_define_method(cWriter, "set_rows", libpng_writer_set_rows, 1);
*/

#if defined(PNG_sBIT_SUPPORTED)
  rb_define_method(cWriter, "set_sBIT", libpng_writer_set_sBIT, 1);
#endif

#if defined(PNG_sCAL_SUPPORTED)
#ifdef PNG_FLOATING_POINT_SUPPORTED
  rb_define_method(cWriter, "set_sCAL", libpng_writer_set_sCAL, 3);
#endif
/*
#ifdef PNG_FIXED_POINT_SUPPORTED
  rb_define_method(cWriter, "set_sCAL_s", libpng_writer_set_sCAL_s, 3);
#endif
*/
#endif

#if defined(PNG_WRITE_SHIFT_SUPPORTED)
  rb_define_method(cWriter, "set_shift", libpng_writer_set_shift, 1);
#endif

#if defined(PNG_sPLT_SUPPORTED)
  rb_define_method(cWriter, "set_sPLT", libpng_writer_set_sPLT, 1);
#endif

#if defined(PNG_sRGB_SUPPORTED)
  rb_define_method(cWriter, "set_sRGB", libpng_writer_set_sRGB, 1);
  rb_define_method(cWriter, "set_sRGB_gAMA_and_cHRM", libpng_writer_set_sRGB_gAMA_and_cHRM, 1);
#endif

/* maybe for Reader only
  rb_define_method(cWriter, "set_strip_16", libpng_writer_set_strip_16, 0);
*/

#if defined(PNG_WRITE_SWAP_SUPPORTED)
  rb_define_method(cWriter, "set_swap", libpng_writer_set_swap, 0);
#endif

#if defined(PNG_WRITE_SWAP_ALPHA_SUPPORTED)
  rb_define_method(cWriter, "set_swap_alpha", libpng_writer_set_swap_alpha, 0);
#endif

#if defined(PNG_TEXT_SUPPORTED)
  rb_define_method(cWriter, "set_text", libpng_writer_set_text, 1);
#endif

#if defined(PNG_tIME_SUPPORTED)
  rb_define_method(cWriter, "set_tIME", libpng_writer_set_tIME, 1);
#endif

#if defined(PNG_tRNS_SUPPORTED)
  rb_define_method(cWriter, "set_tRNS", libpng_writer_set_tRNS, 1);
#endif

/* maybe for Reader only
  rb_define_method(cWriter, "set_tRNS_to_alpha", libpng_writer_set_tRNS_to_alpha, 0);
*/

#if defined(PNG_UNKNOWN_CHUNKS_SUPPORTED)
  rb_define_method(cWriter, "set_unknown_chunks", libpng_writer_set_unknown_chunks, 1);
#endif
/*
#if defined(PNG_WRITE_USER_TRANSFORM_SUPPORTED)
  rb_define_method(cWriter, "set_user_transform_info", libpng_writer_set_user_transform_info, 3);
#endif
  rb_define_method(cWriter, "write_chunk", libpng_writer_write_chunk, 3);
  rb_define_method(cWriter, "write_chunk_start", libpng_writer_write_chunk_start, 2);
  rb_define_method(cWriter, "write_chunk_data", libpng_writer_write_chunk_data, 2);
  rb_define_method(cWriter, "write_chunk_end", libpng_writer_write_chunk_end, 0);
*/

  rb_define_method(cWriter, "set_write_status_fn", libpng_writer_set_write_status_fn, 1);

  rb_define_method(cWriter, "write_end", libpng_writer_write_end, 0);
  rb_define_method(cWriter, "write_image", libpng_writer_write_image, 1);
  rb_define_method(cWriter, "write_info", libpng_writer_write_info, 0);

/*
#if defined(PNG_INFO_IMAGE_SUPPORTED)
  rb_define_method(cWriter, "write_png", libpng_writer_write_png, 2);
#endif
*/
}
