[gthumb: 1/129] added a tool to rotate images of any degree



commit bfad6e812457a67c62ef17206aa83750e1df3486
Author: Paolo Bacchilega <paobac src gnome org>
Date:   Wed Apr 13 11:14:23 2011 +0200

    added a tool to rotate images of any degree
    
    First batch of changes to add a tool to rotate images
    of any degree.
    
    [new feature, bug #627563]

 data/icons/hicolor/16x16/actions/Makefile.am       |    1 +
 data/icons/hicolor/16x16/actions/tool-rotate.png   |  Bin 0 -> 703 bytes
 data/icons/hicolor/22x22/actions/Makefile.am       |    1 +
 data/icons/hicolor/22x22/actions/tool-rotate.png   |  Bin 0 -> 863 bytes
 extensions/file_tools/Makefile.am                  |    6 +
 extensions/file_tools/data/Makefile.am             |    5 +-
 .../data/gthumb_rotate_options.schemas.in          |   17 +
 extensions/file_tools/data/ui/Makefile.am          |    4 +-
 extensions/file_tools/data/ui/rotate-options.ui    |  479 +++++++++++
 extensions/file_tools/gth-file-tool-mirror.c       |    2 +-
 extensions/file_tools/gth-file-tool-resize.c       |    2 +-
 extensions/file_tools/gth-file-tool-rotate-right.c |    2 +-
 extensions/file_tools/gth-file-tool-rotate.c       |  524 ++++++++++++
 extensions/file_tools/gth-file-tool-rotate.h       |   53 ++
 extensions/file_tools/gth-image-rotator.c          |  555 ++++++++++++
 extensions/file_tools/gth-image-rotator.h          |   74 ++
 extensions/file_tools/gth-transform-resize.c       |  890 ++++++++++++++++++++
 extensions/file_tools/gth-transform-resize.h       |   60 ++
 extensions/file_tools/main.c                       |    7 +-
 extensions/file_tools/preferences.h                |    2 +
 gthumb/cairo-utils.c                               |  118 +++-
 gthumb/cairo-utils.h                               |    4 +
 gthumb/gtk-utils.c                                 |    3 +-
 23 files changed, 2779 insertions(+), 30 deletions(-)
---
diff --git a/data/icons/hicolor/16x16/actions/Makefile.am b/data/icons/hicolor/16x16/actions/Makefile.am
index 8ee5cd0..f7cb81a 100644
--- a/data/icons/hicolor/16x16/actions/Makefile.am
+++ b/data/icons/hicolor/16x16/actions/Makefile.am
@@ -26,6 +26,7 @@ icons_DATA = 				\
 	tool-mirror.png			\
 	tool-red-eye.png		\
 	tool-resize.png			\
+	tool-rotate.png			\
 	tool-rotate-270.png		\
 	tool-rotate-90.png		\
 	tool-sharpen.png
diff --git a/data/icons/hicolor/16x16/actions/tool-rotate.png b/data/icons/hicolor/16x16/actions/tool-rotate.png
new file mode 100644
index 0000000..60ab412
Binary files /dev/null and b/data/icons/hicolor/16x16/actions/tool-rotate.png differ
diff --git a/data/icons/hicolor/22x22/actions/Makefile.am b/data/icons/hicolor/22x22/actions/Makefile.am
index ee56140..253bb44 100644
--- a/data/icons/hicolor/22x22/actions/Makefile.am
+++ b/data/icons/hicolor/22x22/actions/Makefile.am
@@ -19,6 +19,7 @@ icons_DATA = 			\
 	tool-enhance.png	\
 	tool-red-eye.png	\
 	tool-resize.png		\
+	tool-rotate.png		\
 	tool-sharpen.png	\
 	zoom-fit-width.png
 
diff --git a/data/icons/hicolor/22x22/actions/tool-rotate.png b/data/icons/hicolor/22x22/actions/tool-rotate.png
new file mode 100644
index 0000000..38dae1f
Binary files /dev/null and b/data/icons/hicolor/22x22/actions/tool-rotate.png differ
diff --git a/extensions/file_tools/Makefile.am b/extensions/file_tools/Makefile.am
index c09df38..6625eb8 100644
--- a/extensions/file_tools/Makefile.am
+++ b/extensions/file_tools/Makefile.am
@@ -19,12 +19,15 @@ HEADER_FILES = 				\
 	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-save.h		\
 	gth-file-tool-save-as.h		\
 	gth-file-tool-sharpen.h		\
 	gth-file-tool-undo.h		\
+	gth-image-rotator.h		\
+	gth-transform-resize.h		\
 	preferences.h
 
 enum-types.h: $(HEADER_FILES) $(GLIB_MKENUMS)
@@ -64,12 +67,15 @@ libfile_tools_la_SOURCES = 		\
 	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-rotator.c		\
+	gth-transform-resize.c		\
 	main.c
 
 libfile_tools_la_CFLAGS = $(GTHUMB_CFLAGS) -I$(top_srcdir) -I$(top_builddir)/gthumb 
diff --git a/extensions/file_tools/data/Makefile.am b/extensions/file_tools/data/Makefile.am
index f4f2f1e..8b2a4ff 100644
--- a/extensions/file_tools/data/Makefile.am
+++ b/extensions/file_tools/data/Makefile.am
@@ -1,7 +1,10 @@
 SUBDIRS = ui
 
 schemadir = @GCONF_SCHEMA_FILE_DIR@
-schema_in_files = gthumb_crop_options.schemas.in gthumb_resize_options.schemas.in
+schema_in_files = 				\
+	gthumb_crop_options.schemas.in		\
+	gthumb_resize_options.schemas.in	\
+	gthumb_rotate_options.schemas.in
 schema_DATA = $(schema_in_files:.schemas.in=.schemas)
 
 @INTLTOOL_SCHEMAS_RULE@
diff --git a/extensions/file_tools/data/gthumb_rotate_options.schemas.in b/extensions/file_tools/data/gthumb_rotate_options.schemas.in
new file mode 100644
index 0000000..ed9c164
--- /dev/null
+++ b/extensions/file_tools/data/gthumb_rotate_options.schemas.in
@@ -0,0 +1,17 @@
+<gconfschemafile>
+    <schemalist>
+
+      <schema>
+	<key>/schemas/apps/gthumb/ext/resize/unit</key>
+	<applyto>/apps/gthumb/ext/resize/unit</applyto>
+	<owner>gthumb</owner>
+	<type>string</type>
+	<default>percentage</default>
+	<locale name="C">
+	  <short></short>
+	  <long></long>
+	</locale>
+      </schema>
+
+    </schemalist>
+</gconfschemafile>
diff --git a/extensions/file_tools/data/ui/Makefile.am b/extensions/file_tools/data/ui/Makefile.am
index 12e0d50..cc7927b 100644
--- a/extensions/file_tools/data/ui/Makefile.am
+++ b/extensions/file_tools/data/ui/Makefile.am
@@ -2,7 +2,9 @@ uidir = $(pkgdatadir)/ui
 ui_DATA = 				\
 	adjust-colors-options.ui 	\
 	crop-options.ui			\
-	resize-options.ui
+	resize-options.ui		\
+	rotate-options.ui		\
+	sharpen-options.ui
 EXTRA_DIST = $(ui_DATA)
 
 -include $(top_srcdir)/git.mk
diff --git a/extensions/file_tools/data/ui/rotate-options.ui b/extensions/file_tools/data/ui/rotate-options.ui
new file mode 100644
index 0000000..152df48
--- /dev/null
+++ b/extensions/file_tools/data/ui/rotate-options.ui
@@ -0,0 +1,479 @@
+<?xml version="1.0"?>
+<interface>
+  <requires lib="gtk+" version="2.16"/>
+  <!-- interface-naming-policy project-wide -->
+  <object class="GtkAlignment" id="options">
+    <property name="visible">True</property>
+    <property name="top_padding">6</property>
+    <child>
+      <object class="GtkVBox" id="vbox2">
+        <property name="visible">True</property>
+        <child>
+          <object class="GtkVBox" id="vbox1">
+            <property name="visible">True</property>
+            <property name="spacing">12</property>
+            <child>
+              <object class="GtkFrame" id="frame2">
+                <property name="visible">True</property>
+                <property name="label_xalign">0</property>
+                <property name="shadow_type">none</property>
+                <child>
+                  <object class="GtkAlignment" id="alignment2">
+                    <property name="visible">True</property>
+                    <property name="top_padding">6</property>
+                    <property name="left_padding">12</property>
+                    <child>
+                      <object class="GtkVBox" id="vbox4">
+                        <property name="visible">True</property>
+                        <property name="spacing">6</property>
+                        <child>
+                          <object class="GtkLabel" id="angle_label">
+                            <property name="visible">True</property>
+                            <property name="xalign">0</property>
+                            <property name="label">45&#xB0;</property>
+                            <attributes>
+                              <attribute name="size" value="24000"/>
+                            </attributes>
+                          </object>
+                          <packing>
+                            <property name="position">0</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkHBox" id="angle_box">
+                            <property name="visible">True</property>
+                            <child>
+                              <placeholder/>
+                            </child>
+                          </object>
+                          <packing>
+                            <property name="position">1</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkCheckButton" id="step_checkbutton">
+                            <property name="label" translatable="yes">5 degrees</property>
+                            <property name="can_focus">True</property>
+                            <property name="receives_default">False</property>
+                            <property name="draw_indicator">True</property>
+                          </object>
+                          <packing>
+                            <property name="position">2</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkHBox" id="small_angle_box">
+                            <property name="visible">True</property>
+                            <child>
+                              <placeholder/>
+                            </child>
+                          </object>
+                          <packing>
+                            <property name="position">3</property>
+                          </packing>
+                        </child>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+                <child type="label">
+                  <object class="GtkLabel" id="label1">
+                    <property name="visible">True</property>
+                    <property name="label" translatable="yes">Angle</property>
+                    <property name="use_markup">True</property>
+                    <attributes>
+                      <attribute name="weight" value="bold"/>
+                    </attributes>
+                  </object>
+                </child>
+              </object>
+              <packing>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkFrame" id="frame4">
+                <property name="label_xalign">0</property>
+                <property name="shadow_type">none</property>
+                <child>
+                  <object class="GtkAlignment" id="alignment4">
+                    <property name="visible">True</property>
+                    <property name="top_padding">6</property>
+                    <property name="left_padding">12</property>
+                    <child>
+                      <object class="GtkTable" id="table3">
+                        <property name="visible">True</property>
+                        <property name="n_rows">2</property>
+                        <property name="column_spacing">6</property>
+                        <property name="row_spacing">6</property>
+                        <child>
+                          <object class="GtkHBox" id="hbox4">
+                            <property name="visible">True</property>
+                            <property name="spacing">6</property>
+                            <child>
+                              <object class="GtkSpinButton" id="center_x_spinbutton">
+                                <property name="visible">True</property>
+                                <property name="can_focus">True</property>
+                                <property name="invisible_char">&#x25CF;</property>
+                                <property name="width_chars">6</property>
+                                <property name="adjustment">center_x_adjustment</property>
+                                <property name="climb_rate">1</property>
+                                <property name="numeric">True</property>
+                                <property name="update_policy">if-valid</property>
+                              </object>
+                              <packing>
+                                <property name="expand">False</property>
+                                <property name="fill">False</property>
+                                <property name="position">0</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkComboBox" id="unit_combobox">
+                                <property name="visible">True</property>
+                                <property name="model">unit_liststore</property>
+                                <property name="active">0</property>
+                                <child>
+                                  <object class="GtkCellRendererText" id="cellrenderertext3"/>
+                                  <attributes>
+                                    <attribute name="text">1</attribute>
+                                  </attributes>
+                                </child>
+                              </object>
+                              <packing>
+                                <property name="expand">False</property>
+                                <property name="fill">False</property>
+                                <property name="position">1</property>
+                              </packing>
+                            </child>
+                          </object>
+                          <packing>
+                            <property name="x_options">GTK_FILL</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkHBox" id="hbox5">
+                            <property name="visible">True</property>
+                            <property name="spacing">6</property>
+                            <child>
+                              <object class="GtkSpinButton" id="center_y_spinbutton">
+                                <property name="visible">True</property>
+                                <property name="can_focus">True</property>
+                                <property name="invisible_char">&#x25CF;</property>
+                                <property name="width_chars">6</property>
+                                <property name="adjustment">center_y_adjustment</property>
+                                <property name="climb_rate">1</property>
+                                <property name="numeric">True</property>
+                                <property name="update_policy">if-valid</property>
+                              </object>
+                              <packing>
+                                <property name="expand">False</property>
+                                <property name="fill">False</property>
+                                <property name="position">0</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <placeholder/>
+                            </child>
+                          </object>
+                          <packing>
+                            <property name="top_attach">1</property>
+                            <property name="bottom_attach">2</property>
+                            <property name="x_options">GTK_FILL</property>
+                          </packing>
+                        </child>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+                <child type="label">
+                  <object class="GtkLabel" id="label7">
+                    <property name="visible">True</property>
+                    <property name="label" translatable="yes">Center</property>
+                    <property name="use_markup">True</property>
+                    <attributes>
+                      <attribute name="weight" value="bold"/>
+                    </attributes>
+                  </object>
+                </child>
+              </object>
+              <packing>
+                <property name="position">1</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkFrame" id="frame1">
+                <property name="visible">True</property>
+                <property name="label_xalign">0</property>
+                <property name="shadow_type">none</property>
+                <child>
+                  <object class="GtkAlignment" id="alignment1">
+                    <property name="visible">True</property>
+                    <property name="top_padding">6</property>
+                    <property name="left_padding">12</property>
+                    <child>
+                      <object class="GtkComboBox" id="size_combobox">
+                        <property name="visible">True</property>
+                        <property name="model">size_liststore</property>
+                        <child>
+                          <object class="GtkCellRendererText" id="cellrenderertext2"/>
+                          <attributes>
+                            <attribute name="text">0</attribute>
+                          </attributes>
+                        </child>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+                <child type="label">
+                  <object class="GtkLabel" id="label3">
+                    <property name="visible">True</property>
+                    <property name="label" translatable="yes">Size</property>
+                    <property name="use_markup">True</property>
+                    <attributes>
+                      <attribute name="weight" value="bold"/>
+                    </attributes>
+                  </object>
+                </child>
+              </object>
+              <packing>
+                <property name="position">2</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkFrame" id="frame3">
+                <property name="visible">True</property>
+                <property name="label_xalign">0</property>
+                <property name="shadow_type">none</property>
+                <child>
+                  <object class="GtkAlignment" id="alignment3">
+                    <property name="visible">True</property>
+                    <property name="top_padding">6</property>
+                    <property name="left_padding">12</property>
+                    <child>
+                      <object class="GtkVBox" id="vbox3">
+                        <property name="visible">True</property>
+                        <property name="spacing">6</property>
+                        <child>
+                          <object class="GtkTable" id="table2">
+                            <property name="visible">True</property>
+                            <property name="n_rows">2</property>
+                            <property name="n_columns">2</property>
+                            <property name="column_spacing">6</property>
+                            <property name="row_spacing">6</property>
+                            <child>
+                              <object class="GtkLabel" id="label4">
+                                <property name="xalign">0</property>
+                                <property name="label" translatable="yes">_Background:</property>
+                                <property name="use_underline">True</property>
+                              </object>
+                              <packing>
+                                <property name="top_attach">1</property>
+                                <property name="bottom_attach">2</property>
+                                <property name="x_options">GTK_FILL</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkHBox" id="grid_box">
+                                <property name="visible">True</property>
+                                <property name="spacing">6</property>
+                                <child>
+                                  <placeholder/>
+                                </child>
+                              </object>
+                              <packing>
+                                <property name="left_attach">1</property>
+                                <property name="right_attach">2</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkCheckButton" id="grid_checkbutton">
+                                <property name="label" translatable="yes">_Grid:</property>
+                                <property name="visible">True</property>
+                                <property name="can_focus">True</property>
+                                <property name="receives_default">False</property>
+                                <property name="use_underline">True</property>
+                                <property name="draw_indicator">True</property>
+                              </object>
+                              <packing>
+                                <property name="x_options">GTK_FILL</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkHBox" id="hbox3">
+                                <child>
+                                  <object class="GtkColorButton" id="background_colorbutton">
+                                    <property name="visible">True</property>
+                                    <property name="can_focus">True</property>
+                                    <property name="receives_default">True</property>
+                                    <property name="color">#000000000000</property>
+                                  </object>
+                                  <packing>
+                                    <property name="expand">False</property>
+                                    <property name="position">0</property>
+                                  </packing>
+                                </child>
+                              </object>
+                              <packing>
+                                <property name="left_attach">1</property>
+                                <property name="right_attach">2</property>
+                                <property name="top_attach">1</property>
+                                <property name="bottom_attach">2</property>
+                              </packing>
+                            </child>
+                          </object>
+                          <packing>
+                            <property name="position">0</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                        <child>
+                          <placeholder/>
+                        </child>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+                <child type="label">
+                  <object class="GtkLabel" id="label2">
+                    <property name="visible">True</property>
+                    <property name="label" translatable="yes">Options</property>
+                    <property name="use_markup">True</property>
+                    <attributes>
+                      <attribute name="weight" value="bold"/>
+                    </attributes>
+                  </object>
+                </child>
+              </object>
+              <packing>
+                <property name="position">3</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkHSeparator" id="hseparator1">
+            <property name="visible">True</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="padding">6</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkHButtonBox" id="hbuttonbox1">
+            <property name="visible">True</property>
+            <property name="spacing">12</property>
+            <property name="layout_style">center</property>
+            <child>
+              <object class="GtkButton" id="ok_button">
+                <property name="label">gtk-ok</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="use_underline">True</property>
+                <property name="use_stock">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="cancel_button">
+                <property name="label">gtk-cancel</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="use_stock">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">False</property>
+            <property name="padding">6</property>
+            <property name="position">2</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+  </object>
+  <object class="GtkListStore" id="unit_liststore">
+    <columns>
+      <!-- column-name type -->
+      <column type="gint"/>
+      <!-- column-name name -->
+      <column type="gchararray"/>
+    </columns>
+    <data>
+      <row>
+        <col id="0">0</col>
+        <col id="1" translatable="yes">pixels</col>
+      </row>
+      <row>
+        <col id="0">1</col>
+        <col id="1" translatable="yes">%</col>
+      </row>
+    </data>
+  </object>
+  <object class="GtkAdjustment" id="center_x_adjustment">
+    <property name="lower">1</property>
+    <property name="upper">999999</property>
+    <property name="step_increment">1</property>
+  </object>
+  <object class="GtkAdjustment" id="center_y_adjustment">
+    <property name="lower">1</property>
+    <property name="upper">999999</property>
+    <property name="step_increment">1</property>
+  </object>
+  <object class="GtkListStore" id="grid_liststore">
+    <columns>
+      <!-- column-name name -->
+      <column type="gchararray"/>
+    </columns>
+    <data>
+      <row>
+        <col id="0" translatable="yes">None</col>
+      </row>
+      <row>
+        <col id="0" translatable="yes">Number of lines</col>
+      </row>
+      <row>
+        <col id="0" translatable="yes">Space between lines</col>
+      </row>
+    </data>
+  </object>
+  <object class="GtkListStore" id="size_liststore">
+    <columns>
+      <!-- column-name name -->
+      <column type="gchararray"/>
+    </columns>
+    <data>
+      <row>
+        <col id="0" translatable="yes">Original size</col>
+      </row>
+      <row>
+        <col id="0" translatable="yes">Bounding box</col>
+      </row>
+      <row>
+        <col id="0" translatable="yes">Crop corners</col>
+      </row>
+      <row>
+        <col id="0" translatable="yes">Crop corners keeping ratio</col>
+      </row>
+    </data>
+  </object>
+</interface>
diff --git a/extensions/file_tools/gth-file-tool-mirror.c b/extensions/file_tools/gth-file-tool-mirror.c
index 22f478d..22ad1a1 100644
--- a/extensions/file_tools/gth-file-tool-mirror.c
+++ b/extensions/file_tools/gth-file-tool-mirror.c
@@ -70,7 +70,7 @@ gth_file_tool_mirror_update_sensitivity (GthFileTool *base)
 static void
 gth_file_tool_mirror_instance_init (GthFileToolMirror *self)
 {
-	gth_file_tool_construct (GTH_FILE_TOOL (self), "tool-mirror", _("Mirror"), NULL, TRUE);
+	gth_file_tool_construct (GTH_FILE_TOOL (self), "tool-mirror", _("Mirror"), NULL, FALSE);
 	/*gtk_widget_set_tooltip_text (GTK_WIDGET (self), _("Automatic white balance correction"));*/
 }
 
diff --git a/extensions/file_tools/gth-file-tool-resize.c b/extensions/file_tools/gth-file-tool-resize.c
index 56a2dbb..63f1ca2 100644
--- a/extensions/file_tools/gth-file-tool-resize.c
+++ b/extensions/file_tools/gth-file-tool-resize.c
@@ -546,7 +546,7 @@ static void
 gth_file_tool_resize_instance_init (GthFileToolResize *self)
 {
 	self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GTH_TYPE_FILE_TOOL_RESIZE, GthFileToolResizePrivate);
-	gth_file_tool_construct (GTH_FILE_TOOL (self), "tool-resize", _("Resize..."), _("Resize"), TRUE);
+	gth_file_tool_construct (GTH_FILE_TOOL (self), "tool-resize", _("Resize..."), _("Resize"), FALSE);
 }
 
 
