/*
 * Copyright 2009 Canonical Ltd.
 *
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of either or both of the following licenses:
 *
 * 1) the GNU Lesser General Public License version 3, as published by the
 * Free Software Foundation; and/or
 * 2) the GNU Lesser General Public License version 2.1, as published by
 * the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranties of
 * MERCHANTABILITY, SATISFACTORY QUALITY or FITNESS FOR A PARTICULAR
 * PURPOSE.  See the applicable version of the GNU Lesser General Public
 * License for more details.
 *
 * You should have received a copy of both the GNU Lesser General Public
 * License version 3 and version 2.1 along with this program.  If not, see
 * <http://www.gnu.org/licenses/>
 *
 * Authored by: Neil Jagdish Patel <neil.patel@canonical.com>
 *
 */

#if HAVE_CONFIG_H
#include <config.h>
#endif

#include <math.h>
#include <GL/glew.h>
#include <GL/glxew.h>

#include <clutter/clutter.h>
#include <clutter-gtk/clutter-gtk.h>

#include "ctk-gfx-private.h"
#include "ctk-arb-asm-private.h"
#include "ctk-main.h"
#include "ctk-private.h"
#include "../data/shaders/shaders.h"

#define DEFAULT_DPI        96
#define DEFAULT_POINT_SIZE 10.0f

static gboolean ctk_inited = FALSE;

static gboolean has_fbo                           = FALSE;
static gboolean has_glsl                          = FALSE;
static gboolean has_shader_objects                = FALSE;
static gboolean has_vertex_shader                 = FALSE;
static gboolean has_fragment_shader               = FALSE;
static gboolean has_vertex_program                = FALSE;
static gboolean has_fragment_program              = FALSE;
static gboolean has_texture_non_power_of_two      = FALSE;
static gboolean has_texture_rectangle             = FALSE;

static gboolean has_opengl_1_1 = FALSE;
static gboolean has_opengl_1_2 = FALSE;
static gboolean has_opengl_1_3 = FALSE;
static gboolean has_opengl_1_4 = FALSE;
static gboolean has_opengl_1_5 = FALSE;
static gboolean has_opengl_2_0 = FALSE;
static gboolean has_opengl_2_1 = FALSE;
static gboolean has_opengl_3_0 = FALSE;
/*static gboolean has_opengl_3_1 = FALSE;
static gboolean has_opengl_3_2 = FALSE;*/

static gboolean has_glsl_1_10 = FALSE;
static gboolean has_glsl_1_20 = FALSE;
static gboolean has_glsl_1_30 = FALSE;
static gboolean has_glsl_1_40 = FALSE;

static gboolean all_glsl_shaders_compiled_and_ready = FALSE;
static gboolean all_asm_shaders_compiled_and_ready = FALSE;

static gdouble pixel_per_em = 10.0f;

