[gthumb] new tool: special effects



commit f795de2114681abbe0a7a06acbea78a58a3d74b8
Author: Paolo Bacchilega <paobac src gnome org>
Date:   Wed Jan 7 12:56:38 2015 +0100

    new tool: special effects

 data/icons/hicolor/16x16/actions/Makefile.am       |    1 +
 .../16x16/actions/special-effects-symbolic.svg     |  167 +++++
 extensions/cairo_io/cairo-image-surface-xcf.c      |   60 +--
 extensions/file_tools/Makefile.am                  |  124 ++--
 extensions/file_tools/cairo-blur.h                 |    6 +-
 extensions/file_tools/cairo-effects.c              |  450 ++++++++++++++
 extensions/file_tools/cairo-effects.h              |   58 ++
 extensions/file_tools/data/ui/Makefile.am          |    1 +
 extensions/file_tools/data/ui/effects-options.ui   |   44 ++
 extensions/file_tools/gth-file-tool-effects.c      |  655 ++++++++++++++++++++
 extensions/file_tools/gth-file-tool-effects.h      |   61 ++
 extensions/file_tools/main.c                       |   16 +
 gthumb/Makefile.am                                 |    2 +
 gthumb/gimp-op.c                                   |   57 ++
 gthumb/gimp-op.h                                   |   59 ++
 15 files changed, 1645 insertions(+), 116 deletions(-)
---
diff --git a/data/icons/hicolor/16x16/actions/Makefile.am b/data/icons/hicolor/16x16/actions/Makefile.am
index 197e4e7..6d52531 100644
--- a/data/icons/hicolor/16x16/actions/Makefile.am
+++ b/data/icons/hicolor/16x16/actions/Makefile.am
@@ -46,6 +46,7 @@ icons_DATA =                                  \
        site-photobucket.png                    \
        site-picasaweb.png                      \
        site-twentythree.png                    \
+       spacial-effects-symbolic.svg            \
        swap-values-symbolic.svg                \
        tag-symbolic.svg                        \
        tools-symbolic.svg                      \
diff --git a/data/icons/hicolor/16x16/actions/special-effects-symbolic.svg 
b/data/icons/hicolor/16x16/actions/special-effects-symbolic.svg
new file mode 100644
index 0000000..2aabd87
--- /dev/null
+++ b/data/icons/hicolor/16x16/actions/special-effects-symbolic.svg
@@ -0,0 +1,167 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/";
+   xmlns:cc="http://creativecommons.org/ns#";
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#";
+   xmlns:svg="http://www.w3.org/2000/svg";
+   xmlns="http://www.w3.org/2000/svg";
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd";
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape";
+   width="16"
+   height="16"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.91+devel r13821 custom"
+   sodipodi:docname="special-effects-symbolic.svg">
+  <defs
+     id="defs4">
+    <linearGradient
+       id="linearGradient3946">
+      <stop
+         id="stop3948"
+         offset="0"
+         style="stop-color:#000000;stop-opacity:0.74698794;" />
+      <stop
+         id="stop3950"
+         offset="1"
+         style="stop-color:#000000;stop-opacity:0.02409638;" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient3830-9">
+      <stop
+         style="stop-color:#000000;stop-opacity:0.74698794;"
+         offset="0"
+         id="stop3832-7" />
+      <stop
+         style="stop-color:#000000;stop-opacity:0.02409638;"
+         offset="1"
+         id="stop3834-5" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient3984">
+      <stop
+         style="stop-color:#f4deba;stop-opacity:1;"
+         offset="0"
+         id="stop3986" />
+      <stop
+         style="stop-color:#de9625;stop-opacity:0;"
+         offset="1"
+         id="stop3988" />
+    </linearGradient>
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#555753"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="1"
+     inkscape:pageshadow="2"
+     inkscape:zoom="22.627417"
+     inkscape:cx="-4.8421885"
+     inkscape:cy="10.750332"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:snap-grids="true"
+     inkscape:window-width="1680"
+     inkscape:window-height="984"
+     inkscape:window-x="0"
+     inkscape:window-y="27"
+     inkscape:window-maximized="1"
+     showborder="true"
+     fit-margin-top="0"
+     fit-margin-right="0"
+     fit-margin-left="0"
+     fit-margin-bottom="0"
+     showguides="true">
+    <inkscape:grid
+       type="xygrid"
+       id="grid7044"
+       empspacing="8"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true"
+       originx="-31.97559px"
+       originy="-816.00002px" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage"; />
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Livello 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(-31.97559,-220.36218)">
+    <path
+       sodipodi:type="star"
+       
style="opacity:1;fill:#bebebe;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="path4157"
+       sodipodi:sides="5"
+       sodipodi:cx="35.988739"
+       sodipodi:cy="224.53897"
+       sodipodi:r1="4.472136"
+       sodipodi:r2="1.675368"
+       sodipodi:arg1="1.1071487"
+       sodipodi:arg2="1.7494959"
+       inkscape:flatsided="false"
+       inkscape:rounded="0"
+       inkscape:randomized="0"
+       d="m 37.988739,228.53897 -2.297797,-2.35131 -2.888395,1.48949 1.526172,-2.91193 -2.309155,-2.28675 
3.241023,0.55164 1.461259,-2.90278 0.476891,3.25286 3.212262,0.49274 -2.946288,1.45874 z"
+       inkscape:transform-center-x="0.30715807"
+       inkscape:transform-center-y="1.5471892"
+       transform="matrix(0.64913853,-0.86150281,0.86150281,0.64913853,-179.99636,110.92512)" />
+    <path
+       transform="matrix(0.17246664,-0.59507374,0.59507374,0.17246664,-97.308777,206.28836)"
+       inkscape:transform-center-y="0.63390317"
+       inkscape:transform-center-x="-0.10387294"
+       d="m 37.988739,228.53897 -2.297797,-2.35131 -2.888395,1.48949 1.526172,-2.91193 -2.309155,-2.28675 
3.241023,0.55164 1.461259,-2.90278 0.476891,3.25286 3.212262,0.49274 -2.946288,1.45874 z"
+       inkscape:randomized="0"
+       inkscape:rounded="0"
+       inkscape:flatsided="false"
+       sodipodi:arg2="1.7494959"
+       sodipodi:arg1="1.1071487"
+       sodipodi:r2="1.675368"
+       sodipodi:r1="4.472136"
+       sodipodi:cy="224.53897"
+       sodipodi:cx="35.988739"
+       sodipodi:sides="5"
+       id="path4167"
+       
style="opacity:1;fill:#bebebe;fill-opacity:0.74840766;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       sodipodi:type="star" />
+    <path
+       sodipodi:type="star"
+       
style="opacity:1;fill:#bebebe;fill-opacity:0.69745225;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       id="path4169"
+       sodipodi:sides="5"
+       sodipodi:cx="35.988739"
+       sodipodi:cy="224.53897"
+       sodipodi:r1="4.472136"
+       sodipodi:r2="1.675368"
+       sodipodi:arg1="1.1071487"
+       sodipodi:arg2="1.7494959"
+       inkscape:flatsided="false"
+       inkscape:rounded="0"
+       inkscape:randomized="0"
+       d="m 37.988739,228.53897 -2.297797,-2.35131 -2.888395,1.48949 1.526172,-2.91193 -2.309155,-2.28675 
3.241023,0.55164 1.461259,-2.90278 0.476891,3.25286 3.212262,0.49274 -2.946288,1.45874 z"
+       inkscape:transform-center-x="0.37691353"
+       inkscape:transform-center-y="0.50563846"
+       transform="matrix(0.31512729,-0.24426588,0.24426588,0.31512729,-20.343182,164.16536)" />
+    <path
+       
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#bebebe;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background
 :accumulate"
+       d="m 39.751212,228.25156 c -0.449408,9e-5 -0.670595,0.54685 -0.347656,0.85938 l 6.011567,6.18638 c 
0.471254,0.49084 1.197872,-0.23577 0.707032,-0.70703 l -6.011568,-6.18639 c -0.09421,-0.0974 
-0.223892,-0.15234 -0.359375,-0.15234 z"
+       id="path4171"
+       inkscape:connector-curvature="0"
+       sodipodi:nodetypes="cccccc" />
+  </g>
+</svg>
diff --git a/extensions/cairo_io/cairo-image-surface-xcf.c b/extensions/cairo_io/cairo-image-surface-xcf.c
index 946ba9a..f200f3d 100644
--- a/extensions/cairo_io/cairo-image-surface-xcf.c
+++ b/extensions/cairo_io/cairo-image-surface-xcf.c
@@ -26,36 +26,9 @@
 #include "cairo-image-surface-xcf.h"
 
 