diff --git a/extensions/file_tools/gth-file-tool-rotate-right.c b/extensions/file_tools/gth-file-tool-rotate-right.c
index c81b292..cdf1b4a 100644
--- a/extensions/file_tools/gth-file-tool-rotate-right.c
+++ b/extensions/file_tools/gth-file-tool-rotate-right.c
@@ -70,7 +70,7 @@ gth_file_tool_rotate_right_update_sensitivity (GthFileTool *base)
 static void
 gth_file_tool_rotate_right_instance_init (GthFileToolRotateRight *self)
 {
-	gth_file_tool_construct (GTH_FILE_TOOL (self), "tool-rotate-90", _("Rotate Right"), NULL, FALSE);
+	gth_file_tool_construct (GTH_FILE_TOOL (self), "tool-rotate-90", _("Rotate Right"), NULL, TRUE);
 	/*gtk_widget_set_tooltip_text (GTK_WIDGET (self), _("Automatic white balance correction"));*/
 }
 
diff --git a/extensions/file_tools/gth-file-tool-rotate.c b/extensions/file_tools/gth-file-tool-rotate.c
new file mode 100644
index 0000000..caa57ed
--- /dev/null
+++ b/extensions/file_tools/gth-file-tool-rotate.c
@@ -0,0 +1,524 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2011 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/gth-image-viewer-page.h>
+#include "gth-file-tool-rotate.h"
+#include "gth-image-rotator.h"
+#include "preferences.h"
+
+
+#define GET_WIDGET(x) (_gtk_builder_get_widget (self->priv->builder, (x)))
+#define HIGH_QUALITY_INTERPOLATION GDK_INTERP_HYPER
+#define APPLY_DELAY 50
+#define DEFAULT_GRID 15
+
+
+static gpointer parent_class = NULL;
+
+
+struct _GthFileToolRotatePrivate {
+	GtkBuilder      *builder;
+	GtkAdjustment   *angle_adj;
+	GtkAdjustment   *small_angle_adj;
+	GthImageRotator *rotator;
+	int              pixbuf_width;
+	int              pixbuf_height;
+	GdkPixbuf       *tmp_pixbuf;
+	int              new_width;
+	int              new_height;
+	GthUnit          unit;
+	guint            apply_event;
+	double           step;
+	gboolean         use_grid;
+	GtkAdjustment   *grid_adj;
+};
+
+
+static void
+gth_file_tool_rotate_update_sensitivity (GthFileTool *base)
+{
+	GtkWidget *window;
+	GtkWidget *viewer_page;
+
+	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))
+		gtk_widget_set_sensitive (GTK_WIDGET (base), FALSE);
+	else
+		gtk_widget_set_sensitive (GTK_WIDGET (base), TRUE);
+}
+
+
+static void
+cancel_button_clicked_cb (GtkButton *button,
+			  gpointer   user_data)
+{
+	GthFileToolRotate *self = user_data;
+
+	if (self->priv->apply_event != 0) {
+		g_source_remove (self->priv->apply_event);
+		self->priv->apply_event = 0;
+	}
+
+	gth_file_tool_hide_options (GTH_FILE_TOOL (self));
+}
+
+
+static void
+ok_button_clicked_cb (GtkButton *button,
+		      gpointer   user_data)
+{
+	GthFileToolRotate *self = user_data;
+	GdkPixbuf         *new_pixbuf;
+
+	new_pixbuf = gth_image_rotator_get_result (self->priv->rotator);
+	if (new_pixbuf != 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_pixbuf (GTH_IMAGE_VIEWER_PAGE (viewer_page), new_pixbuf, TRUE);
+		gth_file_tool_hide_options (GTH_FILE_TOOL (self));
+
+		g_object_unref (new_pixbuf);
+	}
+}
+
+
+static void
+center_position_changed_cb (GtkSpinButton *spinbutton,
+			    gpointer       user_data)
+{
+	/*GthFileToolRotate *self = user_data;
+
+	FIXME */
+}
+
+
+static void
+unit_combobox_changed_cb (GtkComboBox *combobox,
+			  gpointer     user_data)
+{
+	/* FIXME GthFileToolRotate *self = user_data;
+
+	g_signal_handlers_block_by_data (GET_WIDGET ("resize_width_spinbutton"), self);
+	g_signal_handlers_block_by_data (GET_WIDGET ("resize_height_spinbutton"), self);
+
+	self->priv->unit = gtk_combo_box_get_active (combobox);
+	if (self->priv->unit == GTH_UNIT_PERCENTAGE) {
+		double p;
+
+		gtk_spin_button_set_digits (GTK_SPIN_BUTTON (GET_WIDGET ("resize_width_spinbutton")), 2);
+		gtk_spin_button_set_digits (GTK_SPIN_BUTTON (GET_WIDGET ("resize_height_spinbutton")), 2);
+
+		p = ((double) self->priv->new_width) / self->priv->pixbuf_width * 100.0;
+		gtk_spin_button_set_value (GTK_SPIN_BUTTON (GET_WIDGET ("resize_width_spinbutton")), p);
+		p = ((double) self->priv->new_height) / self->priv->pixbuf_height * 100.0;
+		gtk_spin_button_set_value (GTK_SPIN_BUTTON (GET_WIDGET ("resize_height_spinbutton")), p);
+	}
+	else if (self->priv->unit == GTH_UNIT_PIXELS) {
+		gtk_spin_button_set_digits (GTK_SPIN_BUTTON (GET_WIDGET ("resize_width_spinbutton")), 0);
+		gtk_spin_button_set_digits (GTK_SPIN_BUTTON (GET_WIDGET ("resize_height_spinbutton")), 0);
+		gtk_spin_button_set_value (GTK_SPIN_BUTTON (GET_WIDGET ("resize_width_spinbutton")), self->priv->new_width);
+		gtk_spin_button_set_value (GTK_SPIN_BUTTON (GET_WIDGET ("resize_height_spinbutton")), self->priv->new_height);
+	}
+
+	g_signal_handlers_unblock_by_data (GET_WIDGET ("resize_width_spinbutton"), self);
+	g_signal_handlers_unblock_by_data (GET_WIDGET ("resize_height_spinbutton"), self);
+
+	selection_width_value_changed_cb (GTK_SPIN_BUTTON (GET_WIDGET ("resize_width_spinbutton")), self);
+	*/
+}
+
+
+static void
+_gth_file_tool_rotate (GthFileToolRotate *self,
+		       double             angle)
+{
+	char *s;
+
+	gth_image_rotator_set_angle (self->priv->rotator, angle);
+	s = g_strdup_printf ("%2.2f°", angle);
+	gtk_label_set_text (GTK_LABEL (GET_WIDGET ("angle_label")), s);
+
+	g_free (s);
+}
+
+
+static gboolean
+apply_small_angle_cb (gpointer user_data)
+{
+	GthFileToolRotate *self = user_data;
+	double             angle;
+
+	self->priv->apply_event = 0;
+
+	angle = gtk_adjustment_get_value (self->priv->angle_adj) + gtk_adjustment_get_value (self->priv->small_angle_adj);
+	_gth_file_tool_rotate (self, angle);
+
+	return FALSE;
+}
+
+
+static void
+small_angle_value_changed_cb (GtkAdjustment *adj,
+			      gpointer       user_data)
+{
+	GthFileToolRotate *self = user_data;
+
+	if (self->priv->apply_event == 0)
+		self->priv->apply_event = g_timeout_add (APPLY_DELAY, apply_small_angle_cb, self);
+}
+
+
+static void
+angle_value_changed_cb (GtkAdjustment *adj,
+			gpointer       user_data)
+{
+	GthFileToolRotate *self = user_data;
+	double             angle;
+
+	if (self->priv->apply_event != 0) {
+		g_source_remove (self->priv->apply_event);
+		self->priv->apply_event = 0;
+	}
+
+	if (self->priv->step != 0) {
+		double angle;
+		double rounded_angle;
+
+		angle = gtk_adjustment_get_value (self->priv->angle_adj) / self->priv->step;
+		rounded_angle = round (angle);
+		if (angle != rounded_angle) {
+			angle = rounded_angle * self->priv->step;
+			gtk_adjustment_set_value (self->priv->angle_adj, angle);
+			return;
+		}
+	}
+
+	g_signal_handlers_block_by_func (self->priv->small_angle_adj, small_angle_value_changed_cb, self);
+	gtk_adjustment_set_value (self->priv->small_angle_adj, 0.0);
+	g_signal_handlers_unblock_by_func (self->priv->small_angle_adj, small_angle_value_changed_cb, self);
+
+	angle = gtk_adjustment_get_value (self->priv->angle_adj) + gtk_adjustment_get_value (self->priv->small_angle_adj);
+	_gth_file_tool_rotate (self, angle);
+}
+
+
+static void
+step_checkbutton_toggled_cb (GtkToggleButton *button,
+			     gpointer         user_data)
+{
+	GthFileToolRotate *self = user_data;
+
+	if (gtk_toggle_button_get_active (button))
+		self->priv->step = 5.0;
+	else
+		self->priv->step = 0.1;
+	gtk_adjustment_set_step_increment (self->priv->angle_adj, self->priv->step);
+	angle_value_changed_cb (NULL, user_data);
+}
+
+
+static void
+grid_value_changed_cb (GtkAdjustment *adj,
+		       gpointer       user_data)
+{
+	GthFileToolRotate *self = user_data;
+
+	if (self->priv->use_grid)
+		gth_image_rotator_set_grid (self->priv->rotator, TRUE, (int) gtk_adjustment_get_value (self->priv->grid_adj));
+}
+
+
+static void
+grid_checkbutton_toggled_cb (GtkToggleButton *button,
+		     	     gpointer         user_data)
+{
+	GthFileToolRotate *self = user_data;
+
+	self->priv->use_grid = gtk_toggle_button_get_active (button);
+	if (self->priv->use_grid)
+		gth_image_rotator_set_grid (self->priv->rotator, TRUE, (int) gtk_adjustment_get_value (self->priv->grid_adj));
+	else
+		gth_image_rotator_set_grid (self->priv->rotator, FALSE, 0);
+}
+
+
+static void
+size_combobox_changed_cb (GtkComboBox *combo_box,
+			  gpointer     user_data)
+{
+	GthFileToolRotate *self = user_data;
+
+	gth_image_rotator_set_resize (self->priv->rotator, (GthTransformResize) gtk_combo_box_get_active (combo_box));
+}
+
+
+static GtkWidget *
+gth_file_tool_rotate_get_options (GthFileTool *base)
+{
+	GthFileToolRotate *self;
+	GtkWidget         *window;
+	GtkWidget         *viewer_page;
+	GtkWidget         *viewer;
+	GdkPixbuf         *src_pixbuf;
+	GtkAllocation      allocation;
+	int                max_size;
+	int                width;
+	int                height;
+	GtkWidget         *options;
+
+	self = (GthFileToolRotate *) 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;
+
+	viewer = gth_image_viewer_page_get_image_viewer (GTH_IMAGE_VIEWER_PAGE (viewer_page));
+	src_pixbuf = gth_image_viewer_get_current_pixbuf (GTH_IMAGE_VIEWER (viewer));
+	if (src_pixbuf == NULL)
+		return NULL;
+
+	gtk_widget_get_allocation (viewer, &allocation);
+	max_size = MIN (allocation.width, allocation.height);
+
+	self->priv->pixbuf_width = gdk_pixbuf_get_width (src_pixbuf);
+	self->priv->pixbuf_height = gdk_pixbuf_get_height (src_pixbuf);
+	width = self->priv->pixbuf_width;
+	height = self->priv->pixbuf_height;
+	if (scale_keeping_ratio (&width, &height, max_size, max_size, FALSE))
+		self->priv->tmp_pixbuf = _gdk_pixbuf_scale_simple_safe (src_pixbuf, width, height, GDK_INTERP_BILINEAR);
+	else
+		self->priv->tmp_pixbuf = gdk_pixbuf_copy (src_pixbuf);
+
+	self->priv->unit = eel_gconf_get_enum (PREF_ROTATE_UNIT, GTH_TYPE_UNIT, GTH_UNIT_PERCENTAGE);
+	self->priv->builder = _gtk_builder_new_from_file ("rotate-options.ui", "file_tools");
+
+	options = _gtk_builder_get_widget (self->priv->builder, "options");
+	gtk_widget_show (options);
+
+	if (self->priv->unit == GTH_UNIT_PIXELS) {
+		gtk_spin_button_set_digits (GTK_SPIN_BUTTON (GET_WIDGET ("center_x_spinbutton")), 0);
+		gtk_spin_button_set_digits (GTK_SPIN_BUTTON (GET_WIDGET ("center_y_spinbutton")), 0);
+		gtk_spin_button_set_value (GTK_SPIN_BUTTON (GET_WIDGET ("center_x_spinbutton")), self->priv->pixbuf_width / 2);
+		gtk_spin_button_set_value (GTK_SPIN_BUTTON (GET_WIDGET ("center_y_spinbutton")), self->priv->pixbuf_height / 2);
+	}
+	else if (self->priv->unit == GTH_UNIT_PERCENTAGE) {
+		gtk_spin_button_set_digits (GTK_SPIN_BUTTON (GET_WIDGET ("center_x_spinbutton")), 2);
+		gtk_spin_button_set_digits (GTK_SPIN_BUTTON (GET_WIDGET ("center_y_spinbutton")), 2);
+		gtk_spin_button_set_value (GTK_SPIN_BUTTON (GET_WIDGET ("center_x_spinbutton")), 50.0);
+		gtk_spin_button_set_value (GTK_SPIN_BUTTON (GET_WIDGET ("center_y_spinbutton")), 50.0);
+	}
+	gtk_combo_box_set_active (GTK_COMBO_BOX (GET_WIDGET ("unit_combobox")), self->priv->unit);
+
+	gtk_combo_box_set_active (GTK_COMBO_BOX (GET_WIDGET ("size_combobox")), 0);
+
+	self->priv->angle_adj = gimp_scale_entry_new (GET_WIDGET ("angle_box"),
+						      NULL,
+						      0.0, -180.0, 180.0, 5.0, 10.0, 0);
+	self->priv->small_angle_adj = gimp_scale_entry_new (GET_WIDGET ("small_angle_box"),
+							    NULL,
+							    0.0, -5.0, 5.0, 0.01, 0.1, 2);
+	self->priv->grid_adj = gimp_scale_entry_new (GET_WIDGET ("grid_box"),
+						     NULL,
+						     DEFAULT_GRID, 1.0, 50.0, 1.0, 10.0, 0);
+
+	g_signal_connect (GET_WIDGET ("ok_button"),
+			  "clicked",
+			  G_CALLBACK (ok_button_clicked_cb),
+			  self);
+	g_signal_connect (GET_WIDGET ("cancel_button"),
+			  "clicked",
+			  G_CALLBACK (cancel_button_clicked_cb),
+			  self);
+	g_signal_connect (GET_WIDGET ("center_x_spinbutton"),
+			  "value-changed",
+			  G_CALLBACK (center_position_changed_cb),
+			  self);
+	g_signal_connect (GET_WIDGET ("center_y_spinbutton"),
+			  "value-changed",
+			  G_CALLBACK (center_position_changed_cb),
+			  self);
+	g_signal_connect (GET_WIDGET ("unit_combobox"),
+			  "changed",
+			  G_CALLBACK (unit_combobox_changed_cb),
+			  self);
+	g_signal_connect (G_OBJECT (self->priv->angle_adj),
+			  "value-changed",
+			  G_CALLBACK (angle_value_changed_cb),
+			  self);
+	g_signal_connect (G_OBJECT (self->priv->small_angle_adj),
+			  "value-changed",
+			  G_CALLBACK (small_angle_value_changed_cb),
+			  self);
+	g_signal_connect (GET_WIDGET ("step_checkbutton"),
+			  "toggled",
+			  G_CALLBACK (step_checkbutton_toggled_cb),
+			  self);
+	g_signal_connect (G_OBJECT (self->priv->grid_adj),
+			  "value-changed",
+			  G_CALLBACK (grid_value_changed_cb),
+			  self);
+	g_signal_connect (GET_WIDGET ("grid_checkbutton"),
+			  "toggled",
+			  G_CALLBACK (grid_checkbutton_toggled_cb),
+			  self);
+	g_signal_connect (GET_WIDGET ("size_combobox"),
+			  "changed",
+			  G_CALLBACK (size_combobox_changed_cb),
+			  self);
+
+	self->priv->rotator = (GthImageRotator *) gth_image_rotator_new (GTH_IMAGE_VIEWER (viewer));
+	gth_image_rotator_set_grid (self->priv->rotator, FALSE, DEFAULT_GRID);
+	_gth_file_tool_rotate (self, 0.0);
+
+	/*g_signal_connect (self->priv->rotator,
+			  "changed",
+			  G_CALLBACK (rotator_changed_cb),
+			  self);
+	 */
+
+	gth_image_viewer_set_tool (GTH_IMAGE_VIEWER (viewer), (GthImageViewerTool *) self->priv->rotator);
+
+	return options;
+}
+
+
+static void
+gth_file_tool_rotate_destroy_options (GthFileTool *base)
+{
+	GthFileToolRotate *self;
+	GtkWidget         *window;
+	GtkWidget         *viewer_page;
+	GtkWidget         *viewer;
+
+	self = (GthFileToolRotate *) base;
+
+	if (self->priv->apply_event != 0) {
+		g_source_remove (self->priv->apply_event);
+		self->priv->apply_event = 0;
+	}
+
+	if (self->priv->builder != NULL) {
+		int unit;
+
+		/* save the dialog options */
+
+		unit = gtk_combo_box_get_active (GTK_COMBO_BOX (GET_WIDGET ("unit_combobox")));
+		eel_gconf_set_enum (PREF_ROTATE_UNIT, GTH_TYPE_UNIT, unit);
+
+		/* destroy the options data */
+
+		_g_object_unref (self->priv->tmp_pixbuf);
+		_g_object_unref (self->priv->builder);
+		self->priv->tmp_pixbuf = NULL;
+		self->priv->builder = NULL;
+	}
+
+	window = gth_file_tool_get_window (GTH_FILE_TOOL (self));
+	viewer_page = gth_browser_get_viewer_page (GTH_BROWSER (window));
+	viewer = gth_image_viewer_page_get_image_viewer (GTH_IMAGE_VIEWER_PAGE (viewer_page));
+	gth_image_viewer_set_tool (GTH_IMAGE_VIEWER (viewer), NULL);
+}
+
+
+static void
+gth_file_tool_rotate_activate (GthFileTool *base)
+{
+	gth_file_tool_show_options (base);
+}
+
+
+static void
+gth_file_tool_rotate_instance_init (GthFileToolRotate *self)
+{
+	self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GTH_TYPE_FILE_TOOL_ROTATE, GthFileToolRotatePrivate);
+	self->priv->tmp_pixbuf = NULL;
+	self->priv->step = 5.0;
+	self->priv->use_grid = TRUE;
+	gth_file_tool_construct (GTH_FILE_TOOL (self), "tool-rotate", _("Rotate..."), _("Rotate"), TRUE);
+}
+
+
+static void
+gth_file_tool_rotate_finalize (GObject *object)
+{
+	GthFileToolRotate *self;
+
+	g_return_if_fail (object != NULL);
+	g_return_if_fail (GTH_IS_FILE_TOOL_ROTATE (object));
+
+	self = (GthFileToolRotate *) object;
+
+	_g_object_unref (self->priv->tmp_pixbuf);
+	_g_object_unref (self->priv->builder);
+
+	/* Chain up */
+	G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+static void
+gth_file_tool_rotate_class_init (GthFileToolRotateClass *class)
+{
+	GObjectClass     *gobject_class;
+	GthFileToolClass *file_tool_class;
+
+	parent_class = g_type_class_peek_parent (class);
+	g_type_class_add_private (class, sizeof (GthFileToolRotatePrivate));
+
+	gobject_class = (GObjectClass*) class;
+	gobject_class->finalize = gth_file_tool_rotate_finalize;
+
+	file_tool_class = (GthFileToolClass *) class;
+	file_tool_class->update_sensitivity = gth_file_tool_rotate_update_sensitivity;
+	file_tool_class->activate = gth_file_tool_rotate_activate;
+	file_tool_class->get_options = gth_file_tool_rotate_get_options;
+	file_tool_class->destroy_options = gth_file_tool_rotate_destroy_options;
+}
+
+
+GType
+gth_file_tool_rotate_get_type (void) {
+	static GType type_id = 0;
+	if (type_id == 0) {
+		static const GTypeInfo g_define_type_info = {
+			sizeof (GthFileToolRotateClass),
+			(GBaseInitFunc) NULL,
+			(GBaseFinalizeFunc) NULL,
+			(GClassInitFunc) gth_file_tool_rotate_class_init,
+			(GClassFinalizeFunc) NULL,
+			NULL,
+			sizeof (GthFileToolRotate),
+			0,
+			(GInstanceInitFunc) gth_file_tool_rotate_instance_init,
+			NULL
+		};
+		type_id = g_type_register_static (GTH_TYPE_FILE_TOOL, "GthFileToolRotate", &g_define_type_info, 0);
+	}
+	return type_id;
+}
diff --git a/extensions/file_tools/gth-file-tool-rotate.h b/extensions/file_tools/gth-file-tool-rotate.h
new file mode 100644
index 0000000..927e3ee
--- /dev/null
+++ b/extensions/file_tools/gth-file-tool-rotate.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2011 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_ROTATE_H
+#define GTH_FILE_TOOL_ROTATE_H
+
+#include <gthumb.h>
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_FILE_TOOL_ROTATE (gth_file_tool_rotate_get_type ())
+#define GTH_FILE_TOOL_ROTATE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_FILE_TOOL_ROTATE, GthFileToolRotate))
+#define GTH_FILE_TOOL_ROTATE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_FILE_TOOL_ROTATE, GthFileToolRotateClass))
+#define GTH_IS_FILE_TOOL_ROTATE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_FILE_TOOL_ROTATE))
+#define GTH_IS_FILE_TOOL_ROTATE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_FILE_TOOL_ROTATE))
+#define GTH_FILE_TOOL_ROTATE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTH_TYPE_FILE_TOOL_ROTATE, GthFileToolRotateClass))
+
+typedef struct _GthFileToolRotate GthFileToolRotate;
+typedef struct _GthFileToolRotateClass GthFileToolRotateClass;
+typedef struct _GthFileToolRotatePrivate GthFileToolRotatePrivate;
+
+struct _GthFileToolRotate {
+	GthFileTool parent_instance;
+	GthFileToolRotatePrivate *priv;
+};
+
+struct _GthFileToolRotateClass {
+	GthFileToolClass parent_class;
+};
+
+GType  gth_file_tool_rotate_get_type  (void);
+
+G_END_DECLS
+
+#endif /* GTH_FILE_TOOL_ROTATE_H */
diff --git a/extensions/file_tools/gth-image-rotator.c b/extensions/file_tools/gth-image-rotator.c
new file mode 100644
index 0000000..d394b83
--- /dev/null
+++ b/extensions/file_tools/gth-image-rotator.c
@@ -0,0 +1,555 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2011 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 <stdlib.h>
+#include <math.h>
+#include "gth-image-rotator.h"
+
+
+enum {
+	CHANGED,
+	LAST_SIGNAL
+};
+
+
+static guint signals[LAST_SIGNAL] = { 0 };
+static gpointer parent_class = NULL;
+
+
+struct _GthImageRotatorPrivate {
+	GthImageViewer     *viewer;
+	int                 original_width;
+	int                 original_height;
+	cairo_surface_t    *image;
+	GdkRectangle        image_area;
+	GdkRectangle        clip_area;
+	GdkRectangle        inner_area;
+	GdkPoint            center;
+	double              angle;
+	gboolean            paint_image;
+	gboolean            paint_grid;
+	int                 grid_lines;
+	GthTransformResize  resize;
+	cairo_matrix_t      matrix;
+};
+
+
+static void
+gth_image_rotator_realize (GthImageViewerTool *base)
+{
+	/* GthImageRotator *self = GTH_IMAGE_ROTATOR (base);
+	FIXME */
+}
+
+
+static void
+gth_image_rotator_unrealize (GthImageViewerTool *base)
+{
+	/* GthImageRotator *self = GTH_IMAGE_ROTATOR (base);
+	FIXME */
+}
+
+
+static void
+_gth_image_rotator_update_tranformation_matrix (GthImageRotator *self)
+{
+	int    tx, ty;
+	double zoom;
+
+	tx = self->priv->image_area.x + self->priv->center.x;
+	ty = self->priv->image_area.y + self->priv->center.y;
+	zoom = gth_image_viewer_get_zoom (self->priv->viewer);
+
+	cairo_matrix_init_identity (&self->priv->matrix);
+	cairo_matrix_translate (&self->priv->matrix, tx, ty);
+	cairo_matrix_rotate (&self->priv->matrix, self->priv->angle);
+	cairo_matrix_translate (&self->priv->matrix, -tx, -ty);
+
+	gth_transform_resize (&self->priv->matrix,
+			      self->priv->resize,
+			      &self->priv->image_area,
+			      &self->priv->clip_area);
+}
+
+
+static void
+update_image_surface (GthImageRotator *self)
+{
+	GtkAllocation  allocation;
+	GdkPixbuf     *src_pixbuf;
+	int            max_size;
+	int            width;
+	int            height;
+	double         zoom;
+	GdkPixbuf     *tmp_pixbuf;
+
+	if (self->priv->image != NULL) {
+		cairo_surface_destroy (self->priv->image);
+		self->priv->image = NULL;
+	}
+
+	src_pixbuf = gth_image_viewer_get_current_pixbuf (GTH_IMAGE_VIEWER (self->priv->viewer));
+	if (src_pixbuf == NULL)
+		return;
+
+	zoom = gth_image_viewer_get_zoom (self->priv->viewer);
+
+	self->priv->original_width = gdk_pixbuf_get_width (src_pixbuf);
+	self->priv->original_height = gdk_pixbuf_get_height (src_pixbuf);
+	width = self->priv->original_width;
+	height = self->priv->original_height;
+	gtk_widget_get_allocation (GTK_WIDGET (self->priv->viewer), &allocation);
+	max_size = MIN (allocation.width, allocation.height) / 1.2;
+	if (scale_keeping_ratio (&width, &height, max_size, max_size, FALSE))
+		tmp_pixbuf = _gdk_pixbuf_scale_simple_safe (src_pixbuf, width, height, GDK_INTERP_BILINEAR);
+	else
+		tmp_pixbuf = gdk_pixbuf_copy (src_pixbuf);
+	self->priv->image = _cairo_image_surface_create_from_pixbuf (tmp_pixbuf);
+	self->priv->image_area.width = width;
+	self->priv->image_area.height = height;
+	self->priv->image_area.x = MAX ((allocation.width - self->priv->image_area.width) / 2, 0);
+	self->priv->image_area.y = MAX ((allocation.height - self->priv->image_area.height) / 2, 0);
+
+	self->priv->center.x = self->priv->image_area.width * 0.5;
+	self->priv->center.y = self->priv->image_area.height * 0.5;
+
+	_gth_image_rotator_update_tranformation_matrix (self);
+
+	g_object_unref (tmp_pixbuf);
+}
+
+
+static void
+gth_image_rotator_size_allocate (GthImageViewerTool *base,
+				 GtkAllocation      *allocation)
+{
+	update_image_surface (GTH_IMAGE_ROTATOR (base));
+}
+
+
+static void
+gth_image_rotator_map (GthImageViewerTool *base)
+{
+	/* void */
+}
+
+
+static void
+gth_image_rotator_unmap (GthImageViewerTool *base)
+{
+	/* void */
+}
+
+
+static void
+paint_image (GthImageRotator *self,
+	     GdkEventExpose  *event,
+	     cairo_t         *cr)
+{
+	cairo_save (cr);
+
+	cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
+	cairo_set_source_surface (cr, self->priv->image,
+				  self->priv->image_area.x,
+				  self->priv->image_area.y);
+	cairo_pattern_set_filter (cairo_get_source (cr), CAIRO_FILTER_FAST);
+  	cairo_rectangle (cr,
+  			 self->priv->image_area.x,
+  			 self->priv->image_area.y,
+  			 self->priv->image_area.width,
+  			 self->priv->image_area.height);
+  	cairo_fill (cr);
+
+  	cairo_restore (cr);
+}
+
+
+static void
+paint_grid (GthImageRotator *self,
+	    GdkEventExpose  *event,
+	    cairo_t         *cr)
+{
+	double ux, uy;
+	double delta;
+	int    n;
+	double grid_x, grid_y;
+	int    i;
+
+	cairo_save (cr);
+
+	ux = 0.5, uy = 0.5;
+	cairo_device_to_user_distance (cr, &ux, &uy);
+	if (ux < uy)
+	    ux = uy;
+	cairo_set_line_width (cr, ux);
+	cairo_set_antialias (cr, CAIRO_ANTIALIAS_DEFAULT);
+
+#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 9, 2)
+	cairo_set_operator (cr, CAIRO_OPERATOR_DIFFERENCE);
+	cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
+#else
+	cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
+#endif
+
+	delta = self->priv->grid_lines /*(double) self->priv->clip_area.width / (self->priv->grid_lines + 1)*/;
+	n = (double) self->priv->clip_area.width / delta + 0.5;
+	grid_x = self->priv->clip_area.x;
+	grid_y = self->priv->clip_area.y;
+	for (i = 0; i <= n; i++) {
+		cairo_move_to (cr, grid_x + 0.5, grid_y + 0.5);
+		cairo_line_to (cr, grid_x + 0.5, grid_y + self->priv->clip_area.height - 0.5);
+		grid_x += delta;
+	}
+
+	delta = self->priv->grid_lines /*(double) self->priv->clip_area.height / (self->priv->grid_lines + 1)*/;
+	n = (double) self->priv->clip_area.height / delta + 0.5;
+	grid_x = self->priv->clip_area.x;
+	grid_y = self->priv->clip_area.y;
+	for (i = 0; i <= n; i++) {
+		cairo_move_to (cr, grid_x + 0.5, grid_y + 0.5);
+		cairo_line_to (cr, grid_x + self->priv->clip_area.width - 0.5, grid_y + 0.5);
+		grid_y += delta;
+	}
+	cairo_stroke (cr);
+
+	cairo_restore (cr);
+}
+
+
+static void
+paint_center (GthImageRotator *self,
+	      GdkEventExpose  *event,
+	      cairo_t         *cr)
+{
+	cairo_set_antialias (cr, CAIRO_ANTIALIAS_DEFAULT);
+
+#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 9, 2)
+	cairo_set_operator (cr, CAIRO_OPERATOR_DIFFERENCE);
+	cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
+#else
+	cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
+#endif
+
+  	/* rotation center */
+
+	cairo_translate (cr,
+			 self->priv->image_area.x + self->priv->center.x + 0.5,
+			 self->priv->image_area.y + self->priv->center.y + 0.5);
+	cairo_arc (cr, 0.0, 0.0, 10.0, 0.0, 2 * M_PI);
+
+	cairo_move_to (cr, 0.0, - 10.0);
+	cairo_line_to (cr, 0.0, 10.0);
+
+	cairo_move_to (cr, 0.0 - 10.0, 0.0);
+	cairo_line_to (cr, 0.0 + 10.0, 0.0);
+
+	cairo_stroke (cr);
+}
+
+
+static void
+gth_image_rotator_expose (GthImageViewerTool *base,
+			  GdkEventExpose     *event,
+			  cairo_t            *cr)
+{
+	GthImageRotator *self = GTH_IMAGE_ROTATOR (base);
+	GtkStyle        *style;
+	GtkAllocation    allocation;
+	cairo_matrix_t   matrix;
+
+	if (self->priv->image == NULL)
+		return;
+
+	cairo_save (cr);
+
+  	cairo_rectangle (cr,
+  			 event->area.x,
+  			 event->area.y,
+  			 event->area.width,
+  			 event->area.height);
+  	cairo_clip (cr);
+
+  	/* background */
+
+	style = gtk_widget_get_style (GTK_WIDGET (self->priv->viewer));
+	gtk_widget_get_allocation (GTK_WIDGET (self->priv->viewer), &allocation);
+	gdk_cairo_set_source_color (cr, &style->bg[GTK_STATE_NORMAL]);
+	cairo_rectangle (cr,
+			 0,
+			 0,
+			 allocation.width,
+			 allocation.height);
+	cairo_fill (cr);
+
+	/* clip box */
+
+  	cairo_rectangle (cr,
+  			 self->priv->clip_area.x,
+  			 self->priv->clip_area.y,
+  			 self->priv->clip_area.width,
+  			 self->priv->clip_area.height);
+  	cairo_clip_preserve (cr);
+  	cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
+  	cairo_fill (cr);
+
+  	/* image */
+
+  	matrix = self->priv->matrix;
+	cairo_set_matrix (cr, &matrix);
+
+	if (self->priv->paint_image)
+		paint_image (self, event, cr);
+
+	/* grid */
+
+	cairo_matrix_init_identity (&matrix);
+	cairo_set_matrix (cr, &matrix);
+
+	if (self->priv->paint_grid)
+		paint_grid (self, event, cr);
+
+	paint_center (self, event, cr);
+
+	cairo_restore (cr);
+}
+
+
+static gboolean
+gth_image_rotator_button_release (GthImageViewerTool *base,
+				  GdkEventButton     *event)
+{
+	/* FIXME */
+
+	return FALSE;
+}
+
+
+static gboolean
+gth_image_rotator_button_press (GthImageViewerTool *base,
+				GdkEventButton     *event)
+{
+	/* FIXME */
+
+	return FALSE;
+}
+
+
+static gboolean
+gth_image_rotator_motion_notify (GthImageViewerTool *base,
+				 GdkEventMotion     *event)
+{
+	/* FIXME */
+
+	return FALSE;
+}
+
+
+static void
+gth_image_rotator_image_changed (GthImageViewerTool *base)
+{
+	update_image_surface (GTH_IMAGE_ROTATOR (base));
+}
+
+
+static void
+gth_image_rotator_zoom_changed (GthImageViewerTool *base)
+{
+	update_image_surface (GTH_IMAGE_ROTATOR (base));
+}
+
+
+static void
+gth_image_rotator_instance_init (GthImageRotator *self)
+{
+	self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GTH_TYPE_IMAGE_ROTATOR, GthImageRotatorPrivate);
+	self->priv->image = NULL;
+	self->priv->paint_image = TRUE;
+	self->priv->paint_grid = FALSE;
+	self->priv->grid_lines = 0;
+	self->priv->resize = GTH_TRANSFORM_RESIZE_CLIP;
+}
+
+
+static void
+gth_image_rotator_finalize (GObject *object)
+{
+	GthImageRotator *self;
+
+	g_return_if_fail (object != NULL);
+	g_return_if_fail (GTH_IS_IMAGE_ROTATOR (object));
+
+	self = (GthImageRotator *) object;
+	if (self->priv->image != NULL)
+		cairo_surface_destroy (self->priv->image);
+
+	/* Chain up */
+	G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+static void
+gth_image_rotator_class_init (GthImageRotatorClass *class)
+{
+	GObjectClass *gobject_class;
+
+	parent_class = g_type_class_peek_parent (class);
+	g_type_class_add_private (class, sizeof (GthImageRotatorPrivate));
+
+	gobject_class = (GObjectClass*) class;
+	gobject_class->finalize = gth_image_rotator_finalize;
+
+	signals[CHANGED] = g_signal_new ("changed",
+					 G_TYPE_FROM_CLASS (class),
+					 G_SIGNAL_RUN_LAST,
+					 G_STRUCT_OFFSET (GthImageRotatorClass, changed),
+					 NULL, NULL,
+					 g_cclosure_marshal_VOID__VOID,
+					 G_TYPE_NONE,
+					 0);
+}
+
+
+static void
+gth_image_rotator_gth_image_tool_interface_init (GthImageViewerToolIface *iface)
+{
+	iface->realize = gth_image_rotator_realize;
+	iface->unrealize = gth_image_rotator_unrealize;
+	iface->size_allocate = gth_image_rotator_size_allocate;
+	iface->map = gth_image_rotator_map;
+	iface->unmap = gth_image_rotator_unmap;
+	iface->expose = gth_image_rotator_expose;
+	iface->button_press = gth_image_rotator_button_press;
+	iface->button_release = gth_image_rotator_button_release;
+	iface->motion_notify = gth_image_rotator_motion_notify;
+	iface->image_changed = gth_image_rotator_image_changed;
+	iface->zoom_changed = gth_image_rotator_zoom_changed;
+}
+
+
+GType
+gth_image_rotator_get_type (void)
+{
+	static GType type = 0;
+
+	if (! type) {
+		GTypeInfo type_info = {
+			sizeof (GthImageRotatorClass),
+			NULL,
+			NULL,
+			(GClassInitFunc) gth_image_rotator_class_init,
+			NULL,
+			NULL,
+			sizeof (GthImageRotator),
+			0,
+			(GInstanceInitFunc) gth_image_rotator_instance_init
+		};
+		static const GInterfaceInfo gth_image_tool_info = {
+			(GInterfaceInitFunc) gth_image_rotator_gth_image_tool_interface_init,
+			(GInterfaceFinalizeFunc) NULL,
+			NULL
+		};
+
+		type = g_type_register_static (G_TYPE_OBJECT,
+					       "GthImageRotator",
+					       &type_info,
+					       0);
+		g_type_add_interface_static (type, GTH_TYPE_IMAGE_VIEWER_TOOL, &gth_image_tool_info);
+	}
+
+	return type;
+}
+
+
+GthImageViewerTool *
+gth_image_rotator_new (GthImageViewer *viewer)
+{
+	GthImageRotator *rotator;
+
+	rotator = g_object_new (GTH_TYPE_IMAGE_ROTATOR, NULL);
+	rotator->priv->viewer = viewer;
+	rotator->priv->angle = 0.0;
+
+	return GTH_IMAGE_VIEWER_TOOL (rotator);
+}
+
+
+void
+gth_image_rotator_set_center (GthImageRotator *self,
+			      int              x,
+			      int              y)
+{
+	self->priv->center.x = x;
+	self->priv->center.y = y;
+	_gth_image_rotator_update_tranformation_matrix (self);
+	gtk_widget_queue_draw (GTK_WIDGET (self->priv->viewer));
+
+	g_signal_emit (self, signals[CHANGED], 0);
+}
+
+
+void
+gth_image_rotator_set_angle (GthImageRotator *self,
+			     double           angle)
+{
+	double radiants;
+
+	radiants = angle * M_PI / 180.0;
+	if (radiants == self->priv->angle)
+		return;
+	self->priv->angle = radiants;
+	_gth_image_rotator_update_tranformation_matrix (self);
+	gtk_widget_queue_draw (GTK_WIDGET (self->priv->viewer));
+
+	g_signal_emit (self, signals[CHANGED], 0);
+}
+
+
+void
+gth_image_rotator_set_grid (GthImageRotator *self,
+			    gboolean         use_grid,
+		     	    int              lines)
+{
+	self->priv->paint_grid = use_grid;
+	self->priv->grid_lines = lines;
+	gtk_widget_queue_draw (GTK_WIDGET (self->priv->viewer));
+
+	g_signal_emit (self, signals[CHANGED], 0);
+}
+
+
+void
+gth_image_rotator_set_resize (GthImageRotator    *self,
+			      GthTransformResize  resize)
+{
+	self->priv->resize = resize;
+	_gth_image_rotator_update_tranformation_matrix (self);
+	gtk_widget_queue_draw (GTK_WIDGET (self->priv->viewer));
+
+	g_signal_emit (self, signals[CHANGED], 0);
+}
+
+
+GdkPixbuf *
+gth_image_rotator_get_result (GthImageRotator *self)
+{
+	return NULL;
+}
diff --git a/extensions/file_tools/gth-image-rotator.h b/extensions/file_tools/gth-image-rotator.h
new file mode 100644
index 0000000..34bd40d
--- /dev/null
+++ b/extensions/file_tools/gth-image-rotator.h
@@ -0,0 +1,74 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2011 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_IMAGE_ROTATOR_H
+#define GTH_IMAGE_ROTATOR_H
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gthumb.h>
+#include "gth-transform-resize.h"
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_IMAGE_ROTATOR            (gth_image_rotator_get_type ())
+#define GTH_IMAGE_ROTATOR(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_IMAGE_ROTATOR, GthImageRotator))
+#define GTH_IMAGE_ROTATOR_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_IMAGE_ROTATOR, GthImageRotatorClass))
+#define GTH_IS_IMAGE_ROTATOR(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_IMAGE_ROTATOR))
+#define GTH_IS_IMAGE_ROTATOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_IMAGE_ROTATOR))
+#define GTH_IMAGE_ROTATOR_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), GTH_TYPE_IMAGE_ROTATOR, GthImageRotatorClass))
+
+typedef struct _GthImageRotator         GthImageRotator;
+typedef struct _GthImageRotatorClass    GthImageRotatorClass;
+typedef struct _GthImageRotatorPrivate  GthImageRotatorPrivate;
+
+struct _GthImageRotator
+{
+	GObject __parent;
+	GthImageRotatorPrivate *priv;
+};
+
+struct _GthImageRotatorClass
+{
+	GObjectClass __parent_class;
+
+	/*< signals >*/
+
+	void (* changed) (GthImageRotator *rotator);
+};
+
+GType                 gth_image_rotator_get_type    (void);
+GthImageViewerTool *  gth_image_rotator_new         (GthImageViewer     *viewer);
+void                  gth_image_rotator_set_center  (GthImageRotator    *rotator,
+						     int                 x,
+						     int                 y);
+void                  gth_image_rotator_set_angle   (GthImageRotator    *rotator,
+						     double              angle);
+void                  gth_image_rotator_set_grid    (GthImageRotator    *self,
+						     gboolean            use_grid,
+						     int                 lines);
+void                  gth_image_rotator_set_resize  (GthImageRotator    *self,
+						     GthTransformResize  resize);
+GdkPixbuf *           gth_image_rotator_get_result  (GthImageRotator    *self);
+
+G_END_DECLS
+
+#endif /* GTH_IMAGE_ROTATOR_H */
diff --git a/extensions/file_tools/gth-transform-resize.c b/extensions/file_tools/gth-transform-resize.c
new file mode 100644
index 0000000..23d2048
--- /dev/null
+++ b/extensions/file_tools/gth-transform-resize.c
@@ -0,0 +1,890 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2011 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/>.
+ */
+
+/* Based on the file app/core/gimp-transform-resize.h of gimp 2.6
+ *
+ * GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-2001 Spencer Kimball, Peter Mattis, and others
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <math.h>
+#include <glib.h>
+#include "gth-transform-resize.h"
+
+
+#define HAVE_ISFINITE 1
+
+
+#if defined (HAVE_FINITE)
+#define FINITE(x) finite(x)
+#elif defined (HAVE_ISFINITE)
+#define FINITE(x) isfinite(x)
+#elif defined (G_OS_WIN32)
+#define FINITE(x) _finite(x)
+#else
+#error "no FINITE() implementation available?!"
+#endif
+
+#define EPSILON       0.00000001
+#define MIN4(a,b,c,d) MIN(MIN((a),(b)),MIN((c),(d)))
+#define MAX4(a,b,c,d) MAX(MAX((a),(b)),MAX((c),(d)))
+
+
+typedef struct
+{
+	gdouble x, y;
+} Point;
+
+typedef struct
+{
+	Point   a, b, c, d;
+	gdouble area;
+	gdouble aspect;
+} Rectangle;
+
+
+static void      gth_transform_resize_boundary       (cairo_matrix_t     *inv,
+			       	       	       	      GthTransformResize  resize,
+			       	       	       	      int                 u1,
+			       	       	       	      int                 v1,
+			       	       	       	      int                 u2,
+			       	       	       	      int                 v2,
+			       	       	       	      int                *x1,
+			       	       	       	      int                *y1,
+			       	       	       	      int                *x2,
+			       	       	       	      int                *y2);
+static void      gimp_transform_resize_adjust        (gdouble    dx1,
+                                                      gdouble    dy1,
+                                                      gdouble    dx2,
+                                                      gdouble    dy2,
+                                                      gdouble    dx3,
+                                                      gdouble    dy3,
+                                                      gdouble    dx4,
+                                                      gdouble    dy4,
+                                                      gint      *x1,
+                                                      gint      *y1,
+                                                      gint      *x2,
+                                                      gint      *y2);
+static void      gimp_transform_resize_crop          (gdouble    dx1,
+                                                      gdouble    dy1,
+                                                      gdouble    dx2,
+                                                      gdouble    dy2,
+                                                      gdouble    dx3,
+                                                      gdouble    dy3,
+                                                      gdouble    dx4,
+                                                      gdouble    dy4,
+                                                      gdouble    aspect,
+                                                      gint      *x1,
+                                                      gint      *y1,
+                                                      gint      *x2,
+                                                      gint      *y2);
+
+static void      add_rectangle                       (Point      points[4],
+                                                      Rectangle *r,
+                                                      Point      a,
+                                                      Point      b,
+                                                      Point      c,
+                                                      Point      d);
+static gboolean  intersect                           (Point      a,
+                                                      Point      b,
+                                                      Point      c,
+                                                      Point      d,
+                                                      Point     *i);
+static gboolean  intersect_x                         (Point      a,
+                                                      Point      b,
+                                                      Point      c,
+                                                      Point     *i);
+static gboolean  intersect_y                         (Point      a,
+                                                      Point      b,
+                                                      Point      c,
+                                                      Point     *i);
+static gboolean  in_poly                             (Point      points[4],
+                                                      Point      p);
+static gboolean  point_on_border                     (Point      points[4],
+                                                      Point      p);
+
+static void      find_two_point_rectangle            (Rectangle *r,
+                                                      Point      points[4],
+                                                      gint       p);
+static void      find_three_point_rectangle_corner   (Rectangle *r,
+                                                      Point      points[4],
+                                                      gint       p);
+static void      find_three_point_rectangle          (Rectangle *r,
+                                                      Point      points[4],
+                                                      gint       p);
+static void      find_three_point_rectangle_triangle (Rectangle *r,
+                                                      Point      points[4],
+                                                      gint       p);
+static void      find_maximum_aspect_rectangle       (Rectangle *r,
+                                                      Point      points[4],
+                                                      gint       p);
+
+
+void
+gth_transform_resize (cairo_matrix_t     *matrix,
+		      GthTransformResize  resize,
+		      GdkRectangle       *original,
+		      GdkRectangle       *boundary)
+{
+	int o_x1, o_y1, o_x2, o_y2;
+	int t_x1, t_y1, t_x2, t_y2;
+
+	o_x1 = original->x;
+	o_y1 = original->y;
+	o_x2 = original->x + original->width;
+	o_y2 = original->y + original->height;
+
+	gth_transform_resize_boundary (matrix,
+				       resize,
+				       o_x1, o_y1, o_x2, o_y2,
+				       &t_x1, &t_y1, &t_x2, &t_y2);
+
+	boundary->x = t_x1;
+	boundary->y = t_y1;
+	boundary->width = t_x2 - t_x1;
+	boundary->height = t_y2 - t_y1;
+}
+
+
+static void
+_cairo_matrix_transform_point (cairo_matrix_t *matrix,
+			       double          x,
+			       double          y,
+			       double         *tx,
+			       double         *ty)
+{
+	*tx = x;
+	*ty = y;
+	cairo_matrix_transform_point (matrix, tx, ty);
+}
+
+
+/*
+ * This function wants to be passed the inverse transformation matrix!!
+ */
+static void
+gth_transform_resize_boundary (cairo_matrix_t     *inv,
+			       GthTransformResize  resize,
+			       int                 u1,
+			       int                 v1,
+			       int                 u2,
+			       int                 v2,
+			       int                *x1,
+			       int                *y1,
+			       int                *x2,
+			       int                *y2)
+{
+	double dx1, dx2, dx3, dx4;
+	double dy1, dy2, dy3, dy4;
+
+	g_return_if_fail (inv != NULL);
+
+	/*  initialize with the original boundary  */
+	*x1 = u1;
+	*y1 = v1;
+	*x2 = u2;
+	*y2 = v2;
+
+	/* if clipping then just return the original rectangle */
+	if (resize == GTH_TRANSFORM_RESIZE_CLIP)
+		return;
+
+	_cairo_matrix_transform_point (inv, u1, v1, &dx1, &dy1);
+	_cairo_matrix_transform_point (inv, u2, v1, &dx2, &dy2);
+	_cairo_matrix_transform_point (inv, u1, v2, &dx3, &dy3);
+	_cairo_matrix_transform_point (inv, u2, v2, &dx4, &dy4);
+
+	/*  check if the transformation matrix is valid at all  */
+	if (! FINITE (dx1) || ! FINITE (dy1) ||
+	    ! FINITE (dx2) || ! FINITE (dy2) ||
+	    ! FINITE (dx3) || ! FINITE (dy3) ||
+	    ! FINITE (dx4) || ! FINITE (dy4))
+	{
+		g_warning ("invalid transform matrix");
+		/* since there is no sensible way to deal with this, just do
+		 * the same as with GTH_TRANSFORM_RESIZE_CLIP: return
+		 */
+		return;
+	}
+
+	switch (resize) {
+	case GTH_TRANSFORM_RESIZE_BOUNDING_BOX:
+		/* return smallest rectangle (with sides parallel to x- and y-axis)
+		 * that surrounds the new points */
+		gimp_transform_resize_adjust (dx1, dy1, dx2, dy2, dx3, dy3, dx4, dy4,
+					      x1, y1, x2, y2);
+		break;
+
+	case GTH_TRANSFORM_RESIZE_CROP:
+		gimp_transform_resize_crop (dx1, dy1, dx2, dy2, dx3, dy3, dx4, dy4,
+					    0.0,
+					    x1, y1, x2, y2);
+		break;
+
+	case GTH_TRANSFORM_RESIZE_CROP_KEEP_RATIO:
+		gimp_transform_resize_crop (dx1, dy1, dx2, dy2, dx3, dy3, dx4, dy4,
+					    ((gdouble) u2 - u1) / (v2 - v1),
+					    x1, y1, x2, y2);
+		break;
+
+	default:
+		break;
+	}
+
+	/* ensure that resulting rectangle has at least area 1 */
+	if (*x1 == *x2)
+		(*x2)++;
+
+	if (*y1 == *y2)
+		(*y2)++;
+}
+
+
+/* this calculates the smallest rectangle (with sides parallel to x- and
+ * y-axis) that contains the points d1 to d4
+ */
+static void
+gimp_transform_resize_adjust (gdouble  dx1,
+                              gdouble  dy1,
+                              gdouble  dx2,
+                              gdouble  dy2,
+                              gdouble  dx3,
+                              gdouble  dy3,
+                              gdouble  dx4,
+                              gdouble  dy4,
+                              gint    *x1,
+                              gint    *y1,
+                              gint    *x2,
+                              gint    *y2)
+{
+  *x1 = (gint) floor (MIN4 (dx1, dx2, dx3, dx4));
+  *y1 = (gint) floor (MIN4 (dy1, dy2, dy3, dy4));
+
+  *x2 = (gint) ceil (MAX4 (dx1, dx2, dx3, dx4));
+  *y2 = (gint) ceil (MAX4 (dy1, dy2, dy3, dy4));
+}
+
+static void
+gimp_transform_resize_crop (gdouble  dx1,
+                            gdouble  dy1,
+                            gdouble  dx2,
+                            gdouble  dy2,
+                            gdouble  dx3,
+                            gdouble  dy3,
+                            gdouble  dx4,
+                            gdouble  dy4,
+                            gdouble  aspect,
+                            gint    *x1,
+                            gint    *y1,
+                            gint    *x2,
+                            gint    *y2)
+{
+  Point     points[4];
+  Rectangle r;
+  Point     t,a;
+  gint      i, j;
+  gint      min;
+
+  /*  fill in the points array  */
+  points[0].x = dx1;
+  points[0].y = dy1;
+  points[1].x = dx2;
+  points[1].y = dy2;
+  points[2].x = dx3;
+  points[2].y = dy3;
+  points[3].x = dx4;
+  points[3].y = dy4;
+
+  /* find lowest, rightmost corner of surrounding rectangle */
+  a.x = 0;
+  a.y = 0;
+  for (i = 0; i < 4; i++)
+    {
+      if (points[i].x < a.x)
+        a.x = points[i].x;
+
+      if (points[i].y < a.y)
+        a.y = points[i].y;
+    }
+
+  /* and translate all the points to the first quadrant */
+  for (i = 0; i < 4; i++)
+    {
+      points[i].x += (-a.x) * 2;
+      points[i].y += (-a.y) * 2;
+    }
+
+  /* find the convex hull using Jarvis's March as the points are passed
+   * in different orders due to gimp_matrix3_transform_point()
+   */
+  min = 0;
+  for (i = 0; i < 4; i++)
+    {
+      if (points[i].y < points[min].y)
+        min = i;
+    }
+
+  t = points[0];
+  points[0] = points[min];
+  points[min] = t;
+
+  for (i = 1; i < 4; i++)
+    {
+      gdouble theta, theta_m = 2.0 * G_PI;
+      gdouble theta_v        = 0;
+
+      min = 3;
+
+      for (j = i; j < 4; j++)
+        {
+          gdouble sy = points[j].y - points[i - 1].y;
+          gdouble sx = points[j].x - points[i - 1].x;
+
+          theta = atan2 (sy, sx);
+
+          if ((theta < theta_m) &&
+              ((theta > theta_v) || ((theta == theta_v) && (sx > 0))))
+            {
+              theta_m = theta;
+              min = j;
+            }
+        }
+
+      theta_v = theta_m;
+
+      t = points[i];
+      points[i] = points[min];
+      points[min] = t;
+    }
+
+  /* reverse the order of points */
+  t = points[0];
+  points[0] = points[3];
+  points[3] = t;
+
+  t = points[1];
+  points[1] = points[2];
+  points[2] = t;
+
+  r.a.x = r.a.y = r.b.x = r.b.y = r.c.x = r.c.y = r.d.x = r.d.y = r.area = 0;
+  r.aspect = aspect;
+
+  if (aspect != 0)
+    {
+      for (i = 0; i < 4; i++)
+        find_maximum_aspect_rectangle (&r, points, i);
+    }
+  else
+    {
+      for (i = 0; i < 4; i++)
+        {
+          find_three_point_rectangle          (&r, points, i);
+          find_three_point_rectangle_corner   (&r, points, i);
+          find_two_point_rectangle            (&r, points, i);
+          find_three_point_rectangle_triangle (&r, points, i);
+        }
+    }
+
+  if (r.area == 0)
+    {
+      /* saveguard if something went wrong, adjust and give warning */
+      gimp_transform_resize_adjust (dx1, dy1, dx2, dy2, dx3, dy3, dx4, dy4,
+                                    x1, y1, x2, y2);
+      g_warning ("no rectangle found by algorith, no cropping done");
+      return;
+    }
+  else
+    {
+      /* round and translate the calculated points back */
+      *x1 = floor (r.a.x + 0.5);
+      *y1 = floor (r.a.y + 0.5);
+      *x2 = ceil  (r.c.x - 0.5);
+      *y2 = ceil  (r.c.y - 0.5);
+
+      *x1 = *x1 - ((-a.x) * 2);
+      *y1 = *y1 - ((-a.y) * 2);
+      *x2 = *x2 - ((-a.x) * 2);
+      *y2 = *y2 - ((-a.y) * 2);
+      return;
+    }
+}
+
+static void
+find_three_point_rectangle (Rectangle *r,
+                            Point      points[4],
+                            gint       p)
+{
+  Point a = points[p       % 4];  /* 0 1 2 3 */
+  Point b = points[(p + 1) % 4];  /* 1 2 3 0 */
+  Point c = points[(p + 2) % 4];  /* 2 3 0 1 */
+  Point d = points[(p + 3) % 4];  /* 3 0 1 2 */
+  Point i1;                       /* intersection point */
+  Point i2;                       /* intersection point */
+  Point i3;                       /* intersection point */
+
+  if (intersect_x (b, c, a,  &i1) &&
+      intersect_y (c, d, i1, &i2) &&
+      intersect_x (d, a, i2, &i3))
+    add_rectangle (points, r, i3, i3, i1, i1);
+
+  if (intersect_y (b, c, a,  &i1) &&
+      intersect_x (c, d, i1, &i2) &&
+      intersect_y (d, a, i2, &i3))
+    add_rectangle (points, r, i3, i3, i1, i1);
+
+  if (intersect_x (d, c, a,  &i1) &&
+      intersect_y (c, b, i1, &i2) &&
+      intersect_x (b, a, i2, &i3))
+    add_rectangle (points, r, i3, i3, i1, i1);
+
+  if (intersect_y (d, c, a,  &i1) &&
+      intersect_x (c, b, i1, &i2) &&
+      intersect_y (b, a, i2, &i3))
+    add_rectangle (points, r, i3, i3, i1, i1);
+}
+
+static void
+find_three_point_rectangle_corner (Rectangle *r,
+                                   Point      points[4],
+                                   gint       p)
+{
+  Point a = points[p       % 4];  /* 0 1 2 3 */
+  Point b = points[(p + 1) % 4];  /* 1 2 3 0 */
+  Point c = points[(p + 2) % 4];  /* 2 3 0 2 */
+  Point d = points[(p + 3) % 4];  /* 3 0 2 1 */
+  Point i1;                       /* intersection point */
+  Point i2;                       /* intersection point */
+
+  if (intersect_x (b, c, a , &i1) &&
+      intersect_y (c, d, i1, &i2))
+    add_rectangle (points, r, a, a, i1, i2);
+
+  if (intersect_y (b, c, a , &i1) &&
+      intersect_x (c, d, i1, &i2))
+    add_rectangle (points, r, a, a, i1, i2);
+
+  if (intersect_x (c, d, a , &i1) &&
+      intersect_y (b, c, i1, &i2))
+    add_rectangle (points, r, a, a, i1, i2);
+
+  if (intersect_y (c, d, a , &i1) &&
+      intersect_x (b, c, i1, &i2))
+    add_rectangle (points, r, a, a, i1, i2);
+}
+
+static void
+find_two_point_rectangle (Rectangle *r,
+                          Point      points[4],
+                          gint       p)
+{
+  Point a = points[ p      % 4];  /* 0 1 2 3 */
+  Point b = points[(p + 1) % 4];  /* 1 2 3 0 */
+  Point c = points[(p + 2) % 4];  /* 2 3 0 1 */
+  Point d = points[(p + 3) % 4];  /* 3 0 1 2 */
+  Point i1;                       /* intersection point */
+  Point i2;                       /* intersection point */
+  Point mid;                      /* Mid point */
+
+  add_rectangle (points, r, a, a, c, c);
+  add_rectangle (points, r, b, b, d, d);
+
+  if (intersect_x (c, b, a, &i1) &&
+      intersect_y (c, b, a, &i2))
+    {
+      mid.x = ( i1.x + i2.x ) / 2.0;
+      mid.y = ( i1.y + i2.y ) / 2.0;
+
+      add_rectangle (points, r, a, a, mid, mid);
+    }
+}
+
+static void
+find_three_point_rectangle_triangle (Rectangle *r,
+                                     Point      points[4],
+                                     gint       p)
+{
+  Point a = points[p % 4];        /* 0 1 2 3 */
+  Point b = points[(p + 1) % 4];  /* 1 2 3 0 */
+  Point c = points[(p + 2) % 4];  /* 2 3 0 1 */
+  Point d = points[(p + 3) % 4];  /* 3 0 1 2 */
+  Point i1;                       /* intersection point */
+  Point i2;                       /* intersection point */
+  Point mid;
+
+  mid.x = (a.x + b.x) / 2.0;
+  mid.y = (a.y + b.y) / 2.0;
+
+  if (intersect_x (b, c, mid, &i1) &&
+      intersect_y (a, d, mid, &i2))
+    add_rectangle (points, r, mid, mid, i1, i2);
+
+  if (intersect_y (b, c, mid, &i1) &&
+      intersect_x (a, d, mid, &i2))
+    add_rectangle (points, r, mid, mid, i1, i2);
+
+  if (intersect_x (a, d, mid, &i1) &&
+      intersect_y (b, c, mid, &i2))
+    add_rectangle (points, r, mid, mid, i1, i2);
+
+  if (intersect_y (a, d, mid, &i1) &&
+      intersect_x (b, c, mid, &i2))
+    add_rectangle (points, r, mid, mid, i1, i2);
+}
+
+static void
+find_maximum_aspect_rectangle (Rectangle *r,
+                               Point      points[4],
+                               gint       p)
+{
+  Point a = points[ p      % 4];  /* 0 1 2 3 */
+  Point b = points[(p + 1) % 4];  /* 1 2 3 0 */
+  Point c = points[(p + 2) % 4];  /* 2 3 0 1 */
+  Point d = points[(p + 3) % 4];  /* 3 0 1 2 */
+  Point i1;                       /* intersection point */
+  Point i2;                       /* intersection point */
+  Point i3;                       /* intersection point */
+
+  if (intersect_x (b, c, a, &i1))
+    {
+      i2.x = i1.x + 1.0 * r->aspect;
+      i2.y = i1.y + 1.0;
+
+      if (intersect (d, a, i1, i2, &i3))
+        add_rectangle (points, r, i1, i3, i1, i3);
+
+      if (intersect (a, b, i1, i2, &i3))
+        add_rectangle (points, r, i1, i3, i1, i3);
+
+      if (intersect (c, d, i1, i2, &i3))
+        add_rectangle (points, r, i1, i3, i1, i3);
+
+      i2.x = i1.x - 1.0 * r->aspect;
+      i2.y = i1.y + 1.0;
+
+      if (intersect (d, a, i1, i2, &i3))
+        add_rectangle (points, r, i1, i3, i1, i3);
+
+      if (intersect (a, b, i1, i2, &i3))
+        add_rectangle (points, r, i1, i3, i1, i3);
+
+      if (intersect (c, d, i1, i2, &i3))
+        add_rectangle (points, r, i1, i3, i1, i3);
+    }
+
+  if (intersect_y (b, c, a, &i1))
+    {
+      i2.x = i1.x + 1.0 * r->aspect;
+      i2.y = i1.y + 1.0;
+
+      if (intersect (d, a, i1, i2, &i3))
+        add_rectangle (points, r, i1, i3, i1, i3);
+
+      if (intersect (a, b, i1, i2, &i3))
+        add_rectangle (points, r, i1, i3, i1, i3);
+
+      if (intersect (c, d, i1, i2, &i3))
+        add_rectangle (points, r, i1, i3, i1, i3);
+
+      i2.x = i1.x - 1.0 * r->aspect;
+      i2.y = i1.y + 1.0;
+
+      if (intersect (d, a, i1, i2, &i3))
+        add_rectangle (points, r, i1, i3, i1, i3);
+
+      if (intersect (a, b, i1, i2, &i3))
+        add_rectangle (points, r, i1, i3, i1, i3);
+
+      if (intersect (c, d, i1, i2, &i3))
+        add_rectangle (points, r, i1, i3, i1, i3);
+    }
+
+  if (intersect_x (c, d, a,  &i1))
+    {
+      i2.x = i1.x + 1.0 * r->aspect;
+      i2.y = i1.y + 1.0;
+
+      if (intersect (d, a, i1, i2, &i3))
+        add_rectangle (points, r, i1, i3, i1, i3);
+
+      if (intersect (a, b, i1, i2, &i3))
+        add_rectangle (points, r, i1, i3, i1, i3);
+
+      if (intersect (b, c, i1, i2, &i3))
+        add_rectangle (points, r, i1, i3, i1, i3);
+
+      i2.x = i1.x - 1.0 * r->aspect;
+      i2.y = i1.y + 1.0;
+
+      if (intersect (d, a, i1, i2, &i3))
+        add_rectangle (points, r, i1, i3, i1, i3);
+
+      if (intersect (a, b, i1, i2, &i3))
+        add_rectangle (points, r, i1, i3, i1, i3);
+
+      if (intersect (b, c, i1, i2, &i3))
+        add_rectangle (points, r, i1, i3, i1, i3);
+    }
+
+  if (intersect_y (c, d, a,  &i1))
+    {
+      i2.x = i1.x + 1.0 * r->aspect;
+      i2.y = i1.y + 1.0;
+
+      if (intersect (d, a, i1, i2, &i3))
+        add_rectangle (points, r, i1, i3, i1, i3);
+
+      if (intersect (a, b, i1, i2, &i3))
+        add_rectangle (points, r, i1, i3, i1, i3);
+
+      if (intersect (b, c, i1, i2, &i3))
+        add_rectangle (points, r, i1, i3, i1, i3);
+
+      i2.x = i1.x - 1.0 * r->aspect;
+      i2.y = i1.y + 1.0;
+
+      if (intersect (d, a, i1, i2, &i3))
+        add_rectangle (points, r, i1, i3, i1, i3);
+
+      if (intersect (a, b, i1, i2, &i3))
+        add_rectangle (points, r, i1, i3, i1, i3);
+
+      if (intersect (b, c, i1, i2, &i3))
+        add_rectangle (points, r, i1, i3, i1, i3);
+    }
+}
+
+/* check if point is inside the polygon "points", if point is on border
+ * its still inside.
+ */
+static gboolean
+in_poly (Point points[4],
+         Point p)
+{
+  Point p1, p2;
+  gint  counter = 0;
+  gint  i;
+
+  p1 = points[0];
+
+  for (i = 1; i <= 4; i++)
+    {
+      p2 = points[i % 4];
+
+      if (p.y > MIN (p1.y, p2.y))
+        {
+          if (p.y <= MAX (p1.y, p2.y))
+            {
+              if (p.x <= MAX (p1.x, p2.x))
+                {
+                  if (p1.y != p2.y)
+                    {
+                      gdouble xinters = ((p.y - p1.y) * (p2.x - p1.x) /
+                                         (p2.y - p1.y) + p1.x);
+
+                      if (p1.x == p2.x || p.x <= xinters)
+                        counter++;
+                    }
+                }
+            }
+        }
+
+      p1 = p2;
+    }
+
+  /* border check */
+  if (point_on_border (points, p))
+    return TRUE;
+
+  return (counter % 2 != 0);
+}
+
+/* check if the point p lies on the polygon "points"
+ */
+static gboolean
+point_on_border (Point points[4],
+                 Point p)
+{
+  gint i;
+
+  for (i = 0; i <= 4; i++)
+    {
+      Point   a  = points[i % 4];
+      Point   b  = points[(i + 1) % 4];
+      gdouble a1 = (b.y - a.y);
+      gdouble b1 = (a.x - b.x);
+      gdouble c1 = a1 * a.x + b1 * a.y;
+      gdouble c2 = a1 * p.x + b1 * p.y;
+
+      if (ABS (c1 - c2) < EPSILON &&
+          MIN (a.x, b.x) <= p.x   &&
+          MAX (a.x, b.x) >= p.x   &&
+          MIN (a.y, b.y) <= p.y   &&
+          MAX (a.y, b.y) >= p.y)
+        return TRUE;
+    }
+
+  return FALSE;
+}
+
+/* calculate the intersection point of the line a-b with the line c-d
+ * and write it to i, if existing.
+ */
+static gboolean
+intersect (Point  a,
+           Point  b,
+           Point  c,
+           Point  d,
+           Point *i)
+{
+  gdouble a1  = (b.y - a.y);
+  gdouble b1  = (a.x - b.x);
+  gdouble c1  = a1 * a.x + b1 * a.y;
+
+  gdouble a2  = (d.y - c.y);
+  gdouble b2  = (c.x - d.x);
+  gdouble c2  = a2 * c.x + b2 * c.y;
+  gdouble det = a1 * b2 - a2 * b1;
+
+  if (det == 0)
+    return FALSE;
+
+  i->x = (b2 * c1 - b1 * c2) / det;
+  i->y = (a1 * c2 - a2 * c1) / det;
+
+  return TRUE;
+}
+
+/* calculate the intersection point of the line a-b with the vertical line
+ * through c and write it to i, if existing.
+ */
+static gboolean
+intersect_x (Point  a,
+             Point  b,
+             Point  c,
+             Point *i)
+{
+  Point   d = c;
+  d.y += 1;
+
+  return intersect(a,b,c,d,i);
+}
+
+/* calculate the intersection point of the line a-b with the horizontal line
+ * through c and write it to i, if existing.
+ */
+static gboolean
+intersect_y (Point  a,
+             Point  b,
+             Point  c,
+             Point *i)
+{
+  Point   d = c;
+  d.x += 1;
+
+  return intersect(a,b,c,d,i);
+}
+
+/* this takes the smallest ortho-aligned (the sides of the rectangle are
+ * parallel to the x- and y-axis) rectangle fitting around the points a to d,
+ * checks if the whole rectangle is inside the polygon described by points and
+ * writes it to r if the area is bigger than the rectangle already stored in r.
+ */
+static void
+add_rectangle (Point      points[4],
+               Rectangle *r,
+               Point      a,
+               Point      b,
+               Point      c,
+               Point      d)
+{
+  gdouble width;
+  gdouble height;
+  gdouble minx, maxx;
+  gdouble miny, maxy;
+
+  /* get the orthoaligned (the sides of the rectangle are parallel to the x-
+   * and y-axis) rectangle surrounding the points a to d.
+   */
+  minx = MIN4 (a.x, b.x, c.x, d.x);
+  maxx = MAX4 (a.x, b.x, c.x, d.x);
+  miny = MIN4 (a.y, b.y, c.y, d.y);
+  maxy = MAX4 (a.y, b.y, c.y, d.y);
+
+  a.x = minx;
+  a.y = miny;
+
+  b.x = maxx;
+  b.y = miny;
+
+  c.x = maxx;
+  c.y = maxy;
+
+  d.x = minx;
+  d.y = maxy;
+
+  width  =  maxx - minx;
+  height =  maxy - miny;
+
+  /* check if this rectangle is inside the polygon "points" */
+  if (in_poly (points, a) &&
+      in_poly (points, b) &&
+      in_poly (points, c) &&
+      in_poly (points, d))
+    {
+      gdouble area = width * height;
+
+      /* check if the new rectangle is larger (in terms of area)
+       * than the currently stored rectangle in r, if yes store
+       * new rectangle to r
+       */
+      if (r->area <= area)
+        {
+          r->a.x = a.x;
+          r->a.y = a.y;
+
+          r->b.x = b.x;
+          r->b.y = b.y;
+
+          r->c.x = c.x;
+          r->c.y = c.y;
+
+          r->d.x = d.x;
+          r->d.y = d.y;
+
+          r->area = area;
+        }
+    }
+}
diff --git a/extensions/file_tools/gth-transform-resize.h b/extensions/file_tools/gth-transform-resize.h
new file mode 100644
index 0000000..488aad4
--- /dev/null
+++ b/extensions/file_tools/gth-transform-resize.h
@@ -0,0 +1,60 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2011 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/>.
+ */
+
+/* Based from the file app/core/gimp-transform-resize.h of gimp 2.6
+ *
+ * GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995-2001 Spencer Kimball, Peter Mattis, and others
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_TRANSFORM_RESIZE_H
+#define GTH_TRANSFORM_RESIZE_H
+
+#include <gdk/gdk.h>
+#include <cairo.h>
+
+typedef enum {
+	GTH_TRANSFORM_RESIZE_CLIP,
+	GTH_TRANSFORM_RESIZE_BOUNDING_BOX,
+	GTH_TRANSFORM_RESIZE_CROP,
+	GTH_TRANSFORM_RESIZE_CROP_KEEP_RATIO
+} GthTransformResize;
+
+void gth_transform_resize (cairo_matrix_t     *matrix,
+			   GthTransformResize  resize,
+			   GdkRectangle       *original,
+			   GdkRectangle       *boundary);
+
+#endif  /*  GTH_TRANSFORM_RESIZE_H  */
diff --git a/extensions/file_tools/main.c b/extensions/file_tools/main.c
index 6931e66..e95dd5a 100644
--- a/extensions/file_tools/main.c
+++ b/extensions/file_tools/main.c
@@ -34,6 +34,7 @@
 #include "gth-file-tool-negative.h"
 #include "gth-file-tool-redo.h"
 #include "gth-file-tool-resize.h"
+#include "gth-file-tool-rotate.h"
 #include "gth-file-tool-rotate-left.h"
 #include "gth-file-tool-rotate-right.h"
 #include "gth-file-tool-save.h"
@@ -57,11 +58,11 @@ gthumb_extension_activate (void)
 	gth_main_register_type ("file-tools", GTH_TYPE_FILE_TOOL_DESATURATE);
 	gth_main_register_type ("file-tools", GTH_TYPE_FILE_TOOL_NEGATIVE);
 
-	gth_main_register_type ("file-tools", GTH_TYPE_FILE_TOOL_MIRROR);
-	gth_main_register_type ("file-tools", GTH_TYPE_FILE_TOOL_FLIP);
 	gth_main_register_type ("file-tools", GTH_TYPE_FILE_TOOL_ROTATE_RIGHT);
 	gth_main_register_type ("file-tools", GTH_TYPE_FILE_TOOL_ROTATE_LEFT);
-
+	gth_main_register_type ("file-tools", GTH_TYPE_FILE_TOOL_MIRROR);
+	gth_main_register_type ("file-tools", GTH_TYPE_FILE_TOOL_FLIP);
+	gth_main_register_type ("file-tools", GTH_TYPE_FILE_TOOL_ROTATE);
 	gth_main_register_type ("file-tools", GTH_TYPE_FILE_TOOL_RESIZE);
 	gth_main_register_type ("file-tools", GTH_TYPE_FILE_TOOL_CROP);
 
diff --git a/extensions/file_tools/preferences.h b/extensions/file_tools/preferences.h
index 2d3c836..132c163 100644
--- a/extensions/file_tools/preferences.h
+++ b/extensions/file_tools/preferences.h
@@ -43,6 +43,8 @@ G_BEGIN_DECLS
 #define PREF_RESIZE_ASPECT_RATIO_INVERT "/apps/gthumb/ext/resize/aspect_ratio_invert"
 #define PREF_RESIZE_HIGH_QUALITY        "/apps/gthumb/ext/resize/high_quality"
 
+#define PREF_ROTATE_UNIT                "/apps/gthumb/ext/rotate/unit"
+
 G_END_DECLS
 
 #endif /* PREFERENCES_H */
diff --git a/gthumb/cairo-utils.c b/gthumb/cairo-utils.c
index 5b72c29..47ced00 100644
--- a/gthumb/cairo-utils.c
+++ b/gthumb/cairo-utils.c
@@ -46,6 +46,27 @@ _gdk_color_to_cairo_color_255 (GdkColor          *g_color,
 }
 
 
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN /* BGRA */
+#define SET_PIXEL(red, green, blue, alpha)			\
+			s_iter[0] = blue;			\
+			s_iter[1] = green;			\
+			s_iter[2] = red;			\
+			s_iter[3] = alpha;
+#elif G_BYTE_ORDER == G_BIG_ENDIAN /* ARGB */
+#define SET_PIXEL(red, green, blue, alpha)			\
+			s_iter[0] = alpha;			\
+			s_iter[1] = red;			\
+			s_iter[2] = green;			\
+			s_iter[3] = blue;
+#else /* PDP endianness: RABG */
+#define SET_PIXEL(red, green, blue, alpha)			\
+			s_iter[0] = red;			\
+			s_iter[1] = alpha;			\
+			s_iter[2] = blue;			\
+			s_iter[3] = green;
+#endif
+
+
 void
 _cairo_paint_full_gradient (cairo_surface_t *surface,
 			    GdkColor        *h_color1,
@@ -85,8 +106,7 @@ _cairo_paint_full_gradient (cairo_surface_t *surface,
 	        x = (double) (height - h) / height;
 
 	        for (w = 0; w < width; w++) {
-	        	y = (double) (width - w) / width;
-
+	        	y        = (double) (width - w) / width;
 			x_y      = x * y;
 			x_1_y    = x * (1.0 - y);
 			y_1_x    = y * (1.0 - x);
@@ -96,25 +116,7 @@ _cairo_paint_full_gradient (cairo_surface_t *surface,
 			green = hcolor1.g * x_y + hcolor2.g * x_1_y + vcolor1.g * y_1_x + vcolor2.g * _1_x_1_y;
 			blue  = hcolor1.b * x_y + hcolor2.b * x_1_y + vcolor1.b * y_1_x + vcolor2.b * _1_x_1_y;
 
-#if G_BYTE_ORDER == G_LITTLE_ENDIAN
-	        	/* BGRA */
-			s_iter[0] = blue;
-			s_iter[1] = green;
-			s_iter[2] = red;
-			s_iter[3] = 0xff;
-#elif G_BYTE_ORDER == G_BIG_ENDIAN
-			/* ARGB */
-			s_iter[0] = 0xff;
-			s_iter[1] = red;
-			s_iter[2] = green;
-			s_iter[3] = blue;
-#else /* PDP endianness */
-			/* RABG */
-			s_iter[0] = red;
-			s_iter[1] = 0xff;
-			s_iter[2] = blue;
-			s_iter[3] = green;
-#endif
+			SET_PIXEL (red, green, blue, 0xff);
 
 			s_iter += 4;
 		}
@@ -124,6 +126,80 @@ _cairo_paint_full_gradient (cairo_surface_t *surface,
 }
 
 
+cairo_surface_t *
+_cairo_image_surface_create_from_pixbuf (GdkPixbuf *pixbuf)
+{
+	cairo_surface_t *surface;
+	int              width;
+	int              height;
+	int              p_stride;
+	int              p_n_channels;
+	guchar          *p_pixels;
+	int              s_stride;
+	unsigned char   *s_pixels;
+	int              h, w;
+
+	g_object_get (G_OBJECT (pixbuf),
+		      "width", &width,
+		      "height", &height,
+		      "rowstride", &p_stride,
+		      "n-channels", &p_n_channels,
+		      "pixels", &p_pixels,
+		      NULL );
+	surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
+	s_stride = cairo_image_surface_get_stride (surface);
+	s_pixels = cairo_image_surface_get_data (surface);
+
+	if (p_n_channels == 4) {
+		guchar *s_iter;
+		guchar *p_iter;
+		double  alpha_factor;
+		guchar  red, green, blue, alpha;
+
+		for (h = 0; h < height; h++) {
+			s_iter = s_pixels;
+			p_iter = p_pixels;
+
+			for (w = 0; w < width; w++) {
+				alpha = p_iter[3];
+				alpha_factor = (double) alpha / 255.0;
+				red   = (guchar) (alpha_factor * p_iter[0]) ;
+				green = (guchar) (alpha_factor * p_iter[1]);
+				blue  = (guchar) (alpha_factor * p_iter[2]);
+
+				SET_PIXEL (red, green, blue, alpha);
+
+				s_iter += 4;
+				p_iter += p_n_channels;
+			}
+
+			s_pixels += s_stride;
+			p_pixels += p_stride;
+		}
+	}
+	else {
+		guchar *s_iter;
+		guchar *p_iter;
+
+		for (h = 0; h < height; h++) {
+			s_iter = s_pixels;
+			p_iter = p_pixels;
+
+			for (w = 0; w < width; w++) {
+				SET_PIXEL (p_iter[0], p_iter[1], p_iter[2], 0xff);
+				s_iter += 4;
+				p_iter += p_n_channels;
+			}
+
+			s_pixels += s_stride;
+			p_pixels += p_stride;
+		}
+	}
+
+	return surface;
+}
+
+
 void
 _cairo_draw_rounded_box (cairo_t *cr,
 			 double   x,
diff --git a/gthumb/cairo-utils.h b/gthumb/cairo-utils.h
index ace63b7..be1d010 100644
--- a/gthumb/cairo-utils.h
+++ b/gthumb/cairo-utils.h
@@ -24,6 +24,7 @@
 
 #include <glib.h>
 #include <gdk/gdk.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
 #include <cairo.h>
 
 typedef struct {
@@ -49,6 +50,9 @@ void         _cairo_paint_full_gradient         (cairo_surface_t   *surface,
 				 	 	 GdkColor          *h_color2,
 				 	 	 GdkColor          *v_color1,
 				 	 	 GdkColor          *v_color2);
+cairo_surface_t *
+	     _cairo_image_surface_create_from_pixbuf
+	     	     	     	     	        (GdkPixbuf         *pixbuf);
 void         _cairo_draw_rounded_box            (cairo_t           *cr,
 			 	 	 	 double             x,
 			 	 	 	 double             y,
diff --git a/gthumb/gtk-utils.c b/gthumb/gtk-utils.c
index a5e33a2..1ca5412 100644
--- a/gthumb/gtk-utils.c
+++ b/gthumb/gtk-utils.c
@@ -1302,7 +1302,8 @@ gimp_scale_entry_new (GtkWidget *parent_box,
 	gtk_box_pack_start (GTK_BOX (parent_box), hbox, TRUE, TRUE, 0);
 	gtk_widget_show_all (hbox);
 
-	gtk_label_set_mnemonic_widget (label, scale);
+	if (label != NULL)
+		gtk_label_set_mnemonic_widget (label, scale);
 
 	return (GtkAdjustment *) adj;
 }



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