/*
  Module       : image.c
  Purpose      : Routines dealing with image display
  More         : see qiv README
  Policy       : GNU GPL
  Homepage     : http://qiv.spiegl.de/
  Original     : http://www.klografx.net/qiv/
*/

#include <stdio.h>
#include <string.h>
#include <gdk/gdkx.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include "qiv.h"
#include "xmalloc.h"
#include "qiv.xpm"
#include "math.h"

static void setup_win(qiv_image *);
static void calc_color_map(qiv_image *q);
static void apply_color_map(GdkPixbuf* pb);
//static void setup_magnify(qiv_image *, qiv_mgl *); // [lc]

static int used_masks_before=0;
static struct timeval load_before, load_after;
static double load_elapsed;
static GdkCursor *cursor, *visible_cursor, *invisible_cursor;
// color map lookup table for brightness, contrast and gamma adjust
static guchar qiv_cm[256];

int img_from_pixbuf_loader(char * image_name, int * has_alpha, GError ** err, qiv_image *q)
{
    GError    *error=NULL;
    GdkPixbuf *pixbuf_ori = NULL;
    GdkPixbuf *pixbuf = NULL;
    guchar * pixeldata = NULL;
    int pb_w, pb_h;
    const gchar *gdk_orientation = NULL;
#ifdef SUPPORT_LCMS
    char *icc_profile;
    cmsHPROFILE h_emb_profile;
    cmsHTRANSFORM h_emb_transform;
#endif
    pixbuf_ori = gdk_pixbuf_new_from_file(image_name, &error);
    if (error != NULL)
    {
        *err=error;
        q->pb = NULL;
        return -1;
    }
    *has_alpha = gdk_pixbuf_get_has_alpha(pixbuf_ori);
#if GDK_PIXBUF_MINOR >= 12
    if(autorotate)
    {
        gdk_orientation = gdk_pixbuf_get_option(pixbuf_ori, "orientation");
        if(gdk_orientation)
        {
#ifdef DEBUG
            printf("orientation %s\n", gdk_orientation);
#endif
            pixbuf = gdk_pixbuf_apply_embedded_orientation(pixbuf_ori);
            g_object_unref(pixbuf_ori);
            pixbuf_ori = pixbuf;
        }
    }
#else
#warning autoration needs at least gdk version 2.12
#endif

#ifdef SUPPORT_LCMS
    pixeldata = gdk_pixbuf_get_pixels(pixbuf_ori);
    pb_w = gdk_pixbuf_get_width(pixbuf_ori);
    pb_h = gdk_pixbuf_get_height(pixbuf_ori);

    if((icc_profile=get_icc_profile(image_name)))
    {
#ifdef DEBUG
        g_print("has color profile\n");
#endif
        h_emb_profile = cmsOpenProfileFromMem(icc_profile+sizeof(cmsUInt32Number), *(cmsUInt32Number *)icc_profile);
        if(h_display_profile==NULL)
        {
            h_display_profile = cmsCreate_sRGBProfile();
        }

        if (*has_alpha == 0)
        {
            h_emb_transform = cmsCreateTransform(h_emb_profile, TYPE_RGB_8, h_display_profile, TYPE_RGB_8, INTENT_PERCEPTUAL, 0);
        }
        else
        {
            h_emb_transform = cmsCreateTransform(h_emb_profile, TYPE_RGBA_8, h_display_profile, TYPE_RGBA_8, INTENT_PERCEPTUAL, 0);
        }
        if(h_emb_transform)
        {
            cmsDoTransform(h_emb_transform, pixeldata, pixeldata, pb_w*pb_h);
            cmsCloseProfile(h_emb_profile);
            cmsDeleteTransform(h_emb_transform);
        }
        else
        {
            printf("qiv warning: %s contains corrupt color profile\n",image_name);
        }
        free(icc_profile);
    }

    /* do the color transform */
    else if (cms_transform && h_cms_transform)
    {
        cmsDoTransform(h_cms_transform, pixeldata, pixeldata, pb_w*pb_h);
    }
#endif

#ifdef HAVE_EXIF
    artist = get_exif_artist(image_name);
    //printf("DEBUG: Artist: %s\n", artist);
#endif
    q->pb = gdk_pixbuf_copy(pixbuf_ori);
    g_object_unref(pixbuf_ori);
    return 0;
}

// small wrapper around pixbuf loader
int load_image(char* image_name, int* has_alpha, qiv_image *q)
{
  int im;
  GError * error = NULL;

  im = img_from_pixbuf_loader(image_name, has_alpha, &error, q);

  if(im != 0 && error != NULL)
  {
    // Report GDK error to user if loading failed
    fprintf(stderr, "Unable to read file: %s\n", error->message);
  }
  if(error != NULL)
  {
    // free error
    g_error_free (error);
  }
  return im;
}

/*
 *    Load & display image
 */