/* Private Methods */
static void
get_capabilities ()
{
  GLenum err = glewInit();
  if (GLEW_OK != err)
  {
    g_error ("Unable to initialise Glew: glewInit Error: %s",
             glewGetErrorString (err));
  }

  if (GLEW_EXT_framebuffer_object)
    {
      has_fbo = TRUE;
    }
  else
    {
      g_message ("WARNING: Framebuffer objects are not supported on this gpu. "
                 "Some effects will not work correctly");
    }

  if (GLEW_ARB_shading_language_100)
    {
      has_glsl = TRUE;
    }
  /*else
    {
      g_message ("WARNING: GLSL is not supported on this gpu. "
                 "Some effects will not work correctly");
    }*/

    if (GLEW_ARB_shader_objects)
    {
      has_shader_objects = TRUE;
    }
  /*else
    {
      g_message ("WARNING: Shader programming is not supported on this gpu. "
                 "Some effects will not work correctly");
    }*/

  if (GLEW_ARB_vertex_shader)
    {
      has_vertex_shader = TRUE;
    }
  /*else
    {
      g_message ("WARNING: Programmable vertex shader is not supported on this gpu. "
                 "Some effects will not work correctly");
    }*/

  if (GLEW_ARB_fragment_shader)
    {
      has_fragment_shader = TRUE;
    }
  /*else
    {
      g_message ("WARNING: Programmable fragment shader is not supported on this gpu. "
                 "Some effects will not work correctly");
    }*/

  if (GLEW_ARB_vertex_program)
    {
      has_vertex_program = TRUE;
    }
  else
    {
      g_message ("WARNING: Programmable vertex shader is not supported on this gpu. "
                 "Some effects will not work correctly");
    }

  if (GLEW_ARB_fragment_program)
    {
      has_fragment_program = TRUE;
    }
  else
    {
      g_message ("WARNING: Programmable fragment shader is not supported on this gpu. "
                 "Some effects will not work correctly");
    }
  /*
    The use of non_power_of_two_texture and texture_rectangles is very pervasive is graphics programming.
    Though, we don't have a strict requirement for these features, they are very symptomatic of the type of GPU the program is running on.
    The warning messages has been commented, but the developper can always query if the feature are available.
  */

  if (GLEW_ARB_texture_non_power_of_two)
    {
      has_texture_non_power_of_two = TRUE;
    }
  else
    {
      /*g_message ("WARNING: Non power of two textures are not supported on this gpu. "
                 "Some effects will not work correctly");*/
    }

  if (GLEW_NV_texture_rectangle)
    {
      has_texture_rectangle = TRUE;
    }
  else
    {
      /*g_message ("WARNING: Texture rectangles are not supported on this gpu. "
                 "Some effects will not work correctly");*/
    }

  has_opengl_1_1  = GLEW_VERSION_1_1 ? TRUE : FALSE;
  has_opengl_1_2  = GLEW_VERSION_1_2 ? TRUE : FALSE;
  has_opengl_1_3  = GLEW_VERSION_1_3 ? TRUE : FALSE;
  has_opengl_1_4  = GLEW_VERSION_1_4 ? TRUE : FALSE;
  has_opengl_1_5  = GLEW_VERSION_1_5 ? TRUE : FALSE;
  has_opengl_2_0  = GLEW_VERSION_2_0 ? TRUE : FALSE;
  has_opengl_2_1  = GLEW_VERSION_2_1 ? TRUE : FALSE;
  has_opengl_3_0  = GLEW_VERSION_3_0 ? TRUE : FALSE;
  /*has_opengl_3_1  = GLEW_VERSION_3_1 ? TRUE : FALSE;
  has_opengl_3_2  = GLEW_VERSION_3_2 ? TRUE : FALSE;*/


  /* GL_SHADING_LANGUAGE_VERSION is supported starting at OpenGL 2.0 */
  const guchar* str = glGetString (GL_SHADING_LANGUAGE_VERSION);
  if (glGetError() != GL_NO_ERROR)
    {
      /* see GL_shading_language_100 spec */
      if (has_glsl)
        {
          has_glsl_1_10 = TRUE;
          has_glsl_1_20 = FALSE;
          has_glsl_1_30 = FALSE;
          has_glsl_1_40 = FALSE;
        }
      else
        {
          has_glsl_1_10 = FALSE;
          has_glsl_1_20 = FALSE;
          has_glsl_1_30 = FALSE;
          has_glsl_1_40 = FALSE;
        }
    }
  else
    {
      if (str == 0)
        {
          /* invalid string */
          has_glsl_1_10 = FALSE;
          has_glsl_1_20 = FALSE;
          has_glsl_1_30 = FALSE;
          has_glsl_1_40 = FALSE;
        }
      else if ((str[0] == '1') && (str[1] == '.') && (str[2] == '1'))
        {
          has_glsl_1_10 = TRUE;
          has_glsl_1_20 = FALSE;
          has_glsl_1_30 = FALSE;
          has_glsl_1_40 = FALSE;
        }
      else if ((str[0] == '1') && (str[1] == '.') && (str[2] == '2'))
        {
          has_glsl_1_10 = TRUE;
          has_glsl_1_20 = TRUE;
          has_glsl_1_30 = FALSE;
          has_glsl_1_40 = FALSE;
        }
      else if ((str[0] == '1') && (str[1] == '.') && (str[2] == '3'))
        {
          has_glsl_1_10 = TRUE;
          has_glsl_1_20 = TRUE;
          has_glsl_1_30 = TRUE;
          has_glsl_1_40 = FALSE;
        }
      else if ((str[0] == '1') && (str[1] == '.') && (str[2] == '4'))
        {
          has_glsl_1_10 = TRUE;
          has_glsl_1_20 = TRUE;
          has_glsl_1_30 = TRUE;
          has_glsl_1_40 = TRUE;
        }
    }
}

