Clone in a terminal:
BackgroundImage.vala
/*
	Copyright (C) 2012 2013 2014 2015 Johan Mattsson

	This library is free software; you can redistribute it and/or modify 
	it under the terms of the GNU Lesser General Public License as 
	published by the Free Software Foundation; either version 3 of the 
	License, or (at your option) any later version.

	This library 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 
	Lesser General Public License for more details.
*/

using Cairo;
using Math;

namespace BirdFont {
	
public class BackgroundImage : GLib.Object {
	
	public string name = "";
	public Gee.ArrayList<BackgroundSelection> selections;

	/** Image position in canvas coordinates. */
	public double img_x = 0;
	public double img_y = 0;

	public double img_scale_x {
		get {
			return img_scale_x_size;
		}
		
		set {
			if (value > 0.0001) {
				img_scale_x_size = value;
			}
		}
	}
	
	public double img_scale_y {
		get {
			return img_scale_y_size;
		}
		
		set {
			if (value > 0.0001) {
				img_scale_y_size = value;
			}
		}
	}
	
	private double img_scale_x_size = 1;	
	private double img_scale_y_size = 1;
	
	public double img_rotation = 0;
	private double preview_img_rotation = 0;
	
	private int size = -1;

	public int active_handle = -1;
	public int selected_handle = -1;
	
	private ImageSurface? background_image = null;
	private ImageSurface? original_image = null;
	
	private string path;

	public bool high_contrast = false;
	private Gee.ArrayList<TracedPoint> points = new Gee.ArrayList<TracedPoint> ();
	private Gee.ArrayList<TracedPoint> start_points = new Gee.ArrayList<TracedPoint> ();
	
	public signal void updated ();

	private ScaledBackgrounds? scaled = null;
	private ImageSurface? contrast_image = null;

	public double img_offset_x {
		get { return img_x + Glyph.xc (); }
		set { img_x = value - Glyph.xc (); }
	}

	public double img_offset_y {
		get { return Glyph.yc () - img_y; }
		set { img_y = Glyph.yc () - value; }
	}
	
	public int size_margin {
		get {
			if (unlikely (size == -1)) {
				size = (int) (Math.sqrt (Math.pow (get_img ().get_height (), 2) 
					+ Math.pow (get_img ().get_width (), 2)) + 0.5);
			}
			
			return size;
		}
	}

	public int margin_left {
		get {
			return size_margin - get_img ().get_width ();
		}
	}

	public int margin_top {
		get {
			return size_margin - get_img ().get_height ();
		}
	}
	
	public double img_middle_x {
		get { return img_x + (size_margin * img_scale_x) / 2; }
		set { img_x = value - (size_margin * img_scale_x) / 2; }
	}
	
	public double img_middle_y {
		get { return img_y - (size_margin * img_scale_y) / 2; }
		set { img_y = value + (size_margin * img_scale_y) / 2; }
	}
				
	public BackgroundImage (string file_name) {
		path = file_name;
		selections = new Gee.ArrayList<BackgroundSelection> ();
	}

	public BackgroundImage copy () {
		BackgroundImage bg = new BackgroundImage (path);
			
		bg.img_x = img_x;
		bg.img_y = img_y;

		bg.img_scale_x = img_scale_x;
		bg.img_scale_y = img_scale_y;
		bg.img_rotation = img_rotation;

		bg.high_contrast = high_contrast;
		
		foreach (BackgroundSelection b in selections) {
			bg.selections.add (b);
		}

		return bg;		
	}

	public ScaledBackgrounds get_scaled_backgrounds () {
		if (scaled == null) {
			ImageSurface rotated = rotate ((ImageSurface) get_padded_image ());
			scaled = new ScaledBackgrounds (rotated);
		}
		
		return (!) scaled;
 	}

	public void add_selection (BackgroundSelection bs) {
		selections.add (bs);
	}

	public void set_high_contrast (bool t) {
		high_contrast = t;
	}

	public double get_margin_width () {
		return ((size_margin - get_img ().get_width ()) / 2.0);
	}

	public double get_margin_height () {
		return ((size_margin - get_img ().get_height ()) / 2.0);
	}

	public void set_img_offset (double x, double y) {
		img_offset_x = x;
		img_offset_y = y;
	}

	public void set_position (double coordinate_x, double coordinate_y) {
		img_x = coordinate_x;
		img_y = coordinate_y;
	}
	
	public ImageSurface get_img () {
		if (!path.has_suffix (".png")) {
			create_png ();
		}
		
		if (background_image == null) {
			background_image = new ImageSurface.from_png (path);
			original_image = new ImageSurface.from_png (path);
		}
		
		return (!) background_image;
	}

	public ImageSurface get_original () {
		if (!path.has_suffix (".png")) {
			create_png ();
		}
		
		if (background_image == null) {
			background_image = new ImageSurface.from_png (path);
			original_image = new ImageSurface.from_png (path);			
		}
		
		return (!) original_image;
	}
	
	public bool is_valid () {
		FileInfo file_info;
		File file = File.new_for_path (path);
	
		if (!file.query_exists ()) {
			return false;
		}
		
		try {
			file_info = file.query_info ("*", FileQueryInfoFlags.NONE);
			
			if (file_info.get_size () == 0) {
				return false;
			}
		} catch (GLib.Error e) {
			warning (e.message);
			return false;
		}
				
		return true;
	}