void qiv_load_image(qiv_image *q)
{
  struct stat statbuf;
  const char * image_name = image_names[ image_idx];
  int im=0;
  int has_alpha=0, rot;

  q->exposed=0;
  gettimeofday(&load_before, 0);

  stat(image_name, &statbuf);
  current_mtime = statbuf.st_mtime;
  file_size = statbuf.st_size;

#ifdef DEBUG
  g_print("loading %s\n",image_name);
#endif

  im = load_image((char*)image_name, &has_alpha, q);

  if (im) { /* error */
    q->error = 1;
    q->orig_w = 400;
    q->orig_h = 300;
  } else { /* Retrieve image properties */
    q->error = 0;
    q->orig_w = gdk_pixbuf_get_width(q->pb);
    q->orig_h = gdk_pixbuf_get_height(q->pb);

    if (rotation > 10) {
      /* conditional rotation -- apply rotation only if image fits better */
      int screen_is_wide = monitor[q->mon_id].width > monitor[q->mon_id].height;
      int image_is_wide = q->orig_w > q->orig_h;
      int does_not_fit = q->orig_w > monitor[q->mon_id].width || q->orig_h > monitor[q->mon_id].height;
      if (screen_is_wide != image_is_wide && does_not_fit)
        rot = rotation - 10; /* we want the rotation (will be 11 -> 1 or 13 -> 3) */
      else
        rot = 0; /* don't rotate */
    } else
      rot = rotation;

    if (rot) {
      if (rot != 2) {
        swap(&q->orig_w, &q->orig_h);
        swap(&q->win_w, &q->win_h);
      }
    }

    if (rot && rot != 2)
      correct_image_position(q);
  }

  if (first) {
    setup_win(q);
  }

  check_size(q, TRUE);

  /* desktop-background -> exit */
  if (to_root || to_root_t || to_root_s) {
    if (im) {
      fprintf(stderr, "qiv: cannot load background_image\n");
      qiv_exit(1);
    }
    set_desktop_image(q);
#if GDK_MAJOR_VERSION > 2
    // Hmm. GDK3 needs some time for drawing, gdk_flush does not seem to help
    usleep(100*1000);
#endif
    qiv_exit(0);
  }

  gettimeofday(&load_after, 0);
  load_elapsed = ((load_after.tv_sec +  load_after.tv_usec / 1.0e6) -
                 (load_before.tv_sec + load_before.tv_usec / 1.0e6));

  update_image(q, FULL_REDRAW);
//    if (magnify && !fullscreen) {  // [lc]
//     setup_magnify(q, &magnify_img);
//     update_magnify(q, &magnify_img, FULL_REDRAW, 0, 0);
//    }
}

static void setup_win(qiv_image *q)
{
  GdkWindowAttr attr;
  static GList  icon_list = {NULL, NULL, NULL};

  /*TODO: thw 25.5.24: should that even be here?*/
  //destroy_image(q);

  if (!fullscreen) {
    attr.window_type=GDK_WINDOW_TOPLEVEL;
    attr.wclass=GDK_INPUT_OUTPUT;
    attr.event_mask=GDK_ALL_EVENTS_MASK;
    attr.x = center ? q->win_x : 0;
    attr.y = center ? q->win_y : 0;
    attr.width  = q->win_w;
    attr.height = q->win_h;
    q->win = gdk_window_new(NULL, &attr, GDK_WA_X|GDK_WA_Y);

    if (center) {
      GdkGeometry geometry = {
        .min_width = q->win_w,
        .min_height = q->win_h,
        .max_width = q->win_w,
        .max_height = q->win_h,
        .win_gravity = GDK_GRAVITY_STATIC
      };
      gdk_window_set_geometry_hints(q->win, &geometry,
        GDK_HINT_MIN_SIZE | GDK_HINT_MAX_SIZE | GDK_HINT_WIN_GRAVITY);
      gdk_window_move_resize(q->win, q->win_x, q->win_y, q->win_w, q->win_h);
    } else {
      GdkGeometry geometry = {
        .min_width = q->win_w,
        .min_height = q->win_h,
        .max_width = q->win_w,
        .max_height = q->win_h,
      };
      gdk_window_set_geometry_hints(q->win, &geometry,
        GDK_HINT_MIN_SIZE | GDK_HINT_MAX_SIZE);
      gdk_window_resize(q->win, q->win_w, q->win_h);
    }
   if (!(to_root || to_root_t || to_root_s))
    gdk_window_lower(q->win);

  } else { /* fullscreen */

    attr.window_type=GDK_WINDOW_TOPLEVEL;
    attr.wclass=GDK_INPUT_OUTPUT;
    attr.event_mask=GDK_ALL_EVENTS_MASK;
//    attr.x = 0;
//    attr.y = 0;
    attr.x = monitor[q->mon_id].x;
    attr.y = monitor[q->mon_id].y;
//    printf("mon_id %d \n",q->mon_id);
    attr.width=monitor[q->mon_id].width;
    attr.height=monitor[q->mon_id].height;

//    q->win = gdk_window_new(NULL, &attr, GDK_WA_X|GDK_WA_Y|GDK_WA_TYPE_HINT);
    q->win = gdk_window_new(NULL, &attr, GDK_WA_X|GDK_WA_Y);
    gdk_window_set_cursor(q->win, cursor);
    if (!(to_root || to_root_t || to_root_s))
    {
      gdk_window_fullscreen(q->win);
      gdk_window_show(q->win);
    }
  }

  /* this is all done only once */
  if(icon_list.data==NULL)
  {
    icon_list.data = gdk_pixbuf_new_from_xpm_data(qiv_icon);
    invisible_cursor = gdk_cursor_new_for_display(gdk_display_get_default(), GDK_BLANK_CURSOR);
    cursor = visible_cursor = gdk_cursor_new_for_display(gdk_display_get_default(), CURSOR);
  }

  gdk_window_set_icon_list(q->win, &icon_list);
  gdk_window_set_cursor(q->win, cursor);
}

void hide_cursor(qiv_image *q)
{
  if (cursor != invisible_cursor)
    gdk_window_set_cursor(q->win, cursor = invisible_cursor);
}

void show_cursor(qiv_image *q)
{
  if (cursor != visible_cursor)
    gdk_window_set_cursor(q->win, cursor = visible_cursor);
}

/* set image as background */

