[gitg] Add diff renderer for images



commit b2ed43d22de03d0f504dd2e5b8b840af5e992950
Author: Jesse van den Kieboom <jessevdk gnome org>
Date:   Sun Jan 10 13:24:33 2016 +0100

    Add diff renderer for images

 libgitg/Makefile.am                                |    7 +
 libgitg/gitg-diff-image-composite.vala             |   91 ++++++
 libgitg/gitg-diff-image-difference.vala            |   65 +++++
 libgitg/gitg-diff-image-overlay.vala               |   80 ++++++
 libgitg/gitg-diff-image-side-by-side.vala          |  290 ++++++++++++++++++++
 libgitg/gitg-diff-image-slider.vala                |   94 +++++++
 libgitg/gitg-diff-image-surface-cache.vala         |   29 ++
 libgitg/gitg-diff-view-file-renderer-image.vala    |  152 ++++++++++
 libgitg/gitg-diff-view-file.vala                   |    5 +
 libgitg/gitg-diff-view.vala                        |   51 +++-
 libgitg/resources/resources.xml                    |    1 +
 .../ui/gitg-diff-view-file-renderer-image.ui       |  123 +++++++++
 libgitg/resources/ui/libgitg-style.css             |    8 +
 13 files changed, 990 insertions(+), 6 deletions(-)
---
diff --git a/libgitg/Makefile.am b/libgitg/Makefile.am
index 245d103..73981cf 100644
--- a/libgitg/Makefile.am
+++ b/libgitg/Makefile.am
@@ -67,6 +67,12 @@ libgitg_libgitg_1_0_la_VALASOURCES =                         \
        libgitg/gitg-credentials-manager.vala                   \
        libgitg/gitg-date.vala                                  \
        libgitg/gitg-diff-stat.vala                             \
+       libgitg/gitg-diff-image-composite.vala                  \
+       libgitg/gitg-diff-image-difference.vala                 \
+       libgitg/gitg-diff-image-overlay.vala                    \
+       libgitg/gitg-diff-image-side-by-side.vala               \
+       libgitg/gitg-diff-image-slider.vala                     \
+       libgitg/gitg-diff-image-surface-cache.vala              \
        libgitg/gitg-diff-view.vala                             \
        libgitg/gitg-diff-view-file.vala                        \
        libgitg/gitg-diff-view-file-info.vala                   \
@@ -74,6 +80,7 @@ libgitg_libgitg_1_0_la_VALASOURCES =                          \
        libgitg/gitg-diff-view-file-renderer.vala               \
        libgitg/gitg-diff-view-file-renderer-binary.vala        \
        libgitg/gitg-diff-view-file-renderer-text.vala          \
+       libgitg/gitg-diff-view-file-renderer-image.vala         \
        libgitg/gitg-diff-view-lines-renderer.vala              \
        libgitg/gitg-diff-selectable.vala                       \
        libgitg/gitg-diff-view-commit-details.vala              \