	public string get_png_base64 () {
		try {
			File file = File.new_for_path (path);
			FileInfo file_info = file.query_info ("*", FileQueryInfoFlags.NONE);
			uint8[] buffer = new uint8[file_info.get_size ()];
			FileInputStream file_stream;
			DataInputStream png_stream;
			
			if (!file.query_exists ()) {
				warning (@"Can't to save image $path, file does not exist.");
				return "";
			}
			
			if (is_null (buffer)) {
				warning (@"Can not allocate a buffer of $(file_info.get_size ()) bytes to store $path.");
				return "";
			}
			
			file_stream = file.read ();
			
			png_stream = new DataInputStream (file_stream);
			png_stream.read (buffer);
			
			return Base64.encode (buffer);
		} catch (GLib.Error e) {
			warning (e.message);
		}
		
		return "";

	}

	public void create_background_folders (Font font) {
		File dir;
		
		dir = BirdFont.get_settings_directory ();
		if (!dir.query_exists ()) {
			DirUtils.create ((!) dir.get_path (), 0755);
		}
		
		dir = font.get_backgrounds_folder ();
		if (!dir.query_exists ()) {
			DirUtils.create ((!) dir.get_path (), 0755);
		}

		dir = get_child (font.get_backgrounds_folder (), "parts");
		if (!dir.query_exists ()) {
			DirUtils.create ((!) dir.get_path (), 0755);
		}
	}

	public void copy_if_new (File destination) {
		if (!destination.query_exists ()) {
			copy_file (destination);
		}
	}

	public void copy_file (File destination) {
		File source;
		FileInfo info;
		
		try {
			if (destination.query_exists ()) {
				info = destination.query_info ("standard::*", FileQueryInfoFlags.NONE);
				if (info.get_file_type () == FileType.DIRECTORY) {
					warning (@"$((!) destination.get_path ()) is a directory.");
					return;
				}
			}
			
			if (!((!)destination.get_parent ()).query_exists ()) {
				warning (@"Directory for file $((!) destination.get_path ()) is not created.");
				return;
			}
			
			if (destination.query_exists ()) {
				warning (@"Image $((!) destination.get_path ()) is already created.");
				return;
			}
			
			source = File.new_for_path (path);
			source.copy (destination, FileCopyFlags.NONE);
		} catch (GLib.Error e) {
			warning (e.message);
		}
	}

	public string get_sha1 () {
		try {
			File file = File.new_for_path (path);
			FileInfo file_info;
			uint8[] buffer;
			FileInputStream file_stream;
			DataInputStream png_stream;
			
			if (!file.query_exists ()) {
				warning (@"Can't save $path file does not exist.");
				return "";
			}
			
			file_info = file.query_info ("*", FileQueryInfoFlags.NONE);
			
			if (file_info.get_size () == 0) {
				warning (@"length of image $path is zero");
				return "";
			}
			
			buffer = new uint8[file_info.get_size ()];
			file_stream = file.read ();
			png_stream = new DataInputStream (file_stream);

			png_stream.read (buffer);
			
			return Checksum.compute_for_data (ChecksumType.SHA1, buffer);
		} catch (GLib.Error e) {
			warning (e.message);
		}
		
		return "";
	}
	
	private void create_png () {
		string file_name = @"$path.png";
		Font font = BirdFont.get_current_font ();
		File folder = font.get_backgrounds_folder ();
		File original = File.new_for_path (file_name);
		File png_image = get_child (folder, @"full_$((!)original.get_basename ())");
		bool converted;
		
		if (png_image.query_exists ()) {
			path = (!) png_image.get_path ();
			return;
		}
		
		if (is_null (path)) {
			warning ("Background image path is null.");
			return;
		}
		
		try  {
			folder.make_directory ();
		} catch (GLib.Error e) {
			warning (e.message);
		}
		
		converted = MainWindow.native_window.convert_to_png (path.dup (), ((!) png_image.get_path ()).dup ());
		
		if (!converted) {
			warning ("Failed to convert image: $(path)");
			return;
		}
		
		path = (!) png_image.get_path ();
	}

	private ScaledBackgrounds get_image () {
		return get_scaled_backgrounds ();
	}
	
	public void start_rotation_preview () {
		preview_img_rotation = img_rotation;
	}

	public void preview_img_rotation_from_coordinate (double x, double y, double view_zoom) {
		double rotation;
		ScaledBackgrounds backgrounds;
		ScaledBackground backgound;
		
		if (get_img_rotation_from_coordinate (x, y, out rotation)) {	
			backgrounds = get_scaled_backgrounds ();
			backgound = backgrounds.get_image (view_zoom * img_scale_x); // FIXME: y
			
			img_rotation = rotation;
			
			if (!high_contrast) {
				backgound.rotate (rotation - preview_img_rotation);
			} else {
				contrast_image = null;
			}
		}
	}

	public void set_img_rotation_from_coordinate (double x, double y) {
		double rotation;
		if (get_img_rotation_from_coordinate (x, y, out rotation)) {
			img_rotation = rotation;
			Task task = new Task (cache_scaled_image); // cache all sizes
			MainWindow.run_blocking_task (task);
		}
	}
	
	void cache_scaled_image () {
		scaled = null;
		contrast_image = null;
		get_image ();
	}
	