void set_desktop_image(qiv_image *q)
{
  GdkWindow *root_win = gdk_get_default_root_window();
  gint root_w = screen_x, root_h = screen_y;
  gint root_x = 0, root_y = 0;

  if (to_root || to_root_t) {
    root_w = q->win_w;
    root_h = q->win_h;
  }

  if (to_root) {
    root_x = (screen_x - root_w) / 2;
    root_y = (screen_y - root_h) / 2;
  }

#if GDK_MAJOR_VERSION < 3
  GdkPixmap *pixmap;

#ifdef DEBUG
  if (mask)  g_print("*** image has transparency\n");
#endif

  gdk_window_clear(root_win);
  if (to_root_t) {
    gdk_pixbuf_render_pixmap_and_mask_for_colormap (q->pb, gdk_window_get_colormap(q->win), &pixmap, NULL, 128);
    gdk_window_set_back_pixmap(root_win, pixmap, FALSE);
  } else {

    GdkPixbuf* pb_tmp;
    if (to_root_s){
      if (gdk_pixbuf_get_has_alpha(q->pb)){
        pb_tmp = gdk_pixbuf_scale_simple(q->pb, screen_x, screen_y, GDK_INTERP_BILINEAR);
      } else {
        pb_tmp = gdk_pixbuf_composite_color_simple(q->pb, screen_x, screen_y, GDK_INTERP_BILINEAR,
            255, 0x08, 0x00666666, 0x00aaaaaa);
      }
    } else {

      if(to_root)
      {
        pb_tmp = gdk_pixbuf_new(GDK_COLORSPACE_RGB, gdk_pixbuf_get_has_alpha(q->pb), 8, screen_x, screen_y);
        // fill with background color when not tiling nor scaling
        guint32 col = (((image_bg.red >> 8)) << 24) + (((image_bg.green >> 8)) << 16) + ((image_bg.blue >> 8)<<8);
        gdk_pixbuf_fill(pb_tmp, col);
      }
      else
      {
        pb_tmp = gdk_pixbuf_new(GDK_COLORSPACE_RGB, gdk_pixbuf_get_has_alpha(q->pb), 8, root_w, root_h);
      }
      gdk_pixbuf_copy_area(q->pb, 0, 0, q->win_w, q->win_h, pb_tmp, root_x, root_y);
    }

    gdk_pixbuf_render_pixmap_and_mask_for_colormap (pb_tmp, gdk_window_get_colormap(q->win), &pixmap, NULL, 128);
    gdk_window_set_back_pixmap(root_win, pixmap, FALSE);
    g_object_unref(pb_tmp);
  }
  gdk_window_clear(root_win);
  gdk_display_flush(gdk_display_get_default());
#else
  cairo_t *cr;
  GdkPixbuf* pb_tmp;
  cairo_surface_t *pattern_surface;

  if (to_root_s)
  {
    if (!gdk_pixbuf_get_has_alpha(q->pb))
    {
      pb_tmp = gdk_pixbuf_scale_simple(q->pb, screen_x, screen_y, GDK_INTERP_BILINEAR);
    }
    else
    {
      pb_tmp = gdk_pixbuf_composite_color_simple(q->pb, screen_x, screen_y, GDK_INTERP_BILINEAR,
          255, 0x08, 0x00666666, 0x00aaaaaa);
    }
  }
  else
  {
    if(to_root)
    {
      pb_tmp = gdk_pixbuf_new(GDK_COLORSPACE_RGB, gdk_pixbuf_get_has_alpha(q->pb), 8, screen_x, screen_y);
      // fill with background color when not tiling nor scaling
      guint32 col = ((int)((image_bg.red * 255)) << 24) + (((int)(image_bg.green * 255)) << 16) + ((int)(image_bg.blue * 255)<<8);
      gdk_pixbuf_fill(pb_tmp, col);
    }
    else
    {
      // fill with tiling
      pb_tmp = gdk_pixbuf_new(GDK_COLORSPACE_RGB, gdk_pixbuf_get_has_alpha(q->pb), 8, root_w, root_w);
    }
    gdk_pixbuf_copy_area(q->pb, 0, 0, q->win_w, q->win_h, pb_tmp, root_x, root_y);
  }

  if(to_root_t)
  {
    pattern_surface = gdk_window_create_similar_surface(q->win, CAIRO_CONTENT_COLOR,  q->win_w, q->win_h);
  }
  else
  {
    pattern_surface = gdk_window_create_similar_surface(q->win, CAIRO_CONTENT_COLOR,  screen_x, screen_y);
  }
  //cairo_surface_flush(pattern_surface);
  cr = cairo_create(pattern_surface);
  gdk_cairo_set_source_pixbuf(cr, pb_tmp, 0, 0);
  cairo_paint(cr);
  cairo_destroy(cr);

  cairo_pattern_t *pattern;
  pattern = cairo_pattern_create_for_surface(pattern_surface);
  cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REPEAT);

  gdk_window_set_background_pattern(root_win, pattern);

  cr = gdk_cairo_create(root_win);
  cairo_set_source(cr, pattern);
  cairo_paint(cr);
  cairo_destroy(cr);

  cairo_pattern_destroy(pattern);
  cairo_surface_destroy(pattern_surface);
  g_object_unref(pb_tmp);

  gdk_display_flush(gdk_display_get_default());

#endif
}

void zoom_in(qiv_image *q)
{
  int zoom_percentage;
  int w_old, h_old;

  /* first compute current zoom_factor */
  if (maxpect || scale_down || fixed_window_size) {
    zoom_percentage=myround((1.0-(q->orig_w - q->win_w)/(double)q->orig_w)*100 / highDPIfactor);
    zoom_factor=(zoom_percentage - 100) / 10;
  }

  maxpect = scale_down = 0;

  // zoom in faster if above 100%
  if (zoom_factor < 0) {
    zoom_factor++;
  } else {
    zoom_factor += 5;
  }

  w_old = q->win_w;
  h_old = q->win_h;
  q->win_w = (gint)(q->orig_w * (1 + zoom_factor * 0.1) * highDPIfactor);
  q->win_h = (gint)(q->orig_h * (1 + zoom_factor * 0.1) * highDPIfactor);

  /* adapt image position */
  q->win_x -= (q->win_w - w_old) / 2;
  q->win_y -= (q->win_h - h_old) / 2;

  if (fullscreen) {
    if (center)
      center_image(q);
    else
      correct_image_position(q);
  } else {
    correct_image_position(q);
  }
}