-/* Optimizations taken from xcftools 1.0.7 written by Henning Makholm
- *
- * xL : Layer color
- * xI : Image color
- * aL : Layer alpha
- * */
-
-
-#define TILE_WIDTH                     64
-#define MAX_TILE_SIZE                  (TILE_WIDTH * TILE_WIDTH * 4 * 1.5)
-#define ADD_ALPHA(v, a)                        (add_alpha_table[v][a])
-#define DISSOLVE_SEED                  737893334
-#define CLAMP_TEMP(x, min, max)                (temp = (x), CLAMP (temp, min, max))
-#define ABS_TEMP2(x)                   (temp2 = (x), (temp2 < 0) ? -temp2: temp2)
-#define CLAMP_PIXEL(x)                 CLAMP_TEMP (x, 0, 255)
-#define GIMP_OP_NORMAL(xL, xI, aL)     CLAMP_PIXEL (ADD_ALPHA (xL, aL) + ADD_ALPHA (xI, 255 - aL))
-#define GIMP_OP_LIGHTEN_ONLY(xL, xI)   MAX (xI, xL)
-#define GIMP_OP_SCREEN(xL, xI)         CLAMP_PIXEL (255 ^ ADD_ALPHA (255 - xI, 255 - xL))
-#define GIMP_OP_DODGE(xL, xI)          GIMP_OP_DIVIDE (255-xL, xI)
-#define GIMP_OP_ADDITION(xL, xI)       CLAMP_PIXEL (xI + xL)
-#define GIMP_OP_DARKEN_ONLY(xL, xI)    MIN (xI, xL)
-#define GIMP_OP_MULTIPLY(xL, xI)       CLAMP_PIXEL (ADD_ALPHA (xL, xI))
-#define GIMP_OP_BURN(xL, xI)           CLAMP_PIXEL (255 - GIMP_OP_DIVIDE (xL, 255 - xI))
-#define GIMP_OP_SOFT_LIGHT(xL, xI)     CLAMP_PIXEL (ADD_ALPHA (xI, xI) + 2 * ADD_ALPHA (xL, ADD_ALPHA (xI, 
255 - xI)))
-#define GIMP_OP_HARD_LIGHT(xL, xI)     CLAMP_PIXEL (xL > 128 ? 255 ^ ADD_ALPHA (255 - xI, 2 * (255 - xL)) : 
ADD_ALPHA (xI, 2 * xL))
-#define GIMP_OP_DIFFERENCE(xL, xI)     CLAMP_PIXEL (ABS_TEMP2 (xI - xL))
-#define GIMP_OP_SUBTRACT(xL, xI)       CLAMP_PIXEL (xI - xL)
-#define GIMP_OP_GRAIN_EXTRACT(xL, xI)  CLAMP_PIXEL ((int) xI - xL + 128)
-#define GIMP_OP_GRAIN_MERGE(xL, xI)    CLAMP_PIXEL ((int) xI + xL - 128)
-#define GIMP_OP_DIVIDE(xL, xI)         CLAMP_PIXEL ((int) (xI) * 256 / (1 + (xL)))
+#define TILE_WIDTH     64
+#define MAX_TILE_SIZE  (TILE_WIDTH * TILE_WIDTH * 4 * 1.5)
+#define DISSOLVE_SEED  737893334
 
 
 typedef enum {
@@ -147,31 +120,6 @@ typedef struct {
 static int cairo_rgba[4]  = { CAIRO_RED, CAIRO_GREEN, CAIRO_BLUE, CAIRO_ALPHA };
 static int cairo_graya[2] = { 0, CAIRO_ALPHA };
 static int cairo_indexed[2] = { 0, CAIRO_ALPHA };
-static guchar add_alpha_table[256][256];
-static GOnce  xcf_init_once = G_ONCE_INIT;
-
-
-static gpointer
-xcf_init (gpointer data)
-{
-       int v;
-       int a;
-       int r;
-
-       /* add_alpha_table[v][a] = v * a / 255 */
-
-       for (v = 0; v < 128; v++) {
-               for (a = 0; a <= v; a++) {
-                       r = (v * a + 127) / 255;
-                       add_alpha_table[v][a] = add_alpha_table[a][v] = r;
-                       add_alpha_table[255-v][a] = add_alpha_table[a][255-v] = a - r;
-                       add_alpha_table[v][255-a] = add_alpha_table[255-a][v] = v - r;
-                       add_alpha_table[255-v][255-a] = add_alpha_table[255-a][255-v] = (255 - a) - (v - r);
-               }
-       }
-
-       return NULL;
-}
 
 
 /* -- GDataInputStream functions -- */
@@ -985,7 +933,7 @@ _cairo_image_surface_create_from_xcf (GInputStream  *istream,
 
        performance (DEBUG_INFO, "start loading");
 
-       g_once (&xcf_init_once, xcf_init, NULL);
+       gimp_op_init ();
 
        performance (DEBUG_INFO, "end init");
 
diff --git a/extensions/file_tools/Makefile.am b/extensions/file_tools/Makefile.am
index 22c8d46..c81bbaf 100644
--- a/extensions/file_tools/Makefile.am
+++ b/extensions/file_tools/Makefile.am
@@ -7,33 +7,38 @@ ENUM_TYPES =          \
        enum-types.h    \
        enum-types.c
 
-HEADER_FILES =                                 \
-       cairo-blur.h                    \
-       cairo-rotate.h                  \
-       gth-curve.h                     \
-       gth-curve-editor.h              \
-       gth-file-tool-adjust-colors.h   \
-       gth-file-tool-adjust-contrast.h \
-       gth-file-tool-crop.h            \
-       gth-file-tool-curves.h          \
-       gth-file-tool-flip.h            \
-       gth-file-tool-grayscale.h       \
-       gth-file-tool-mirror.h          \
-       gth-file-tool-negative.h        \
-       gth-file-tool-redo.h            \
-       gth-file-tool-resize.h          \
-       gth-file-tool-rotate.h          \
-       gth-file-tool-rotate-left.h     \
-       gth-file-tool-rotate-right.h    \
-       gth-file-tool-rotate.h          \
-       gth-file-tool-save.h            \
-       gth-file-tool-save-as.h         \
-       gth-file-tool-sharpen.h         \
-       gth-file-tool-undo.h            \
-       gth-image-line-tool.h           \
-       gth-image-rotator.h             \
-       gth-points.h                    \
-       gth-preview-tool.h              \
+HEADER_FILES =                                         \
+       cairo-blur.h                            \
+       cairo-effects.h                         \
+       cairo-rotate.h                          \
+       gth-curve.h                             \
+       gth-curve-editor.h                      \
+       gth-curve-preset.h                      \
+       gth-curve-preset-editor-dialog.h        \
+       gth-file-tool-adjust-colors.h           \
+       gth-file-tool-adjust-contrast.h         \
+       gth-file-tool-crop.h                    \
+       gth-file-tool-curves.h                  \
+       gth-file-tool-effects.h                 \
+       gth-file-tool-flip.h                    \
+       gth-file-tool-grayscale.h               \
+       gth-file-tool-lomo.h                    \
+       gth-file-tool-mirror.h                  \
+       gth-file-tool-negative.h                \
+       gth-file-tool-redo.h                    \
+       gth-file-tool-resize.h                  \
+       gth-file-tool-rotate.h                  \
+       gth-file-tool-rotate-left.h             \
+       gth-file-tool-rotate-right.h            \
+       gth-file-tool-rotate.h                  \
+       gth-file-tool-save.h                    \
+       gth-file-tool-save-as.h                 \
+       gth-file-tool-sharpen.h                 \
+       gth-file-tool-undo.h                    \
+       gth-image-line-tool.h                   \
+       gth-image-rotator.h                     \
+       gth-points.h                            \
+       gth-preview-tool.h                      \
        preferences.h
 
 enum-types.h: $(HEADER_FILES)
@@ -55,36 +60,41 @@ enum-types.c: $(HEADER_FILES)
                $^> xgen-$(@F) \
        && mv -f xgen-$(@F) enum-types.c )
 
-libfile_tools_la_SOURCES =             \
-       $(ENUM_TYPES)                   \
-       $(HEADER_FILES)                 \
-       cairo-blur.c                    \
-       cairo-rotate.c                  \
-       callbacks.c                     \
-       callbacks.h                     \
-       gth-curve.c                     \
-       gth-curve-editor.c              \
-       gth-file-tool-adjust-colors.c   \
-       gth-file-tool-adjust-contrast.c \
-       gth-file-tool-crop.c            \
-       gth-file-tool-curves.c          \
-       gth-file-tool-flip.c            \
-       gth-file-tool-grayscale.c       \
-       gth-file-tool-mirror.c          \
-       gth-file-tool-negative.c        \
-       gth-file-tool-redo.c            \
-       gth-file-tool-resize.c          \
-       gth-file-tool-rotate.c          \
-       gth-file-tool-rotate-left.c     \
-       gth-file-tool-rotate-right.c    \
-       gth-file-tool-save.c            \
-       gth-file-tool-save-as.c         \
-       gth-file-tool-sharpen.c         \
-       gth-file-tool-undo.c            \
-       gth-image-line-tool.c           \
-       gth-image-rotator.c             \
-       gth-points.c                    \
-       gth-preview-tool.c              \
+libfile_tools_la_SOURCES =                     \
+       $(ENUM_TYPES)                           \
+       $(HEADER_FILES)                         \
+       cairo-blur.c                            \
+       cairo-effects.c                         \
+       cairo-rotate.c                          \
+       callbacks.c                             \
+       callbacks.h                             \
+       gth-curve.c                             \
+       gth-curve-editor.c                      \
+       gth-curve-preset.c                      \
+       gth-curve-preset-editor-dialog.c        \
+       gth-file-tool-adjust-colors.c           \
+       gth-file-tool-adjust-contrast.c         \
+       gth-file-tool-crop.c                    \
+       gth-file-tool-curves.c                  \
+       gth-file-tool-effects.c                 \
+       gth-file-tool-flip.c                    \
+       gth-file-tool-grayscale.c               \
+       gth-file-tool-lomo.c                    \
+       gth-file-tool-mirror.c                  \
+       gth-file-tool-negative.c                \
+       gth-file-tool-redo.c                    \
+       gth-file-tool-resize.c                  \
+       gth-file-tool-rotate.c                  \
+       gth-file-tool-rotate-left.c             \
+       gth-file-tool-rotate-right.c            \
+       gth-file-tool-save.c                    \
+       gth-file-tool-save-as.c                 \
+       gth-file-tool-sharpen.c                 \
+       gth-file-tool-undo.c                    \
+       gth-image-line-tool.c                   \
+       gth-image-rotator.c                     \
+       gth-points.c                            \
+       gth-preview-tool.c                      \
        main.c
 
 libfile_tools_la_CFLAGS = $(GTHUMB_CFLAGS) -I$(top_srcdir) -I$(top_builddir)/gthumb 
diff --git a/extensions/file_tools/cairo-blur.h b/extensions/file_tools/cairo-blur.h
index c2a2e8d..a563975 100644
--- a/extensions/file_tools/cairo-blur.h
+++ b/extensions/file_tools/cairo-blur.h
@@ -19,8 +19,8 @@
  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#ifndef GDK_PIXBUF_BLUR_H
-#define GDK_PIXBUF_BLUR_H
+#ifndef CAIRO_BLUR_H
+#define CAIRO_BLUR_H
 
 #include <glib.h>
 #include <cairo.h>
@@ -39,4 +39,4 @@ gboolean _cairo_image_surface_sharpen  (cairo_surface_t *source,
 
 G_END_DECLS
 
-#endif /* GDK_PIXBUF_BLUR_H */
+#endif /* CAIRO_BLUR_H */
diff --git a/extensions/file_tools/cairo-effects.c b/extensions/file_tools/cairo-effects.c
new file mode 100644
index 0000000..63d710a
--- /dev/null
+++ b/extensions/file_tools/cairo-effects.c
@@ -0,0 +1,450 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2014 The Free Software Foundation, Inc.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <math.h>
+#include "cairo-effects.h"
+#include "gth-curve.h"
+
+
+gboolean
+cairo_image_surface_apply_curves (cairo_surface_t  *source,
+                                 GthCurve        **curve,
+                                 GthAsyncTask     *task)
+{
+       long            *value_map[GTH_HISTOGRAM_N_CHANNELS];
+       int              c, v;
+       int              width;
+       int              height;
+       int              source_stride;
+       unsigned char   *p_source_line;
+       int              x, y;
+       gboolean         cancelled = FALSE;
+       double           progress;
+       unsigned char   *p_source;
+       unsigned char    image_red, image_green, image_blue, image_alpha;
+
+       for (c = GTH_HISTOGRAM_CHANNEL_VALUE; c <= GTH_HISTOGRAM_CHANNEL_BLUE; c++) {
+               value_map[c] = g_new (long, 256);
+               for (v = 0; v <= 255; v++) {
+                       double u = gth_curve_eval (curve[c], v);
+                       if (c > GTH_HISTOGRAM_CHANNEL_VALUE)
+                               u = value_map[GTH_HISTOGRAM_CHANNEL_VALUE][(int)u];
+                       value_map[c][v] = u;
+               }
+       }
+
+       width = cairo_image_surface_get_width (source);
+       height = cairo_image_surface_get_height (source);
+       source_stride = cairo_image_surface_get_stride (source);
+
+       p_source_line = _cairo_image_surface_flush_and_get_data (source);
+       for (y = 0; y < height; y++) {
+               gth_async_task_get_data (task, NULL, &cancelled, NULL);
+               if (cancelled)
+                       break;
+
+               progress = (double) y / height;
+               gth_async_task_set_data (task, NULL, NULL, &progress);
+
+               p_source = p_source_line;
+               for (x = 0; x < width; x++) {
+                       CAIRO_GET_RGBA (p_source, image_red, image_green, image_blue, image_alpha);
+
+                       image_red   = value_map[GTH_HISTOGRAM_CHANNEL_RED][image_red];
+                       image_green = value_map[GTH_HISTOGRAM_CHANNEL_GREEN][image_green];
+                       image_blue  = value_map[GTH_HISTOGRAM_CHANNEL_BLUE][image_blue];
+
+                       CAIRO_SET_RGBA (p_source, image_red, image_green, image_blue, image_alpha);
+
+                       p_source += 4;
+               }
+               p_source_line += source_stride;
+       }
+       cairo_surface_mark_dirty (source);
+
+       for (c = GTH_HISTOGRAM_CHANNEL_VALUE; c <= GTH_HISTOGRAM_CHANNEL_BLUE; c++)
+               g_free (value_map[c]);
+
+       return ! cancelled;
+}
+
+
+gboolean
+cairo_image_surface_apply_vignette (cairo_surface_t  *source,
+                                   GthCurve        **curve,
+                                   GthAsyncTask     *task)
+{
+       gboolean         local_curves;
+       long            *value_map[GTH_HISTOGRAM_N_CHANNELS];
+       int              c, v;
+       int              width;
+       int              height;
+       int              source_stride;
+       unsigned char   *p_source_line;
+       int              x, y;
+       gboolean         cancelled = FALSE;
+       double           progress;
+       unsigned char   *p_source;
+       unsigned char    image_red, image_green, image_blue, image_alpha;
+       unsigned char    red, green, blue, alpha;
+       double           center_x, center_y,  d, min_d, max_d;
+       unsigned char    temp;
+       GthPoint         f1, f2, p;
+
+       gimp_op_init ();
+
+       local_curves = (curve == NULL);
+       if (local_curves) {
+               curve = g_new (GthCurve *, GTH_HISTOGRAM_N_CHANNELS);
+               curve[GTH_HISTOGRAM_CHANNEL_VALUE] = gth_curve_new_for_points (GTH_TYPE_BEZIER, 3, 0,0, 
152,103, 255,255);
+               curve[GTH_HISTOGRAM_CHANNEL_RED] = gth_curve_new_for_points (GTH_TYPE_BEZIER, 0);
+               curve[GTH_HISTOGRAM_CHANNEL_GREEN] = gth_curve_new_for_points (GTH_TYPE_BEZIER, 0);
+               curve[GTH_HISTOGRAM_CHANNEL_BLUE] = gth_curve_new_for_points (GTH_TYPE_BEZIER, 0);
+       }
+
+       for (c = GTH_HISTOGRAM_CHANNEL_VALUE; c <= GTH_HISTOGRAM_CHANNEL_BLUE; c++) {
+               value_map[c] = g_new (long, 256);
+               for (v = 0; v <= 255; v++) {
+                       double u = gth_curve_eval (curve[c], v);
+                       if (c > GTH_HISTOGRAM_CHANNEL_VALUE)
+                               u = value_map[GTH_HISTOGRAM_CHANNEL_VALUE][(int)u];
+                       value_map[c][v] = u;
+               }
+       }
+
+       width = cairo_image_surface_get_width (source);
+       height = cairo_image_surface_get_height (source);
+       source_stride = cairo_image_surface_get_stride (source);
+
+       center_x = width / 2.0;
+       center_y = height / 2.0;
+       max_d = 2 * sqrt (SQR (center_x) + SQR (center_y));
+
+       {
+               double a = MAX (width, height) / 2.0;
+               double b = MIN (height, width) / 2.0;
+
+               a = a - (a / 10);
+               b = b - (b / 10);
+
+               double e = sqrt (1.0 - SQR (b) / SQR (a));
+               double c = a * e;
+               min_d = 2 * sqrt (SQR (b) + SQR (c));
+
+               if (width > height) {
+                       f1.x = center_x - c;
+                       f1.y = center_y;
+                       f2.x = center_x + c;
+                       f2.y = center_y;
+               }
+               else {
+                       f1.x = center_x;
+                       f1.y = center_y - c;
+                       f2.x = center_x;
+                       f2.y = center_y + c;
+               }
+       }
+
+       p_source_line = _cairo_image_surface_flush_and_get_data (source);
+       for (y = 0; y < height; y++) {
+               gth_async_task_get_data (task, NULL, &cancelled, NULL);
+               if (cancelled)
+                       break;
+
+               progress = (double) y / height;
+               gth_async_task_set_data (task, NULL, NULL, &progress);
+
+               p_source = p_source_line;
+               for (x = 0; x < width; x++) {
+                       p.x = x;
+                       p.y = y;
+                       d = gth_point_distance (&p, &f1) + gth_point_distance (&p, &f2);
+                       if (d >= min_d) {
+                               CAIRO_GET_RGBA (p_source, image_red, image_green, image_blue, image_alpha);
+                               red   = value_map[GTH_HISTOGRAM_CHANNEL_RED][image_red];
+                               green = value_map[GTH_HISTOGRAM_CHANNEL_GREEN][image_green];
+                               blue  = value_map[GTH_HISTOGRAM_CHANNEL_BLUE][image_blue];
+
+                               if (d <= max_d)
+                                       alpha = 255 * ((d - min_d) / (max_d - min_d));
+                               else
+                                       alpha = 255;
+
+                               p_source[CAIRO_RED] = GIMP_OP_NORMAL (red, image_red, alpha);
+                               p_source[CAIRO_GREEN] = GIMP_OP_NORMAL (green, image_green, alpha);
+                               p_source[CAIRO_BLUE] = GIMP_OP_NORMAL (blue, image_blue, alpha);
+                               p_source[CAIRO_ALPHA] = GIMP_OP_NORMAL (255, image_alpha, alpha);
+                       }
+
+                       p_source += 4;
+               }
+               p_source_line += source_stride;
+       }
+       cairo_surface_mark_dirty (source);
+
+       if (local_curves) {
+               for (c = GTH_HISTOGRAM_CHANNEL_VALUE; c <= GTH_HISTOGRAM_CHANNEL_BLUE; c++) {
+                       g_object_unref (curve[c]);
+                       g_free (value_map[c]);
+               }
+       }
+
+       return ! cancelled;
+}
+
+
+gboolean
+cairo_image_surface_apply_bcs (cairo_surface_t  *source,
+                              double            brightness,
+                              double            contrast,
+                              double            saturation,
+                              GthAsyncTask     *task)
+{
+       PixbufCache     *cache;
+       int              i;
+       double           midtone_distance[256];
+       int              width;
+       int              height;
+       int              source_stride;
+       unsigned char   *p_source_line;
+       int              x, y;
+       gboolean         cancelled = FALSE;
+       double           progress;
+       unsigned char   *p_source;
+       unsigned char    image_red, image_green, image_blue, image_alpha;
+       unsigned char    values[4];
+       unsigned char    red, green, blue, alpha;
+       unsigned char    temp, min, max, lightness, value;
+
+       gimp_op_init ();
+       cache = pixbuf_cache_new ();
+       for (i = 0; i < 256; i++)
+               midtone_distance[i] = 0.667 * (1 - SQR (((double) i - 127.0) / 127.0));
+
+       if (saturation < 0)
+               saturation = tan (saturation * G_PI_2);
+
+       width = cairo_image_surface_get_width (source);
+       height = cairo_image_surface_get_height (source);
+       source_stride = cairo_image_surface_get_stride (source);
+       p_source_line = _cairo_image_surface_flush_and_get_data (source);
+
+       for (y = 0; y < height; y++) {
+               gth_async_task_get_data (task, NULL, &cancelled, NULL);
+               if (cancelled)
+                       break;
+
+               progress = (double) y / height;
+               gth_async_task_set_data (task, NULL, NULL, &progress);
+
+               p_source = p_source_line;
+               for (x = 0; x < width; x++) {
+                       int channel;
+
+                       CAIRO_GET_RGBA (p_source, values[0], values[1], values[2], values[3]);
+
+                       /* brightness / contrast */
+
+                       for (channel = 0; channel < 3; channel++) {
+                               value = values[channel];
+
+                               if (! pixbuf_cache_get (cache, channel + 1, &value)) {
+                                       int tmp = value;
+
+                                       if (brightness > 0)
+                                               tmp = interpolate_value (value, 0, brightness);
+                                       else if (brightness < 0)
+                                               tmp = interpolate_value (value, 255, - brightness);
+                                       value = CLAMP (tmp, 0, 255);
+
+                                       if (contrast < 0)
+                                               tmp = interpolate_value (value, 127, tan (contrast * G_PI_2));
+                                       else if (contrast > 0)
+                                               tmp = interpolate_value (value, 127, contrast);
+                                       value = CLAMP (tmp, 0, 255);
+
+                                       pixbuf_cache_set (cache, channel + 1, values[channel], value);
+                               }
+
+                               values[channel] = value;
+                       }
+
+                       /* saturation */
+
+                       if (saturation != 0.0) {
+                               guchar min, max, lightness;
+                               int    tmp;
+
+                               max = MAX (MAX (values[0], values[1]), values[2]);
+                               min = MIN (MIN (values[0], values[1]), values[2]);
+                               lightness = (max + min) / 2;
+
+                               tmp = interpolate_value (values[0], lightness, saturation);
+                               values[0] = CLAMP (tmp, 0, 255);
+
+                               tmp = interpolate_value (values[1], lightness, saturation);
+                               values[1] = CLAMP (tmp, 0, 255);
+
+                               tmp = interpolate_value (values[2], lightness, saturation);
+                               values[2] = CLAMP (tmp, 0, 255);
+                       }
+
+                       CAIRO_SET_RGBA (p_source, values[0], values[1], values[2], values[3]);
+
+                       p_source += 4;
+               }
+               p_source_line += source_stride;
+       }
+       cairo_surface_mark_dirty (source);
+
+       pixbuf_cache_free (cache);
+
+       return ! cancelled;
+}
+
+
+gboolean
+cairo_image_surface_colorize (cairo_surface_t  *source,
+                             guchar            color_red,
+                             guchar            color_green,
+                             guchar            color_blue,
+                             guchar            color_alpha,
+                             GthAsyncTask     *task)
+{
+       int              i;
+       double           midtone_distance[256];
+       int              width;
+       int              height;
+       int              source_stride;
+       unsigned char   *p_source_line;
+       int              x, y;
+       gboolean         cancelled = FALSE;
+       double           progress;
+       unsigned char   *p_source;
+       unsigned char    image_red, image_green, image_blue, image_alpha;
+       unsigned char    red, green, blue, alpha;
+       unsigned char    temp, min, max, lightness;
+
+       gimp_op_init ();
+       for (i = 0; i < 256; i++)
+               midtone_distance[i] = 0.667 * (1 - SQR (((double) i - 127.0) / 127.0));
+
+       width = cairo_image_surface_get_width (source);
+       height = cairo_image_surface_get_height (source);
+       source_stride = cairo_image_surface_get_stride (source);
+       p_source_line = _cairo_image_surface_flush_and_get_data (source);
+
+       for (y = 0; y < height; y++) {
+               gth_async_task_get_data (task, NULL, &cancelled, NULL);
+               if (cancelled)
+                       break;
+
+               progress = (double) y / height;
+               gth_async_task_set_data (task, NULL, NULL, &progress);
+
+               p_source = p_source_line;
+               for (x = 0; x < width; x++) {
+                       CAIRO_GET_RGBA (p_source, image_red, image_green, image_blue, image_alpha);
+
+                       /* desaturate */
+
+                       max = MAX (MAX (image_red, image_green), image_blue);
+                       min = MIN (MIN (image_red, image_green), image_blue);
+                       lightness = (max + min) / 2;
+
+                       /* colorize */
+
+                       red = lightness + color_red * midtone_distance[lightness];
+                       green = lightness + color_green * midtone_distance[lightness];
+                       blue = lightness + color_blue * midtone_distance[lightness];
+                       alpha = ADD_ALPHA (image_alpha, color_alpha);
+
+                       p_source[CAIRO_RED] = GIMP_OP_NORMAL (red, image_red, alpha);
+                       p_source[CAIRO_GREEN] = GIMP_OP_NORMAL (green, image_green, alpha);
+                       p_source[CAIRO_BLUE] = GIMP_OP_NORMAL (blue, image_blue, alpha);
+                       p_source[CAIRO_ALPHA] = GIMP_OP_NORMAL (255, image_alpha, alpha);
+
+                       p_source += 4;
+               }
+               p_source_line += source_stride;
+       }
+       cairo_surface_mark_dirty (source);
+
+       return ! cancelled;
+}
+
+
+gboolean
+cairo_image_surface_add_color (cairo_surface_t  *source,
+                              guchar            color_red,
+                              guchar            color_green,
+                              guchar            color_blue,
+                              guchar            color_alpha,
+                              GthAsyncTask     *task)
+{
+       int              i;
+       int              width;
+       int              height;
+       int              source_stride;
+       unsigned char   *p_source_line;
+       int              x, y;
+       gboolean         cancelled = FALSE;
+       double           progress;
+       unsigned char   *p_source;
+       unsigned char    image_red, image_green, image_blue, image_alpha;
+       unsigned char    temp, alpha;
+
+       gimp_op_init ();
+
+       width = cairo_image_surface_get_width (source);
+       height = cairo_image_surface_get_height (source);
+       source_stride = cairo_image_surface_get_stride (source);
+       p_source_line = _cairo_image_surface_flush_and_get_data (source);
+
+       for (y = 0; y < height; y++) {
+               gth_async_task_get_data (task, NULL, &cancelled, NULL);
+               if (cancelled)
+                       break;
+
+               progress = (double) y / height;
+               gth_async_task_set_data (task, NULL, NULL, &progress);
+
+               p_source = p_source_line;
+               for (x = 0; x < width; x++) {
+                       CAIRO_GET_RGBA (p_source, image_red, image_green, image_blue, image_alpha);
+
+                       alpha = ADD_ALPHA (image_alpha, color_alpha);
+
+                       p_source[CAIRO_RED] = GIMP_OP_NORMAL (color_red, image_red, alpha);
+                       p_source[CAIRO_GREEN] = GIMP_OP_NORMAL (color_green, image_green, alpha);
+                       p_source[CAIRO_BLUE] = GIMP_OP_NORMAL (color_blue, image_blue, alpha);
+                       p_source[CAIRO_ALPHA] = GIMP_OP_NORMAL (255, image_alpha, alpha);
+
+                       p_source += 4;
+               }
+               p_source_line += source_stride;
+       }
+       cairo_surface_mark_dirty (source);
+
+       return ! cancelled;
+}
+
diff --git a/extensions/file_tools/cairo-effects.h b/extensions/file_tools/cairo-effects.h
new file mode 100644
index 0000000..0a5dec2
--- /dev/null
+++ b/extensions/file_tools/cairo-effects.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2014 The Free Software Foundation, Inc.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef CAIRO_EFFECTS_H
+#define CAIRO_EFFECTS_H
+
+#include <glib.h>
+#include <cairo.h>
+#include <gthumb.h>
+#include "gth-curve.h"
+
+G_BEGIN_DECLS
+
+gboolean cairo_image_surface_apply_curves      (cairo_surface_t  *source,
+                                                GthCurve        **curve,
+                                                GthAsyncTask     *task);
+gboolean cairo_image_surface_apply_vignette    (cairo_surface_t  *source,
+                                                GthCurve        **curve,
+                                                GthAsyncTask     *task);
+gboolean cairo_image_surface_apply_bcs         (cairo_surface_t  *source,
+                                                double            brightness,
+                                                double            contrast,
+                                                double            saturation,
+                                                GthAsyncTask     *task);
+gboolean cairo_image_surface_colorize          (cairo_surface_t  *source,
+                                                guchar            color_red,
+                                                guchar            color_green,
+                                                guchar            color_blue,
+                                                guchar            color_alpha,
+                                                GthAsyncTask     *task);
+gboolean cairo_image_surface_add_color         (cairo_surface_t  *source,
+                                                guchar            color_red,
+                                                guchar            color_green,
+                                                guchar            color_blue,
+                                                guchar            color_alpha,
+                                                GthAsyncTask     *task);
+
+G_END_DECLS
+
+#endif /* CAIRO_EFFECTS_H */
diff --git a/extensions/file_tools/data/ui/Makefile.am b/extensions/file_tools/data/ui/Makefile.am
index 3db21b9..8d6d1e5 100644
--- a/extensions/file_tools/data/ui/Makefile.am
+++ b/extensions/file_tools/data/ui/Makefile.am
@@ -4,6 +4,7 @@ ui_DATA =                               \
        adjust-contrast-options.ui      \
        crop-options.ui                 \
        curves-options.ui               \
+       effects-options.ui              \
        grayscale-options.ui            \
        resize-options.ui               \
        rotate-options.ui               \
diff --git a/extensions/file_tools/data/ui/effects-options.ui 
b/extensions/file_tools/data/ui/effects-options.ui
new file mode 100644
index 0000000..226e60f
--- /dev/null
+++ b/extensions/file_tools/data/ui/effects-options.ui
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.18.3 -->
+<interface>
+  <requires lib="gtk+" version="3.12"/>
+  <object class="GtkAlignment" id="options">
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <child>
+      <object class="GtkBox" id="box1">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="orientation">vertical</property>
+        <child>
+          <object class="GtkBox" id="box2">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="border_width">12</property>
+            <property name="orientation">vertical</property>
+            <property name="spacing">12</property>
+            <child>
+              <object class="GtkBox" id="filter_grid_box">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <child>
+                  <placeholder/>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+  </object>
+</interface>
diff --git a/extensions/file_tools/gth-file-tool-effects.c b/extensions/file_tools/gth-file-tool-effects.c
new file mode 100644
index 0000000..a93cdb0
--- /dev/null
+++ b/extensions/file_tools/gth-file-tool-effects.c
@@ -0,0 +1,655 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2014 Free Software Foundation, Inc.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <math.h>
+#include <gthumb.h>
+#include <extensions/image_viewer/image-viewer.h>
+#include "cairo-blur.h"
+#include "cairo-effects.h"
+#include "gth-curve.h"
+#include "gth-file-tool-effects.h"
+#include "gth-preview-tool.h"
+
+
+#define GET_WIDGET(x) (_gtk_builder_get_widget (self->priv->builder, (x)))
+#define APPLY_DELAY 150
+#define PREVIEW_SIZE 0.9
+
+
+G_DEFINE_TYPE (GthFileToolEffects, gth_file_tool_effects, GTH_TYPE_IMAGE_VIEWER_PAGE_TOOL)
+
+
+struct _GthFileToolEffectsPrivate {
+       cairo_surface_t    *destination;
+       cairo_surface_t    *preview;
+       GtkBuilder         *builder;
+       GthTask            *image_task;
+       GthImageViewerTool *preview_tool;
+       guint               apply_event;
+       gboolean            apply_to_original;
+       gboolean            closing;
+       gboolean            view_original;
+       int                 method;
+       int                 last_applied_method;
+       GtkWidget          *filter_grid;
+};
+
+
+static void apply_changes (GthFileToolEffects *self);
+
+
+static void
+image_task_completed_cb (GthTask  *task,
+                        GError   *error,
+                        gpointer  user_data)
+{
+       GthFileToolEffects *self = user_data;
+       GthImage           *destination_image;
+
+       g_signal_handlers_disconnect_matched (task, G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA, 0, 0, NULL, 
image_task_completed_cb, self);
+       self->priv->image_task = NULL;
+
+       if (self->priv->closing) {
+               g_object_unref (task);
+               gth_image_viewer_page_tool_reset_image (GTH_IMAGE_VIEWER_PAGE_TOOL (self));
+               return;
+       }
+
+       if (error != NULL) {
+               if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+                       apply_changes (self);
+               g_object_unref (task);
+               return;
+       }
+
+       destination_image = gth_image_task_get_destination (GTH_IMAGE_TASK (task));
+       if (destination_image == NULL) {
+               g_object_unref (task);
+               return;
+       }
+
+       cairo_surface_destroy (self->priv->destination);
+       self->priv->destination = gth_image_get_cairo_surface (destination_image);
+       self->priv->last_applied_method = self->priv->method;
+
+       if (self->priv->apply_to_original) {
+               if (self->priv->destination != NULL) {
+                       GtkWidget *window;
+                       GtkWidget *viewer_page;
+
+                       window = gth_file_tool_get_window (GTH_FILE_TOOL (self));
+                       viewer_page = gth_browser_get_viewer_page (GTH_BROWSER (window));
+                       gth_image_viewer_page_set_image (GTH_IMAGE_VIEWER_PAGE (viewer_page), 
self->priv->destination, TRUE);
+               }
+
+               gth_file_tool_hide_options (GTH_FILE_TOOL (self));
+       }
+       else {
+               if (! self->priv->view_original)
+                       gth_preview_tool_set_image (GTH_PREVIEW_TOOL (self->priv->preview_tool), 
self->priv->destination);
+       }
+
+       g_object_unref (task);
+}
+
+
+static gboolean
+apply_cb (gpointer user_data)
+{
+       GthFileToolEffects *self = user_data;
+       GtkWidget          *window;
+
+       if (self->priv->apply_event != 0) {
+               g_source_remove (self->priv->apply_event);
+               self->priv->apply_event = 0;
+       }
+
+       if (self->priv->image_task != NULL) {
+               gth_task_cancel (self->priv->image_task);
+               return FALSE;
+       }
+
+       window = gth_file_tool_get_window (GTH_FILE_TOOL (self));
+
+       self->priv->image_task = gth_filter_grid_get_task (GTH_FILTER_GRID (self->priv->filter_grid), 
self->priv->method);
+       if (self->priv->apply_to_original)
+               gth_image_task_set_source_surface (GTH_IMAGE_TASK (self->priv->image_task), 
gth_image_viewer_page_tool_get_source (GTH_IMAGE_VIEWER_PAGE_TOOL (self)));
+       else
+               gth_image_task_set_source_surface (GTH_IMAGE_TASK (self->priv->image_task), 
self->priv->preview);
+       g_signal_connect (self->priv->image_task,
+                         "completed",
+                         G_CALLBACK (image_task_completed_cb),
+                         self);
+       gth_browser_exec_task (GTH_BROWSER (window), self->priv->image_task, FALSE);
+
+       return FALSE;
+}
+
+
+static void
+apply_changes (GthFileToolEffects *self)
+{
+       if (self->priv->apply_event != 0) {
+               g_source_remove (self->priv->apply_event);
+               self->priv->apply_event = 0;
+       }
+       self->priv->apply_event = g_timeout_add (APPLY_DELAY, apply_cb, self);
+}
+
+
+static void
+gth_file_tool_effects_reset_image (GthImageViewerPageTool *base)
+{
+       GthFileToolEffects *self = GTH_FILE_TOOL_EFFECTS (base);
+
+       if (self->priv->image_task != NULL) {
+               self->priv->closing = TRUE;
+               return;
+       }
+
+       if (self->priv->apply_event != 0) {
+               g_source_remove (self->priv->apply_event);
+               self->priv->apply_event = 0;
+       }
+
+       gth_image_viewer_page_reset (GTH_IMAGE_VIEWER_PAGE (gth_image_viewer_page_tool_get_page 
(GTH_IMAGE_VIEWER_PAGE_TOOL (self))));
+       gth_file_tool_hide_options (GTH_FILE_TOOL (self));
+}
+
+
+static void
+filter_grid_activated_cb (GthFilterGrid        *filter_grid,
+                         int            filter_id,
+                         gpointer       user_data)
+{
+       GthFileToolEffects *self = user_data;
+
+       self->priv->view_original = (filter_id == GTH_FILTER_GRID_NO_FILTER);
+       if (self->priv->view_original) {
+               gth_preview_tool_set_image (GTH_PREVIEW_TOOL (self->priv->preview_tool), self->priv->preview);
+       }
+       else if (filter_id == self->priv->last_applied_method) {
+               gth_preview_tool_set_image (GTH_PREVIEW_TOOL (self->priv->preview_tool), 
self->priv->destination);
+       }
+       else {
+               self->priv->method = filter_id;
+               apply_changes (self);
+       }
+}
+
+
+static GtkWidget *
+gth_file_tool_effects_get_options (GthFileTool *base)
+{
+       GthFileToolEffects *self;
+       GtkWidget          *window;
+       GtkWidget          *viewer_page;
+       GtkWidget          *viewer;
+       cairo_surface_t    *source;
+       GtkWidget          *options;
+       int                 width, height;
+       GtkAllocation       allocation;
+
+       self = (GthFileToolEffects *) base;
+
+       window = gth_file_tool_get_window (base);
+       viewer_page = gth_browser_get_viewer_page (GTH_BROWSER (window));
+       if (! GTH_IS_IMAGE_VIEWER_PAGE (viewer_page))
+               return NULL;
+
+       cairo_surface_destroy (self->priv->destination);
+       cairo_surface_destroy (self->priv->preview);
+
+       viewer = gth_image_viewer_page_get_image_viewer (GTH_IMAGE_VIEWER_PAGE (viewer_page));
+       source = gth_image_viewer_page_tool_get_source (GTH_IMAGE_VIEWER_PAGE_TOOL (self));
+       if (source == NULL)
+               return NULL;
+
+       width = cairo_image_surface_get_width (source);
+       height = cairo_image_surface_get_height (source);
+       gtk_widget_get_allocation (GTK_WIDGET (viewer), &allocation);
+       if (scale_keeping_ratio (&width, &height, PREVIEW_SIZE * allocation.width, PREVIEW_SIZE * 
allocation.height, FALSE))
+               self->priv->preview = _cairo_image_surface_scale_bilinear (source, width, height);
+       else
+               self->priv->preview = cairo_surface_reference (source);
+
+       self->priv->destination = cairo_surface_reference (self->priv->preview);
+       self->priv->apply_to_original = FALSE;
+       self->priv->closing = FALSE;
+
+       self->priv->builder = _gtk_builder_new_from_file ("effects-options.ui", "file_tools");
+       options = _gtk_builder_get_widget (self->priv->builder, "options");
+       gtk_widget_show (options);
+
+       self->priv->filter_grid = gth_filter_grid_new ();
+       gth_hook_invoke ("add-special-effect", self->priv->filter_grid);
+       gtk_widget_show (self->priv->filter_grid);
+       gtk_box_pack_start (GTK_BOX (GET_WIDGET ("filter_grid_box")), self->priv->filter_grid, TRUE, FALSE, 
0);
+
+       g_signal_connect (self->priv->filter_grid,
+                         "activated",
+                         G_CALLBACK (filter_grid_activated_cb),
+                         self);
+
+       self->priv->preview_tool = gth_preview_tool_new ();
+       gth_preview_tool_set_image (GTH_PREVIEW_TOOL (self->priv->preview_tool), self->priv->preview);
+       gth_image_viewer_set_tool (GTH_IMAGE_VIEWER (viewer), self->priv->preview_tool);
+       gth_filter_grid_generate_previews (GTH_FILTER_GRID (self->priv->filter_grid), source);
+
+       return options;
+}
+
+
+static void
+gth_file_tool_effects_destroy_options (GthFileTool *base)
+{
+       GthFileToolEffects *self;
+       GtkWidget          *window;
+       GtkWidget          *viewer_page;
+
+       self = (GthFileToolEffects *) base;
+
+       if (self->priv->apply_event != 0) {
+               g_source_remove (self->priv->apply_event);
+               self->priv->apply_event = 0;
+       }
+
+       window = gth_file_tool_get_window (GTH_FILE_TOOL (self));
+       viewer_page = gth_browser_get_viewer_page (GTH_BROWSER (window));
+       gth_image_viewer_page_reset_viewer_tool (GTH_IMAGE_VIEWER_PAGE (viewer_page));
+       gth_viewer_page_update_sensitivity (GTH_VIEWER_PAGE (viewer_page));
+
+       _g_clear_object (&self->priv->builder);
+
+       _cairo_clear_surface (&self->priv->preview);
+       _cairo_clear_surface (&self->priv->destination);
+       self->priv->method = GTH_FILTER_GRID_NO_FILTER;
+       self->priv->last_applied_method = GTH_FILTER_GRID_NO_FILTER;
+}
+
+
+static void
+gth_file_tool_effects_apply_options (GthFileTool *base)
+{
+       GthFileToolEffects *self;
+
+       self = (GthFileToolEffects *) base;
+
+       if (! self->priv->view_original) {
+               self->priv->apply_to_original = TRUE;
+               apply_changes (self);
+       }
+}
+
+
+static void
+gth_file_tool_effects_finalize (GObject *object)
+{
+       GthFileToolEffects *self;
+
+       g_return_if_fail (object != NULL);
+       g_return_if_fail (GTH_IS_FILE_TOOL_EFFECTS (object));
+
+       self = (GthFileToolEffects *) object;
+
+       _g_clear_object (&self->priv->builder);
+       _cairo_clear_surface (&self->priv->preview);
+       _cairo_clear_surface (&self->priv->destination);
+
+       G_OBJECT_CLASS (gth_file_tool_effects_parent_class)->finalize (object);
+}
+
+
+static void
+gth_file_tool_effects_class_init (GthFileToolEffectsClass *klass)
+{
+       GObjectClass                *gobject_class;
+       GthFileToolClass            *file_tool_class;
+       GthImageViewerPageToolClass *image_viewer_page_tool_class;
+
+       g_type_class_add_private (klass, sizeof (GthFileToolEffectsPrivate));
+
+       gobject_class = (GObjectClass*) klass;
+       gobject_class->finalize = gth_file_tool_effects_finalize;
+
+       file_tool_class = GTH_FILE_TOOL_CLASS (klass);
+       file_tool_class->get_options = gth_file_tool_effects_get_options;
+       file_tool_class->destroy_options = gth_file_tool_effects_destroy_options;
+       file_tool_class->apply_options = gth_file_tool_effects_apply_options;
+
+       image_viewer_page_tool_class = (GthImageViewerPageToolClass *) klass;
+       image_viewer_page_tool_class->reset_image = gth_file_tool_effects_reset_image;
+}
+
+
+static void
+gth_file_tool_effects_init (GthFileToolEffects *self)
+{
+       self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GTH_TYPE_FILE_TOOL_EFFECTS, 
GthFileToolEffectsPrivate);
+       self->priv->preview = NULL;
+       self->priv->destination = NULL;
+       self->priv->builder = NULL;
+       self->priv->method = GTH_FILTER_GRID_NO_FILTER;
+       self->priv->last_applied_method = GTH_FILTER_GRID_NO_FILTER;
+       self->priv->view_original = FALSE;
+
+       gth_file_tool_construct (GTH_FILE_TOOL (self),
+                                "special-effects-symbolic",
+                                _("Special Effects"),
+                                GTH_TOOLBOX_SECTION_COLORS);
+}
+
+
+/* -- Warmer -- */
+
+
+static gpointer
+warmer_exec (GthAsyncTask *task,
+            gpointer      user_data)
+{
+       cairo_surface_t *original;
+       cairo_surface_t *source;
+       GthCurve        *curve[GTH_HISTOGRAM_N_CHANNELS];
+       gboolean         cancelled = FALSE;
+
+       original = gth_image_task_get_source_surface (GTH_IMAGE_TASK (task));
+       source = _cairo_image_surface_copy (original);
+
+       curve[GTH_HISTOGRAM_CHANNEL_VALUE] = gth_curve_new_for_points (GTH_TYPE_BEZIER, 0);
+       curve[GTH_HISTOGRAM_CHANNEL_RED] = gth_curve_new_for_points (GTH_TYPE_BEZIER, 3, 0,0, 117,136, 
255,255);
+       curve[GTH_HISTOGRAM_CHANNEL_GREEN] = gth_curve_new_for_points (GTH_TYPE_BEZIER, 0);
+       curve[GTH_HISTOGRAM_CHANNEL_BLUE] = gth_curve_new_for_points (GTH_TYPE_BEZIER, 3, 0,0, 136,119, 
255,255);
+       if (cairo_image_surface_apply_curves (source, curve, task))
+               gth_image_task_set_destination_surface (GTH_IMAGE_TASK (task), source);
+
+       g_object_unref (curve[GTH_HISTOGRAM_CHANNEL_BLUE]);
+       g_object_unref (curve[GTH_HISTOGRAM_CHANNEL_GREEN]);
+       g_object_unref (curve[GTH_HISTOGRAM_CHANNEL_RED]);
+       g_object_unref (curve[GTH_HISTOGRAM_CHANNEL_VALUE]);
+       cairo_surface_destroy (source);
+       cairo_surface_destroy (original);
+
+       return NULL;
+}
+
+
+void
+warmer_add_to_special_effects (GthFilterGrid *grid)
+{
+       gth_filter_grid_add_filter (grid,
+                                   GTH_FILTER_GRID_NEW_FILTER_ID,
+                                   gth_image_task_new (_("Applying changes"), NULL, warmer_exec, NULL, NULL, 
NULL),
+                                   /* Translators: this is the name of a filter that produces warmer colors 
*/
+                                   _("Warmer"),
+                                   NULL);
+}
+
+
+/* -- Cooler -- */
+
+
+static gpointer
+cooler_exec (GthAsyncTask *task,
+            gpointer      user_data)
+{
+       cairo_surface_t *original;
+       cairo_surface_t *source;
+       GthCurve        *curve[GTH_HISTOGRAM_N_CHANNELS];
+       gboolean         cancelled = FALSE;
+
+       original = gth_image_task_get_source_surface (GTH_IMAGE_TASK (task));
+       source = _cairo_image_surface_copy (original);
+
+       curve[GTH_HISTOGRAM_CHANNEL_VALUE] = gth_curve_new_for_points (GTH_TYPE_BEZIER, 0);
+       curve[GTH_HISTOGRAM_CHANNEL_RED] = gth_curve_new_for_points (GTH_TYPE_BEZIER, 3, 0,0, 136,119, 
255,255);
+       curve[GTH_HISTOGRAM_CHANNEL_GREEN] = gth_curve_new_for_points (GTH_TYPE_BEZIER, 0);
+       curve[GTH_HISTOGRAM_CHANNEL_BLUE] = gth_curve_new_for_points (GTH_TYPE_BEZIER, 3, 0,0, 117,136, 
255,255);
+       if (cairo_image_surface_apply_curves (source, curve, task))
+               gth_image_task_set_destination_surface (GTH_IMAGE_TASK (task), source);
+
+       g_object_unref (curve[GTH_HISTOGRAM_CHANNEL_BLUE]);
+       g_object_unref (curve[GTH_HISTOGRAM_CHANNEL_GREEN]);
+       g_object_unref (curve[GTH_HISTOGRAM_CHANNEL_RED]);
+       g_object_unref (curve[GTH_HISTOGRAM_CHANNEL_VALUE]);
+       cairo_surface_destroy (source);
+       cairo_surface_destroy (original);
+
+       return NULL;
+}
+
+
+void
+cooler_add_to_special_effects (GthFilterGrid *grid)
+{
+       gth_filter_grid_add_filter (grid,
+                                   GTH_FILTER_GRID_NEW_FILTER_ID,
+                                   gth_image_task_new (_("Applying changes"), NULL, cooler_exec, NULL, NULL, 
NULL),
+                                   /* Translators: this is the name of a filter that produces cooler colors 
*/
+                                   _("Cooler"),
+                                   NULL);
+}
+
+
+/* -- Instagram -- */
+
+
+static gpointer
+instagram_exec (GthAsyncTask *task,
+               gpointer      user_data)
+{
+       cairo_surface_t *original;
+       cairo_surface_t *source;
+       GthCurve        *curve[GTH_HISTOGRAM_N_CHANNELS];
+       gboolean         cancelled = FALSE;
+
+       original = gth_image_task_get_source_surface (GTH_IMAGE_TASK (task));
+       source = _cairo_image_surface_copy (original);
+
+       curve[GTH_HISTOGRAM_CHANNEL_VALUE] = gth_curve_new_for_points (GTH_TYPE_BEZIER, 0);
+       curve[GTH_HISTOGRAM_CHANNEL_RED] = gth_curve_new_for_points (GTH_TYPE_BEZIER, 4, 0,0, 75,83, 198,185, 
255,255);
+       curve[GTH_HISTOGRAM_CHANNEL_GREEN] = gth_curve_new_for_points (GTH_TYPE_BEZIER, 4, 0,0, 70,63, 
184,189, 255,255);
+       curve[GTH_HISTOGRAM_CHANNEL_BLUE] = gth_curve_new_for_points (GTH_TYPE_BEZIER, 4, 0,0, 76,74, 
191,176, 255,255);
+       cancelled = ! cairo_image_surface_apply_curves (source, curve, task);
+       if (! cancelled) {
+               cancelled = ! cairo_image_surface_apply_vignette (source, NULL, task);
+               if (! cancelled)
+                       gth_image_task_set_destination_surface (GTH_IMAGE_TASK (task), source);
+       }
+
+       g_object_unref (curve[GTH_HISTOGRAM_CHANNEL_BLUE]);
+       g_object_unref (curve[GTH_HISTOGRAM_CHANNEL_GREEN]);
+       g_object_unref (curve[GTH_HISTOGRAM_CHANNEL_RED]);
+       g_object_unref (curve[GTH_HISTOGRAM_CHANNEL_VALUE]);
+       cairo_surface_destroy (source);
+       cairo_surface_destroy (original);
+
+       return NULL;
+}
+
+
+void
+instagram_add_to_special_effects (GthFilterGrid *grid)
+{
+       gth_filter_grid_add_filter (grid,
+                                   GTH_FILTER_GRID_NEW_FILTER_ID,
+                                   gth_image_task_new (_("Applying changes"), NULL, instagram_exec, NULL, 
NULL, NULL),
+                                   _("Instagram"),
+                                   NULL);
+}
+
+
+/* -- Cherry -- */
+
+
+static gpointer
+cherry_exec (GthAsyncTask *task,
+            gpointer      user_data)
+{
+       cairo_surface_t *original;
+       cairo_surface_t *source;
+       GthCurve        *curve[GTH_HISTOGRAM_N_CHANNELS];
+       gboolean         cancelled = FALSE;
+
+       original = gth_image_task_get_source_surface (GTH_IMAGE_TASK (task));
+       source = _cairo_image_surface_copy (original);
+
+       curve[GTH_HISTOGRAM_CHANNEL_VALUE] = gth_curve_new_for_points (GTH_TYPE_BEZIER, 0);
+       curve[GTH_HISTOGRAM_CHANNEL_RED] = gth_curve_new_for_points (GTH_TYPE_BEZIER, 5, 0,12, 74,79, 
134,156, 188,209, 239,255);
+       curve[GTH_HISTOGRAM_CHANNEL_GREEN] = gth_curve_new_for_points (GTH_TYPE_BEZIER, 5, 12,0, 78,67, 
138,140, 189,189, 252,233);
+       curve[GTH_HISTOGRAM_CHANNEL_BLUE] = gth_curve_new_for_points (GTH_TYPE_BEZIER, 5, 0,8, 77,100, 
139,140, 202,186, 255,244);
+       if (cairo_image_surface_apply_curves (source, curve, task)
+           && cairo_image_surface_apply_vignette (source, NULL, task))
+       {
+               gth_image_task_set_destination_surface (GTH_IMAGE_TASK (task), source);
+       }
+
+       g_object_unref (curve[GTH_HISTOGRAM_CHANNEL_BLUE]);
+       g_object_unref (curve[GTH_HISTOGRAM_CHANNEL_GREEN]);
+       g_object_unref (curve[GTH_HISTOGRAM_CHANNEL_RED]);
+       g_object_unref (curve[GTH_HISTOGRAM_CHANNEL_VALUE]);
+       cairo_surface_destroy (source);
+       cairo_surface_destroy (original);
+
+       return NULL;
+}
+
+
+void
+cherry_add_to_special_effects (GthFilterGrid *grid)
+{
+       gth_filter_grid_add_filter (grid,
+                                   GTH_FILTER_GRID_NEW_FILTER_ID,
+                                   gth_image_task_new (_("Applying changes"), NULL, cherry_exec, NULL, NULL, 
NULL),
+                                   _("Cherry"),
+                                   NULL);
+}
+
+
+/* -- Fresh Blue -- */
+
+
+static gpointer
+grapes_exec (GthAsyncTask *task,
+                gpointer      user_data)
+{
+       cairo_surface_t *original;
+       cairo_surface_t *source;
+       GthCurve        *curve[GTH_HISTOGRAM_N_CHANNELS];
+       gboolean         cancelled = FALSE;
+
+       original = gth_image_task_get_source_surface (GTH_IMAGE_TASK (task));
+       source = _cairo_image_surface_copy (original);
+
+       /*
+       curve[GTH_HISTOGRAM_CHANNEL_VALUE] = gth_curve_new_for_points (GTH_TYPE_BEZIER, 3, 0,0, 126,138, 
255,255);
+       curve[GTH_HISTOGRAM_CHANNEL_RED] = gth_curve_new_for_points (GTH_TYPE_BEZIER, 3, 78,41, 145,142, 
230,233);
+       curve[GTH_HISTOGRAM_CHANNEL_GREEN] = gth_curve_new_for_points (GTH_TYPE_BEZIER, 4, 0,4, 40,34, 
149,145, 255,255);
+       curve[GTH_HISTOGRAM_CHANNEL_BLUE] = gth_curve_new_for_points (GTH_TYPE_BEZIER, 0);
+       */
+
+       /*
+       curve[GTH_HISTOGRAM_CHANNEL_VALUE] = gth_curve_new_for_points (GTH_TYPE_BEZIER, 3, 0,0, 126,137, 
255,255);
+       curve[GTH_HISTOGRAM_CHANNEL_RED] = gth_curve_new_for_points (GTH_TYPE_BEZIER, 4, 0,8, 47,32, 207,223, 
255,244);
+       curve[GTH_HISTOGRAM_CHANNEL_GREEN] = gth_curve_new_for_points (GTH_TYPE_BEZIER, 4, 0,4, 40,34, 
149,145, 255,255);
+       curve[GTH_HISTOGRAM_CHANNEL_BLUE] = gth_curve_new_for_points (GTH_TYPE_BEZIER, 3, 0,0, 119,137, 
255,255);
+       */
+
+       curve[GTH_HISTOGRAM_CHANNEL_VALUE] = gth_curve_new_for_points (GTH_TYPE_BEZIER, 0);
+       curve[GTH_HISTOGRAM_CHANNEL_RED] = gth_curve_new_for_points (GTH_TYPE_BEZIER, 5, 0,8, 77,100, 
139,140, 202,186, 255,244);
+       curve[GTH_HISTOGRAM_CHANNEL_GREEN] = gth_curve_new_for_points (GTH_TYPE_BEZIER, 5, 12,0, 78,67, 
138,140, 189,189, 252,233);
+       curve[GTH_HISTOGRAM_CHANNEL_BLUE] = gth_curve_new_for_points (GTH_TYPE_BEZIER, 5, 0,12, 74,79, 
134,156, 188,209, 239,255);
+
+       if (cairo_image_surface_apply_curves (source, curve, task)
+           && cairo_image_surface_apply_vignette (source, NULL, task))
+       {
+               gth_image_task_set_destination_surface (GTH_IMAGE_TASK (task), source);
+       }
+
+       g_object_unref (curve[GTH_HISTOGRAM_CHANNEL_BLUE]);
+       g_object_unref (curve[GTH_HISTOGRAM_CHANNEL_GREEN]);
+       g_object_unref (curve[GTH_HISTOGRAM_CHANNEL_RED]);
+       g_object_unref (curve[GTH_HISTOGRAM_CHANNEL_VALUE]);
+       cairo_surface_destroy (source);
+       cairo_surface_destroy (original);
+
+       return NULL;
+}
+
+
+void
+grapes_add_to_special_effects (GthFilterGrid *grid)
+{
+       gth_filter_grid_add_filter (grid,
+                                   GTH_FILTER_GRID_NEW_FILTER_ID,
+                                   gth_image_task_new (_("Applying changes"), NULL, grapes_exec, NULL, NULL, 
NULL),
+                                   _("Grapes"),
+                                   NULL);
+}
+
+
+/**/
+
+
+static gpointer
+vintage_exec (GthAsyncTask *task,
+        gpointer      user_data)
+{
+       cairo_surface_t *original;
+       cairo_surface_t *source;
+       GthCurve        *curve[GTH_HISTOGRAM_N_CHANNELS];
+       gboolean         cancelled = FALSE;
+
+       original = gth_image_task_get_source_surface (GTH_IMAGE_TASK (task));
+       source = _cairo_image_surface_copy (original);
+
+       curve[GTH_HISTOGRAM_CHANNEL_VALUE] = gth_curve_new_for_points (GTH_TYPE_BEZIER, 3, 0,0, 76,173, 
255,255);
+       curve[GTH_HISTOGRAM_CHANNEL_RED] = gth_curve_new_for_points (GTH_TYPE_BEZIER, 0);
+       curve[GTH_HISTOGRAM_CHANNEL_GREEN] = gth_curve_new_for_points (GTH_TYPE_BEZIER, 0);
+       curve[GTH_HISTOGRAM_CHANNEL_BLUE] = gth_curve_new_for_points (GTH_TYPE_BEZIER, 0);
+
+       if (cairo_image_surface_colorize (source, 112, 66, 20, 255, task)
+           && cairo_image_surface_apply_bcs (source, 0, -20 / 100, -20 / 100, task)
+           && cairo_image_surface_apply_vignette (source, curve, task))
+       {
+                       gth_image_task_set_destination_surface (GTH_IMAGE_TASK (task), source);
+       }
+
+       g_object_unref (curve[GTH_HISTOGRAM_CHANNEL_BLUE]);
+       g_object_unref (curve[GTH_HISTOGRAM_CHANNEL_GREEN]);
+       g_object_unref (curve[GTH_HISTOGRAM_CHANNEL_RED]);
+       g_object_unref (curve[GTH_HISTOGRAM_CHANNEL_VALUE]);
+       cairo_surface_destroy (source);
+       cairo_surface_destroy (original);
+
+       return NULL;
+}
+
+
+void
+vintage_add_to_special_effects (GthFilterGrid *grid)
+{
+       gth_filter_grid_add_filter (grid,
+                                   GTH_FILTER_GRID_NEW_FILTER_ID,
+                                   gth_image_task_new (_("Applying changes"), NULL, vintage_exec, NULL, 
NULL, NULL),
+                                   _("Vintage"),
+                                   NULL);
+}
diff --git a/extensions/file_tools/gth-file-tool-effects.h b/extensions/file_tools/gth-file-tool-effects.h
new file mode 100644
index 0000000..2b60678
--- /dev/null
+++ b/extensions/file_tools/gth-file-tool-effects.h
@@ -0,0 +1,61 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2014 Free Software Foundation, Inc.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GTH_FILE_TOOL_EFFECTS_H
+#define GTH_FILE_TOOL_EFFECTS_H
+
+#include <gthumb.h>
+#include <extensions/image_viewer/image-viewer.h>
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_FILE_TOOL_EFFECTS (gth_file_tool_effects_get_type ())
+#define GTH_FILE_TOOL_EFFECTS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_FILE_TOOL_EFFECTS, 
GthFileToolEffects))
+#define GTH_FILE_TOOL_EFFECTS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_FILE_TOOL_EFFECTS, 
GthFileToolEffectsClass))
+#define GTH_IS_FILE_TOOL_EFFECTS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_FILE_TOOL_EFFECTS))
+#define GTH_IS_FILE_TOOL_EFFECTS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_FILE_TOOL_EFFECTS))
+#define GTH_FILE_TOOL_EFFECTS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTH_TYPE_FILE_TOOL_EFFECTS, 
GthFileToolEffectsClass))
+
+typedef struct _GthFileToolEffects GthFileToolEffects;
+typedef struct _GthFileToolEffectsClass GthFileToolEffectsClass;
+typedef struct _GthFileToolEffectsPrivate GthFileToolEffectsPrivate;
+
+struct _GthFileToolEffects {
+       GthImageViewerPageTool parent_instance;
+       GthFileToolEffectsPrivate *priv;
+};
+
+struct _GthFileToolEffectsClass {
+       GthImageViewerPageToolClass parent_class;
+};
+
+GType  gth_file_tool_effects_get_type  (void);
+
+void warmer_add_to_special_effects     (GthFilterGrid *);
+void cooler_add_to_special_effects     (GthFilterGrid *);
+void instagram_add_to_special_effects  (GthFilterGrid *);
+void cherry_add_to_special_effects     (GthFilterGrid *);
+void grapes_add_to_special_effects     (GthFilterGrid *);
+void vintage_add_to_special_effects    (GthFilterGrid *);
+
+G_END_DECLS
+
+#endif /* GTH_FILE_TOOL_EFFECTS_H */
diff --git a/extensions/file_tools/main.c b/extensions/file_tools/main.c
index 0051fa9..cbfc0fd 100644
--- a/extensions/file_tools/main.c
+++ b/extensions/file_tools/main.c
@@ -28,6 +28,7 @@
 #include "gth-file-tool-adjust-contrast.h"
 #include "gth-file-tool-crop.h"
 #include "gth-file-tool-curves.h"
