// // CairoExtensions.cs // // Author: // Jonathan Pobst // // Copyright (c) 2010 Jonathan Pobst // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // Some functions are from Paint.NET: ///////////////////////////////////////////////////////////////////////////////// // Paint.NET // // Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // // Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // // See license-pdn.txt for full licensing and attribution details. // ///////////////////////////////////////////////////////////////////////////////// using System; using Cairo; using System.Collections.Generic; namespace Pinta.Core { public static class CairoExtensions { // Most of these functions return an affected area // This can be ignored if you don't need it #region context public static Rectangle DrawRectangle (this Context g, Rectangle r, Color color, int lineWidth) { // Put it on a pixel line if (lineWidth == 1) r = new Rectangle (r.X - 0.5, r.Y - 0.5, r.Width, r.Height); g.Save (); g.MoveTo (r.X, r.Y); g.LineTo (r.X + r.Width, r.Y); g.LineTo (r.X + r.Width, r.Y + r.Height); g.LineTo (r.X, r.Y + r.Height); g.LineTo (r.X, r.Y); g.Color = color; g.LineWidth = lineWidth; g.LineCap = LineCap.Square; Rectangle dirty = g.StrokeExtents (); g.Stroke (); g.Restore (); return dirty; } public static Path CreateRectanglePath (this Context g, Rectangle r) { g.Save (); g.MoveTo (r.X, r.Y); g.LineTo (r.X + r.Width, r.Y); g.LineTo (r.X + r.Width, r.Y + r.Height); g.LineTo (r.X, r.Y + r.Height); g.LineTo (r.X, r.Y); Path path = g.CopyPath (); g.Restore (); return path; } public static Rectangle FillRectangle (this Context g, Rectangle r, Color color) { g.Save (); g.MoveTo (r.X, r.Y); g.LineTo (r.X + r.Width, r.Y); g.LineTo (r.X + r.Width, r.Y + r.Height); g.LineTo (r.X, r.Y + r.Height); g.LineTo (r.X, r.Y); g.Color = color; Rectangle dirty = g.StrokeExtents (); g.Fill (); g.Restore (); return dirty; } public static Rectangle FillRectangle (this Context g, Rectangle r, Pattern pattern) { g.Save (); g.MoveTo (r.X, r.Y); g.LineTo (r.X + r.Width, r.Y); g.LineTo (r.X + r.Width, r.Y + r.Height); g.LineTo (r.X, r.Y + r.Height); g.LineTo (r.X, r.Y); g.Pattern = pattern; Rectangle dirty = g.StrokeExtents (); g.Fill (); g.Restore (); return dirty; } public static Rectangle DrawPolygonal (this Context g, PointD[] points, Color color) { Random rand = new Random (); g.Save (); g.MoveTo (points[0]); foreach (var point in points) { g.LineTo (point.X - rand.NextDouble () * 0, point.Y); //g.Stroke(); } g.Color = color; Rectangle dirty = g.StrokeExtents (); g.Stroke (); g.Restore (); return dirty; } public static Rectangle FillPolygonal (this Context g, PointD[] points, Color color) { g.Save (); g.MoveTo (points[0]); foreach (var point in points) g.LineTo (point); g.Color = color; Rectangle dirty = g.StrokeExtents (); g.Fill (); g.Restore (); return dirty; } public static Rectangle FillStrokedRectangle (this Context g, Rectangle r, Color fill, Color stroke, int lineWidth) { double x = r.X; double y = r.Y; g.Save (); // Put it on a pixel line if (lineWidth == 1) { x += 0.5; y += 0.5; } g.MoveTo (x, y); g.LineTo (x + r.Width, y); g.LineTo (x + r.Width, y + r.Height); g.LineTo (x, y + r.Height); g.LineTo (x, y); g.Color = fill; g.FillPreserve (); g.Color = stroke; g.LineWidth = lineWidth; g.LineCap = LineCap.Square; Rectangle dirty = g.StrokeExtents (); g.Stroke (); g.Restore (); return dirty; } public static Rectangle DrawEllipse (this Context g, Rectangle r, Color color, int lineWidth) { double rx = r.Width / 2; double ry = r.Height / 2; double cx = r.X + rx; double cy = r.Y + ry; double c1 = 0.552285; g.Save (); g.MoveTo (cx + rx, cy); g.CurveTo (cx + rx, cy - c1 * ry, cx + c1 * rx, cy - ry, cx, cy - ry); g.CurveTo (cx - c1 * rx, cy - ry, cx - rx, cy - c1 * ry, cx - rx, cy); g.CurveTo (cx - rx, cy + c1 * ry, cx - c1 * rx, cy + ry, cx, cy + ry); g.CurveTo (cx + c1 * rx, cy + ry, cx + rx, cy + c1 * ry, cx + rx, cy); g.ClosePath (); g.Color = color; g.LineWidth = lineWidth; Rectangle dirty = g.StrokeExtents (); g.Stroke (); g.Restore (); return dirty; } public static Rectangle FillEllipse (this Context g, Rectangle r, Color color) { double rx = r.Width / 2; double ry = r.Height / 2; double cx = r.X + rx; double cy = r.Y + ry; double c1 = 0.552285; g.Save (); g.MoveTo (cx + rx, cy); g.CurveTo (cx + rx, cy - c1 * ry, cx + c1 * rx, cy - ry, cx, cy - ry); g.CurveTo (cx - c1 * rx, cy - ry, cx - rx, cy - c1 * ry, cx - rx, cy); g.CurveTo (cx - rx, cy + c1 * ry, cx - c1 * rx, cy + ry, cx, cy + ry); g.CurveTo (cx + c1 * rx, cy + ry, cx + rx, cy + c1 * ry, cx + rx, cy); g.ClosePath (); g.Color = color; Rectangle dirty = g.StrokeExtents (); g.Fill (); g.Restore (); return dirty; } public static Path CreateEllipsePath (this Context g, Rectangle r) { double rx = r.Width / 2; double ry = r.Height / 2; double cx = r.X + rx; double cy = r.Y + ry; double c1 = 0.552285; g.Save (); g.MoveTo (cx + rx, cy); g.CurveTo (cx + rx, cy - c1 * ry, cx + c1 * rx, cy - ry, cx, cy - ry); g.CurveTo (cx - c1 * rx, cy - ry, cx - rx, cy - c1 * ry, cx - rx, cy); g.CurveTo (cx - rx, cy + c1 * ry, cx - c1 * rx, cy + ry, cx, cy + ry); g.CurveTo (cx + c1 * rx, cy + ry, cx + rx, cy + c1 * ry, cx + rx, cy); g.ClosePath (); Path path = g.CopyPath (); g.Restore (); return path; } public static Rectangle FillStrokedEllipse (this Context g, Rectangle r, Color fill, Color stroke, int lineWidth) { double rx = r.Width / 2; double ry = r.Height / 2; double cx = r.X + rx; double cy = r.Y + ry; double c1 = 0.552285; g.Save (); g.MoveTo (cx + rx, cy); g.CurveTo (cx + rx, cy - c1 * ry, cx + c1 * rx, cy - ry, cx, cy - ry); g.CurveTo (cx - c1 * rx, cy - ry, cx - rx, cy - c1 * ry, cx - rx, cy); g.CurveTo (cx - rx, cy + c1 * ry, cx - c1 * rx, cy + ry, cx, cy + ry); g.CurveTo (cx + c1 * rx, cy + ry, cx + rx, cy + c1 * ry, cx + rx, cy); g.ClosePath (); g.Color = fill; g.FillPreserve (); g.Color = stroke; g.LineWidth = lineWidth; Rectangle dirty = g.StrokeExtents (); g.Stroke (); g.Restore (); return dirty; } public static Rectangle FillStrokedRoundedRectangle (this Context g, Rectangle r, double radius, Color fill, Color stroke, int lineWidth) { g.Save (); if ((radius > r.Height / 2) || (radius > r.Width / 2)) radius = Math.Min (r.Height / 2, r.Width / 2); g.MoveTo (r.X, r.Y + radius); g.Arc (r.X + radius, r.Y + radius, radius, Math.PI, -Math.PI / 2); g.LineTo (r.X + r.Width - radius, r.Y); g.Arc (r.X + r.Width - radius, r.Y + radius, radius, -Math.PI / 2, 0); g.LineTo (r.X + r.Width, r.Y + r.Height - radius); g.Arc (r.X + r.Width - radius, r.Y + r.Height - radius, radius, 0, Math.PI / 2); g.LineTo (r.X + radius, r.Y + r.Height); g.Arc (r.X + radius, r.Y + r.Height - radius, radius, Math.PI / 2, Math.PI); g.ClosePath (); g.Color = fill; g.FillPreserve (); g.Color = stroke; g.LineWidth = lineWidth; Rectangle dirty = g.StrokeExtents (); g.Stroke (); g.Restore (); return dirty; } public static Rectangle FillRoundedRectangle (this Context g, Rectangle r, double radius, Color fill) { g.Save (); if ((radius > r.Height / 2) || (radius > r.Width / 2)) radius = Math.Min (r.Height / 2, r.Width / 2); g.MoveTo (r.X, r.Y + radius); g.Arc (r.X + radius, r.Y + radius, radius, Math.PI, -Math.PI / 2); g.LineTo (r.X + r.Width - radius, r.Y); g.Arc (r.X + r.Width - radius, r.Y + radius, radius, -Math.PI / 2, 0); g.LineTo (r.X + r.Width, r.Y + r.Height - radius); g.Arc (r.X + r.Width - radius, r.Y + r.Height - radius, radius, 0, Math.PI / 2); g.LineTo (r.X + radius, r.Y + r.Height); g.Arc (r.X + radius, r.Y + r.Height - radius, radius, Math.PI / 2, Math.PI); g.ClosePath (); g.Color = fill; Rectangle dirty = g.StrokeExtents (); g.Fill (); g.Restore (); return dirty; } public static void FillRegion (this Context g, Gdk.Region region, Color color) { g.Save (); g.Color = color; foreach (Gdk.Rectangle r in region.GetRectangles ()) { g.MoveTo (r.X, r.Y); g.LineTo (r.X + r.Width, r.Y); g.LineTo (r.X + r.Width, r.Y + r.Height); g.LineTo (r.X, r.Y + r.Height); g.LineTo (r.X, r.Y); g.Color = color; g.StrokeExtents (); g.Fill (); } g.Restore (); } public static Rectangle DrawRoundedRectangle (this Context g, Rectangle r, double radius, Color stroke, int lineWidth) { g.Save (); Path p = g.CreateRoundedRectanglePath (r, radius); g.AppendPath (p); g.Color = stroke; g.LineWidth = lineWidth; Rectangle dirty = g.StrokeExtents (); g.Stroke (); g.Restore (); (p as IDisposable).Dispose (); return dirty; } public static Path CreateRoundedRectanglePath (this Context g, Rectangle r, double radius) { g.Save (); if ((radius > r.Height / 2) || (radius > r.Width / 2)) radius = Math.Min (r.Height / 2, r.Width / 2); g.MoveTo (r.X, r.Y + radius); g.Arc (r.X + radius, r.Y + radius, radius, Math.PI, -Math.PI / 2); g.LineTo (r.X + r.Width - radius, r.Y); g.Arc (r.X + r.Width - radius, r.Y + radius, radius, -Math.PI / 2, 0); g.LineTo (r.X + r.Width, r.Y + r.Height - radius); g.Arc (r.X + r.Width - radius, r.Y + r.Height - radius, radius, 0, Math.PI / 2); g.LineTo (r.X + radius, r.Y + r.Height); g.Arc (r.X + radius, r.Y + r.Height - radius, radius, Math.PI / 2, Math.PI); g.ClosePath (); Path p = g.CopyPath (); g.Restore (); return p; } public static void QuadraticCurveTo (this Context g, double x1, double y1, double x2, double y2) { var c_x = g.CurrentPoint.X; var c_y = g.CurrentPoint.Y; var cp1x = c_x + 2.0 / 3.0 * (x1 - c_x); var cp1y = c_y + 2.0 / 3.0 * (y1 - c_y); var cp2x = cp1x + (x2 - c_x) / 3.0; var cp2y = cp1y + (y2 - c_y) / 3.0; g.CurveTo (cp1x, cp1y, cp2x, cp2y, x2, y2); } public static Rectangle DrawLine (this Context g, PointD p1, PointD p2, Color color, int lineWidth) { // Put it on a pixel line if (lineWidth == 1) p1 = new PointD (p1.X - 0.5, p1.Y - 0.5); g.Save (); g.MoveTo (p1.X, p1.Y); g.LineTo (p2.X, p2.Y); g.Color = color; g.LineWidth = lineWidth; g.LineCap = LineCap.Square; Rectangle dirty = g.StrokeExtents (); g.Stroke (); g.Restore (); return dirty; } private static Pango.Style CairoToPangoSlant (FontSlant slant) { switch (slant) { case FontSlant.Italic: return Pango.Style.Italic; case FontSlant.Oblique: return Pango.Style.Oblique; default: return Pango.Style.Normal; } } private static Pango.Weight CairoToPangoWeight (FontWeight weight) { return (weight == FontWeight.Bold) ? Pango.Weight.Bold : Pango.Weight.Normal; } public static Rectangle DrawText (this Context g, PointD p, string family, FontSlant slant, FontWeight weight, double size, Color color, string text, bool antiAliasing) { g.Save (); g.MoveTo (p.X, p.Y); g.Color = color; g.Antialias = antiAliasing ? Antialias.Subpixel : Antialias.None; Pango.Layout layout = Pango.CairoHelper.CreateLayout (g); Pango.FontDescription fd = new Pango.FontDescription (); fd.Family = family; fd.Style = CairoToPangoSlant (slant); fd.Weight = CairoToPangoWeight (weight); fd.AbsoluteSize = size * Pango.Scale.PangoScale; layout.FontDescription = fd; layout.SetText (text); Pango.CairoHelper.ShowLayoutLine (g, layout.Lines[0]); Pango.Rectangle unused = Pango.Rectangle.Zero; Pango.Rectangle te = Pango.Rectangle.Zero; layout.GetExtents (out unused, out te); (layout as IDisposable).Dispose (); g.Restore (); return new Rectangle (te.X, te.Y, te.Width, te.Height); } public static void DrawPixbuf (this Context g, Gdk.Pixbuf pixbuf, Point dest) { g.Save (); Gdk.CairoHelper.SetSourcePixbuf (g, pixbuf, dest.X, dest.Y); g.Paint (); g.Restore (); } public static void DrawLinearGradient (this Context g, Surface oldsurface, GradientColorMode mode, Color c1, Color c2, PointD p1, PointD p2) { g.Save (); Gradient gradient = new Cairo.LinearGradient (p1.X, p1.Y, p2.X, p2.Y); if (mode == GradientColorMode.Color) { gradient.AddColorStop (0, c1); gradient.AddColorStop (1, c2); g.Source = gradient; g.Paint (); } else if (mode == GradientColorMode.Transparency) { gradient.AddColorStop (0, new Color (0, 0, 0, 1)); gradient.AddColorStop (1, new Color (0, 0, 0, 0)); g.Source = new SurfacePattern (oldsurface); g.Mask (gradient); } g.Restore (); } public static void DrawLinearReflectedGradient (this Context g, Surface oldsurface, GradientColorMode mode, Color c1, Color c2, PointD p1, PointD p2) { g.Save (); Gradient gradient = new Cairo.LinearGradient (p1.X, p1.Y, p2.X, p2.Y); if (mode == GradientColorMode.Color) { gradient.AddColorStop (0, c1); gradient.AddColorStop (0.5, c2); gradient.AddColorStop (1, c1); g.Source = gradient; g.Paint (); } else if (mode == GradientColorMode.Transparency) { gradient.AddColorStop (0, new Color (0, 0, 0, 1)); gradient.AddColorStop (0.5, new Color (0, 0, 0, 0)); gradient.AddColorStop (1, new Color (0, 0, 0, 1)); g.Source = new SurfacePattern (oldsurface); g.Mask (gradient); } g.Restore (); } public static void DrawRadialGradient (this Context g, Surface oldsurface, GradientColorMode mode, Color c1, Color c2, PointD p1, PointD p2, double r1, double r2) { g.Save (); Gradient gradient = new Cairo.RadialGradient (p1.X, p1.Y, r1, p2.X, p2.Y, r2); if (mode == GradientColorMode.Color) { gradient.AddColorStop (0, c1); gradient.AddColorStop (1, c2); g.Source = gradient; g.Paint (); } else if (mode == GradientColorMode.Transparency) { gradient.AddColorStop (0, new Color (0, 0, 0, 1)); gradient.AddColorStop (1, new Color (0, 0, 0, 0)); g.Source = new SurfacePattern (oldsurface); g.Mask (gradient); } g.Restore (); } #endregion public static double Distance (this PointD s, PointD e) { return Magnitude (new PointD (s.X - e.X, s.Y - e.Y)); } public static double Magnitude (this PointD p) { return Math.Sqrt (p.X * p.X + p.Y * p.Y); } public static Cairo.Rectangle ToCairoRectangle (this Gdk.Rectangle r) { return new Cairo.Rectangle (r.X, r.Y, r.Width, r.Height); } public static Cairo.Point Location (this Cairo.Rectangle r) { return new Cairo.Point ((int)r.X, (int)r.Y); } public static Cairo.Rectangle Clamp (this Cairo.Rectangle r) { double x = r.X; double y = r.Y; double w = r.Width; double h = r.Height; if (x < 0) { w -= x; x = 0; } if (y < 0) { h -= y; y = 0; } return new Cairo.Rectangle (x, y, w, h); } public static Gdk.Rectangle ToGdkRectangle (this Cairo.Rectangle r) { return new Gdk.Rectangle ((int)r.X, (int)r.Y, (int)r.Width, (int)r.Height); } public static bool ContainsPoint (this Cairo.Rectangle r, double x, double y) { if (x < r.X || x >= r.X + r.Width) return false; if (y < r.Y || y >= r.Y + r.Height) return false; return true; } public static bool ContainsPoint (this Cairo.Rectangle r, Cairo.PointD point) { return ContainsPoint (r, point.X, point.Y); } public unsafe static Gdk.Pixbuf ToPixbuf (this Cairo.ImageSurface surfSource) { Cairo.ImageSurface surf = surfSource.Clone (); surf.Flush (); ColorBgra* dstPtr = (ColorBgra*)surf.DataPtr; int len = surf.Data.Length / 4; for (int i = 0; i < len; i++) { if (dstPtr->A != 0) *dstPtr = (ColorBgra.FromBgra (dstPtr->R, dstPtr->G, dstPtr->B, dstPtr->A)); dstPtr++; } Gdk.Pixbuf pb = new Gdk.Pixbuf (surf.Data, true, 8, surf.Width, surf.Height, surf.Stride); (surf as IDisposable).Dispose (); return pb; } public unsafe static Color GetPixel (this Cairo.ImageSurface surf, int x, int y) { ColorBgra* dstPtr = (ColorBgra*)surf.DataPtr; dstPtr += (x) + (y * surf.Width); return new Color (dstPtr->R / 255f, dstPtr->G / 255f, dstPtr->B / 255f, dstPtr->A / 255f); } public unsafe static void SetPixel (this Cairo.ImageSurface surf, int x, int y, Color color) { ColorBgra* dstPtr = (ColorBgra*)surf.DataPtr; dstPtr += (x) + (y * surf.Width); dstPtr->R = (byte)(color.R * 255); dstPtr->G = (byte)(color.G * 255); dstPtr->B = (byte)(color.B * 255); dstPtr->A = (byte)(color.A * 255); } public unsafe static void SetPixel (this Cairo.ImageSurface surf, ColorBgra* surfDataPtr, int surfWidth, int x, int y, Color color) { ColorBgra* dstPtr = surfDataPtr; dstPtr += (x) + (y * surfWidth); dstPtr->R = (byte)(color.R * 255); dstPtr->G = (byte)(color.G * 255); dstPtr->B = (byte)(color.B * 255); dstPtr->A = (byte)(color.A * 255); } public unsafe static ColorBgra GetColorBgra (this Cairo.ImageSurface surf, int x, int y) { ColorBgra* dstPtr = (ColorBgra*)surf.DataPtr; dstPtr += (x) + (y * surf.Width); return *dstPtr; } public unsafe static void SetColorBgra (this Cairo.ImageSurface surf, ColorBgra color, int x, int y) { ColorBgra* dstPtr = (ColorBgra*)surf.DataPtr; dstPtr += (x) + (y * surf.Width); *dstPtr = color; } public unsafe static void SetColorBgra (this Cairo.ImageSurface surf, ColorBgra* surfDataPtr, int surfWidth, ColorBgra color, int x, int y) { ColorBgra* dstPtr = surfDataPtr; dstPtr += (x) + (y * surfWidth); *dstPtr = color; } public unsafe static ColorBgra GetColorBgra (this Cairo.ImageSurface surf, ColorBgra* surfDataPtr, int surfWidth, int x, int y) { ColorBgra* dstPtr = surfDataPtr; dstPtr += (x) + (y * surfWidth); return *dstPtr; } public static ColorBgra ToColorBgra (this Cairo.Color color) { ColorBgra c = new ColorBgra (); c.R = (byte)(color.R * 255); c.G = (byte)(color.G * 255); c.B = (byte)(color.B * 255); c.A = (byte)(color.A * 255); return c; } public static Cairo.Color ToCairoColor (this ColorBgra color) { Cairo.Color c = new Cairo.Color (); c.R = color.R / 255d; c.G = color.G / 255d; c.B = color.B / 255d; c.A = color.A / 255d; return c; } public static Gdk.Color ToGdkColor (this ColorBgra color) { Gdk.Color c = new Gdk.Color (color.R, color.G, color.B); return c; } public static string ToString2 (this Cairo.Color c) { return string.Format ("R: {0} G: {1} B: {2} A: {3}", c.R, c.G, c.B, c.A); } public static string ToString2 (this Cairo.PointD c) { return string.Format ("{0}, {1}", c.X, c.Y); } public static uint ToUint (this Cairo.Color c) { return Pinta.Core.ColorBgra.BgraToUInt32 ((int)(c.B * 255), (int)(c.R * 255), (int)(c.G * 255), (int)(c.A * 255)); } public static Gdk.Size ToSize (this Cairo.Point point) { return new Gdk.Size (point.X, point.Y); } public static ImageSurface Clone (this ImageSurface surf) { ImageSurface newsurf = new ImageSurface (surf.Format, surf.Width, surf.Height); using (Context g = new Context (newsurf)) { g.SetSource (surf); g.Paint (); } return newsurf; } public static unsafe bool ContainsTranslucent (this ImageSurface surf) { bool ret = false; ColorBgra* ptr = (ColorBgra*)surf.DataPtr; int width = surf.Width; for (int x = 0; x < width; x++) for (int y = 0; y < surf.Height; y++) { int a = (int)surf.GetColorBgra (ptr, width, x, y).A; if (a > 0 && a < 255) { Console.WriteLine (surf.GetColorBgra (ptr, width, x, y).ToString ()); ret = true; } } return ret; } public static Path Clone (this Path path) { Path newpath; using (Context g = new Context (PintaCore.Layers.CurrentLayer.Surface)) { g.AppendPath (path); newpath = g.CopyPath (); } return newpath; } public static void Clear (this ImageSurface surface) { using (Context g = new Context (surface)) { g.Operator = Operator.Clear; g.Paint (); } } public static Gdk.Rectangle GetBounds (this Path path) { Rectangle rect; using (Context g = new Context (PintaCore.Layers.CurrentLayer.Surface)) { g.AppendPath (PintaCore.Layers.SelectionPath); // We don't want the bounding box to include a stroke width // of 1, but setting it to 0 returns an empty rectangle. Set // it to a sufficiently small width and rounding takes care of it g.LineWidth = .01; rect = g.StrokeExtents (); } int x = (int)Math.Round (rect.X); int y = (int)Math.Round (rect.Y); int w = (int)Math.Round (rect.Width); int h = (int)Math.Round (rect.Height); return new Gdk.Rectangle (x, y, w - x, h - y); } public static Gdk.Color ToGdkColor (this Cairo.Color color) { Gdk.Color c = new Gdk.Color (); c.Blue = (ushort)(color.B * ushort.MaxValue); c.Red = (ushort)(color.R * ushort.MaxValue); c.Green = (ushort)(color.G * ushort.MaxValue); return c; } public static ushort GdkColorAlpha (this Cairo.Color color) { return (ushort)(color.A * ushort.MaxValue); } public static double GetBottom (this Rectangle rect) { return rect.Y + rect.Height; } public static double GetRight (this Rectangle rect) { return rect.X + rect.Width; } /// /// Determines if the requested pixel coordinate is within bounds. /// /// The x coordinate. /// The y coordinate. /// true if (x,y) is in bounds, false if it's not. public static bool IsVisible (this ImageSurface surf, int x, int y) { return x >= 0 && x < surf.Width && y >= 0 && y < surf.Height; } public static unsafe ColorBgra* GetPointAddressUnchecked (this ImageSurface surf, int x, int y) { ColorBgra* dstPtr = (ColorBgra*)surf.DataPtr; dstPtr += (x) + (y * surf.Width); return dstPtr; } public static unsafe ColorBgra* GetPointAddressUnchecked (this ImageSurface surf, ColorBgra* surfDataPtr, int surfWidth, int x, int y) { ColorBgra* dstPtr = surfDataPtr; dstPtr += (x) + (y * surfWidth); return dstPtr; } public static unsafe ColorBgra GetPointUnchecked (this ImageSurface surf, int x, int y) { ColorBgra* dstPtr = (ColorBgra*)surf.DataPtr; dstPtr += (x) + (y * surf.Width); return *dstPtr; } // This isn't really an extension method, since it doesn't use // the passed in argument, but it's nice to have the same calling // convention as the uncached version. If you can use this one // over the other, it is much faster in tight loops (like effects). public static unsafe ColorBgra GetPointUnchecked (this ImageSurface surf, ColorBgra* surfDataPtr, int surfWidth, int x, int y) { ColorBgra* dstPtr = surfDataPtr; dstPtr += (x) + (y * surfWidth); return *dstPtr; } public static unsafe ColorBgra* GetRowAddressUnchecked (this ImageSurface surf, int y) { ColorBgra* dstPtr = (ColorBgra*)surf.DataPtr; dstPtr += y * surf.Width; return dstPtr; } public static unsafe ColorBgra* GetRowAddressUnchecked (this ImageSurface surf, ColorBgra* surfDataPtr, int surfWidth, int y) { ColorBgra* dstPtr = surfDataPtr; dstPtr += y * surfWidth; return dstPtr; } public static unsafe ColorBgra* GetPointAddress (this ImageSurface surf, int x, int y) { if (x < 0 || x >= surf.Width) throw new ArgumentOutOfRangeException ("x", "Out of bounds: x=" + x.ToString ()); return surf.GetPointAddressUnchecked (x, y); } public static unsafe ColorBgra* GetPointAddress (this ImageSurface surf, Gdk.Point point) { return surf.GetPointAddress (point.X, point.Y); } public static Gdk.Rectangle GetBounds (this ImageSurface surf) { return new Gdk.Rectangle (0, 0, surf.Width, surf.Height); } public static Gdk.Size GetSize (this ImageSurface surf) { return new Gdk.Size (surf.Width, surf.Height); } /// /// There was a bug in gdk-sharp where this returns incorrect values. /// We will probably have to use this for a long time until every distro /// has an updated gdk. /// public static bool ContainsCorrect (this Gdk.Rectangle r, int x, int y) { return ((((x >= r.Left) && (x < r.Right)) && (y >= r.Top)) && (y < r.Bottom)); } /// /// There was a bug in gdk-sharp where this returns incorrect values. /// We will probably have to use this for a long time until every distro /// has an updated gdk. /// public static bool ContainsCorrect (this Gdk.Rectangle r, Gdk.Point pt) { return r.ContainsCorrect (pt.X, pt.Y); } public static unsafe ColorBgra GetBilinearSample (this ImageSurface src, float x, float y) { return GetBilinearSample (src, (ColorBgra*)src.DataPtr, src.Width, src.Height, x, y); } public static unsafe ColorBgra GetBilinearSample (this ImageSurface src, ColorBgra* srcDataPtr, int srcWidth, int srcHeight, float x, float y) { if (!Utility.IsNumber (x) || !Utility.IsNumber (y)) return ColorBgra.Transparent; float u = x; float v = y; if (u >= 0 && v >= 0 && u < srcWidth && v < srcHeight) { unchecked { int iu = (int)Math.Floor (u); uint sxfrac = (uint)(256 * (u - (float)iu)); uint sxfracinv = 256 - sxfrac; int iv = (int)Math.Floor (v); uint syfrac = (uint)(256 * (v - (float)iv)); uint syfracinv = 256 - syfrac; uint wul = (uint)(sxfracinv * syfracinv); uint wur = (uint)(sxfrac * syfracinv); uint wll = (uint)(sxfracinv * syfrac); uint wlr = (uint)(sxfrac * syfrac); int sx = iu; int sy = iv; int sleft = sx; int sright; if (sleft == (srcWidth - 1)) sright = sleft; else sright = sleft + 1; int stop = sy; int sbottom; if (stop == (srcHeight - 1)) sbottom = stop; else sbottom = stop + 1; ColorBgra* cul = src.GetPointAddressUnchecked (srcDataPtr, srcWidth, sleft, stop); ColorBgra* cur = cul + (sright - sleft); ColorBgra* cll = src.GetPointAddressUnchecked (srcDataPtr, srcWidth, sleft, sbottom); ColorBgra* clr = cll + (sright - sleft); ColorBgra c = ColorBgra.BlendColors4W16IP (*cul, wul, *cur, wur, *cll, wll, *clr, wlr); return c; } } else { return ColorBgra.FromUInt32 (0); } } public static unsafe ColorBgra GetBilinearSampleClamped (this ImageSurface src, float x, float y) { return GetBilinearSampleClamped (src, (ColorBgra*)src.DataPtr, src.Width, src.Height, x, y); } public static unsafe ColorBgra GetBilinearSampleClamped (this ImageSurface src, ColorBgra* srcDataPtr, int srcWidth, int srcHeight, float x, float y) { if (!Utility.IsNumber (x) || !Utility.IsNumber (y)) return ColorBgra.Transparent; float u = x; float v = y; if (u < 0) u = 0; else if (u > srcWidth - 1) u = srcWidth - 1; if (v < 0) v = 0; else if (v > srcHeight - 1) v = srcHeight - 1; unchecked { int iu = (int)Math.Floor (u); uint sxfrac = (uint)(256 * (u - (float)iu)); uint sxfracinv = 256 - sxfrac; int iv = (int)Math.Floor (v); uint syfrac = (uint)(256 * (v - (float)iv)); uint syfracinv = 256 - syfrac; uint wul = (uint)(sxfracinv * syfracinv); uint wur = (uint)(sxfrac * syfracinv); uint wll = (uint)(sxfracinv * syfrac); uint wlr = (uint)(sxfrac * syfrac); int sx = iu; int sy = iv; int sleft = sx; int sright; if (sleft == (srcWidth - 1)) sright = sleft; else sright = sleft + 1; int stop = sy; int sbottom; if (stop == (srcHeight - 1)) sbottom = stop; else sbottom = stop + 1; ColorBgra* cul = src.GetPointAddressUnchecked (srcDataPtr, srcWidth, sleft, stop); ColorBgra* cur = cul + (sright - sleft); ColorBgra* cll = src.GetPointAddressUnchecked (srcDataPtr, srcWidth, sleft, sbottom); ColorBgra* clr = cll + (sright - sleft); ColorBgra c = ColorBgra.BlendColors4W16IP (*cul, wul, *cur, wur, *cll, wll, *clr, wlr); return c; } } public static unsafe ColorBgra GetBilinearSampleWrapped (this ImageSurface src, float x, float y) { return GetBilinearSampleWrapped (src, (ColorBgra*)src.DataPtr, src.Width, src.Height, x, y); } public static unsafe ColorBgra GetBilinearSampleWrapped (this ImageSurface src, ColorBgra* srcDataPtr, int srcWidth, int srcHeight, float x, float y) { if (!Utility.IsNumber (x) || !Utility.IsNumber (y)) return ColorBgra.Transparent; float u = x; float v = y; unchecked { int iu = (int)Math.Floor (u); uint sxfrac = (uint)(256 * (u - (float)iu)); uint sxfracinv = 256 - sxfrac; int iv = (int)Math.Floor (v); uint syfrac = (uint)(256 * (v - (float)iv)); uint syfracinv = 256 - syfrac; uint wul = (uint)(sxfracinv * syfracinv); uint wur = (uint)(sxfrac * syfracinv); uint wll = (uint)(sxfracinv * syfrac); uint wlr = (uint)(sxfrac * syfrac); int sx = iu; if (sx < 0) sx = (srcWidth - 1) + ((sx + 1) % srcWidth); else if (sx > (srcWidth - 1)) sx = sx % srcWidth; int sy = iv; if (sy < 0) sy = (srcHeight - 1) + ((sy + 1) % srcHeight); else if (sy > (srcHeight - 1)) sy = sy % srcHeight; int sleft = sx; int sright; if (sleft == (srcWidth - 1)) sright = 0; else sright = sleft + 1; int stop = sy; int sbottom; if (stop == (srcHeight - 1)) sbottom = 0; else sbottom = stop + 1; ColorBgra cul = src.GetPointUnchecked (srcDataPtr, srcWidth, sleft, stop); ColorBgra cur = src.GetPointUnchecked (srcDataPtr, srcWidth, sright, stop); ColorBgra cll = src.GetPointUnchecked (srcDataPtr, srcWidth, sleft, sbottom); ColorBgra clr = src.GetPointUnchecked (srcDataPtr, srcWidth, sright, sbottom); ColorBgra c = ColorBgra.BlendColors4W16IP (cul, wul, cur, wur, cll, wll, clr, wlr); return c; } } public static void TranslatePointsInPlace (this Point[] Points, int dx, int dy) { for (int i = 0; i < Points.Length; ++i) { Points[i].X += dx; Points[i].Y += dy; } } private struct Edge { public int miny; // int public int maxy; // int public int x; // fixed point: 24.8 public int dxdy; // fixed point: 24.8 public Edge (int miny, int maxy, int x, int dxdy) { this.miny = miny; this.maxy = maxy; this.x = x; this.dxdy = dxdy; } } public static Scanline[] GetScans (this Point[] points) { int ymax = 0; // Build edge table Edge[] edgeTable = new Edge[points.Length]; int edgeCount = 0; for (int i = 0; i < points.Length; ++i) { Point top = points[i]; Point bottom = points[(i + 1) % points.Length]; int dy; if (top.Y > bottom.Y) { Point temp = top; top = bottom; bottom = temp; } dy = bottom.Y - top.Y; if (dy != 0) { edgeTable[edgeCount] = new Edge (top.Y, bottom.Y, top.X << 8, (((bottom.X - top.X) << 8) / dy)); ymax = Math.Max (ymax, bottom.Y); ++edgeCount; } } // Sort edge table by miny for (int i = 0; i < edgeCount - 1; ++i) { int min = i; for (int j = i + 1; j < edgeCount; ++j) if (edgeTable[j].miny < edgeTable[min].miny) min = j; if (min != i) { Edge temp = edgeTable[min]; edgeTable[min] = edgeTable[i]; edgeTable[i] = temp; } } // Compute how many scanlines we will be emitting int scanCount = 0; int activeLow = 0; int activeHigh = 0; int yscan1 = edgeTable[0].miny; // we assume that edgeTable[0].miny == yscan while (activeHigh < edgeCount - 1 && edgeTable[activeHigh + 1].miny == yscan1) { ++activeHigh; } while (yscan1 <= ymax) { // Find new edges where yscan == miny while (activeHigh < edgeCount - 1 && edgeTable[activeHigh + 1].miny == yscan1) { ++activeHigh; } int count = 0; for (int i = activeLow; i <= activeHigh; ++i) { if (edgeTable[i].maxy > yscan1) { ++count; } } scanCount += count / 2; ++yscan1; // Remove edges where yscan == maxy while (activeLow < edgeCount - 1 && edgeTable[activeLow].maxy <= yscan1) { ++activeLow; } if (activeLow > activeHigh) activeHigh = activeLow; } // Allocate scanlines that we'll return Scanline[] scans = new Scanline[scanCount]; // Active Edge Table (AET): it is indices into the Edge Table (ET) int[] active = new int[edgeCount]; int activeCount = 0; int yscan2 = edgeTable[0].miny; int scansIndex = 0; // Repeat until both the ET and AET are empty while (yscan2 <= ymax) { // Move any edges from the ET to the AET where yscan == miny for (int i = 0; i < edgeCount; ++i) { if (edgeTable[i].miny == yscan2) { active[activeCount] = i; ++activeCount; } } // Sort the AET on x for (int i = 0; i < activeCount - 1; ++i) { int min = i; for (int j = i + 1; j < activeCount; ++j) if (edgeTable[active[j]].x < edgeTable[active[min]].x) min = j; if (min != i) { int temp = active[min]; active[min] = active[i]; active[i] = temp; } } // For each pair of entries in the AET, fill in pixels between their info for (int i = 0; i < activeCount; i += 2) { Edge el = edgeTable[active[i]]; Edge er = edgeTable[active[i + 1]]; int startx = (el.x + 0xff) >> 8; // ceil(x) int endx = er.x >> 8; // floor(x) scans[scansIndex] = new Scanline (startx, yscan2, endx - startx); ++scansIndex; } ++yscan2; // Remove from the AET any edge where yscan == maxy int k = 0; while (k < activeCount && activeCount > 0) { if (edgeTable[active[k]].maxy == yscan2) { // remove by shifting everything down one for (int j = k + 1; j < activeCount; ++j) active[j - 1] = active[j]; --activeCount; } else { ++k; } } // Update x for each entry in AET for (int i = 0; i < activeCount; ++i) edgeTable[active[i]].x += edgeTable[active[i]].dxdy; } return scans; } public static Path CreatePolygonPath (this Context g, Point[][] polygonSet) { g.Save (); Point p; for (int i = 0; i < polygonSet.Length; i++) { if (polygonSet[i].Length == 0) continue; p = polygonSet[i][0]; g.MoveTo (p.X, p.Y); for (int j = 1; j < polygonSet[i].Length; j++) { p = polygonSet[i][j]; g.LineTo (p.X, p.Y); } g.ClosePath (); } Path path = g.CopyPath (); g.Restore (); return path; } public static Gdk.Point ToGdkPoint (this PointD point) { return new Gdk.Point ((int)point.X, (int)point.Y); } } }