static void
create_asm_shader_programs()
{
  if (has_vertex_program
      && has_fragment_program)
    {
      g_shTexture_asm       = ctk_create_shader_asm_program_from_source     (VertexProgram_vparb, TextureProgram_fparb);
      g_shTextureAlpha_asm  = ctk_create_shader_asm_program_from_source     (VertexProgram_vparb, TextureAlphaProgram_fparb);
      g_shBlur_asm          = ctk_create_shader_asm_program_from_source     (VertexProgram_vparb, Gaussian7x7Program_fparb);
      g_shExp_asm           = ctk_create_shader_asm_program_from_source     (VertexProgram_vparb, ExponentProgram_fparb);
      g_shMultipassBlur_asm = ctk_create_shader_asm_program_from_source     (VertexProgram_vparb, MultipassBlur_fparb);
      g_shTextureMask_asm   = ctk_create_shader_asm_program_from_source     (VertexProgram_vparb, TextureMask_fparb);
    }
  else
    {
      g_shTexture_asm       = 0;
      g_shTextureAlpha_asm  = 0;
      g_shBlur_asm          = 0;
      g_shExp_asm           = 0;
      g_shMultipassBlur_asm = 0;
      g_shTextureMask_asm   = 0;
    }

    if (!(g_shTexture_asm   &&
      g_shTextureAlpha_asm  &&
      g_shBlur_asm          &&
      g_shExp_asm           &&
      g_shMultipassBlur_asm &&
      g_shTextureMask_asm))
      {
        /* Not all shaders compiled succesfully */
        all_asm_shaders_compiled_and_ready = FALSE;
      }
    else
      {
        all_asm_shaders_compiled_and_ready = TRUE;
      }
}

#if 0
static void
create_glsl_shader_programs()
{
  /* Without support for GLSL, do not attempt to create the shaders */
  /* It is enough to just test CTK_CAPABILITY_VERTEX_SHADER and CTK_CAPABILITY_FRAGMENT_SHADER
     as they rely on CTK_CAPABILITY_GLSL and CTK_CAPABILITY_SHADER_OBJECTS. */
  if (has_glsl
      && has_vertex_shader
      && has_fragment_shader)
    {
      g_shTexture = ctk_create_shader_program_from_source(SHADERDIR"/PostProcessVertexShader.vtx", SHADERDIR"/ColorMask.frag");
      g_shTextureAlpha = ctk_create_shader_program_from_source(SHADERDIR"/PostProcessVertexShader.vtx", SHADERDIR"/AlphaMask.frag");
      g_shBlurH0    = ctk_create_shader_program_from_source(SHADERDIR"/PostProcessVertexShader.vtx", SHADERDIR"/BlurH0.frag");
      g_shBlurV0    = ctk_create_shader_program_from_source(SHADERDIR"/PostProcessVertexShader.vtx", SHADERDIR"/BlurV0.frag");
      g_shBlurH1    = ctk_create_shader_program_from_source(SHADERDIR"/PostProcessVertexShader.vtx", SHADERDIR"/BlurH1.frag");
      g_shBlurV1    = ctk_create_shader_program_from_source(SHADERDIR"/PostProcessVertexShader.vtx", SHADERDIR"/BlurV1.frag");
      g_shExp       = ctk_create_shader_program_from_source(SHADERDIR"/PostProcessVertexShader.vtx", SHADERDIR"/Exponent.frag");
    }
  else
    {
      g_shTexture       = 0;
      g_shTextureAlpha  = 0;
      g_shBlurH0        = 0;
      g_shBlurV0        = 0;
      g_shBlurH1        = 0;
      g_shBlurV1        = 0;
      g_shExp           = 0;
    }

    if (!(g_shTexture   &&
      g_shTextureAlpha  &&
      g_shBlurH0        &&
      g_shBlurV0        &&
      g_shBlurH1        &&
      g_shBlurV1        &&
      g_shExp))
      {
        /* Not all shaders compiled succesfully */
        all_glsl_shaders_compiled_and_ready = FALSE;
      }
    else
      {
        all_glsl_shaders_compiled_and_ready = TRUE;
      }
}
#endif

static void
calculate_pixel_per_em ()
{
  gchar*                font_name       = NULL;
  GString*              string          = NULL;
  PangoFontDescription* desc            = NULL;
  gint                  dpi             = DEFAULT_DPI;
  gdouble               screen_dpi;
  gdouble               font_point_size = DEFAULT_POINT_SIZE;

  // get font-name, something like "Times 12.5"
  g_object_get (gtk_settings_get_default (), "gtk-font-name", &font_name, NULL);

  // create Pango font-description from that string
  string = g_string_new (font_name);
  g_free ((gpointer) font_name);
  desc = pango_font_description_from_string (string->str);

  // extract text point-size
  if (pango_font_description_get_size_is_absolute (desc))
    font_point_size = (gdouble) pango_font_description_get_size (desc);
  else
    font_point_size = (gdouble) pango_font_description_get_size (desc) /
                      (gdouble) PANGO_SCALE;

  pango_font_description_free (desc);

  // get screen DPI
  g_object_get (gtk_settings_get_default (), "gtk-xft-dpi", &dpi, NULL);

  // GtkSettings return it as 1024 * dots/inch
  screen_dpi = (gdouble) dpi / 1024.0f;

  // trigger pixel-per-em calculation
  pixel_per_em = font_point_size * screen_dpi / 72.0f;
}