	public bool get_img_rotation_from_coordinate (double x, double y, out double rotation) {
		double bcx, bcy;
		double a, b, c, length;

		rotation = 0;
		
		bcx = img_middle_x;
		bcy = img_middle_y;

		a = bcx - x;
		b = bcy - y;
		c = a * a + b * b;
		
		if (c == 0) {
			return false;
		}
		
		length = sqrt (fabs (c));	
		
		if (c < 0) {
			length = -length;
		}
	
		rotation = (y > bcy) ? acos (a / length) + PI : -acos (a / length) + PI;
		return true;
	}
	
	public void set_img_scale (double xs, double ys) {
		img_scale_x = xs;
		img_scale_y = ys;
	}

	public void reset_scale (Glyph g) {
		double w, h;

		w = g.get_width ();
		h = g.get_height ();

		img_scale_x = 1;			
		img_scale_y = 1;
		
		img_offset_x = g.get_line ("left").pos;
		img_offset_y = g.get_line ("top").pos;		
	}

	public void draw (Context cr, WidgetAllocation allocation,
			double view_offset_x, double view_offset_y, double view_zoom) {
				
		double scale_x, scale_y;
		double image_scale_x, image_scale_y;

		ScaledBackgrounds backgrounds = get_scaled_backgrounds ();
		
		if (unlikely (get_img ().status () != Cairo.Status.SUCCESS)) {
			warning (@"Background image is invalid. (\"$path\")\n");
			MainWindow.get_current_glyph ().set_background_visible (false);
			return;
		}

		image_scale_x = img_scale_x;
		image_scale_y = img_scale_y;

		ImageSurface scaled_image;
		Context scaled_context;

		if (!high_contrast) {
			ScaledBackground scaled;
			ScaledBackgroundPart part;

			scaled = backgrounds.get_image (view_zoom * img_scale_x); // FIXME: y
			
			double part_offset_x = img_offset_x - view_offset_x;
			part_offset_x /= img_scale_x / scaled.get_scale ();
			part_offset_x = -part_offset_x;

			double part_offset_y = img_offset_y - view_offset_y;
			part_offset_y /= img_scale_y / scaled.get_scale ();
			part_offset_y = -part_offset_y;

			double part_allocation_width;
			double part_allocation_height;

			part_allocation_height = allocation.height;
			part_allocation_height /= view_zoom;
			part_allocation_height /= image_scale_x;

			part_allocation_width = allocation.width;
			part_allocation_width /= view_zoom;
			part_allocation_width /= image_scale_y;
			
			part = scaled.get_part (part_offset_x, part_offset_y, 
				(int) (part_allocation_width), (int) (part_allocation_height)); 

			scale_x = view_zoom * image_scale_x;
			scale_y = view_zoom * image_scale_y;
			
			scale_x /= part.get_scale ();
			scale_y /= part.get_scale ();
						
			scaled_image = new ImageSurface (Format.ARGB32, allocation.width, allocation.height);
			
			scaled_context = new Context (scaled_image);

			scaled_context.scale (scale_x, scale_y);
			
			double scaled_x = part.offset_x;
			double scaled_y = part.offset_y;
			
			scaled_x += view_zoom * (img_offset_x / scale_x - view_offset_x / scale_x);
			scaled_y += view_zoom * (img_offset_y / scale_y - view_offset_y / scale_y);
			
			scaled_context.set_source_surface (part.get_image (), scaled_x, scaled_y);
			scaled_context.paint ();
		} else {
			ImageSurface contrast = get_contrast_image ();
			
			image_scale_x = img_scale_x * ((double) size_margin / contrast.get_width ());
			image_scale_y = img_scale_y * ((double) size_margin / contrast.get_height ());			
			
			scaled_image = new ImageSurface (Format.ARGB32, allocation.width, allocation.height);
			Context contrast_context = new Context (scaled_image);
			contrast_context.save ();

			contrast_context.set_source_rgba (1, 1, 1, 1);
			contrast_context.rectangle (0, 0, allocation.width, allocation.height);
			contrast_context.fill ();

			// scale both canvas and image at the same time
			scale_x = view_zoom * image_scale_x;
			scale_y = view_zoom * image_scale_y;
			
			contrast_context.scale (scale_x, scale_y);
			contrast_context.translate (-view_offset_x / image_scale_x, -view_offset_y / image_scale_y);
			
			contrast_context.set_source_surface (contrast, img_offset_x / image_scale_x, img_offset_y / image_scale_y);

			contrast_context.paint ();
			contrast_context.restore ();
		}
		
		// add it
		cr.save ();		
		cr.set_source_surface (scaled_image, 0, 0);
		cr.paint ();
		cr.restore ();
	}
	
	public Surface get_padded_image () {
		double x, y;
		double iw, ih;
		int h, w;
		double oy, ox;
		
		Surface o;
		Surface sg;
		Context cg;

		double wc, hc;
		
		o = get_original ();
		
		// add margin
		sg = new Surface.similar (o, o.get_content (), size_margin, size_margin);
		cg = new Context (sg);
		
		wc = get_margin_width ();
		hc = get_margin_height ();
		
		Theme.color (cg, "Background 1");
		cg.rectangle (0, 0, size_margin, size_margin);
		cg.fill ();
		
		cg.set_source_surface (get_img (), wc, hc);
		cg.paint ();

		x = Glyph.reverse_path_coordinate_x (img_offset_x);
		y = Glyph.reverse_path_coordinate_y (img_offset_y);
		
		ih = (int) get_img ().get_height ();
		iw = (int) get_img ().get_width ();
		
		w = (int) iw;
		h = (int) ih;

		oy = size_margin;
		ox = size_margin;

		return sg;
	}
	