void zoom_out(qiv_image *q)
{
  int zoom_percentage;
  int w_old, h_old;

  /* first compute current zoom_factor */
  if (maxpect || scale_down || fixed_window_size) {
    zoom_percentage=myround((1.0-(q->orig_w - q->win_w)/(double)q->orig_w)*100 / highDPIfactor);
    zoom_factor=(zoom_percentage - 100) / 10;
  }

  maxpect = scale_down = 0;

  w_old = q->win_w;
  h_old = q->win_h;

  if(zoom_factor > -9 && q->win_w > MIN(64, q->orig_w) && q->win_h > MIN(64, q->orig_h)) {
    zoom_factor--;
    q->win_w = (gint)(q->orig_w * (1 + zoom_factor * 0.1) * highDPIfactor);
    q->win_h = (gint)(q->orig_h * (1 + zoom_factor * 0.1) * highDPIfactor);

    /* adapt image position */
    q->win_x -= (q->win_w - w_old) / 2;
    q->win_y -= (q->win_h - h_old) / 2;

    if (fullscreen) {
      if (center)
        center_image(q);
      else
        correct_image_position(q);
    } else {
      correct_image_position(q);
    }
  } else {
    snprintf(infotext, sizeof infotext, "(Cannot zoom out more)");
    fprintf(stderr, "qiv: cannot zoom out more\n");
  }
}

void zoom_maxpect(qiv_image *q)
{
  double zx = (double)monitor[q->mon_id].width / (double)q->orig_w;
  double zy = (double)monitor[q->mon_id].height / (double)q->orig_h;

  /* titlebar and frames ignored on purpose to use full height/width of screen */
  q->win_w = (gint)(q->orig_w * MIN(zx, zy));
  q->win_h = (gint)(q->orig_h * MIN(zx, zy));
  center_image(q);
}

/*
  Set display settings to startup values
  which are used whenever a new image is loaded.
*/

void reload_image(qiv_image *q)
{
  int im;
  int has_alpha = 0;

  im = load_image(image_names[image_idx], &has_alpha, q);

  if (im && watch_file)
    return;

  struct stat statbuf;
  stat(image_names[image_idx], &statbuf);
  current_mtime = statbuf.st_mtime;

  if (im!=0)
  {
    q->error = 1;
    q->orig_w = 400;
    q->orig_h = 300;
  } else { /* Retrieve image properties */
    q->error = 0;
    q->orig_w = gdk_pixbuf_get_width(q->pb);
    q->orig_h = gdk_pixbuf_get_height(q->pb);
  }

  q->win_w = (gint)(q->orig_w * (1 + zoom_factor * 0.1) * highDPIfactor);
  q->win_h = (gint)(q->orig_h * (1 + zoom_factor * 0.1) * highDPIfactor);
  reset_mod(q);
  if (center) center_image(q);
}

void check_size(qiv_image *q, gint reset)
{
  if (maxpect || (scale_down && (q->orig_w*highDPIfactor>monitor[q->mon_id].width || q->orig_h*highDPIfactor>monitor[q->mon_id].height))) {
    zoom_maxpect(q);
  } else if (reset || (scale_down && (q->win_w<q->orig_w*highDPIfactor || q->win_h<q->orig_h*highDPIfactor))) {
    reset_coords(q);
  }
  if (center){
    center_image(q);
  }
  else if (fullscreen) {
    if (q->win_x >  monitor[q->mon_id].x)
      q->win_x -= monitor[q->mon_id].x;
    if (q->win_y >  monitor[q->mon_id].y)
      q->win_y -= monitor[q->mon_id].y;
  }
  else {
    if (q->win_x <  monitor[q->mon_id].x)
      q->win_x += monitor[q->mon_id].x;
    if (q->win_y <  monitor[q->mon_id].y)
      q->win_y += monitor[q->mon_id].y;
  }

}

void reset_coords(qiv_image *q)
{
  if (fixed_window_size) {
    double w_o_ratio = (double)(fixed_window_size) / q->orig_w;
    q->win_w = fixed_window_size;
    q->win_h = q->orig_h * w_o_ratio;
  } else {
    q->win_w = (gint)(q->orig_w * (1 + zoom_factor * 0.1) * highDPIfactor);
    q->win_h = (gint)(q->orig_h * (1 + zoom_factor * 0.1) * highDPIfactor);
  }
}

/* Something changed the image.  Redraw it. */