+#include "gth-file-tool-effects.h"
 #include "gth-file-tool-flip.h"
 #include "gth-file-tool-grayscale.h"
 #include "gth-file-tool-mirror.h"
@@ -57,6 +58,7 @@ gthumb_extension_activate (void)
        gth_main_register_type ("file-tools", GTH_TYPE_FILE_TOOL_GRAYSCALE);
        gth_main_register_type ("file-tools", GTH_TYPE_FILE_TOOL_CURVES);
        gth_main_register_type ("file-tools", GTH_TYPE_FILE_TOOL_NEGATIVE);
+       gth_main_register_type ("file-tools", GTH_TYPE_FILE_TOOL_EFFECTS);
 
        gth_main_register_type ("file-tools", GTH_TYPE_FILE_TOOL_ROTATE_LEFT);
        gth_main_register_type ("file-tools", GTH_TYPE_FILE_TOOL_ROTATE_RIGHT);
@@ -68,6 +70,20 @@ gthumb_extension_activate (void)
        gth_main_register_type ("file-tools", GTH_TYPE_FILE_TOOL_CROP);
 
        gth_hook_add_callback ("gth-browser-file-list-key-press", 10, G_CALLBACK 
(file_tools__gth_browser_file_list_key_press_cb), NULL);
+
+       /**
+        * Add a filter to the filter list shown in the Effects tool
+        *
+        * @filter_grid (GthFilterGrid*): the filter grid to add the effect to.
+        **/
+       gth_hook_register ("add-special-effect", 1);
+
+       gth_hook_add_callback ("add-special-effect", 10, G_CALLBACK (warmer_add_to_special_effects), NULL);
+       gth_hook_add_callback ("add-special-effect", 20, G_CALLBACK (cooler_add_to_special_effects), NULL);
+       gth_hook_add_callback ("add-special-effect", 30, G_CALLBACK (cherry_add_to_special_effects), NULL);
+       gth_hook_add_callback ("add-special-effect", 40, G_CALLBACK (grapes_add_to_special_effects), NULL);
+       gth_hook_add_callback ("add-special-effect", 50, G_CALLBACK (instagram_add_to_special_effects), NULL);
+       gth_hook_add_callback ("add-special-effect", 70, G_CALLBACK (vintage_add_to_special_effects), NULL);
 }
 
 
