/// <summary>
		/// Allows you to find the bounding box for a "region" that is described as an
		/// array of bounding boxes.
		/// </summary>
		/// <param name="rectsF">The "region" you want to find a bounding box for.</param>
		/// <returns>A RectangleF structure that surrounds the Region.</returns>
		public static Rectangle GetRegionBounds (Rectangle[] rects, int startIndex, int length)
		{
			if (rects.Length == 0) {
				return Rectangle.Empty;
			}

			int left = rects[startIndex].Left;
			int top = rects[startIndex].Top;
			int right = rects[startIndex].Right;
			int bottom = rects[startIndex].Bottom;

			for (int i = startIndex + 1; i < startIndex + length; ++i) {
				Rectangle rect = rects[i];

				if (rect.Left < left) {
					left = rect.Left;
				}

				if (rect.Top < top) {
					top = rect.Top;
				}

				if (rect.Right > right) {
					right = rect.Right;
				}

				if (rect.Bottom > bottom) {
					bottom = rect.Bottom;
				}
			}

			return Rectangle.FromLTRB (left, top, right, bottom);
		}
		public static Rectangle Intersect (Rectangle a, Rectangle b)
		{
			return Rectangle.FromLTRB (
				Math.Max (a.Left, b.Left),
				Math.Max (a.Top, b.Top),
				Math.Min (a.Right, b.Right),
				Math.Min (a.Bottom, b.Bottom));
		}
		public void Intersect (Rectangle r)
		{
			var new_r = Rectangle.Intersect (this, r);

			X = new_r.X;
			Y = new_r.Y;
			Width = new_r.Width;
			Height = new_r.Height;
		}
		protected override unsafe void AddSurfaceRectangleToHistogram (ISurface surface, Rectangle rect)
		{
			long[] histogramB = histogram[0];
			long[] histogramG = histogram[1];
			long[] histogramR = histogram[2];

			int rect_right = rect.Right;

			for (int y = rect.Y; y <= rect.Bottom; ++y) {
				ColorBgra* ptr = surface.GetPointAddress (rect.X, y);
				for (int x = rect.X; x <= rect_right; ++x) {
					++histogramB[ptr->B];
					++histogramG[ptr->G];
					++histogramR[ptr->R];
					++ptr;
				}
			}
		}
		public Task ApplyAsync (ISurface src, ISurface dst, Rectangle roi)
		{
			return ApplyAsync (src, dst, dst.Bounds, CancellationToken.None);
		}
		protected void ApplyLoop (ISurface lhs, ISurface rhs, ISurface dst, Rectangle roi, CancellationToken token, IRenderProgress progress)
		{
			var completed_lines = new bool[roi.Height];
			var last_completed_index = 0;

			if (Settings.SingleThreaded || roi.Height <= 1) {
				for (var y = roi.Y; y <= roi.Bottom; ++y) {
					if (token.IsCancellationRequested)
						return;

					var dstPtr = dst.GetRowAddress (y);
					var lhsPtr = lhs.GetRowAddress (y);
					var rhsPtr = rhs.GetRowAddress (y);

					Apply (lhsPtr, rhsPtr, dstPtr, roi.Width);

					completed_lines[y - roi.Top] = true;

					if (progress != null) {
						var last_y = FindLastCompletedLine (completed_lines, last_completed_index);
						last_completed_index = last_y;
						progress.CompletedRoi = new Rectangle (roi.X, roi.Y, roi.Width, last_y);
						progress.PercentComplete = (float)last_y / (float)roi.Height;
					}
				}
			} else {
				ParallelExtensions.OrderedFor (roi.Y, roi.Bottom + 1, token, (y) => {
					var dstPtr = dst.GetRowAddress (y);
					var lhsPtr = lhs.GetRowAddress (y);
					var rhsPtr = rhs.GetRowAddress (y);

					Apply (lhsPtr, rhsPtr, dstPtr, roi.Width);

					completed_lines[y - roi.Top] = true;

					if (progress != null) {
						var last_y = FindLastCompletedLine (completed_lines, last_completed_index);
						last_completed_index = last_y;
						progress.CompletedRoi = new Rectangle (roi.X, roi.Y, roi.Width, last_y);
						progress.PercentComplete = (float)last_y / (float)roi.Height;
					}
				});
			}
		}
		public void Apply (ISurface lhs, ISurface rhs, ISurface dst, Rectangle roi)
		{
			ApplyLoop (lhs, rhs, dst, roi, CancellationToken.None, null);
		}
		/// <summary>
		/// Performs the actual work of rendering an effect. Do not call base.Render ().
		/// </summary>
		/// <param name="src">The source surface. DO NOT MODIFY.</param>
		/// <param name="dst">The destination surface.</param>
		/// <param name="roi">A rectangle of interest (roi) specifying the area to modify. Only these areas should be modified</param>
		protected unsafe virtual void RenderLine (ISurface src, ISurface dst, Rectangle roi)
		{
			var srcPtr = src.GetPointAddress (roi.X, roi.Y);
			var dstPtr = dst.GetPointAddress (roi.X, roi.Y);
			Render (srcPtr, dstPtr, roi.Width);
		}
		public Task ApplyAsync (ISurface lhs, ISurface rhs, ISurface dst, Rectangle roi, CancellationToken token)
		{
			return Task.Factory.StartNew (() => ApplyLoop (lhs, rhs, dst, dst.Bounds, token, null));
		}
		public void Set (Rectangle rect, bool newValue)
		{
			for (int y = rect.Y; y <= rect.Bottom; ++y) {
				for (int x = rect.X; x <= rect.Right; ++x) {
					Set (x, y, newValue);
				}
			}
		}
		public Task RenderAsync (ISurface src, ISurface dst, Rectangle roi, CancellationToken token, IRenderProgress progress)
		{
			return Task.Factory.StartNew (() => RenderLoop (src, dst, roi, token, progress));
		}
		public Task ApplyAsync (ISurface src, ISurface dst, Rectangle roi, CancellationToken token)
		{
			return Task.Factory.StartNew (() => ApplyLoop (src, dst, roi, token, null));
		}
		public void Apply (ISurface surface, Rectangle roi)
		{
			ApplyLoop (surface, roi, CancellationToken.None, null);
		}
		protected abstract void AddSurfaceRectangleToHistogram (ISurface surface, Rectangle rect);
		//public void UpdateHistogram(Surface surface)
		//{
		//    Clear();
		//    AddSurfaceRectangleToHistogram(surface, surface.Bounds);
		//    OnHistogramUpdated();
		//}

		public void UpdateHistogram (ISurface surface, Rectangle rect)
		{
			Clear ();
			AddSurfaceRectangleToHistogram (surface, rect);
			OnHistogramUpdated ();
		}
		/// <summary>
		/// Render the effect from the source surface to the destination surface.
		/// </summary>
		/// <param name="src">The source surface.</param>
		/// <param name="dst">The destination surface.</param>
		/// <param name="roi">A rectangle of interest (roi) specifying the area(s) to modify. Only these areas should be modified.</param>
		public void Render (ISurface src, ISurface dst, Rectangle roi)
		{
			RenderLoop (src, dst, roi, CancellationToken.None, null);
		}
		/// <summary>
		/// Render the effect on the specified surface within the specified rectangle of interest.
		/// </summary>
		/// <param name="surface">Surface to use a the source and destination.</param>
		/// <param name="roi">A rectangle of interest (roi) specifying the area(s) to modify. Only these areas should be modified.</param>
		public void Render (ISurface surface, Rectangle roi)
		{
			RenderLoop (surface, roi, CancellationToken.None, null);
		}
		protected virtual void OnBeginRender (ISurface src, ISurface dst, Rectangle roi)
		{
		}
		public Task ApplyAsync (ISurface src, ISurface dst, Rectangle roi, CancellationToken token, IRenderProgress progress)
		{
			return Task.Factory.StartNew (() => ApplyLoop (src, dst, dst.Bounds, token, progress));
		}
		protected virtual void RenderLoop (ISurface src, ISurface dst, Rectangle roi, CancellationToken token, IRenderProgress progress)
		{
			src.BeginUpdate ();
			dst.BeginUpdate ();

			OnBeginRender (src, dst, roi);

			var completed_lines = new bool[roi.Height];
			var last_completed_index = 0;

			if (Settings.SingleThreaded || roi.Height <= 1) {
				for (var y = roi.Y; y <= roi.Bottom; ++y) {
					if (token.IsCancellationRequested)
						return;

					RenderLine (src, dst, new Rectangle (roi.X, y, roi.Width, 1));

					completed_lines[y - roi.Top] = true;

					if (progress != null) {
						var last_y = FindLastCompletedLine (completed_lines, last_completed_index);
						last_completed_index = last_y;
						progress.CompletedRoi = new Rectangle (roi.X, roi.Y, roi.Width, last_y);
						progress.PercentComplete = (float)last_y / (float)roi.Height;
					}
				}
			} else {
				ParallelExtensions.OrderedFor (roi.Y, roi.Bottom + 1, token, (y) => {
					RenderLine (src, dst, new Rectangle (roi.X, y, roi.Width, 1));

					completed_lines[y - roi.Top] = true;

					if (progress != null) {
						var last_y = FindLastCompletedLine (completed_lines, last_completed_index);
						last_completed_index = last_y;
						progress.CompletedRoi = new Rectangle (roi.X, roi.Y, roi.Width, last_y);
						progress.PercentComplete = (float)last_y / (float)roi.Height;
					}
				});
			}

			src.EndUpdate ();
			dst.EndUpdate ();
		}
		public Rectangle UnscaleRectangle (Rectangle rect)
		{
			return new Rectangle (UnscalePoint (rect.Location), UnscaleSize (rect.Size));
		}
		private SurfaceDiff (BitArray bitmask, Rectangle bounds, Pinta.ImageManipulation.ColorBgra[] pixels)
		{
			this.bitmask = bitmask;
			this.bounds = bounds;
			this.pixels = pixels;
		}
		public void Invert (Rectangle rect)
		{
			for (int y = rect.Y; y <= rect.Bottom; ++y) {
				for (int x = rect.X; x <= rect.Right; ++x) {
					Invert (x, y);
				}
			}
		}
		public Task RenderAsync (ISurface surface, Rectangle roi, CancellationToken token)
		{
			return Task.Factory.StartNew (() => RenderLoop (surface, roi, token, null));
		}
		public Task ApplyAsync (ISurface surface, Rectangle roi)
		{
			return ApplyAsync (surface, roi, CancellationToken.None);
		}
		protected unsafe virtual void RenderLoop (ISurface surface, Rectangle roi, CancellationToken token, IRenderProgress progress)
		{
			var dst = new ColorBgra[surface.Height * surface.Width];

			fixed (ColorBgra* dst_ptr = dst) {
				var dst_wrap = new ColorBgraArrayWrapper (dst_ptr, surface.Width, surface.Height);

				surface.BeginUpdate ();
				dst_wrap.BeginUpdate ();

				OnBeginRender (surface, dst_wrap, roi);

				var completed_lines = new bool[roi.Height];
				var last_completed_index = 0;

				if (Settings.SingleThreaded || roi.Height <= 1) {
					for (var y = roi.Y; y <= roi.Bottom; ++y) {
						if (token.IsCancellationRequested)
							return;

						RenderLine (surface, dst_wrap, new Rectangle (roi.X, y, roi.Width, 1));

						completed_lines[y - roi.Top] = true;

						if (progress != null) {
							var last_y = FindLastCompletedLine (completed_lines, last_completed_index);
							last_completed_index = last_y;
							progress.CompletedRoi = new Rectangle (roi.X, roi.Y, roi.Width, last_y);
							progress.PercentComplete = (float)last_y / (float)roi.Height;
						}
					}
				} else {
					ParallelExtensions.OrderedFor (roi.Y, roi.Bottom + 1, token, (y) => {
						RenderLine (surface, dst_wrap, new Rectangle (roi.X, y, roi.Width, 1));

						completed_lines[y - roi.Top] = true;

						if (progress != null) {
							var last_y = FindLastCompletedLine (completed_lines, last_completed_index);
							last_completed_index = last_y;
							progress.CompletedRoi = new Rectangle (roi.X, roi.Y, roi.Width, last_y);
							progress.PercentComplete = (float)last_y / (float)roi.Height;
						}
					});
				}

				// Copy the result from our temp destination back into the source
				var op = new IdentityOp ();
				op.ApplyAsync (dst_wrap, surface, token).Wait ();

				surface.EndUpdate ();
				dst_wrap.EndUpdate ();
			}
		}
		public Task ApplyAsync (ISurface surface, Rectangle roi, CancellationToken token)
		{
			return Task.Factory.StartNew (() => ApplyLoop (surface, surface.Bounds, token, null));
		}
		public Task ApplyAsync (ISurface lhs, ISurface rhs, ISurface dst, Rectangle roi)
		{
			return ApplyAsync (lhs, rhs, dst, roi, CancellationToken.None);
		}
		public static unsafe SurfaceDiff Create (ISurface original, ISurface updated_surf, bool force = false)
		{
			if (original.Width != updated_surf.Width || original.Height != updated_surf.Height) {
				// If the surface changed size, only throw an error if the user forced the use of a diff.
				if (force) {
					throw new InvalidOperationException ("SurfaceDiff requires surfaces to be same size.");
				} else {
					return null;
				}
			}

			// Cache some pinvokes
			var orig_width = original.Width;
			var orig_height = original.Height;

#if DEBUG_DIFF
			Console.WriteLine ("Original surface size: {0}x{1}", orig_width, orig_height);
			System.Diagnostics.Stopwatch timer = new System.Diagnostics.Stopwatch();
			timer.Start();
#endif

			// STEP 1 - Find the bounds of the changed pixels.
			var orig_ptr = (int*)original.GetRowAddress (0);
			var updated_ptr = (int*)updated_surf.GetRowAddress (0);

			DiffBounds diff_bounds = new DiffBounds (orig_width, orig_height);
			object diff_bounds_lock = new Object();

			// Split up the work among several threads, each of which processes one row at a time
			// and updates the bounds of the changed pixels it has seen so far. At the end, the
			// results from each thread are merged together to find the overall bounds of the changed
			// pixels.
			Parallel.For<DiffBounds>(0, orig_height, () => new DiffBounds (orig_width, orig_height),
                     		(row, loop, my_bounds) => {

					var offset = row * orig_width;
					var orig = orig_ptr + offset;
					var updated = updated_ptr + offset;
					bool change_in_row = false;

					for (int i = 0; i < orig_width; ++i) {
						if (*(orig++) != *(updated++)) {
							change_in_row = true;
							my_bounds.left = System.Math.Min(my_bounds.left, i);
							my_bounds.right = System.Math.Max(my_bounds.right, i);
						}				
					}

					if (change_in_row) {
						my_bounds.top = System.Math.Min(my_bounds.top, row);
						my_bounds.bottom = System.Math.Max(my_bounds.bottom, row);
					}

					return my_bounds;

			},	(my_bounds) => {
					lock (diff_bounds_lock) {
						diff_bounds.Merge (my_bounds);
					}
					return;
			});

			var bounds = new Rectangle (diff_bounds.left, diff_bounds.top,
			                                diff_bounds.right - diff_bounds.left + 1,
			                                diff_bounds.bottom - diff_bounds.top + 1);

#if DEBUG_DIFF
			Console.WriteLine ("Truncated surface size: {0}x{1}", bounds.Width, bounds.Height);
#endif

			// STEP 2 - Create a bitarray of whether each pixel in the bounds has changed, and count
			// how many changed pixels we need to store.
			var bitmask = new BitArray (bounds.Width * bounds.Height);
			int index = 0;
			int num_changed = 0;

			int bottom = bounds.Bottom;
			int right = bounds.Right;
			int bounds_x = bounds.X;
			int bounds_y = bounds.Y;

			for (int y = bounds_y; y <= bottom; ++y) {
				var offset = y * orig_width;
				var updated = updated_ptr + offset + bounds_x;
				var orig = orig_ptr + offset + bounds_x;

				for (int x = bounds_x; x <= right; ++x) {
					bool changed = *(orig++) != *(updated++);
					bitmask[index++] = changed;
					if (changed) {
						num_changed++;
					}
				}
			}			

			var savings = 100 - (float)num_changed / (float)(orig_width * orig_height) * 100;
#if DEBUG_DIFF
			Console.WriteLine ("Compressed bitmask: {0}/{1} = {2}%", num_changed, orig_height * orig_width, 100 - savings);
#endif

			if (!force && savings < MINIMUM_SAVINGS_PERCENT) {
#if DEBUG_DIFF
				Console.WriteLine ("Savings too small, returning null");
#endif
				return null;
			}

			// Store the old pixels.
			var pixels = new ColorBgra[num_changed];
			var new_ptr = (ColorBgra*)original.GetRowAddress (0);
			int mask_index = 0;

			fixed (ColorBgra* fixed_ptr = pixels) {
				var pixel_ptr = fixed_ptr;

				for (int y = bounds_y; y <= bottom; ++y) {
					var new_pixel_ptr = new_ptr + bounds_x + y * orig_width;

					for (int x = bounds_x; x <= right; ++x) {
						if (bitmask[mask_index++]) {
							*pixel_ptr++ = *new_pixel_ptr;
						}

						new_pixel_ptr++;
					}
				}
			}

#if DEBUG_DIFF
			timer.Stop();
			System.Console.WriteLine("SurfaceDiff time: " + timer.ElapsedMilliseconds);
#endif

			return new SurfaceDiff (bitmask, bounds, pixels);
		}
		public Task RenderAsync (ISurface src, ISurface dst, Rectangle roi)
		{
			return RenderAsync (src, dst, roi, CancellationToken.None);
		}