void update_image(qiv_image *q, int mode)
{
#if GDK_MAJOR_VERSION < 3
  GdkBitmap *mask = NULL;
#endif
  double elapsed=0;
  int i;
  cairo_t *cr;

  if (q->error) {
    g_snprintf(q->win_title, sizeof q->win_title,
        "qiv: ERROR! cannot load image: %s", image_names[image_idx]);
    gdk_display_beep(gdk_display_get_default());

    /* take this image out of the file list */
    --images;
    for(i=image_idx;i<images;++i) {
      image_names[i] = image_names[i+1];
    }

    /* If deleting the last file out of x */
    if(images == image_idx)
      image_idx = 0;

    /* If deleting the only file left */
    if(!images) {
#ifdef DEBUG
      g_print("*** deleted last file in list. Exiting.\n");
#endif
      exit(0);
    }
    /* else load the next image */
    qiv_load_image(q);
    return;

  } else {

    if (mode == MOVED) {
      g_snprintf(q->win_title, sizeof q->win_title,
                 "qiv: %s (%dx%d) %d%% [%d/%d] b%d/c%d/g%d %s",
                 image_names[image_idx], q->orig_w, q->orig_h,
                 myround( (1.0-(q->orig_w - q->win_w)/(double)q->orig_w)*100 / highDPIfactor ),
                 image_idx+1, images,
                 q->mod.brightness, q->mod.contrast, q->mod.gamma, infotext);
      snprintf(infotext, sizeof infotext, "(-)");

    } // mode == MOVED
    else
    {
#ifdef DEBUG
      if (gdk_pixbuf_get_has_alpha(q->pb))  g_print("*** image has transparency\n");
#endif

      g_snprintf(q->win_title, sizeof q->win_title,
                 "qiv: %s (%dx%d) %d%% [%d/%d] b%d/c%d/g%d %s %1.01fs",
                 image_names[image_idx], q->orig_w, q->orig_h,
                 myround( (1.0-(q->orig_w - q->win_w)/(double)q->orig_w)*100 / highDPIfactor ),
                 image_idx+1, images,
                 q->mod.brightness, q->mod.contrast, q->mod.gamma, infotext, load_elapsed+elapsed);
      snprintf(infotext, sizeof infotext, "(-)");
    }
  }

  gdk_window_set_title(q->win, q->win_title);

  q->text_len = strlen(q->win_title);
  pango_layout_set_text(layout, q->win_title, -1);
  pango_layout_get_pixel_size (layout, &(q->text_w), &(q->text_h));

  if(comment && comment_window) {
    pango_layout_set_text(layoutComment, comment, -1);
    pango_layout_get_pixel_size (layoutComment, &(q->comment_w), &(q->comment_h));
  }

  if(artist && artist_window && strcmp(artist, artist_ignore)) {
    pango_layout_set_text(layoutArtist, artist, -1);
    pango_layout_get_pixel_size (layoutArtist, &(q->artist_w), &(q->artist_h));
  }

  if (!fullscreen) {
#if GDK_MAJOR_VERSION < 3
    gdk_window_clear(q->win);
#endif
    GdkGeometry geometry = {
      .min_width = q->win_w,
      .min_height = q->win_h,
      .max_width = q->win_w,
      .max_height = q->win_h,
      .win_gravity = GDK_GRAVITY_STATIC
    };
    gdk_window_set_geometry_hints(q->win, &geometry,
      GDK_HINT_MIN_SIZE | GDK_HINT_MAX_SIZE | GDK_HINT_WIN_GRAVITY);

    if(first) {
      gdk_window_show(q->win);
      first = 0;
      q->exposed=0;
    }

    if(mode != MIN_REDRAW)
       gdk_window_move_resize(q->win, q->win_x, q->win_y, q->win_w, q->win_h);
    if (!q->error) {
      //gdk_window_set_back_pixmap(q->win, q->p, FALSE);
      cr = gdk_cairo_create(q->win);

      GdkPixbuf* pb_tmp;
      if (gdk_pixbuf_get_n_channels(q->pb) == 3){
          pb_tmp = gdk_pixbuf_scale_simple(q->pb, q->win_w, q->win_h, GDK_INTERP_BILINEAR);
      } else {
#if GDK_MAJOR_VERSION < 3
          GdkPixbuf* pb_tmp2;
          pb_tmp2 = gdk_pixbuf_scale_simple(q->pb, q->win_w, q->win_h, GDK_INTERP_BILINEAR);
          gdk_pixbuf_render_pixmap_and_mask_for_colormap (pb_tmp2, gdk_window_get_colormap(q->win), NULL, &mask, 128);
          g_object_unref(pb_tmp2);
#endif
          pb_tmp = gdk_pixbuf_composite_color_simple(q->pb, q->win_w, q->win_h, GDK_INTERP_BILINEAR,
                  255, 0x08, 0x00666666, 0x00aaaaaa);
      }
      if (q->mod.gamma != DEFAULT_GAMMA ||
          q->mod.brightness != DEFAULT_BRIGHTNESS ||
          q->mod.contrast != DEFAULT_CONTRAST) {
          calc_color_map(q);
          apply_color_map(pb_tmp);
      }

      gdk_cairo_set_source_pixbuf (cr, pb_tmp, 0, 0);
      //cairo_rectangle(cr, 0, 0, q->win_w, q->win_h);
      cairo_paint(cr);
      cairo_destroy(cr);
      /* remove or set transparency mask */
      if (used_masks_before) {
        if (transparency) {
#if GDK_MAJOR_VERSION < 3
          gdk_window_shape_combine_mask(q->win, mask, 0, 0);
#else
          // GDK3 Code
          cairo_surface_t *surface;
          cairo_t *cr_m;
          cairo_region_t *region;

          surface = cairo_image_surface_create (CAIRO_FORMAT_A1, q->win_w, q->win_h);
          cr_m = cairo_create(surface);
          gdk_cairo_set_source_pixbuf (cr_m, q->pb, 0, 0);
          cairo_paint(cr_m);
          region = gdk_cairo_region_create_from_surface (surface);
          gdk_window_shape_combine_region(q->win, region, 0, 0);
          cairo_destroy(cr_m);
          //printf("combine shape\n");
#endif
        }
        else
        {
#if GDK_MAJOR_VERSION < 3
          gdk_window_shape_combine_mask(q->win, 0, 0, 0);
#endif
          used_masks_before = 0;
        }
      }
      else
      {
        if (transparency && (gdk_pixbuf_get_n_channels(q->pb) == 4)) {
#if GDK_MAJOR_VERSION < 3
          gdk_window_shape_combine_mask(q->win, mask, 0, 0);
#else
          // GDK3 Code
          cairo_surface_t *surface;
          cairo_t *cr_m;
          cairo_region_t *region;

          surface = cairo_image_surface_create (CAIRO_FORMAT_A1, q->win_w, q->win_h);
          cr_m = cairo_create(surface);
          gdk_cairo_set_source_pixbuf (cr_m, q->pb, 0, 0);
          cairo_paint(cr_m);
          cairo_destroy(cr_m);
          region = gdk_cairo_region_create_from_surface (surface);
          gdk_window_shape_combine_region(q->win, region, 0, 0);
          //printf("combine shape\n");
#endif
          used_masks_before=1;
        }
      }
      g_object_unref(pb_tmp);
    }
    //printf("----\n");
    //gdk_window_clear(q->win);

    if(statusbar_window)
    {
#ifdef DEBUG
        g_print("*** print statusbar at (%d, %d)\n", MAX(2,q->win_w-q->text_w-10), MAX(2,q->win_h-q->text_h-10));
#endif
      cr = gdk_cairo_create (q->win);

      set_cairo_color(cr, 0);
      cairo_rectangle(cr, MAX(2,q->win_w-q->text_w-10), MAX(2,q->win_h-q->text_h-10),  q->text_w+5, q->text_h+5);
      cairo_fill (cr);

      set_cairo_color(cr, STATUSBAR_BG);
      cairo_rectangle(cr, MAX(3,q->win_w-q->text_w-9), MAX(3,q->win_h-q->text_h-9),  q->text_w+3, q->text_h+3);
      cairo_fill (cr);

      set_cairo_color(cr, 0);
      cairo_move_to (cr, MAX(5,q->win_w-q->text_w-7),  MAX(5,q->win_h-7-q->text_h));
      pango_cairo_show_layout(cr, layout);

      cairo_destroy (cr);
    }

    if(comment && comment_window) {
      /* draw comment */
      cr = gdk_cairo_create (q->win);
      set_cairo_color(cr, 0);
      cairo_rectangle(cr, 25, MAX(5,q->win_h-q->comment_h-29), q->comment_w+5, q->comment_h+5);
      cairo_fill (cr);
      set_cairo_color(cr, COMMENT_BG);
      cairo_rectangle(cr, 26, MAX(6,q->win_h-q->comment_h-28), q->comment_w+3, q->comment_h+3);
      cairo_fill (cr);
      set_cairo_color(cr, 0);
      cairo_move_to (cr, 27, MAX(5,q->win_h - 26 - q->comment_h));
      pango_cairo_show_layout(cr, layoutComment);
      cairo_destroy (cr);
    }

    if(artist && artist_window && strcmp(artist, artist_ignore)) {
      /* draw artist name in top left corner */
      cr = gdk_cairo_create (q->win);
      set_cairo_color(cr, 0);
      cairo_rectangle(cr, 15, 15, q->artist_w+5, q->artist_h+5);
      cairo_fill (cr);
      set_cairo_color(cr, ARTIST_BG);
      cairo_rectangle(cr, 16, 16, q->artist_w+3, q->artist_h+3);
      cairo_fill (cr);
      set_cairo_color(cr, 0);
      cairo_move_to (cr, 17, 17);
      pango_cairo_show_layout(cr, layoutArtist);
      cairo_destroy (cr);
    }

  } // if (!fullscreen)
  else
  {
#define statusbar_x monitor[q->mon_id].width
#define statusbar_y monitor[q->mon_id].height
    if (mode == FULL_REDRAW) {
      // fill window with bachground colour
#if GDK_MAJOR_VERSION < 3
      gdk_window_set_background(q->win, &image_bg);
      gdk_window_clear(q->win);
#else
      cr = gdk_cairo_create (q->win);
      cairo_set_source_rgb(cr, image_bg.red, image_bg.green, image_bg.blue);
      cairo_rectangle(cr, 0, 0, monitor[q->mon_id].width, monitor[q->mon_id].height);
      cairo_fill (cr);
      cairo_destroy (cr);
#endif

    }
    else
    {
      // repaint only areas covered by previous image
      cr = gdk_cairo_create (q->win);
#if GDK_MAJOR_VERSION < 3
      cairo_set_source_rgb(cr, image_bg.red / 65535.0f, image_bg.green / 65535.0f, image_bg.blue / 65535.0f);
#else
      cairo_set_source_rgb(cr, image_bg.red, image_bg.green, image_bg.blue);
#endif
      if (q->win_x > q->win_ox) {
        cairo_rectangle(cr, q->win_ox, q->win_oy, q->win_x - q->win_ox, q->win_oh);
        cairo_fill (cr);
      }
      if (q->win_y > q->win_oy) {
        cairo_rectangle(cr, q->win_ox, q->win_oy, q->win_ow, q->win_y - q->win_oy);
        cairo_fill (cr);
      }
      if (q->win_x + q->win_w < q->win_ox + q->win_ow) {
        cairo_rectangle(cr, q->win_x + q->win_w, q->win_oy, q->win_ox - q->win_x + q->win_ow - q->win_w , q->win_oh);
        cairo_fill(cr);
      }
      if (q->win_y + q->win_h < q->win_oy + q->win_oh) {
        cairo_rectangle(cr, q->win_ox, q->win_y + q->win_h, q->win_ow, q->win_oy - q->win_y + q->win_oh - q->win_h);
        cairo_fill(cr);
      }

      // repaint old statusbar with background color
      if (q->statusbar_was_on && (!statusbar_fullscreen || q->text_ow > q->text_w || q->text_oh > q->text_h))
      {
        cairo_rectangle(cr, statusbar_x-q->text_ow-10, statusbar_y-q->text_oh-10,
                            q->text_ow+5, q->text_oh+5);
        cairo_fill(cr);
      }
      cairo_destroy(cr);
    }

    if (!q->error){
       cr = gdk_cairo_create (q->win);

       GdkPixbuf* pb_tmp;
       if (gdk_pixbuf_get_n_channels(q->pb) == 3){
           pb_tmp = gdk_pixbuf_scale_simple(q->pb, q->win_w, q->win_h, GDK_INTERP_BILINEAR);
       } else {
#if GDK_MAJOR_VERSION < 3
           GdkPixbuf* pb_tmp2;
           pb_tmp2 = gdk_pixbuf_scale_simple(q->pb, q->win_w, q->win_h, GDK_INTERP_BILINEAR);
           gdk_pixbuf_render_pixmap_and_mask_for_colormap (pb_tmp2, gdk_window_get_colormap(q->win), NULL, &mask, 128);
           g_object_unref(pb_tmp2);
#endif
           pb_tmp  = gdk_pixbuf_composite_color_simple(q->pb, q->win_w, q->win_h, GDK_INTERP_BILINEAR,
                      255, 0x08, 0x00666666, 0x00aaaaaa);
       }
       if (q->mod.gamma != DEFAULT_GAMMA ||
           q->mod.brightness != DEFAULT_BRIGHTNESS ||
           q->mod.contrast != DEFAULT_CONTRAST) {
           calc_color_map(q);
           apply_color_map(pb_tmp);
       }

       gdk_cairo_set_source_pixbuf (cr, pb_tmp, q->win_x, q->win_y);
       g_object_unref(pb_tmp);
       /* remove or set transparency mask */
       if (used_masks_before) {
         if (transparency) {
#if GDK_MAJOR_VERSION < 3
           gdk_window_shape_combine_mask(q->win, mask, q->win_x, q->win_y);
#endif
         }
         else
         {
#if GDK_MAJOR_VERSION < 3
           gdk_window_shape_combine_mask(q->win, 0, q->win_x, q->win_y);
#endif
           used_masks_before = 0;
         }
       }
       else
       {
         if (transparency && (gdk_pixbuf_get_n_channels(q->pb) == 4)) {
#if GDK_MAJOR_VERSION < 3
           gdk_window_shape_combine_mask(q->win, mask, q->win_x, q->win_y);
#endif
           used_masks_before=1;
         }
       }

       cairo_rectangle(cr, 0, 0, q->win_w, q->win_h);
       cairo_paint(cr);
       cairo_destroy (cr);
    }

    if (statusbar_fullscreen) {
      cr = gdk_cairo_create (q->win);
      set_cairo_color(cr, 0);
      cairo_rectangle(cr, statusbar_x-q->text_w-10, statusbar_y-q->text_h-10, q->text_w+5, q->text_h+5);
      cairo_fill(cr);

      set_cairo_color(cr, STATUSBAR_BG);
      cairo_rectangle(cr,statusbar_x-q->text_w-9, statusbar_y-q->text_h-9, q->text_w+3, q->text_h+3);
      cairo_fill(cr);

      set_cairo_color(cr, 0);
      cairo_move_to(cr, statusbar_x-q->text_w-7, statusbar_y-7-q->text_h);
      pango_cairo_show_layout(cr, layout);
      cairo_destroy (cr);
    }

    if(comment && comment_window) {
      /* draw comment */
      cr = gdk_cairo_create (q->win);
      set_cairo_color(cr, 0);
      cairo_rectangle(cr, 25, statusbar_y - q->comment_h - 30,
                         q->comment_w + 5, q->comment_h + 5);
      cairo_fill(cr);

      set_cairo_color(cr, COMMENT_BG);
      cairo_rectangle(cr, 26, statusbar_y - q->comment_h - 29,
                         q->comment_w + 3, q->comment_h + 3);
      cairo_fill(cr);

      set_cairo_color(cr, 0);
      cairo_move_to (cr,  27, statusbar_y - 27 - q->comment_h);
      pango_cairo_show_layout(cr, layoutComment);
      cairo_destroy (cr);
    }

    if(artist && artist_window && strcmp(artist, artist_ignore)) {
      /* draw artist name in top left corner*/
      cr = gdk_cairo_create (q->win);
      set_cairo_color(cr, 0);
      cairo_rectangle(cr, 15, 15, q->artist_w + 5, q->artist_h + 5);
      cairo_fill(cr);

      set_cairo_color(cr, ARTIST_BG);
      cairo_rectangle(cr, 16, 16, q->artist_w + 3, q->artist_h + 3);
      cairo_fill(cr);

      set_cairo_color(cr, 0);
      cairo_move_to (cr,  17, 17);
      pango_cairo_show_layout(cr, layoutArtist);
      cairo_destroy (cr);
    }

    q->win_ox = q->win_x;
    q->win_oy = q->win_y;
    q->win_ow = q->win_w;
    q->win_oh = q->win_h;
    q->text_ow = q->text_w;
    q->text_oh = q->text_h;
    q->statusbar_was_on = statusbar_fullscreen;

    if(first) {
      gdk_window_show(q->win);
      first = 0;
    }

    gdk_window_move_resize(q->win, monitor[q->mon_id].x, monitor[q->mon_id].y,
        monitor[q->mon_id].width, monitor[q->mon_id].height);
  }
  gdk_display_flush(gdk_display_get_default());
#if GDK_MAJOR_VERSION < 3
  if(mask) g_object_unref(mask);
#endif
}