	private ImageSurface rotate (ImageSurface padded_image) {
		return rotate_image (padded_image, img_rotation);
	}
	
	public static ImageSurface rotate_image (ImageSurface padded_image, double angle) {
		ImageSurface s;
		Context c;
		
		int w = padded_image.get_width ();
		int h = padded_image.get_height ();
		
		s = new ImageSurface (Format.ARGB32, w, h);
		c = new Context (s);
	
		c.save ();

		c.translate (w * 0.5, h * 0.5);
		c.rotate (angle);
		c.translate (-w * 0.5, -h * 0.5);
		
		c.set_source_surface (padded_image, 0, 0);
		c.paint ();	
		c.restore ();
		
		return s;
	}
	
	public void handler_release (double nx, double ny) {		
		selected_handle = 0;
		handler_move (nx, ny);
	}
			
	public void handler_press (double nx, double ny) {		
		if (is_over_rotate (nx, ny)) {
			selected_handle = 2;
		} else if (is_over_resize (nx, ny)) {
			selected_handle = 1;
		} else {
			selected_handle = 0;
		}	
	}

	bool is_over_rotate (double nx, double ny) {
		double x, y, d;

		x = Glyph.reverse_path_coordinate_x (img_middle_x);
		y = Glyph.reverse_path_coordinate_y (img_middle_y);

		x += cos (img_rotation) * 75;
		y += sin (img_rotation) * 75;

		d = Path.distance (x, nx, y, ny);
		
		return d < 15;
	}

	bool is_over_resize (double nx, double ny) {
		double x, y, size;
		bool inx, iny;
	
		size = 12;

		x = img_middle_x - (img_scale_x * get_img ().get_width () / 2);
		y = img_middle_y - (img_scale_y * get_img ().get_height () / 2);
		
		x = Glyph.reverse_path_coordinate_x (x);
		y = Glyph.reverse_path_coordinate_y (y);
		
		inx = x - size <= nx <= x + size;
		iny = y - size <= ny <= y + size;
	
		return inx && iny;
	}

	public void handler_move (double nx, double ny) {
		int prev_handle = active_handle;

		if (is_over_rotate (nx, ny)) {
			active_handle = 2;
		} else if (is_over_resize (nx, ny)) {
			active_handle = 1;
		} else {
			active_handle = 0;
		}
		
		if (prev_handle != active_handle) {
			GlyphCanvas.redraw ();
		}
	}
	
	public void draw_handle (Context cr, Glyph g) {
		draw_resize_handle (cr, g);
		draw_rotate_handle (cr, g);
	}

	public void draw_resize_handle (Context cr, Glyph g) {
		double x, y;
		cr.save ();
		
		Theme.color (cr, "Menu Background");

		x = img_middle_x - (img_scale_x * get_img ().get_width () / 2);
		y = img_middle_y - (img_scale_y * get_img ().get_height () / 2);

		x = Glyph.reverse_path_coordinate_x (x);
		y = Glyph.reverse_path_coordinate_y (y);
			
		draw_handle_triangle (x, y, cr, g, 6);
		
		cr.restore ();
	}
		
	public void draw_rotate_handle (Context cr, Glyph g) {
		double x, y, hx, hy, x2, y2;
		
		double ivz = 1.0 / (g.view_zoom);
		
		cr.save ();
		
		cr.scale (g.view_zoom, g.view_zoom);
		
		if (selected_handle == 2) {
			Theme.color (cr, "Highlighted 1");
		} else if (active_handle == 2) {
			Theme.color (cr, "Default Background");
		} else {
			Theme.color (cr, "Menu Background");
		}

		x = img_offset_x - g.view_offset_x + (size_margin / 2) * img_scale_x;
		y = img_offset_y - g.view_offset_y + (size_margin / 2) * img_scale_y;
				
		cr.rectangle (x, y, 5 * ivz, 5 * ivz);
		cr.fill ();

		hx = cos (img_rotation) * 75 * ivz;
		hy = sin (img_rotation) * 75 * ivz;
		
		x2 = x + hx;
		y2 = y + hy;

		cr.rectangle (x2, y2, 5 * ivz, 5 * ivz);
		cr.fill ();

		cr.set_line_width (ivz);
		cr.move_to (x + 2.5 * ivz, y + 2.5 * ivz);
		cr.line_to (x2 + 2.5 * ivz, y2 + 2.5 * ivz);
		cr.stroke ();
										
		cr.restore ();
	}

	void draw_handle_triangle (double x, double y, Context cr, Glyph g, int direction, double s = 1) 
		requires (0 < direction < 8)
	{
		double ivz = 1.0 / (g.view_zoom);
		double size;
		
		cr.save ();
		cr.set_line_width (ivz);
		
		if (selected_handle == 1) Theme.color (cr, "Highlighted 1");
		else if (active_handle == 1) Theme.color (cr, "Default Background");
		else Theme.color (cr, "Menu Background");

		size = (8) * s;
		
		cr.scale (1, 1);
		cr.new_path ();
		
		// up + left
		if (direction == 1) {
			cr.move_to (x - size, y - size);
			cr.line_to (x + size, y - size);
			cr.line_to (x - size, y + size);	
		}
		
		if (direction == 6) {
			cr.move_to (x + size, y + size);	
			cr.line_to (x - size, y + size);
			cr.line_to (x - size, y - size);	
		}
		
		cr.close_path();
		cr.fill ();

		cr.restore ();
		
	}