static void
set_font_options ()
{
  ClutterBackend *backend = clutter_get_default_backend ();
  gint dpi;
  cairo_font_options_t *options;
  GtkSettings *settings = gtk_settings_get_default ();

  g_object_get (settings, "gtk-xft-dpi", &dpi, NULL);

  dpi /= PANGO_SCALE;

  if (dpi < 2)
    dpi = 96.0;

  clutter_backend_set_resolution (backend, (gint)dpi);

  options = cairo_font_options_copy (clutter_backend_get_font_options (backend));

  cairo_font_options_set_antialias (options, CAIRO_ANTIALIAS_SUBPIXEL);

  cairo_font_options_set_hint_style (options, CAIRO_HINT_STYLE_SLIGHT);

  cairo_font_options_set_hint_metrics (options, CAIRO_HINT_METRICS_ON);

  cairo_font_options_set_subpixel_order (options, CAIRO_SUBPIXEL_ORDER_RGB);

  clutter_backend_set_font_options (backend, options);

  cairo_font_options_destroy (options);
}

static void
ctk_init_real (gint *argc, gchar ***argv)
{
  if (ctk_inited)
    {
      g_warning ("CluTK has already been intialised");
      return;
    }
  get_capabilities ();

  create_asm_shader_programs ();

  calculate_pixel_per_em ();

  set_font_options ();

  ctk_inited = TRUE;
}

static inline void
_blurinner (guchar* pixel,
	    gint   *zR,
	    gint   *zG,
	    gint   *zB,
	    gint   *zA,
	    gint    alpha,
	    gint    aprec,
	    gint    zprec)
{
  gint R;
  gint G;
  gint B;
  guchar A;

  R = *pixel;
  G = *(pixel + 1);
  B = *(pixel + 2);
  A = *(pixel + 3);

  *zR += (alpha * ((R << zprec) - *zR)) >> aprec;
  *zG += (alpha * ((G << zprec) - *zG)) >> aprec;
  *zB += (alpha * ((B << zprec) - *zB)) >> aprec;
  *zA += (alpha * ((A << zprec) - *zA)) >> aprec;

  *pixel       = *zR >> zprec;
  *(pixel + 1) = *zG >> zprec;
  *(pixel + 2) = *zB >> zprec;
  *(pixel + 3) = *zA >> zprec;
}

static inline void
_blurrow (guchar* pixels,
	  gint    width,
	  gint    height,
	  gint    channels,
	  gint    line,
	  gint    alpha,
	  gint    aprec,
	  gint    zprec)
{
  gint    zR;
  gint    zG;
  gint    zB;
  gint    zA;
  gint    index;
  guchar* scanline;

  scanline = &(pixels[line * width * channels]);

  zR = *scanline << zprec;
  zG = *(scanline + 1) << zprec;
  zB = *(scanline + 2) << zprec;
  zA = *(scanline + 3) << zprec;

  for (index = 0; index < width; index ++)
    _blurinner (&scanline[index * channels], &zR, &zG, &zB, &zA, alpha, aprec,
                zprec);

  for (index = width - 2; index >= 0; index--)
    _blurinner (&scanline[index * channels], &zR, &zG, &zB, &zA, alpha, aprec,
                zprec);
}

static inline void
_blurcol (guchar* pixels,
	  gint    width,
	  gint    height,
	  gint    channels,
	  gint    x,
	  gint    alpha,
	  gint    aprec,
	  gint    zprec)
{
  gint zR;
  gint zG;
  gint zB;
  gint zA;
  gint index;
  guchar* ptr;

  ptr = pixels;
	
  ptr += x * channels;

  zR = *((guchar*) ptr    ) << zprec;
  zG = *((guchar*) ptr + 1) << zprec;
  zB = *((guchar*) ptr + 2) << zprec;
  zA = *((guchar*) ptr + 3) << zprec;

  for (index = width; index < (height - 1) * width; index += width)
    _blurinner ((guchar*) &ptr[index * channels], &zR, &zG, &zB, &zA, alpha,
                aprec, zprec);

  for (index = (height - 2) * width; index >= 0; index -= width)
    _blurinner ((guchar*) &ptr[index * channels], &zR, &zG, &zB, &zA, alpha,
                aprec, zprec);
}