diff --git a/gthumb/Makefile.am b/gthumb/Makefile.am
index 19b880a..025f023 100644
--- a/gthumb/Makefile.am
+++ b/gthumb/Makefile.am
@@ -26,6 +26,7 @@ PUBLIC_HEADER_FILES =                                         \
        color-utils.h                                   \
        dom.h                                           \
        gfixed.h                                        \
+       gimp-op.h                                       \
        gio-utils.h                                     \
        glib-utils.h                                    \
        gnome-desktop-thumbnail.h                       \
@@ -164,6 +165,7 @@ gthumb_SOURCES =                                    \
        dlg-preferences-general.c                       \
        dlg-sort-order.c                                \
        dom.c                                           \
+       gimp-op.c                                       \
        gio-utils.c                                     \
        glib-utils.c                                    \
        gsignature.c                                    \
diff --git a/gthumb/gimp-op.c b/gthumb/gimp-op.c
new file mode 100644
index 0000000..6cabe1f
--- /dev/null
+++ b/gthumb/gimp-op.c
@@ -0,0 +1,57 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2014 Free Software Foundation, Inc.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include "gimp-op.h"
+
+
+guchar add_alpha_table[256][256];
+static GOnce  gimp_op_init_once = G_ONCE_INIT;
+
+
+static gpointer
+init_tables (gpointer data)
+{
+       int v;
+       int a;
+       int r;
+
+       /* add_alpha_table[v][a] = v * a / 255 */
+
+       for (v = 0; v < 128; v++) {
+               for (a = 0; a <= v; a++) {
+                       r = (v * a + 127) / 255;
+                       add_alpha_table[v][a] = add_alpha_table[a][v] = r;
+                       add_alpha_table[255-v][a] = add_alpha_table[a][255-v] = a - r;
+                       add_alpha_table[v][255-a] = add_alpha_table[255-a][v] = v - r;
+                       add_alpha_table[255-v][255-a] = add_alpha_table[255-a][255-v] = (255 - a) - (v - r);
+               }
+       }
+
+       return NULL;
+}
+
+
+void
+gimp_op_init (void)
+{
+       g_once (&gimp_op_init_once, init_tables, NULL);
+}
diff --git a/gthumb/gimp-op.h b/gthumb/gimp-op.h
new file mode 100644
index 0000000..699437d
--- /dev/null
+++ b/gthumb/gimp-op.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2013 Free Software Foundation, Inc.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GIMP_OP_H
+#define GIMP_OP_H
+
+#include <config.h>
+#include <glib.h>
+
+/* Optimizations taken from xcftools 1.0.7 written by Henning Makholm
+ *
+ * xL : Layer color
+ * xI : Image color
+ * aL : Layer alpha
+ * */
+
+#define ADD_ALPHA(v, a)                        (add_alpha_table[v][a])
+#define CLAMP_TEMP(x, min, max)                (temp = (x), CLAMP (temp, min, max))
+#define ABS_TEMP2(x)                   (temp2 = (x), (temp2 < 0) ? -temp2: temp2)
+#define CLAMP_PIXEL(x)                 CLAMP_TEMP (x, 0, 255)
+#define GIMP_OP_NORMAL(xL, xI, aL)     CLAMP_PIXEL (ADD_ALPHA (xL, aL) + ADD_ALPHA (xI, 255 - aL))
+#define GIMP_OP_LIGHTEN_ONLY(xL, xI)   MAX (xI, xL)
+#define GIMP_OP_SCREEN(xL, xI)         CLAMP_PIXEL (255 ^ ADD_ALPHA (255 - xI, 255 - xL))
+#define GIMP_OP_DODGE(xL, xI)          GIMP_OP_DIVIDE (255-xL, xI)
+#define GIMP_OP_ADDITION(xL, xI)       CLAMP_PIXEL (xI + xL)
+#define GIMP_OP_DARKEN_ONLY(xL, xI)    MIN (xI, xL)
+#define GIMP_OP_MULTIPLY(xL, xI)       CLAMP_PIXEL (ADD_ALPHA (xL, xI))
+#define GIMP_OP_BURN(xL, xI)           CLAMP_PIXEL (255 - GIMP_OP_DIVIDE (xL, 255 - xI))
+#define GIMP_OP_SOFT_LIGHT(xL, xI)     CLAMP_PIXEL (ADD_ALPHA (xI, xI) + 2 * ADD_ALPHA (xL, ADD_ALPHA (xI, 
255 - xI)))
+#define GIMP_OP_HARD_LIGHT(xL, xI)     CLAMP_PIXEL (xL > 128 ? 255 ^ ADD_ALPHA (255 - xI, 2 * (255 - xL)) : 
ADD_ALPHA (xI, 2 * xL))
+#define GIMP_OP_DIFFERENCE(xL, xI)     CLAMP_PIXEL (ABS_TEMP2 (xI - xL))
+#define GIMP_OP_SUBTRACT(xL, xI)       CLAMP_PIXEL (xI - xL)
+#define GIMP_OP_GRAIN_EXTRACT(xL, xI)  CLAMP_PIXEL ((int) xI - xL + 128)
+#define GIMP_OP_GRAIN_MERGE(xL, xI)    CLAMP_PIXEL ((int) xI + xL - 128)
+#define GIMP_OP_DIVIDE(xL, xI)         CLAMP_PIXEL ((int) (xI) * 256 / (1 + (xL)))
+
+extern guchar add_alpha_table[256][256];
+
+void gimp_op_init (void);
+
+#endif /* GIMP_OP_H */


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]