	public void update_background () {
		background_image = null;
		contrast_image = null;
		
		GlyphCanvas.redraw ();
		updated ();
	}

	ImageSurface get_contrast_image () {
		if (contrast_image == null) {
			contrast_image  = get_contrast_image_surface ();
		}
		
		return (!) contrast_image;
	}

	ImageSurface get_contrast_image_surface () {
		ImageSurface s;
		Context c;

		ImageSurface sg;		
		int scaled_width;
		
		unowned uchar[] pix_buff;
		int i, len;
		double thres;
		int stride;
		
		ImageSurface img;
		ImageSurface ns;

		double trace_resolution = DrawingTools.auto_trace_resolution.get_value ();
		double threshold = DrawingTools.background_threshold.get_value ();
		
		thres = (threshold - 0.5) * 255;
		
		scaled_width = (int) (600 * trace_resolution);

		s = new ImageSurface (Format.RGB24, scaled_width, scaled_width);
		sg = (ImageSurface) get_padded_image ();	
		sg = rotate (sg);
		c = new Context (s);
	
		c.save ();
		Theme.color (c, "Background 1");
		c.rectangle (0, 0, scaled_width, scaled_width);
		c.fill ();

		c.translate (scaled_width * 0.5, scaled_width * 0.5);
		c.rotate (img_rotation);
		c.translate (-scaled_width * 0.5, -scaled_width * 0.5);

		c.scale ((double) scaled_width / sg.get_width (), (double) scaled_width / sg.get_height ());

		c.set_source_surface (sg, 0, 0);
		c.paint ();	
		c.restore ();

		img = (ImageSurface) s;
		pix_buff = img.get_data ();

		len = s.get_height () * s.get_stride ();

		uint8* outline_img = new uint8[len];

		for (i = 0; i < len - 4; i += 4) {
			uint8 o = (uint8) ((pix_buff[i] + pix_buff[i + 1] + pix_buff[i + 2]) / 3.0);
			uint8 bw = (o < thres) ? 0 : 255;
			outline_img[i] = bw;
			outline_img[i + 1] = bw;
			outline_img[i + 2] = bw;
			outline_img[i + 3] = bw;
		}

		// fill blur with black
		stride = s.get_stride ();
		for (int m = 0; m < 2; m++) {
			i = stride + 4;
			while (i < len - 4 - stride) {
				if ((outline_img[i] == 255 && outline_img[i + 4] == 0
						&& outline_img[i + stride] == 0 && outline_img[i + stride + 4] == 255)
						|| (outline_img[i] == 0 && outline_img[i + 4] == 255
						&& outline_img[i + stride] == 255 && outline_img[i + stride + 4] == 0)) {
					outline_img[i] = 0;
					outline_img[i + 4] = 0;
					outline_img[i + stride] = 0;
					outline_img[i + stride + 4] = 0;
				}
				
				if (outline_img[i] == 255 && outline_img[i + 4] == 0 && outline_img[i + 8] == 255
						|| outline_img[i] == 0 && outline_img[i + 4] == 255 && outline_img[i + 8] == 0) {
					outline_img[i] = 0;
					outline_img[i + 4] = 0;
					outline_img[i + stride] = 0;
					outline_img[i + stride + 4] = 0;
				}
				i += 4;
			}
		}

		ns = new ImageSurface.for_data ((uchar[])outline_img, s.get_format (), s.get_width (), s.get_height (), s.get_stride ());		
		background_image = null;
		original_image = null;
		
		return (ImageSurface) ns;
	}
	