void reset_mod(qiv_image *q)
{
  q->mod.brightness = default_brightness;
  q->mod.contrast = default_contrast;
  q->mod.gamma = default_gamma;
}

void destroy_image(qiv_image *q)
{
  //printf("destroy image\n");
  if (q->win) g_object_unref(q->win);
  q->win = NULL;

  if(!first) {
     if (q->pb) g_object_unref(q->pb);
     q->pb = NULL;
  }
}

void setup_magnify(qiv_image *q, qiv_mgl *m)
{
   GdkWindowAttr mgl_attr;
   GdkGeometry mgl_hints;

   m->win_w=300; m->win_h=200;
   m->zoom=2.0;
   m->exposed=0;

   gdk_window_get_root_origin(q->win, &m->frame_x, &m->frame_y);
// printf("frame %d %d\n", m->frame_x, m->frame_y);

   mgl_attr.window_type=GDK_WINDOW_TOPLEVEL; // Set up attributes for GDK to create a Window
   mgl_attr.wclass=GDK_INPUT_OUTPUT;
   mgl_attr.event_mask=GDK_STRUCTURE_MASK | GDK_EXPOSURE_MASK;
   mgl_attr.width=m->win_w;
   mgl_attr.height=m->win_h;
   mgl_attr.override_redirect=TRUE;
//   m->win=gdk_window_new(NULL,&mgl_attr,GDK_WA_X|GDK_WA_Y|GDK_WA_WMCLASS);
   m->win=gdk_window_new(NULL,&mgl_attr,GDK_WA_X|GDK_WA_Y);
   mgl_hints.min_width=m->win_w;
   mgl_hints.max_width=m->win_w;
   mgl_hints.min_height=m->win_h;
   mgl_hints.max_height=m->win_h;
   gdk_window_set_geometry_hints(m->win, &mgl_hints,
     GDK_HINT_MIN_SIZE | GDK_HINT_MAX_SIZE);
   gdk_window_set_decorations(m->win, GDK_DECOR_BORDER);
   gdk_display_flush(gdk_display_get_default());
}