//
// pixels   image-data
// width    image-width
// height   image-height
// channels image-channels
//
// in-place blur of image 'img' with kernel of approximate radius 'radius'
//
// blurs with two sided exponential impulse response
//
// aprec = precision of alpha parameter in fixed-point format 0.aprec
//
// zprec = precision of state parameters zR,zG,zB and zA in fp format 8.zprec
//
void
_expblur (guchar* pixels,
	  gint    width,
	  gint    height,
	  gint    channels,
	  gint    radius,
	  gint    aprec,
	  gint    zprec)
{
  gint alpha;
  gint row = 0;
  gint col = 0;

  if (radius < 1)
    return;

  // calculate the alpha such that 90% of 
  // the kernel is within the radius.
  // (Kernel extends to infinity)
  alpha = (gint) ((1 << aprec) * (1.0f - expf (-2.3f / (radius + 1.f))));

  for (; row < height; row++)
    _blurrow (pixels, width, height, channels, row, alpha, aprec, zprec);

  for(; col < width; col++)
    _blurcol (pixels, width, height, channels, col, alpha, aprec, zprec);

  return;
}

/* Public Methods */

/**
 * ctk_init:
 * @argc: (inout): the number of arguments in @argv
 * @argv: (array length=argc) (inout) (allow-none): a pointer to an array of
 *   arguments
 *
 * Will initialise CluTK, Clutter-GTK and Clutter. Will also do some run-time
 * checks of the host gpu's capabilities for effects.
 **/
void
ctk_init (gint *argc, gchar ***argv)
{
  gtk_clutter_init (argc, argv);

  ctk_init_real (argc, argv);
}

/**
 * ctk_init_after:
 * @argc: (inout): the number of arguments in @argv
 * @argv: (array length=argc) (inout) (allow-none): a pointer to an array of
 *    arguments
 *
 * Will initialise CluTK only. Use this if Clutter and Clutter-Gtk have already
 * been intialised.
 **/
void
ctk_init_after (int *argc, gchar ***argv)
{
  ctk_init_real (argc, argv);
}

/**
 * ctk_cleanup:
 *
 * Cleanup object created in ctk_init. Shou;d be called when the program terminates.
 **/
void
ctk_cleanup ()
{
#if 0
  ctk_delete_shader_program (g_shTexture);
  ctk_delete_shader_program (g_shTextureAlpha);
  ctk_delete_shader_program (g_shBlurH0);
  ctk_delete_shader_program (g_shBlurV0);
  ctk_delete_shader_program (g_shBlurH1);
  ctk_delete_shader_program (g_shBlurV1);
  ctk_delete_shader_program (g_shExp);
#endif

  ctk_delete_shader_asm_program (g_shTexture_asm);
  ctk_delete_shader_asm_program (g_shTextureAlpha_asm);
  ctk_delete_shader_asm_program (g_shBlur_asm);
  ctk_delete_shader_asm_program (g_shExp_asm);
  ctk_delete_shader_asm_program (g_shMultipassBlur_asm);
  ctk_delete_shader_asm_program (g_shTextureMask_asm);
}

gboolean
ctk_has_capability (CtkCapability capability)
{
  if (!ctk_inited)
    {
      g_warning ("You must initialise CluTK before calling ctk_has_capability");
      return FALSE;
    }

  switch (capability)
    {
      case CTK_CAPABILITY_FBO:
        return has_fbo;

      case CTK_CAPABILITY_GLSL:
        return has_glsl;

      case CTK_CAPABILITY_SHADER_OBJECTS:
        return has_glsl;

      case CTK_CAPABILITY_VERTEX_SHADER:
        return has_vertex_shader;

      case CTK_CAPABILITY_FRAGMENT_SHADER:
        return has_fragment_shader;

      case CTK_CAPABILITY_NON_POWER_OF_TWO_TEXTURE:
        return has_texture_non_power_of_two;

      case CTK_CAPABILITY_TEXTURE_RECTANGLE:
        return has_texture_rectangle;

      case CTK_CAPABILITY_VERTEX_PROGRAM:
        return has_vertex_program;

      case CTK_CAPABILITY_FRAGMENT_PROGRAM:
        return has_fragment_program;

      default:
        g_warning ("Unable to check capability of '%d'", capability);
        break;
    }

  return FALSE;
}