	public PathList autotrace () {
		ImageSurface img;
		int len, w, h, i, s;
		Path p;
		PathList pl;
		uint8* outline_img;
		
		ImageSurface scaled_image;
		double scale;
		
		p = new Path ();
		pl = new PathList ();

		get_img ();

		if (background_image == null) {
			return pl;
		}

		img = get_contrast_image ();

		w = img.get_width();
		h = img.get_height();

		if (unlikely (img.status () != Cairo.Status.SUCCESS)) {
			warning ("Error");
			return pl;
		}
		
		if (img.get_format () != Format.RGB24) {
			warning ("Wrong format");
			return pl;
		}
		
		scaled_image = img;

		img = (ImageSurface) scaled_image;
		w = img.get_width ();
		h = img.get_height ();
		s = img.get_stride ();
		len = s * h;

		outline_img = (uint8*) img.get_data ();

		start_points = new Gee.ArrayList<TracedPoint> ();
		points = new Gee.ArrayList<TracedPoint> ();

		int direction_vertical = 4;
		int direction_horizontal = s; // FIXME: SET AT FIND START
		int last_move = 0;		
		int pp = 0;
		
		scale = 1;
		i = 0;
		while ((i = find_start_point (outline_img, len, s, i)) != -1) {
			pp = 0;
			
			while (4 + s <= i < len - 4 - s) {	
				pp++;
				if (is_traced (i)) {
					Path np = generate_path (outline_img, s, w, h, len);
					
					if (np.points.size >= 3) {
						if (Path.is_counter (pl, np)) {
							np.force_direction (Direction.COUNTER_CLOCKWISE);
						} else {
							np.force_direction (Direction.CLOCKWISE);
						}

						pl.add (np);
					}
					
					break;
				}

				if (outline_img[i] == 255 && outline_img[i + 4] == 255
						&& outline_img[i + s] == 255 && outline_img[i + s + 4] == 255) {
					warning ("Lost path");
					Path np = generate_path (outline_img, s, w, h, len);
					pl.add (np);
					break;
				}

				if (outline_img[i] == 0 && outline_img[i + 4] == 0
						&& outline_img[i + s] == 0 && outline_img[i + s + 4] == 0) {
					warning ("Lost path");
					Path np = generate_path (outline_img, s, w, h, len);
					pl.add (np);
					break;
				}
							
				if (outline_img[i] == 0 && outline_img[i + 4] == 255
						&& outline_img[i + s] == 255 && outline_img[i + s + 4] == 255) {
					points.add (new TracedPoint (i));
					
					if (last_move == direction_horizontal) {
						direction_vertical = -s;
						i += direction_vertical;	
						last_move = direction_vertical;
					} else {
						direction_horizontal = -4;
						i += direction_horizontal;
						last_move = direction_horizontal;
					}				
					
				} else if (outline_img[i] == 0 && outline_img[i + 4] == 255
						&& outline_img[i + s] == 255 && outline_img[i + s + 4] == 255) {
					points.add (new TracedPoint (i));
				
					if (last_move == direction_horizontal) {
						direction_vertical = -s;
						i += direction_vertical;	
						last_move = direction_vertical;
					} else {
						direction_horizontal = -4;
						i += direction_horizontal;	
						last_move = direction_horizontal;
					}

				} else if (outline_img[i] == 255 && outline_img[i + 4] == 0
						&& outline_img[i + s] == 255 && outline_img[i + s + 4] == 255) {
					points.add (new TracedPoint (i));
				
					if (last_move == direction_horizontal) {
						direction_vertical = -s;
						i += direction_vertical;	
						last_move = direction_vertical;
					} else {
						direction_horizontal = 4;
						i += direction_horizontal;	
						last_move = direction_horizontal;
					}

				} else if (outline_img[i] == 0 && outline_img[i + 4] == 0
						&& outline_img[i + s] == 255 && outline_img[i + s + 4] == 0) {
					points.add (new TracedPoint (i));
				
					if (last_move == direction_horizontal) {
						direction_vertical = s;
						i += direction_vertical;	
						last_move = direction_vertical;
					} else {
						direction_horizontal = -4;
						i += direction_horizontal;	
						last_move = direction_horizontal;
					}

				} else if (outline_img[i] == 255 && outline_img[i + 4] == 255
						&& outline_img[i + s] == 255 && outline_img[i + s + 4] == 0) {
					points.add (new TracedPoint (i));
				
					if (last_move == direction_horizontal) {
						direction_vertical = s;
						i += direction_vertical;	
						last_move = direction_vertical;
					} else {
						direction_horizontal = 4;
						i += direction_horizontal;	
						last_move = direction_horizontal;
					}

				} else if (outline_img[i] == 255 && outline_img[i + 4] == 255
						&& outline_img[i + s] == 0 && outline_img[i + s + 4] == 255) {
					points.add (new TracedPoint (i));
					
					if (last_move == direction_horizontal) {
						direction_vertical = s;
						i += direction_vertical;	
						last_move = direction_vertical;
					} else {
						direction_horizontal = -4;
						i += direction_horizontal;	
						last_move = direction_horizontal;
					}
					
				} else if (outline_img[i] == 255 && outline_img[i + 4] == 0
						&& outline_img[i + s] == 0 && outline_img[i + s + 4] == 0) {
					points.add (new TracedPoint (i));
					
					if (last_move == direction_horizontal) {
						direction_vertical = -s;
						i += direction_vertical;	
						last_move = direction_vertical;
					} else {
						direction_horizontal = -4;
						i += direction_horizontal;	
						last_move = direction_horizontal;
					}
				} else if (outline_img[i] == 0 && outline_img[i + 4] == 255
						&& outline_img[i + s] == 0 && outline_img[i + s + 4] == 0) {
					points.add (new TracedPoint (i));
				
					if (last_move == direction_horizontal) {
						direction_vertical = -s;
						i += direction_vertical;	
						last_move = direction_vertical;
					} else {
						direction_horizontal = 4;
						i += direction_horizontal;	
						last_move = direction_horizontal;
					}
				} else if (outline_img[i] == 0 && outline_img[i + 4] == 0
						&& outline_img[i + s] == 0 && outline_img[i + s + 4] == 255) {
					points.add (new TracedPoint (i));
				
					if (last_move == direction_horizontal) {
						direction_vertical = s;
						i += direction_vertical;	
						last_move = direction_vertical;
					} else {
						direction_horizontal = 4;
						i += direction_horizontal;	
						last_move = direction_horizontal;
					}
				} else if (outline_img[i] == 255 && outline_img[i + 4] == 255
						&& outline_img[i + s] == 0 && outline_img[i + s + 4] == 255) {
					points.add (new TracedPoint (i));
				
					if (last_move == direction_horizontal) {
						direction_vertical = s;
						i += direction_vertical;	
						last_move = direction_vertical;
					} else {
						direction_horizontal = -4;
						i += direction_horizontal;	
						last_move = direction_horizontal;
					}
				} else if (outline_img[i] == 255 && outline_img[i + 4] == 255
						&& outline_img[i + s] == 0 && outline_img[i + s + 4] == 0) {
					points.add (new TracedPoint (i));
				
					i += direction_horizontal;
					last_move = direction_horizontal;
					
				} else if (outline_img[i] == 0 && outline_img[i + 4] == 0
						&& outline_img[i + s] == 255 && outline_img[i + s + 4] == 255) {
					points.add (new TracedPoint (i));
				
					i += direction_horizontal;
					last_move = direction_horizontal;
				} else if (outline_img[i] == 255 && outline_img[i + 4] == 0
						&& outline_img[i + s] == 255 && outline_img[i + s + 4] == 0) {
					points.add (new TracedPoint (i));
				
					i += direction_vertical;
					last_move = direction_vertical;
				} else if (outline_img[i] == 0 && outline_img[i + 4] == 255
						&& outline_img[i + s] == 0 && outline_img[i + s + 4] == 255) {
					points.add (new TracedPoint (i));
				
					i += direction_vertical;
					last_move = direction_vertical;
				} else if ((outline_img[i] == 255 && outline_img[i + 4] == 0
							&& outline_img[i + s] == 0 && outline_img[i + s + 4] == 255)
						|| (outline_img[i] == 0 && outline_img[i + 4] == 255
							&& outline_img[i + s] == 255 && outline_img[i + s + 4] == 0)) {
					points.add (new TracedPoint (i));
					
					warning ("Bad edge");
					i += last_move;
						
				} else {
					points.add (new TracedPoint (i));
					warning (@"No direction\n $(outline_img[i]) $(outline_img[i + 4])\n $(outline_img[i + s]) $(outline_img[i + s + 4])");
					i += 4;
				}
			}
		}

		start_points.clear ();
		points.clear ();
		
		return pl;
	}