void update_magnify(qiv_image *q, qiv_mgl *m, int mode, gint xcur, gint ycur)
{
//   GdkWindowAttr mgl_attr;
//   GdkGeometry mgl_hints;
  register  gint xx, yy;  // win_pos_x, win_pos_y;
  cairo_t *cr;

  if (mode == REDRAW ) {

    /* scale position to original size */
    xx=xcur * ((double)q->orig_w/(double)q->win_w);
    yy=ycur * ((double)q->orig_h/(double)q->win_h);

    /* center mouse cursor position */
    xx -= 150 / m->zoom;
    yy -= 100 / m->zoom;

    /* keep magnify part allways inside image */
    if(xx + m->win_w / m->zoom > q->orig_w) {
      xx=q->orig_w - m->win_w / m->zoom;
    }
    if(yy + m->win_h / m->zoom > q->orig_h) {
      yy=q->orig_h - m->win_h / m->zoom;
    }
    if (yy < 0){
        yy=0;
    }
    if (xx < 0){
        xx=0;
    }

    cr = gdk_cairo_create(m->win);
    cairo_scale(cr, m->zoom, m->zoom);
    gdk_cairo_set_source_pixbuf(cr, q->pb, -xx, -yy);
    cairo_paint(cr);
    cairo_destroy(cr);
    gdk_window_show(m->win);

    xx= m->frame_x + xcur - 50 - m->win_w;
    yy= m->frame_y + ycur - 50 - m->win_h;
    if (xx < 0) {
      if (xcur < m->win_w - magnify_img.frame_x)
        xx=m->frame_x + xcur + 50;
      else
        xx=0;
    }
    if (yy < 0) {
      if (ycur < m->win_h - magnify_img.frame_y)
        yy=m->frame_y + ycur + 50;
      else
        yy=0;
    }
//    printf("MGL: m->frame_x: %d, m->frame_y: %d, xx: %d, yy: %d\n", m->frame_x, m->frame_y, xx, yy);
    gdk_window_move(m->win, xx, yy);
  }
  gdk_display_flush(gdk_display_get_default());
}