diff --git a/libgitg/gitg-diff-image-composite.vala b/libgitg/gitg-diff-image-composite.vala
new file mode 100644
index 0000000..0a7c2c3
--- /dev/null
+++ b/libgitg/gitg-diff-image-composite.vala
@@ -0,0 +1,91 @@
+/*
+ * This file is part of gitg
+ *
+ * Copyright (C) 2016 - Jesse van den Kieboom
+ *
+ * gitg 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.
+ *
+ * gitg 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 gitg. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+class Gitg.DiffImageComposite : Gtk.DrawingArea
+{
+       public Gitg.DiffImageSurfaceCache cache { get; set; }
+
+       private void get_natural_size(out int image_width, out int image_height)
+       {
+               var pixbuf = cache.old_pixbuf;
+               var window = get_window();
+
+               double xscale = 1, yscale = 1;
+
+               if (window != null)
+               {
+                       cache.get_old_surface(get_window()).get_device_scale(out xscale, out yscale);
+               }
+
+               image_width = (int)(pixbuf.get_width() / xscale);
+               image_height = (int)(pixbuf.get_height() / yscale);
+       }
+
+       protected void get_sizing(int width, out int image_width, out int image_height)
+       {
+               get_natural_size(out image_width, out image_height);
+
+               // Scale down to fit in width
+               if (image_width > width)
+               {
+                       image_height *= width / image_width;
+                       image_width = width;
+               }
+       }
+
+       protected override void get_preferred_width(out int minimum_width, out int natural_width)
+       {
+               int natural_height;
+
+               get_natural_size(out natural_width, out natural_height);
+               minimum_width = 1;
+       }
+
+       protected override void get_preferred_height_for_width(int width, out int minimum_height, out int 
natural_height)
+       {
+               int image_width, image_height;
+
+               get_sizing(width, out image_width, out image_height);
+
+               minimum_height = image_height;
+               natural_height = image_height;
+       }
+
+       protected override Gtk.SizeRequestMode get_request_mode()
+       {
+               return Gtk.SizeRequestMode.HEIGHT_FOR_WIDTH;
+       }
+
+       protected override bool draw(Cairo.Context cr)
+       {
+               Gtk.Allocation alloc;
+               get_allocation(out alloc);
+
+               var ctx = get_style_context();
+
+               ctx.render_background(cr, alloc.x, alloc.y, alloc.width, alloc.height);
+               return true;
+       }
+
+       protected override void realize()
+       {
+               base.realize();
+               queue_resize();
+       }
+}
diff --git a/libgitg/gitg-diff-image-difference.vala b/libgitg/gitg-diff-image-difference.vala
new file mode 100644
index 0000000..b74bfda
--- /dev/null
+++ b/libgitg/gitg-diff-image-difference.vala
@@ -0,0 +1,65 @@
+/*
+ * This file is part of gitg
+ *
+ * Copyright (C) 2016 - Jesse van den Kieboom
+ *
+ * gitg 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.
+ *
+ * gitg 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 gitg. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+class Gitg.DiffImageDifference : DiffImageComposite
+{
+       protected override bool draw(Cairo.Context cr)
+       {
+               base.draw(cr);
+
+               var window = get_window();
+
+               Gtk.Allocation alloc;
+               get_allocation(out alloc);
+
+               int image_width, image_height;
+               get_sizing(alloc.width, out image_width, out image_height);
+
+               var old_surface = cache.get_old_surface(window);
+               var new_surface = cache.get_new_surface(window);
+
+               int x = (alloc.width - image_width) / 2;
+               int y = 0;
+
+               if (old_surface != null)
+               {
+                       cr.set_source_surface(old_surface, x, y);
+                       cr.paint();
+               }
+
+               if (new_surface != null)
+               {
+                       cr.save();
+                       {
+                               cr.set_operator(Cairo.Operator.DIFFERENCE);
+                               cr.set_source_surface(new_surface, x, y);
+                               cr.paint();
+                       }
+                       cr.restore();
+               }
+
+               return true;
+       }
+
+       protected override void realize()
+       {
+               base.realize();
+               queue_resize();
+       }
+}
diff --git a/libgitg/gitg-diff-image-overlay.vala b/libgitg/gitg-diff-image-overlay.vala
new file mode 100644
index 0000000..b74218b
--- /dev/null
+++ b/libgitg/gitg-diff-image-overlay.vala
@@ -0,0 +1,80 @@
+/*
+ * This file is part of gitg
+ *
+ * Copyright (C) 2016 - Jesse van den Kieboom
+ *
+ * gitg 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.
+ *
+ * gitg 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 gitg. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+class Gitg.DiffImageOverlay : DiffImageComposite
+{
+       private double d_alpha;
+
+       public double alpha
+       {
+               get { return d_alpha; }
+
+               set
+               {
+                       var newalpha = double.max(0, double.min(value, 1));
+
+                       if (newalpha != d_alpha)
+                       {
+                               d_alpha = newalpha;
+                               queue_draw();
+                       }
+               }
+
+               default = 0.5;
+       }
+
+       protected override bool draw(Cairo.Context cr)
+       {
+               base.draw(cr);
+
+               var window = get_window();
+
+               Gtk.Allocation alloc;
+               get_allocation(out alloc);
+
+               int image_width, image_height;
+               get_sizing(alloc.width, out image_width, out image_height);
+
+               var old_surface = cache.get_old_surface(window);
+               var new_surface = cache.get_new_surface(window);
+
+               int x = (alloc.width - image_width) / 2;
+               int y = 0;
+
+               if (old_surface != null && d_alpha != 1)
+               {
+                       cr.set_source_surface(old_surface, x, y);
+                       cr.paint_with_alpha(1 - d_alpha);
+               }
+
+               if (new_surface != null && d_alpha != 0)
+               {
+                       cr.set_source_surface(new_surface, x, y);
+                       cr.paint_with_alpha(d_alpha);
+               }
+
+               return true;
+       }
+
+       protected override void realize()
+       {
+               base.realize();
+               queue_resize();
+       }
+}
diff --git a/libgitg/gitg-diff-image-side-by-side.vala b/libgitg/gitg-diff-image-side-by-side.vala
new file mode 100644
index 0000000..4e15240
--- /dev/null
+++ b/libgitg/gitg-diff-image-side-by-side.vala
@@ -0,0 +1,290 @@
+/*
+ * This file is part of gitg
+ *
+ * Copyright (C) 2016 - Jesse van den Kieboom
+ *
+ * gitg 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.
+ *
+ * gitg 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 gitg. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+class Gitg.DiffImageSideBySide : Gtk.DrawingArea
+{
+       private Pango.Layout d_old_size_layout;
+       private Pango.Layout d_new_size_layout;
+
+       private static const int TEXT_SPACING = 6;
+
+       private Pango.Layout? old_size_layout
+       {
+               get
+               {
+                       if (d_old_size_layout == null && cache.old_pixbuf != null)
+                       {
+                               string message;
+
+                               if (cache.new_pixbuf != null)
+                               {
+                                       // Translators: this label is displayed below the image diff, %s
+                                       // is substituted with the size of the image
+                                       message = _("before (%s)");
+                               }
+                               else
+                               {
+                                       // Translators: this label is displayed below the image diff, %s
+                                       // is substituted with the size of the image
+                                       message = _("removed (%s)");
+                               }
+
+                               d_old_size_layout = 
create_pango_layout(message.printf(@"$(cache.old_pixbuf.get_width()) × $(cache.old_pixbuf.get_height())"));
+                       }
+
+                       return d_old_size_layout;
+               }
+       }
+
+       private Pango.Layout? new_size_layout
+       {
+               get
+               {
+                       if (d_new_size_layout == null && cache.new_pixbuf != null)
+                       {
+                               string message;
+
+                               if (cache.old_pixbuf != null)
+                               {
+                                       // Translators: this label is displayed below the image diff, %s
+                                       // is substituted with the size of the image
+                                       message = _("after (%s)");
+                               }
+                               else
+                               {
+                                       // Translators: this label is displayed below the image diff, %s
+                                       // is substituted with the size of the image
+                                       message = _("added (%s)");
+                               }
+
+                               d_new_size_layout = 
create_pango_layout(message.printf(@"$(cache.new_pixbuf.get_width()) × $(cache.new_pixbuf.get_height())"));
+                       }
+
+                       return d_new_size_layout;
+               }
+       }
+
+       public Gitg.DiffImageSurfaceCache cache { get; set; }
+       public int spacing { get; set; }
+
+       private struct Size
+       {
+               public int width;
+
+               public int image_width;
+               public int image_height;
+       }
+
+       private struct Sizing
+       {
+               public Size old_size;
+               public Size new_size;
+       }
+
+       private Sizing get_sizing(int width)
+       {
+               double ow = 0, oh = 0, nw = 0, nh = 0;
+
+               var old_pixbuf = cache.old_pixbuf;
+               var new_pixbuf = cache.new_pixbuf;
+
+               var window = get_window();
+
+               if (old_pixbuf != null)
+               {
+                       double xscale = 1, yscale = 1;
+
+                       if (window != null)
+                       {
+                               cache.get_old_surface(get_window()).get_device_scale(out xscale, out yscale);
+                       }
+
+                       ow = (double)old_pixbuf.get_width() / xscale;
+                       oh = (double)old_pixbuf.get_height() / yscale;
+               }
+
+               if (new_pixbuf != null)
+               {
+                       double xscale = 1, yscale = 1;
+
+                       if (window != null)
+                       {
+                               cache.get_new_surface(get_window()).get_device_scale(out xscale, out yscale);
+                       }
+
+                       nw = (double)new_pixbuf.get_width() / xscale;
+                       nh = (double)new_pixbuf.get_height() / yscale;
+               }
+
+               var tw = ow + nw;
+
+               width -= spacing;
+
+               double osw = 0, nsw = 0;
+
+               if (tw != 0)
+               {
+                       if (ow != 0)
+                       {
+                               osw = width * (ow / tw);
+                       }
+
+                       if (nw != 0)
+                       {
+                               nsw = width * (nw / tw);
+                       }
+               }
+
+               var oswi = double.min(osw, ow);
+               var nswi = double.min(nsw, nw);
+
+               double oshi = 0, nshi = 0;
+
+               if (ow != 0)
+               {
+                       oshi = oswi / ow * oh;
+               }
+
+               if (nw != 0)
+               {
+                       nshi = nswi / nw * nh;
+               }
+
+               return Sizing() {
+                       old_size = Size() {
+                               width = (int)osw,
+
+                               image_width = (int)oswi,
+                               image_height = (int)oshi
+                       },
+
+                       new_size = Size() {
+                               width = (int)nsw,
+
+                               image_width = (int)nswi,
+                               image_height = (int)nshi
+                       }
+               };
+       }
+
+       protected override void style_updated()
+       {
+               d_old_size_layout = null;
+               d_new_size_layout = null;
+       }
+
+       protected override void get_preferred_height_for_width(int width, out int minimum_height, out int 
natural_height)
+       {
+               var sizing = get_sizing(width);
+               var h = double.max(sizing.old_size.image_height, sizing.new_size.image_height);
+
+               var ol = old_size_layout;
+               var nl = new_size_layout;
+
+               int osw = 0, osh = 0, nsw = 0, nsh = 0;
+
+               if (ol != null)
+               {
+                       ol.get_pixel_size(out osw, out osh);
+               }
+
+               if (nl != null)
+               {
+                       nl.get_pixel_size(out nsw, out nsh);
+               }
+
+               h += TEXT_SPACING + int.max(osh, nsh);
+
+               minimum_height = (int)h;
+               natural_height = (int)h;
+       }
+
+       protected override Gtk.SizeRequestMode get_request_mode()
+       {
+               return Gtk.SizeRequestMode.HEIGHT_FOR_WIDTH;
+       }
+
+       protected override bool draw(Cairo.Context cr)
+       {
+               var window = get_window();
+
+               Gtk.Allocation alloc;
+               get_allocation(out alloc);
+
+               var sizing = get_sizing(alloc.width);
+
+               var old_surface = cache.get_old_surface(window);
+               var new_surface = cache.get_new_surface(window);
+
+               var ctx = get_style_context();
+
+               ctx.render_background(cr, alloc.x, alloc.y, alloc.width, alloc.height);
+
+               double max_height = double.max(sizing.old_size.image_height, sizing.new_size.image_height);
+
+               if (old_surface != null)
+               {
+                       var x = (sizing.old_size.width - sizing.old_size.image_width) * 2 / 3;
+                       var y = (max_height - sizing.old_size.image_height) / 2;
+
+                       cr.set_source_surface(old_surface, x, y);
+                       cr.paint();
+
+                       Pango.Rectangle rect;
+
+                       old_size_layout.get_pixel_extents(null, out rect);
+
+                       ctx.render_layout(cr,
+                                         x + rect.x + (sizing.old_size.image_width - rect.width) / 2,
+                                         rect.y + max_height + TEXT_SPACING,
+                                         old_size_layout);
+               }
+
+               if (new_surface != null)
+               {
+                       var x = (sizing.new_size.width - sizing.new_size.image_width) * 1 / 3;
+                       var y = (max_height - sizing.new_size.image_height) / 2;
+
+                       if (cache.old_pixbuf != null)
+                       {
+                               x += sizing.old_size.width + spacing;
+                       }
+
+                       cr.set_source_surface(new_surface, x, y);
+                       cr.paint();
+
+                       Pango.Rectangle rect;
+
+                       new_size_layout.get_pixel_extents(null, out rect);
+
+                       ctx.render_layout(cr,
+                                         x + rect.x + (sizing.new_size.image_width - rect.width) / 2,
+                                         rect.y + max_height + TEXT_SPACING,
+                                         new_size_layout);
+               }
+
+               return true;
+       }
+
+       protected override void realize()
+       {
+               base.realize();
+               queue_resize();
+       }
+}
diff --git a/libgitg/gitg-diff-image-slider.vala b/libgitg/gitg-diff-image-slider.vala
new file mode 100644
index 0000000..4179f1d
--- /dev/null
+++ b/libgitg/gitg-diff-image-slider.vala
@@ -0,0 +1,94 @@
+/*
+ * This file is part of gitg
+ *
+ * Copyright (C) 2016 - Jesse van den Kieboom
+ *
+ * gitg 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.
+ *
+ * gitg 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 gitg. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+class Gitg.DiffImageSlider : DiffImageComposite
+{
+       private double d_position;
+
+       public double position
+       {
+               get { return d_position; }
+
+               set
+               {
+                       var newpos = double.max(0, double.min(value, 1));
+
+                       if (newpos != d_position)
+                       {
+                               d_position = newpos;
+                               queue_draw();
+                       }
+               }
+
+               default = 0.5;
+       }
+
+       protected override bool draw(Cairo.Context cr)
+       {
+               base.draw(cr);
+
+               var window = get_window();
+
+               Gtk.Allocation alloc;
+               get_allocation(out alloc);
+
+               int image_width, image_height;
+               get_sizing(alloc.width, out image_width, out image_height);
+
+               var old_surface = cache.get_old_surface(window);
+               var new_surface = cache.get_new_surface(window);
+
+               int x = (alloc.width - image_width) / 2;
+               int y = 0;
+
+               int pos = (int)(image_width * position);
+
+               if (old_surface != null)
+               {
+                       cr.save();
+                       {
+                               cr.rectangle(x, y, pos, image_height);
+                               cr.clip();
+                               cr.set_source_surface(old_surface, x, y);
+                               cr.paint();
+                       }
+                       cr.restore();
+               }
+
+               if (new_surface != null)
+               {
+                       cr.save();
+                       {
+                               cr.rectangle(x + pos, y, image_width - pos, image_height);
+                               cr.clip();
+                               cr.set_source_surface(new_surface, x, y);
+                               cr.paint();
+                       }
+                       cr.restore();
+               }
+
+               return true;
+       }
+
+       protected override void realize()
+       {
+               base.realize();
+               queue_resize();
+       }
+}
diff --git a/libgitg/gitg-diff-image-surface-cache.vala b/libgitg/gitg-diff-image-surface-cache.vala
new file mode 100644
index 0000000..f9eff5a
--- /dev/null
+++ b/libgitg/gitg-diff-image-surface-cache.vala
@@ -0,0 +1,29 @@
+/*
+ * This file is part of gitg
+ *
+ * Copyright (C) 2016 - Jesse van den Kieboom
+ *
+ * gitg 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.
+ *
+ * gitg 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 gitg. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+interface Gitg.DiffImageSurfaceCache : Object
+{
+       public abstract Gdk.Pixbuf? old_pixbuf { get; construct set; }
+       public abstract Gdk.Pixbuf? new_pixbuf { get; construct set; }
+
+       public abstract Gdk.Window window { get; construct set; }
+
+       public abstract Cairo.Surface? get_old_surface(Gdk.Window window);
+       public abstract Cairo.Surface? get_new_surface(Gdk.Window window);
+}
diff --git a/libgitg/gitg-diff-view-file-renderer-image.vala b/libgitg/gitg-diff-view-file-renderer-image.vala
new file mode 100644
index 0000000..018efe0
--- /dev/null
+++ b/libgitg/gitg-diff-view-file-renderer-image.vala
@@ -0,0 +1,152 @@
+/*
+ * This file is part of gitg
+ *
+ * Copyright (C) 2016 - Jesse van den Kieboom
+ *
+ * gitg 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.
+ *
+ * gitg 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 gitg. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+[GtkTemplate (ui = "/org/gnome/gitg/ui/gitg-diff-view-file-renderer-image.ui")]
+class Gitg.DiffViewFileRendererImage : Gtk.Grid, DiffViewFileRenderer
+{
+       public Ggit.DiffDelta? delta { get; construct set; }
+       public Repository repository { get; construct set; }
+
+       [GtkChild( name = "diff_image_side_by_side" )]
+       private Gitg.DiffImageSideBySide d_diff_image_side_by_side;
+
+       [GtkChild( name = "diff_image_slider" )]
+       private Gitg.DiffImageSlider d_diff_image_slider;
+
+       [GtkChild( name = "scale_slider_adjustment" )]
+       private Gtk.Adjustment d_scale_slider_adjustment;
+
+       [GtkChild( name = "diff_image_overlay" )]
+       private Gitg.DiffImageOverlay d_diff_image_overlay;
+
+       [GtkChild( name = "scale_overlay_adjustment" )]
+       private Gtk.Adjustment d_scale_overlay_adjustment;
+
+       [GtkChild( name = "diff_image_difference" )]
+       private Gitg.DiffImageDifference d_diff_image_difference;
+
+       [GtkChild( name = "stack_switcher" )]
+       private Gtk.StackSwitcher d_stack_switcher;
+
+       private SurfaceCache d_cache;
+
+       public DiffViewFileRendererImage(Repository repository, Ggit.DiffDelta delta)
+       {
+               Object(repository: repository, delta: delta);
+       }
+
+       construct
+       {
+               d_cache = new SurfaceCache(pixbuf_for_file(delta.get_old_file()),
+                                          pixbuf_for_file(delta.get_new_file()));
+
+               d_diff_image_side_by_side.cache = d_cache;
+               d_diff_image_slider.cache = d_cache;
+               d_diff_image_overlay.cache = d_cache;
+               d_diff_image_difference.cache = d_cache;
+
+               if (d_cache.old_pixbuf == null || d_cache.new_pixbuf == null ||
+                   d_cache.old_pixbuf.get_width() != d_cache.new_pixbuf.get_width() ||
+                   d_cache.old_pixbuf.get_height() != d_cache.new_pixbuf.get_height())
+               {
+                       d_stack_switcher.sensitive = false;
+               }
+
+               d_scale_slider_adjustment.bind_property("value", d_diff_image_slider, "position", 
BindingFlags.DEFAULT | BindingFlags.SYNC_CREATE);
+               d_scale_overlay_adjustment.bind_property("value", d_diff_image_overlay, "alpha", 
BindingFlags.DEFAULT | BindingFlags.SYNC_CREATE);
+       }
+
+       private Gdk.Pixbuf? pixbuf_for_file(Ggit.DiffFile file)
+       {
+               if ((file.get_flags() & Ggit.DiffFlag.VALID_ID) == 0 || file.get_oid().is_zero())
+               {
+                       return null;
+               }
+
+               Ggit.Blob blob;
+
+               try
+               {
+                       blob = repository.lookup<Ggit.Blob>(file.get_oid());
+               }
+               catch (Error e)
+               {
+                       stderr.printf(@"ERROR: failed to load image blob: $(e.message)\n");
+                       return null;
+               }
+
+               var stream = new MemoryInputStream.from_data(blob.get_raw_content(), null);
+
+               try
+               {
+                       return new Gdk.Pixbuf.from_stream(stream);
+               }
+               catch (Error e)
+               {
+                       stderr.printf(@"ERROR: failed to create pixbuf: $(e.message)\n");
+                       return null;
+               }
+       }
+
+       public void add_hunk(Ggit.DiffHunk hunk, Gee.ArrayList<Ggit.DiffLine> lines)
+       {
+       }
+
+       private class SurfaceCache : Object, Gitg.DiffImageSurfaceCache {
+               private Cairo.Surface? d_old_surface;
+               private Cairo.Surface? d_new_surface;
+
+               public Gdk.Pixbuf? old_pixbuf { get; construct set; }
+               public Gdk.Pixbuf? new_pixbuf { get; construct set; }
+
+               public Gdk.Window window { get; construct set; }
+
+               public SurfaceCache(Gdk.Pixbuf? old_pixbuf, Gdk.Pixbuf? new_pixbuf)
+               {
+                       Object(old_pixbuf: old_pixbuf, new_pixbuf: new_pixbuf);
+               }
+
+               public Cairo.Surface? get_old_surface(Gdk.Window window)
+               {
+                       return get_cached_surface(window, old_pixbuf, ref d_old_surface);
+               }
+
+               public Cairo.Surface? get_new_surface(Gdk.Window window)
+               {
+                       return get_cached_surface(window, new_pixbuf, ref d_new_surface);
+               }
+
+               private Cairo.Surface? get_cached_surface(Gdk.Window window, Gdk.Pixbuf? pixbuf, ref 
Cairo.Surface? cached)
+               {
+                       if (pixbuf == null)
+                       {
+                               return null;
+                       }
+
+                       if (cached == null)
+                       {
+                               cached = Gdk.cairo_surface_create_from_pixbuf(pixbuf, 0, window);
+                       }
+
+                       return cached;
+               }
+       }
+}
+
+// ex:ts=4 noet
diff --git a/libgitg/gitg-diff-view-file.vala b/libgitg/gitg-diff-view-file.vala
index 1d4dc00..afec060 100644
--- a/libgitg/gitg-diff-view-file.vala
+++ b/libgitg/gitg-diff-view-file.vala
@@ -128,6 +128,11 @@ class Gitg.DiffViewFile : Gtk.Grid
        public DiffViewFile.image(Repository? repository, Ggit.DiffDelta delta)
        {
                this(repository, delta);
+
+               this.renderer = new DiffViewFileRendererImage(repository, delta);
+               this.renderer.show();
+
+               d_diff_stat_file.hide();
        }
 
        protected override void constructed()
diff --git a/libgitg/gitg-diff-view.vala b/libgitg/gitg-diff-view.vala
index fedde41..2865775 100644
--- a/libgitg/gitg-diff-view.vala
+++ b/libgitg/gitg-diff-view.vala
@@ -52,6 +52,8 @@ public class Gitg.DiffView : Gtk.Grid
        private uint d_reveal_options_timeout;
        private uint d_unreveal_options_timeout;
 
+       private static Gee.HashSet<string> s_image_mime_types;
+
        public Ggit.DiffOptions options
        {
                get
@@ -224,6 +226,16 @@ public class Gitg.DiffView : Gtk.Grid
 
        static construct
        {
+               s_image_mime_types = new Gee.HashSet<string>();
+
+               foreach (var format in Gdk.Pixbuf.get_formats())
+               {
+                       foreach (var mime_type in format.get_mime_types())
+                       {
+                               s_image_mime_types.add(mime_type);
+                       }
+               }
+
                try
                {
                        s_message_regexp = new Regex(".*[\\R\\s]*(?P<message>(?:.|\\R)*?)\\s*$");
@@ -336,13 +348,13 @@ public class Gitg.DiffView : Gtk.Grid
                }
        }
 
-       private string? primary_path(Gitg.DiffViewFile f)
+       private string? primary_path(Ggit.DiffDelta delta)
        {
-               var path = f.delta.get_old_file().get_path();
+               var path = delta.get_old_file().get_path();
 
                if (path == null)
                {
-                       path = f.delta.get_new_file().get_path();
+                       path = delta.get_new_file().get_path();
                }
 
                return path;
@@ -479,7 +491,34 @@ public class Gitg.DiffView : Gtk.Grid
                                                current_is_binary = true;
                                        }
 
-                                       if (current_is_binary)
+                                       string? mime_type_for_image = null;
+
+                                       if (info == null || info.new_file_content_type == null)
+                                       {
+                                               // Guess mime type from old file name in the case of a 
deleted file
+                                               var oldpath = delta.get_old_file().get_path();
+
+                                               if (oldpath != null)
+                                               {
+                                                       bool uncertain;
+                                                       var ctype = 
ContentType.guess(Path.get_basename(oldpath), null, out uncertain);
+
+                                                       if (ctype != null)
+                                                       {
+                                                               mime_type_for_image = 
ContentType.get_mime_type(ctype);
+                                                       }
+                                               }
+                                       }
+                                       else
+                                       {
+                                               mime_type_for_image = 
ContentType.get_mime_type(info.new_file_content_type);
+                                       }
+
+                                       if (mime_type_for_image != null && 
s_image_mime_types.contains(mime_type_for_image))
+                                       {
+                                               current_file = new Gitg.DiffViewFile.image(repository, delta);
+                                       }
+                                       else if (current_is_binary)
                                        {
                                                current_file = new Gitg.DiffViewFile.binary(repository, 
delta);
                                        }
@@ -550,7 +589,7 @@ public class Gitg.DiffView : Gtk.Grid
 
                        if (preserve_expanded && f.expanded)
                        {
-                               var path = primary_path(f);
+                               var path = primary_path(f.delta);
 
                                if (path != null)
                                {
@@ -567,7 +606,7 @@ public class Gitg.DiffView : Gtk.Grid
                for (var i = 0; i < files.size; i++)
                {
                        var file = files[i];
-                       var path = primary_path(file);
+                       var path = primary_path(file.delta);
 
                        file.expanded = d_commit_details.expanded || (path != null && 
was_expanded.contains(path));
 
diff --git a/libgitg/resources/resources.xml b/libgitg/resources/resources.xml
index 3b75da3..b47a8e2 100644
--- a/libgitg/resources/resources.xml
+++ b/libgitg/resources/resources.xml
@@ -5,6 +5,7 @@
     <file compressed="true" preprocess="xml-stripblanks">ui/gitg-authentication-dialog.ui</file>
     <file compressed="true" preprocess="xml-stripblanks">ui/gitg-diff-view.ui</file>
     <file compressed="true" preprocess="xml-stripblanks">ui/gitg-diff-view-file.ui</file>
+    <file compressed="true" preprocess="xml-stripblanks">ui/gitg-diff-view-file-renderer-image.ui</file>
     <file compressed="true" preprocess="xml-stripblanks">ui/gitg-diff-view-file-renderer-text.ui</file>
     <file compressed="true" preprocess="xml-stripblanks">ui/gitg-diff-view-file-renderer-binary.ui</file>
     <file compressed="true" preprocess="xml-stripblanks">ui/gitg-diff-view-options.ui</file>
diff --git a/libgitg/resources/ui/gitg-diff-view-file-renderer-image.ui 
b/libgitg/resources/ui/gitg-diff-view-file-renderer-image.ui
new file mode 100644
index 0000000..7532dbe
--- /dev/null
+++ b/libgitg/resources/ui/gitg-diff-view-file-renderer-image.ui
@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <requires lib="gtk+" version="3.16"/>
+  <template class="GitgDiffViewFileRendererImage" parent="GtkGrid">
+    <property name="visible">True</property>
+    <property name="orientation">vertical</property>
+    <child>
+      <object class="GtkStackSwitcher" id="stack_switcher">
+        <property name="visible">True</property>
+        <property name="stack">stack</property>
+        <property name="hexpand">True</property>
+        <property name="halign">center</property>
+      </object>
+    </child>
+    <child>
+      <object class="GtkStack" id="stack">
+        <property name="visible">True</property>
+        <property name="vexpand">True</property>
+        <property name="hexpand">True</property>
+        <child>
+          <object class="GtkGrid" id="grid_side_by_side">
+            <property name="visible">True</property>
+            <property name="vexpand">True</property>
+            <property name="hexpand">True</property>
+            <property name="margin">12</property>
+            <child>
+              <object class="GitgDiffImageSideBySide" id="diff_image_side_by_side">
+                <property name="visible">True</property>
+                <property name="hexpand">True</property>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="name">side-by-side</property>
+            <property name="title" translatable="yes">Side by side</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkGrid" id="grid_slider">
+            <property name="visible">True</property>
+            <property name="orientation">vertical</property>
+            <property name="vexpand">True</property>
+            <property name="hexpand">True</property>
+            <property name="halign">center</property>
+            <property name="valign">start</property>
+            <child>
+              <object class="GtkScale" id="scale_slider">
+                <property name="visible">True</property>
+                <property name="orientation">horizontal</property>
+                <property name="draw_value">False</property>
+                <property name="adjustment">scale_slider_adjustment</property>
+              </object>
+            </child>
+            <child>
+              <object class="GitgDiffImageSlider" id="diff_image_slider">
+                <property name="visible">True</property>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="name">slider</property>
+            <property name="title" translatable="yes">Slider</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkGrid" id="grid_overlay">
+            <property name="visible">True</property>
+            <property name="orientation">vertical</property>
+            <property name="vexpand">True</property>
+            <property name="hexpand">True</property>
+            <property name="halign">center</property>
+            <property name="valign">start</property>
+            <child>
+              <object class="GtkScale" id="scale_overlay">
+                <property name="visible">True</property>
+                <property name="orientation">horizontal</property>
+                <property name="draw_value">False</property>
+                <property name="adjustment">scale_overlay_adjustment</property>
+              </object>
+            </child>
+            <child>
+              <object class="GitgDiffImageOverlay" id="diff_image_overlay">
+                <property name="visible">True</property>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="name">overlay</property>
+            <property name="title" translatable="yes">Overlay</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkGrid" id="grid_difference">
+            <property name="visible">True</property>
+            <property name="vexpand">True</property>
+            <property name="hexpand">True</property>
+            <property name="halign">center</property>
+            <property name="valign">start</property>
+            <child>
+              <object class="GitgDiffImageDifference" id="diff_image_difference">
+                <property name="visible">True</property>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="name">difference</property>
+            <property name="title" translatable="yes">Difference</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+  </template>
+  <object class="GtkAdjustment" id="scale_slider_adjustment">
+    <property name="lower">0</property>
+    <property name="upper">1</property>
+    <property name="value">0.5</property>
+  </object>
+  <object class="GtkAdjustment" id="scale_overlay_adjustment">
+    <property name="lower">0</property>
+    <property name="upper">1</property>
+    <property name="value">0.5</property>
+  </object>
+</interface>
diff --git a/libgitg/resources/ui/libgitg-style.css b/libgitg/resources/ui/libgitg-style.css
index 494e7fe..d9d2759 100644
--- a/libgitg/resources/ui/libgitg-style.css
+++ b/libgitg/resources/ui/libgitg-style.css
@@ -117,3 +117,11 @@ GitgDiffViewOptions {
 .dark .language-frame {
        background-color: #535353;
 }
+
+GitgDiffImageSideBySide.old {
+       border: 10px solid #ddddff;
+}
+
+GitgDiffImageSideBySide.new {
+       border: 10px solid #ddffdd;
+}


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