	int find_start_point (uint8* outline_img, int len, int s, int start_index) {
		// find start point
		int i = start_index;
		
		if (i < s + 4) {
			i = s + 4;
		}
		
		while (i < len - 4 - s) {
			if (outline_img[i] == 0 && outline_img[i + 4] == 255
					&& outline_img[i + s] == 255 && outline_img[i + s + 4] == 255
					&& !has_start_point (i)) {
				return i;
			} else if (outline_img[i] == 255 && outline_img[i + 4] == 0
					&& outline_img[i + s] == 255 && outline_img[i + s + 4] == 255
					&& !has_start_point (i)) {
				return i;
			} else if (outline_img[i] == 255 && outline_img[i + 4] == 255
					&& outline_img[i + s] == 0 && outline_img[i + s + 4] == 255
					&& !has_start_point (i)) {
				return i;
			} else if (outline_img[i] == 255 && outline_img[i + 4] == 255
					&& outline_img[i + s] == 255 && outline_img[i + s + 4] == 0
					&& !has_start_point (i)) {
				return i;
			} else if (outline_img[i] == 255 && outline_img[i + 4] == 0 
					&& outline_img[i + s] == 0 && outline_img[i + s + 4] == 0
					&& !has_start_point (i)) {
				return i;
			} else if (outline_img[i] == 0 && outline_img[i + 4] == 255
					&& outline_img[i + s] == 0 && outline_img[i + s + 4] == 0
					&& !has_start_point (i)) {
				return i;
			} else if (outline_img[i] == 0 && outline_img[i + 4] == 0
					&& outline_img[i + s] == 255 && outline_img[i + s + 4] == 0
					&& !has_start_point (i)) {
				return i;
			} else if (outline_img[i] == 0 && outline_img[i + 4] == 0
					&& outline_img[i + s] == 0 && outline_img[i + s + 4] == 255
					&& !has_start_point (i)) {
				return i;
			}
			
			i +=4;
		}
		
		return -1;
	}
	
	void find_corner (Path path, int point_index, int end, double points_per_unit, ref double x, ref double y) {
		TracedPoint tp0;
		double sx = 0;
		double sy = 0;
		double d = 0;
		double mind = double.MAX;
		int index = 0;
		int pi;
		EditPoint ep0, ep1;
		double dx, dy;
		
		pi = point_index - 1;
		if (pi < 0) {
			pi += path.points.size;
		}
		ep0 = path.points.get (pi);
		
		pi = point_index + 1;
		pi %= path.points.size;
		ep1 = path.points.get (pi);
		
		Path.find_intersection_handle (ep0.get_left_handle (), ep1.get_right_handle (), out sx, out sy);

		dx = x - ep0.x;
		dy = y - ep0.y;

		sx += 3 * dx;
		sy += 3 * dy;

		dx = x - ep1.x;
		dy = y - ep1.y;

		sx += 3 * dx;
		sy += 3 * dy;
		
		end += (int) (points_per_unit / 2.0);
		for (int i = 0; i < 2 * points_per_unit; i++) {
			index = end - i;
						
			if (index < 0) {
				index += points.size;
			} else {
				index %= points.size;
			}
			
			tp0 = points.get (index);
			
			d = Path.distance (tp0.x, sx, tp0.y, sy);
			if (d < mind) {
				mind = d;
				x = tp0.x;
				y = tp0.y;
			}
		}		
	}
	