void center_image(qiv_image *q)
{

  q->win_x = (monitor[q->mon_id].width  - q->win_w) / 2;
  q->win_y = (monitor[q->mon_id].height - q->win_h) / 2;
  if(!fullscreen)
  {
    q->win_x += monitor[q->mon_id].x;
    q->win_y += monitor[q->mon_id].y;
  }
}

void correct_image_position(qiv_image *q)
{
//  g_print("before: q->win_x = %d, q->win_y = %d, q->win_w = %d\n", q->win_x, q->win_y, q->win_w);

  /* try to keep inside the screen */
  if (q->win_w < screen_x) {
    if (q->win_x < 0)
      q->win_x = 0;
    if (q->win_x + q->win_w > screen_x)
      q->win_x = screen_x - q->win_w;
  } else {
    if (q->win_x > 0)
      q->win_x = 0;
    if (q->win_x + q->win_w < screen_x)
      q->win_x = screen_x - q->win_w;
  }

  /* don't leave ugly borders */
  if (q->win_h < screen_y) {
    if (q->win_y < 0)
      q->win_y = 0;
    if (q->win_y + q->win_h > screen_y)
      q->win_y = screen_y - q->win_h;
  } else {
    if (q->win_y > 0)
      q->win_y = 0;
    if (q->win_y + q->win_h < screen_y)
      q->win_y = screen_y - q->win_h;
  }
//  g_print("after:  q->win_x = %d, q->win_y = %d, q->win_w = %d\n", q->win_x, q->win_y, q->win_w);
}

// Create lookup table to replace RGB pixel values on gamma, brightness, contrast adjustmants
// It is done in a similar fashion as in Imlib2.
// Gamma 1.0 is normal linear, 2.0 brightens and 0.5 darkens, gamma must be > 0.
// Brightness 0 has no effect. -1.0 will turn all black and 1.0 will make everything white.
// Contrast of 1.0 does nothing. 0.0 turns to gray, 2.0 doubles contrast.
// Adjustments are kept in integer steps with 0 meaning no change and calculated into
// corresponding float values here.
static void calc_color_map(qiv_image *q)
{
   int  i;
   float val;
   float gamma, contrast, brightness;

   // brightness adjust calculation
   brightness = q->mod.brightness * 255 / 50.0f;

   // parts of contrast and gamma adjust, which only needs calculation once
   contrast = (q->mod.contrast + 50) / 50.0f;
   gamma = (2 * q->mod.gamma + 100.0f) / 100.0f;
   // Limit gamma to 0.01 minimum value
   if (gamma < 0.01f)
      gamma = 0.01f;

   for(i = 0; i < 256; i++)
   {
        val = 1.0f * i;
        // gamma calculation
        if(q->mod.gamma)
        {
            val = powf((val / 255), (1 / gamma)) * 255;
        }
        // contrast calculation
        if(q->mod.contrast)
        {
            val = (val - 127) * contrast + 127;
        }
        // adjust brightness
        val += brightness;
        // poor man's round function
        val += 0.5f;
        if (val < 0)
           val = 0;
        if (val > 255)
           val = 255;
        qiv_cm[i] = (guchar)val;
   }
}

// replace pixel data with values from previously calculated lookup table
static void apply_color_map(GdkPixbuf* pb)
{
    guchar* pixels;
    int w, h, chan, rs;
    int i, j;

    pixels = gdk_pixbuf_get_pixels(pb);
    w    = gdk_pixbuf_get_width(pb);
    h    = gdk_pixbuf_get_height(pb);
    rs   = gdk_pixbuf_get_rowstride(pb);
    chan = gdk_pixbuf_get_n_channels(pb);

    for(i=0; i < h; i++)
    {
        for(j=0; j < w * chan; j+=chan)
        {
            *(pixels + i*rs + j)   = qiv_cm[*(pixels + i*rs + j)];
            *(pixels + i*rs + j+1) = qiv_cm[*(pixels + i*rs + j+1)];
            *(pixels + i*rs + j+2) = qiv_cm[*(pixels + i*rs + j+2)];
        }
    }
}
