private unsafe void ApplyAndSwap(ImageSurface dst, bool swap) { dst.Flush(); var dest_width = dst.Width; var dst_ptr = (ColorBgra *)dst.DataPtr; var mask_index = 0; ColorBgra swap_pixel; fixed(ColorBgra *fixed_ptr = pixels) { var pixel_ptr = fixed_ptr; dst_ptr += bounds.X + bounds.Y * dest_width; for (int y = bounds.Y; y <= bounds.GetBottom(); y++) { for (int x = bounds.X; x <= bounds.GetRight(); x++) { if (bitmask[mask_index++]) { if (swap) { swap_pixel = *dst_ptr; *dst_ptr = *pixel_ptr; *pixel_ptr++ = swap_pixel; } else { *dst_ptr = *pixel_ptr++; } } dst_ptr++; } dst_ptr += dest_width - bounds.Width; } } dst.MarkDirty(); }
protected override unsafe void AddSurfaceRectangleToHistogram(ImageSurface surface, Gdk.Rectangle rect) { long[] histogramB = histogram[0]; long[] histogramG = histogram[1]; long[] histogramR = histogram[2]; int rect_right = rect.GetRight(); for (int y = rect.Y; y <= rect.GetBottom(); ++y) { ColorBgra *ptr = surface.GetPointAddressUnchecked(rect.X, y); for (int x = rect.X; x <= rect_right; ++x) { ++histogramB[ptr->B]; ++histogramG[ptr->G]; ++histogramR[ptr->R]; ++ptr; } } }
protected unsafe override void OnMouseMove(object o, Gtk.MotionNotifyEventArgs args, Cairo.PointD point) { Document doc = PintaCore.Workspace.ActiveDocument; ColorBgra old_color; ColorBgra new_color; if (mouse_button == 1) { old_color = PintaCore.Palette.PrimaryColor.ToColorBgra(); new_color = PintaCore.Palette.SecondaryColor.ToColorBgra(); } else if (mouse_button == 3) { old_color = PintaCore.Palette.SecondaryColor.ToColorBgra(); new_color = PintaCore.Palette.PrimaryColor.ToColorBgra(); } else { last_point = point_empty; return; } int x = (int)point.X; int y = (int)point.Y; if (last_point.Equals(point_empty)) { last_point = new Point(x, y); } if (doc.Workspace.PointInCanvas(point)) { surface_modified = true; } ImageSurface surf = doc.CurrentUserLayer.Surface; ImageSurface tmp_layer = doc.ToolLayer.Surface; Gdk.Rectangle roi = GetRectangleFromPoints(last_point, new Point(x, y)); roi = PintaCore.Workspace.ClampToImageSize(roi); myTolerance = (int)(Tolerance * 256); tmp_layer.Flush(); ColorBgra *tmp_data_ptr = (ColorBgra *)tmp_layer.DataPtr; int tmp_width = tmp_layer.Width; ColorBgra *surf_data_ptr = (ColorBgra *)surf.DataPtr; int surf_width = surf.Width; // The stencil lets us know if we've already checked this // pixel, providing a nice perf boost // Maybe this should be changed to a BitVector2DSurfaceAdapter? for (int i = roi.X; i <= roi.GetRight(); i++) { for (int j = roi.Y; j <= roi.GetBottom(); j++) { if (stencil[i, j]) { continue; } if (IsColorInTolerance(new_color, surf.GetColorBgraUnchecked(surf_data_ptr, surf_width, i, j))) { *tmp_layer.GetPointAddressUnchecked(tmp_data_ptr, tmp_width, i, j) = AdjustColorDifference(new_color, old_color, surf.GetColorBgraUnchecked(surf_data_ptr, surf_width, i, j)); } stencil[i, j] = true; } } tmp_layer.MarkDirty(); using (Context g = new Context(surf)) { g.AppendPath(doc.Selection.SelectionPath); g.FillRule = FillRule.EvenOdd; g.Clip(); g.Antialias = UseAntialiasing ? Antialias.Subpixel : Antialias.None; g.MoveTo(last_point.X, last_point.Y); g.LineTo(x, y); g.LineWidth = BrushWidth; g.LineJoin = LineJoin.Round; g.LineCap = LineCap.Round; g.SetSource(tmp_layer); g.Stroke(); } doc.Workspace.Invalidate(roi); last_point = new Point(x, y); }
private ColorBgra[] GetPixelsFromPoint(PointD point) { var doc = PintaCore.Workspace.ActiveDocument; var x = (int)point.X; var y = (int)point.Y; var size = SampleSize; var half = size / 2; // Short circuit for single pixel if (size == 1) return new ColorBgra[] { GetPixel (x, y) }; // Find the pixels we need (clamp to the size of the image) var rect = new Gdk.Rectangle (x - half, y - half, size, size); rect.Intersect (new Gdk.Rectangle (Gdk.Point.Zero, doc.ImageSize)); var pixels = new List<ColorBgra> (); for (int i = rect.Left; i <= rect.GetRight (); i++) for (int j = rect.Top; j <= rect.GetBottom (); j++) pixels.Add (GetPixel (i, j)); return pixels.ToArray (); }
public static unsafe SurfaceDiff Create (ImageSurface original, ImageSurface 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.DataPtr; var updated_ptr = (int*)updated_surf.DataPtr; 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 Gdk.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.GetBottom (); int right = bounds.GetRight (); 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.DataPtr; 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); }
//same as RenderRect, except the histogram is alpha-weighted instead of keeping a separate alpha channel histogram. public unsafe void RenderRectWithAlpha( int rad, ImageSurface src, ImageSurface dst, Gdk.Rectangle rect) { int width = src.Width; int height = src.Height; int *leadingEdgeX = stackalloc int[rad + 1]; int stride = src.Stride / sizeof(ColorBgra); // approximately (rad + 0.5)^2 int cutoff = ((rad * 2 + 1) * (rad * 2 + 1) + 2) / 4; for (int v = 0; v <= rad; ++v) { for (int u = 0; u <= rad; ++u) { if (u * u + v * v <= cutoff) { leadingEdgeX[v] = u; } } } const int hLength = 256; int * hb = stackalloc int[hLength]; int * hg = stackalloc int[hLength]; int * hr = stackalloc int[hLength]; uint hSize = (uint)(sizeof(int) * hLength); for (int y = rect.Top; y <= rect.GetBottom(); y++) { SetToZero(hb, hSize); SetToZero(hg, hSize); SetToZero(hr, hSize); int area = 0; int sum = 0; ColorBgra *ps = src.GetPointAddressUnchecked(rect.Left, y); ColorBgra *pd = dst.GetPointAddressUnchecked(rect.Left, y); // assert: v + y >= 0 int top = -Math.Min(rad, y); // assert: v + y <= height - 1 int bottom = Math.Min(rad, height - 1 - y); // assert: u + x >= 0 int left = -Math.Min(rad, rect.Left); // assert: u + x <= width - 1 int right = Math.Min(rad, width - 1 - rect.Left); for (int v = top; v <= bottom; ++v) { ColorBgra *psamp = src.GetPointAddressUnchecked(rect.Left + left, y + v); for (int u = left; u <= right; ++u) { byte w = psamp->A; if ((u * u + v * v) <= cutoff) { ++area; sum += w; hb[psamp->B] += w; hg[psamp->G] += w; hr[psamp->R] += w; } ++psamp; } } for (int x = rect.Left; x <= rect.GetRight(); x++) { *pd = ApplyWithAlpha(*ps, area, sum, hb, hg, hr); // assert: u + x >= 0 left = -Math.Min(rad, x); // assert: u + x <= width - 1 right = Math.Min(rad + 1, width - 1 - x); // Subtract trailing edge top half int v = -1; while (v >= top) { int u = leadingEdgeX[-v]; if (-u >= left) { break; } --v; } while (v >= top) { int u = leadingEdgeX[-v]; ColorBgra *p = unchecked (ps + (v * stride)) - u; byte w = p->A; hb[p->B] -= w; hg[p->G] -= w; hr[p->R] -= w; sum -= w; --area; --v; } // add leading edge top half v = -1; while (v >= top) { int u = leadingEdgeX[-v]; if (u + 1 <= right) { break; } --v; } while (v >= top) { int u = leadingEdgeX[-v]; ColorBgra *p = unchecked (ps + (v * stride)) + u + 1; byte w = p->A; hb[p->B] += w; hg[p->G] += w; hr[p->R] += w; sum += w; ++area; --v; } // Subtract trailing edge bottom half v = 0; while (v <= bottom) { int u = leadingEdgeX[v]; if (-u >= left) { break; } ++v; } while (v <= bottom) { int u = leadingEdgeX[v]; ColorBgra *p = ps + v * stride - u; byte w = p->A; hb[p->B] -= w; hg[p->G] -= w; hr[p->R] -= w; sum -= w; --area; ++v; } // add leading edge bottom half v = 0; while (v <= bottom) { int u = leadingEdgeX[v]; if (u + 1 <= right) { break; } ++v; } while (v <= bottom) { int u = leadingEdgeX[v]; ColorBgra *p = ps + v * stride + u + 1; byte w = p->A; hb[p->B] += w; hg[p->G] += w; hr[p->R] += w; sum += w; ++area; ++v; } ++ps; ++pd; } } }
public static unsafe SurfaceDiff Create(ImageSurface original, ImageSurface 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.DataPtr; var updated_ptr = (int *)updated_surf.DataPtr; 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 Gdk.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.GetBottom(); int right = bounds.GetRight(); 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.DataPtr; 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 static unsafe SurfaceDiff Create(ImageSurface original, ImageSurface updated, bool force = false) { if (original.Width != updated.Width || original.Height != updated.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); #endif // STEP 1 - Create a bitarray of whether each pixel is changed (true for changed) var bitmask = new BitArray(orig_width * orig_height); var hit_first_change = false; var first_change = -1; var last_change = -1; var orig_ptr = (int *)original.DataPtr; var updated_ptr = (int *)updated.DataPtr; for (int i = 0; i < bitmask.Length; i++) { var changed = *(orig_ptr++) == *(updated_ptr++) ? false : true; bitmask.Set(i, changed); if (!hit_first_change) { if (changed) { first_change = i; hit_first_change = true; } } if (changed) { last_change = i; } } // STEP 2 - Figure out the bounds of the changed pixels var first_row = first_change / orig_width; var last_row = last_change / orig_width + 1; // We have to loop through the bitmask to find the first and last column first_change = -1; last_change = -1; hit_first_change = false; for (int x = 0; x < orig_width; x++) { for (int y = 0; y < orig_height; y++) { var changed = bitmask[x + (y * orig_width)]; if (!hit_first_change) { if (changed) { first_change = (x * orig_height) + y; hit_first_change = true; } } if (changed) { last_change = (x * orig_height) + y; } } } var first_col = first_change / orig_height; var last_col = last_change / orig_height + 1; var bounds = new Gdk.Rectangle(first_col, first_row, last_col - first_col, last_row - first_row); #if DEBUG_DIFF Console.WriteLine("Truncated surface size: {0}x{1}", bounds.Width, bounds.Height); #endif // If truncating doesn't save us at least x%, don't bother if (100 - (float)(bounds.Width * bounds.Height) / (float)(orig_width * orig_height) * 100 < MINIMUM_SAVINGS_PERCENT) { bounds = new Gdk.Rectangle(0, 0, orig_width, orig_height); #if DEBUG_DIFF Console.WriteLine("Truncating not worth it, skipping."); #endif } // STEP 3 - Truncate our bitarray to the bounding rectangle if (bounds.Width != orig_width || bounds.Height != orig_height) { var new_bitmask = new BitArray(bounds.Width * bounds.Height); int index = 0; for (int y = bounds.Y; y <= bounds.GetBottom(); y++) { for (int x = bounds.X; x <= bounds.GetRight(); x++) { new_bitmask[index++] = bitmask[y * orig_width + x]; } } bitmask = new_bitmask; } // STEP 4 - Count how many changed pixels we need to store var length = 0; for (int i = 0; i < bitmask.Length; i++) { if (bitmask[i]) { length++; } } var savings = 100 - (float)length / (float)(orig_width * orig_height) * 100; #if DEBUG_DIFF Console.WriteLine("Compressed bitmask: {0}/{1} = {2}%", length, 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[length]; var new_ptr = (ColorBgra *)original.DataPtr; var mask_index = 0; fixed(ColorBgra *fixed_ptr = pixels) { var pixel_ptr = fixed_ptr; new_ptr += bounds.X + bounds.Y * orig_width; for (int y = bounds.Y; y <= bounds.GetBottom(); y++) { for (int x = bounds.X; x <= bounds.GetRight(); x++) { if (bitmask[mask_index++]) { *pixel_ptr++ = *new_ptr; } new_ptr++; } new_ptr += orig_width - bounds.Width; } } return(new SurfaceDiff(bitmask, bounds, pixels)); }
/// <summary> /// 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. /// </summary> public static bool ContainsCorrect(this Gdk.Rectangle r, int x, int y) { return((((x >= r.Left) && (x <= r.GetRight())) && (y >= r.Top)) && (y <= r.GetBottom())); }