gboolean
ctk_has_opengl_version (CtkOpenGLVersion version)
{
  if (!ctk_inited)
    {
      g_warning ("You must initialise CluTK before calling ctk_has_opengl_version");
      return FALSE;
    }

  switch (version)
    {
      case CTK_OPENGL_1_1:
        return has_opengl_1_1;

      case CTK_OPENGL_1_2:
        return has_opengl_1_2;

      case CTK_OPENGL_1_3:
        return has_opengl_1_3;

      case CTK_OPENGL_1_4:
        return has_opengl_1_4;

      case CTK_OPENGL_1_5:
        return has_opengl_1_5;

      case CTK_OPENGL_2_0:
        return has_opengl_2_0;

      case CTK_OPENGL_2_1:
        return has_opengl_2_1;

      case CTK_OPENGL_3_0:
        return has_opengl_3_0;

      /*case CTK_OPENGL_3_1:
        return has_opengl_3_1;

      case CTK_OPENGL_3_2:
        return has_opengl_3_2;*/

      default:
        return FALSE;
    }

  return FALSE;
}

gboolean
ctk_has_glsl_version (CtkGLSLVersion version)
{
  if (!ctk_inited)
    {
      g_warning ("You must initialise CluTK before calling ctk_has_glsl_version");
      return FALSE;
    }

  switch (version)
    {
      case CTK_GLSL_1_10:
        return has_glsl_1_10;

      case CTK_GLSL_1_20:
        return has_glsl_1_20;

      case CTK_GLSL_1_30:
        return has_glsl_1_30;

      case CTK_GLSL_1_40:
        return has_glsl_1_40;

      default:
        return FALSE;
    }

  return FALSE;
}


gboolean
ctk_glsl_shaders_compiled_and_ready ()
{
    if (!ctk_inited)
    {
      g_warning ("You must initialise CluTK before calling ctk_has_glsl_version");
      return FALSE;
    }

    return all_glsl_shaders_compiled_and_ready;
}

gboolean
ctk_asm_shaders_compiled_and_ready ()
{
    if (!ctk_inited)
    {
      g_warning ("You must initialise CluTK before calling ctk_has_glsl_version");
      return FALSE;
    }

    return all_asm_shaders_compiled_and_ready;
}

/**
 * ctk_pixel_to_em:
 * @pixel_value: resolution-dependent measurement to convert to EMs
 *
 * Returns the EM-value corresponding to @pixel_value, considering screen-DPI
 * and font-settings, as gdouble.
 **/
gdouble
ctk_pixel_to_em (gint pixel_value)
{
  return (gdouble) pixel_value / pixel_per_em;
}

/**
 * ctk_em_to_pixel:
 * @em_value: resolution-independent measurement to convert to pixels
 *
 * Returns the number of pixels corresponding to @em_value, considering
 * screen-DPI and font-settings, as gdouble.
 **/
gdouble
ctk_em_to_pixel (gdouble em_value)
{
  return em_value * pixel_per_em;
}

/**
 * ctk_surface_blur:
 * @surface: pointer to a cairo image-surface
 * @radius: unsigned integer acting as the blur-radius to apply
 *
 * Applies an exponential blur on the passed surface executed on the CPU. Not as
 * nice as a real gaussian blur, but much faster.
 **/
void
ctk_surface_blur (cairo_surface_t* surface,
                  guint            radius)
{
  guchar*        pixels;
  guint          width;
  guint          height;
  cairo_format_t format;

  // before we mess with the surface execute any pending drawing
  cairo_surface_flush (surface);

  pixels = cairo_image_surface_get_data (surface);
  width  = cairo_image_surface_get_width (surface);
  height = cairo_image_surface_get_height (surface);
  format = cairo_image_surface_get_format (surface);

  switch (format)
  {
    case CAIRO_FORMAT_ARGB32:
      _expblur (pixels, width, height, 4, radius, 16, 7);
    break;

    case CAIRO_FORMAT_RGB24:
      _expblur (pixels, width, height, 3, radius, 16, 7);
    break;

    case CAIRO_FORMAT_A8:
      _expblur (pixels, width, height, 1, radius, 16, 7);
    break;

    default :
      // do nothing
    break;
  }

  // inform cairo we altered the surfaces contents
  cairo_surface_mark_dirty (surface);	
}