	Path generate_path (uint8* outline_img, int stride, int w, int h, int length) {
		double x, y, np;
		int i, index;
		double sumx, sumy, points_per_unit;
		Path path = new Path ();
		Gee.ArrayList<int?> sp = new Gee.ArrayList<int?> ();
		double corner;
		Gee.ArrayList<TracedPoint> traced = new Gee.ArrayList<TracedPoint> ();
		Gee.ArrayList<EditPoint> corners = new Gee.ArrayList<EditPoint> ();
		EditPoint ep;
		EditPointHandle r, l;
		double la, a;
		PointSelection ps;
		double image_scale_x;
		double image_scale_y;
		TracedPoint average_point;
		int pi;
		ImageSurface img;
		double simplification = DrawingTools.auto_trace_simplify.get_value ();

		img = get_contrast_image ();

		image_scale_x = ((double) size_margin / img.get_width ());
		image_scale_y = ((double) size_margin / img.get_height ());
				
		foreach (TracedPoint p in points) {
			start_points.add (p);
		}

		sumx = 0;
		sumy = 0;
		np = 0;

		points_per_unit = 9;
		corner = PI / 3.5;
		
		i = 0;
		foreach (TracedPoint p in points) {
			index = p.index;
			x = -w * img_scale_x / 2 + (((index + 4) % stride) / 4) * img_scale_x;
			y = h * img_scale_y / 2 +  -((index - x * 4) / stride) * img_scale_y;

			x *= image_scale_x;
			y *= image_scale_y;
			
			x += img_middle_x;
			y += img_middle_y;
			
			p.x = x;
			p.y = y;

			np++;
						
			sumx += x;
			sumy += y;
			
			if (np >= points_per_unit) {
				average_point = new TracedPoint (-1);
				average_point.x = sumx / np;
				average_point.y = sumy / np;
				traced.add (average_point);
				
				sp.add (i);
				
				np = 0;
				sumx = 0;
				sumy = 0;
			}
			
			i++;
		}
		
		if (np != 0) {
			average_point = new TracedPoint (-1);
			average_point.x = sumx / np;
			average_point.y = sumy / np;
			traced.add (average_point);
			sp.add (i);
		}
		
		foreach (TracedPoint avgp in traced) {
			ep = new EditPoint (avgp.x, avgp.y);
			
			path.points.add (ep);
			
			if (DrawingTools.point_type == PointType.CUBIC) {
				ep.type = PointType.CUBIC;
				ep.get_right_handle ().type = PointType.LINE_CUBIC;
				ep.get_left_handle ().type = PointType.LINE_CUBIC;
			} else {
				ep.type = PointType.DOUBLE_CURVE;
				ep.get_right_handle ().type = PointType.LINE_DOUBLE_CURVE;
				ep.get_left_handle ().type = PointType.LINE_DOUBLE_CURVE;
			}
		}

		path.close ();
		path.create_list ();
		path.recalculate_linear_handles ();
		
		// Find corners
		pi = 0;
		for (i = 1; i < sp.size; i += 2) {
			return_val_if_fail (0 <= i < path.points.size, path);
			ep = path.points.get (i);

			pi = i + 2;
			pi %= path.points.size;
			return_val_if_fail (0 <= pi < path.points.size, path);
			l = path.points.get (pi).get_left_handle ();

			pi = i - 2;
			if (pi < 0) {
				pi += path.points.size;
			}
			return_val_if_fail (0 <= pi < path.points.size, path);
			r = path.points.get (pi).get_right_handle ();

			la = l.angle - PI;

			while (la < 0) {
				la += 2 * PI;
			}
			
			if (r.angle > (2.0 / 3.0) * PI && la < PI / 2) {
				la += 2 * PI;
			} else if (la > (2.0 / 3.0) * PI && r.angle < PI / 2) {
				la -= 2 * PI;
			}
			
			a = r.angle - la;

			if (fabs (a) > corner) { // corner			
				ep.set_tie_handle (false);
				find_corner (path, i, (!) sp.get (i), points_per_unit, ref ep.x, ref ep.y);
				corners.add (ep);
			} else {
				ep.set_tie_handle (true);
			}
		}
		
		path.recalculate_linear_handles ();
		path.remove_points_on_points ();
		path.create_list ();
		foreach (EditPoint e in path.points) {
			if (e.tie_handles) {
				e.process_tied_handle ();
			}
		}

		if (simplification > 0.01) {
			for (i = 0; i < path.points.size; i++) {
				ep = path.points.get (i);
				ps = new PointSelection (ep, path);
				if (corners.index_of (ep) == -1) {
					PenTool.remove_point_simplify (ps, simplification);
				} 
			}
		}
		
		for (i = 0; i < path.points.size; i++) {
			ep = path.points.get (i);
			ps = new PointSelection (ep, path);
			if (corners.index_of (ep) == -1) {
				ep.set_selected (true);
			}
		}
		
		path = PenTool.simplify (path, true, simplification);
		points.clear ();
		path.update_region_boundaries ();
		
		return path;
	}
	
	bool has_start_point (int i) {
		foreach (TracedPoint p in start_points) {
			if (p.index == i) {
				return true;
			}
		}
		return false;
	}
	
	bool is_traced (int i) {
		foreach (TracedPoint p in points) {
			if (p.index == i) {
				return true;
			}
		}
		return false;
	}
	
	public void center_in_glyph (Glyph? glyph = null) {
		Glyph g;
		Font f = BirdFont.get_current_font ();
		
		if (glyph != null) {
			g = (!) glyph;
		} else {
			g = MainWindow.get_current_glyph ();
		}
		
		img_middle_x = g.left_limit + (g.right_limit - g.left_limit) / 2;
		img_middle_y = f.bottom_position + (f.top_position - f.bottom_position) / 2;
	}
	
	class TracedPoint {
		public int index;
		
		public double x = 0;
		public double y = 0;
		
		public TracedPoint (int index) {
			this.index = index;
		}
	}
}
	
}

Contact

Email: johan.mattsson.m@gmail.com
Mastodon: typo.social/@birdfont
GitHub: johanmattssonm
LinkedIn: johan-mattsson-2ab324112