public IrregularSurface(Cairo.ImageSurface source, Rectangle[] roi) { this.placedSurfaces = new List <PlacedSurface> (roi.Length); foreach (Rectangle rect in roi) { Rectangle ri = Rectangle.Intersect(source.GetBounds(), rect); if (!ri.IsEmpty) { this.placedSurfaces.Add(new PlacedSurface(source, ri)); } } this.region = Utility.RectanglesToRegion(roi); this.region.Intersect(Region.Rectangle(source.GetBounds())); }
public void Render(List <Layer> layers, Cairo.ImageSurface dst, Point offset) { dst.Flush(); // Our rectangle of interest var r = new Rectangle(offset, dst.GetBounds().Size).ToCairoRectangle(); var doc = PintaCore.Workspace.ActiveDocument; using (var g = new Cairo.Context(dst)) { // Create the transparent checkerboard background g.Translate(-offset.X, -offset.Y); g.FillRectangle(r, tranparent_pattern, new Cairo.PointD(offset.X, offset.Y)); for (var i = 0; i < layers.Count; i++) { var layer = layers[i]; // If we're in LivePreview, substitute current layer with the preview layer if (layer == doc.Layers.CurrentUserLayer && PintaCore.LivePreview.IsEnabled) { layer = CreateLivePreviewLayer(layer); } // If the layer is offset, handle it here if (!layer.Transform.IsIdentity()) { layer = CreateOffsetLayer(layer); } // No need to resize the surface if we're at 100% zoom if (scale_factor.Ratio == 1) { layer.Draw(g, layer.Surface, layer.Opacity, false); } else { using (var scaled = CairoExtensions.CreateImageSurface(Cairo.Format.Argb32, dst.Width, dst.Height)) { g.Save(); // Have to undo the translate set above g.Translate(offset.X, offset.Y); CopyScaled(layer.Surface, scaled, r.ToGdkRectangle()); layer.Draw(g, scaled, layer.Opacity, false); g.Restore(); } } } } // If we are at least 200% and grid is requested, draw it if (enable_pixel_grid && PintaCore.Actions.View.PixelGrid.Value && scale_factor.Ratio <= 0.5d) { RenderPixelGrid(dst, offset); } dst.MarkDirty(); }
private unsafe void RenderOneToOne(Cairo.ImageSurface src, Cairo.ImageSurface dst, Gdk.Point offset, bool checker) { Gdk.Rectangle srcRect = new Gdk.Rectangle(offset, dst.GetBounds().Size); srcRect.Intersect(src.GetBounds()); ColorBgra *src_ptr = (ColorBgra *)src.DataPtr; ColorBgra *dst_ptr = (ColorBgra *)dst.DataPtr; int src_width = src.Width; int dst_width = dst.Width; int dst_height = dst.Height; for (int dstRow = 0; dstRow < srcRect.Height; ++dstRow) { ColorBgra *dstRowPtr = dst.GetRowAddressUnchecked(dst_ptr, dst_width, dstRow); ColorBgra *srcRowPtr = src.GetPointAddressUnchecked(src_ptr, src_width, offset.X, dstRow + offset.Y); int dstCol = offset.X; int dstColEnd = offset.X + srcRect.Width; int checkerY = dstRow + offset.Y; while (dstCol < dstColEnd) { // Blend it over the checkerboard background if (checker) { int b = srcRowPtr->B; int g = srcRowPtr->G; int r = srcRowPtr->R; int a = srcRowPtr->A; int v = (((dstCol ^ checkerY) & 8) << 3) + 191; a = a + (a >> 7); int vmia = v * (256 - a); r = ((r * a) + vmia) >> 8; g = ((g * a) + vmia) >> 8; b = ((b * a) + vmia) >> 8; dstRowPtr->Bgra = (uint)b + ((uint)g << 8) + ((uint)r << 16) + ((uint)255 << 24); } else { *dstRowPtr = *srcRowPtr; } ++dstRowPtr; ++srcRowPtr; ++dstCol; } } }
/// <summary> /// Provides a default implementation for performing dst = F(lhs, rhs) over some rectangle of interest. /// </summary> /// <param name="dst">The Surface to write pixels to.</param> /// <param name="dstOffset">The pixel offset that defines the upper-left of the rectangle-of-interest for the dst Surface.</param> /// <param name="lhs">The Surface to read pixels from for the lhs parameter given to the method <b>ColorBgra Apply(ColorBgra, ColorBgra)</b>b>.</param></param> /// <param name="lhsOffset">The pixel offset that defines the upper-left of the rectangle-of-interest for the lhs Surface.</param> /// <param name="rhs">The Surface to read pixels from for the rhs parameter given to the method <b>ColorBgra Apply(ColorBgra, ColorBgra)</b></param> /// <param name="rhsOffset">The pixel offset that defines the upper-left of the rectangle-of-interest for the rhs Surface.</param> /// <param name="roiSize">The size of the rectangles-of-interest for all Surfaces.</param> public void Apply(Cairo.ImageSurface dst, Point dstOffset, Cairo.ImageSurface lhs, Point lhsOffset, Cairo.ImageSurface rhs, Point rhsOffset, Size roiSize) { // Bounds checking only enabled in Debug builds. #if DEBUG // Create bounding rectangles for each Surface Rectangle dstRect = new Rectangle(dstOffset, roiSize); Rectangle lhsRect = new Rectangle(lhsOffset, roiSize); Rectangle rhsRect = new Rectangle(rhsOffset, roiSize); // Clip those rectangles to those Surface's bounding rectangles Rectangle dstClip = Rectangle.Intersect(dstRect, dst.GetBounds()); Rectangle lhsClip = Rectangle.Intersect(lhsRect, lhs.GetBounds()); Rectangle rhsClip = Rectangle.Intersect(rhsRect, rhs.GetBounds()); // If any of those Rectangles actually got clipped, then throw an exception if (dstRect != dstClip) { throw new ArgumentOutOfRangeException("roiSize", "Destination roi out of bounds"); } if (lhsRect != lhsClip) { throw new ArgumentOutOfRangeException("roiSize", "lhs roi out of bounds"); } if (rhsRect != rhsClip) { throw new ArgumentOutOfRangeException("roiSize", "rhs roi out of bounds"); } #endif // Cache the width and height properties int width = roiSize.Width; int height = roiSize.Height; // Do the work. unsafe { for (int row = 0; row < height; ++row) { ColorBgra *dstPtr = dst.GetPointAddress(dstOffset.X, dstOffset.Y + row); ColorBgra *lhsPtr = lhs.GetPointAddress(lhsOffset.X, lhsOffset.Y + row); ColorBgra *rhsPtr = rhs.GetPointAddress(rhsOffset.X, rhsOffset.Y + row); Apply(dstPtr, lhsPtr, rhsPtr, width); } } }
/// <summary> /// Constructs an IrregularSurface by copying the given region-of-interest from an Image. /// </summary> /// <param name="source">The Surface to copy pixels from.</param> /// <param name="roi">Defines the Region from which to copy pixels from the Image.</param> public IrregularSurface(Cairo.ImageSurface source, Region roi) { Region roiClipped = (Region)roi.Copy(); roiClipped.Intersect(Region.Rectangle(source.GetBounds())); Rectangle[] rects = roiClipped.GetRectangles(); this.placedSurfaces = new List <PlacedSurface> (rects.Length); foreach (Rectangle rect in rects) { this.placedSurfaces.Add(new PlacedSurface(source, rect)); } this.region = roiClipped; }
public void Start (BaseEffect effect) { if (live_preview_enabled) throw new InvalidOperationException ("LivePreviewManager.Start() called while live preview is already enabled."); // Create live preview surface. // Start rendering. // Listen for changes to effectConfiguration object, and restart render if needed. live_preview_enabled = true; apply_live_preview_flag = false; cancel_live_preview_flag = false; layer = PintaCore.Layers.CurrentLayer; this.effect = effect; //TODO Use the current tool layer instead. live_preview_surface = new Cairo.ImageSurface (Cairo.Format.Argb32, PintaCore.Workspace.ImageSize.Width, PintaCore.Workspace.ImageSize.Height); // Handle selection path. PintaCore.Tools.Commit (); selection_path = (PintaCore.Layers.ShowSelection) ? PintaCore.Workspace.ActiveDocument.Selection.SelectionPath : null; render_bounds = (selection_path != null) ? selection_path.GetBounds () : live_preview_surface.GetBounds (); render_bounds = PintaCore.Workspace.ClampToImageSize (render_bounds); history_item = new SimpleHistoryItem (effect.Icon, effect.Name); history_item.TakeSnapshotOfLayer (PintaCore.Layers.CurrentLayerIndex); // Paint the pre-effect layer surface into into the working surface. using (var ctx = new Cairo.Context (live_preview_surface)) { layer.Draw(ctx, layer.Surface, 1); } if (effect.EffectData != null) effect.EffectData.PropertyChanged += EffectData_PropertyChanged; if (Started != null) { Started (this, new LivePreviewStartedEventArgs()); } var settings = new AsyncEffectRenderer.Settings () { ThreadCount = PintaCore.System.RenderThreads, TileWidth = render_bounds.Width, TileHeight = 1, ThreadPriority = ThreadPriority.BelowNormal }; Debug.WriteLine (DateTime.Now.ToString("HH:mm:ss:ffff") + "Start Live preview."); renderer = new Renderer (this, settings); renderer.Start (effect, layer.Surface, live_preview_surface, render_bounds); if (effect.IsConfigurable) { if (!effect.LaunchConfiguration ()) { PintaCore.Chrome.MainWindowBusy = true; Cancel (); } else { PintaCore.Chrome.MainWindowBusy = true; Apply (); } } else { PintaCore.Chrome.MainWindowBusy = true; Apply (); } }
private unsafe void CopyScaledZoomOut(Cairo.ImageSurface src, Cairo.ImageSurface dst, Rectangle roi) { // Tell Cairo we need the latest raw data dst.Flush(); const int fpShift = 12; const int fpFactor = (1 << fpShift); var source_size = src.GetBounds().Size; // Find destination bounds var dst_left = (int)(((long)roi.X * fpFactor * (long)source_size.Width) / (long)destination_size.Width); var dst_top = (int)(((long)roi.Y * fpFactor * (long)source_size.Height) / (long)destination_size.Height); var dst_right = (int)(((long)(roi.X + dst.Width) * fpFactor * (long)source_size.Width) / (long)destination_size.Width); var dst_bottom = (int)(((long)(roi.Y + dst.Height) * fpFactor * (long)source_size.Height) / (long)destination_size.Height); var dx = (dst_right - dst_left) / dst.Width; var dy = (dst_bottom - dst_top) / dst.Height; // Cache pointers to surface raw data and sizes var src_ptr = (ColorBgra *)src.DataPtr; var dst_ptr = (ColorBgra *)dst.DataPtr; var src_width = src.Width; var dst_width = dst.Width; var dst_height = dst.Height; for (int dstRow = 0, fDstY = dst_top; dstRow < dst_height && fDstY < dst_bottom; ++dstRow, fDstY += dy) { var srcY1 = fDstY >> fpShift; // y var srcY2 = (fDstY + (dy >> 2)) >> fpShift; // y + 0.25 var srcY3 = (fDstY + (dy >> 1)) >> fpShift; // y + 0.50 var srcY4 = (fDstY + (dy >> 1) + (dy >> 2)) >> fpShift; // y + 0.75 var src1 = src.GetRowAddressUnchecked(src_ptr, src_width, srcY1); var src2 = src.GetRowAddressUnchecked(src_ptr, src_width, srcY2); var src3 = src.GetRowAddressUnchecked(src_ptr, src_width, srcY3); var src4 = src.GetRowAddressUnchecked(src_ptr, src_width, srcY4); var dstPtr = dst.GetRowAddressUnchecked(dst_ptr, dst_width, dstRow); var checkerY = dstRow + roi.Y; var checkerX = roi.X; var maxCheckerX = checkerX + dst.Width; for (var fDstX = dst_left; checkerX < maxCheckerX && fDstX < dst_right; ++checkerX, fDstX += dx) { var srcX1 = (fDstX + (dx >> 2)) >> fpShift; // x + 0.25 var srcX2 = (fDstX + (dx >> 1) + (dx >> 2)) >> fpShift; // x + 0.75 var srcX3 = fDstX >> fpShift; // x var srcX4 = (fDstX + (dx >> 1)) >> fpShift; // x + 0.50 var p1 = src1 + srcX1; var p2 = src2 + srcX2; var p3 = src3 + srcX3; var p4 = src4 + srcX4; var r = (2 + p1->R + p2->R + p3->R + p4->R) >> 2; var g = (2 + p1->G + p2->G + p3->G + p4->G) >> 2; var b = (2 + p1->B + p2->B + p3->B + p4->B) >> 2; var a = (2 + p1->A + p2->A + p3->A + p4->A) >> 2; // Copy color to destination *dstPtr++ = ColorBgra.FromUInt32((uint)b + ((uint)g << 8) + ((uint)r << 16) + ((uint)a << 24)); } } // Tell Cairo we changed the raw data dst.MarkDirty(); }
private void HandlePintaCoreActionsImageAutoCropActivated(object sender, EventArgs e) { Document doc = PintaCore.Workspace.ActiveDocument; PintaCore.Tools.Commit(); Cairo.ImageSurface image = doc.CurrentUserLayer.Surface; Gdk.Rectangle rect = image.GetBounds(); Cairo.Color borderColor = image.GetPixel(0, 0); bool cropSide = true; int depth = -1; //From the top down while (cropSide) { depth++; for (int i = 0; i < image.Width; i++) { if (!borderColor.Equals(image.GetPixel(i, depth))) { cropSide = false; break; } } //Check if the image is blank/mono-coloured, only need to do it on this scan if (depth == image.Height) { return; } } rect = new Gdk.Rectangle(rect.X, rect.Y + depth, rect.Width, rect.Height - depth); depth = image.Height; cropSide = true; //From the bottom up while (cropSide) { depth--; for (int i = 0; i < image.Width; i++) { if (!borderColor.Equals(image.GetPixel(i, depth))) { cropSide = false; break; } } } rect = new Gdk.Rectangle(rect.X, rect.Y, rect.Width, depth - rect.Y); depth = 0; cropSide = true; //From left to right while (cropSide) { depth++; for (int i = 0; i < image.Height; i++) { if (!borderColor.Equals(image.GetPixel(depth, i))) { cropSide = false; break; } } } rect = new Gdk.Rectangle(rect.X + depth, rect.Y, rect.Width - depth, rect.Height); depth = image.Width; cropSide = true; //From right to left while (cropSide) { depth--; for (int i = 0; i < image.Height; i++) { if (!borderColor.Equals(image.GetPixel(depth, i))) { cropSide = false; break; } } } rect = new Gdk.Rectangle(rect.X, rect.Y, depth - rect.X, rect.Height); CropImageToRectangle(doc, rect); }
private unsafe void RenderOneToOne(List <Layer> layers, Cairo.ImageSurface dst, Gdk.Point offset) { // The first layer should be blended with the transparent checkerboard var checker = true; CheckerBoardOperation checker_op = null; for (int i = 0; i < layers.Count; i++) { var layer = layers[i]; // If we're in LivePreview, substitute current layer with the preview layer if (layer == PintaCore.Layers.CurrentLayer && PintaCore.LivePreview.IsEnabled) { layer = CreateLivePreviewLayer(layer); } // If the layer is offset, handle it here if (!layer.Offset.IsEmpty()) { layer = CreateOffsetLayer(layer); } var src = layer.Surface; // Get the blend mode for this layer and opacity var blend_op = UserBlendOps.GetBlendOp(layer.BlendMode, layer.Opacity); if (checker) { checker_op = new CheckerBoardOperation(layer.Opacity); } // Figure out where our source and destination intersect var srcRect = new Gdk.Rectangle(offset, dst.GetBounds().Size); srcRect.Intersect(src.GetBounds()); // Get pointers to our surfaces var src_ptr = (ColorBgra *)src.DataPtr; var dst_ptr = (ColorBgra *)dst.DataPtr; // Cache widths int src_width = src.Width; int dst_width = dst.Width; for (int dstRow = 0; dstRow < srcRect.Height; ++dstRow) { ColorBgra *dstRowPtr = dst.GetRowAddressUnchecked(dst_ptr, dst_width, dstRow); ColorBgra *srcRowPtr = src.GetPointAddressUnchecked(src_ptr, src_width, offset.X, dstRow + offset.Y); int dstCol = offset.X; int dstColEnd = offset.X + srcRect.Width; int checkerY = dstRow + offset.Y; while (dstCol < dstColEnd) { // Blend it over the checkerboard background if (checker) { *dstRowPtr = checker_op.Apply(*srcRowPtr, dstCol, checkerY); } else { *dstRowPtr = blend_op.Apply(*dstRowPtr, *srcRowPtr); } ++dstRowPtr; ++srcRowPtr; ++dstCol; } } // Only checker the first layer checker = false; } }
public void Start(BaseEffect effect) { if (live_preview_enabled) { throw new InvalidOperationException("LivePreviewManager.Start() called while live preview is already enabled."); } // Create live preview surface. // Start rendering. // Listen for changes to effectConfiguration object, and restart render if needed. live_preview_enabled = true; apply_live_preview_flag = false; cancel_live_preview_flag = false; layer = PintaCore.Layers.CurrentLayer; this.effect = effect; //TODO Use the current tool layer instead. live_preview_surface = new Cairo.ImageSurface(Cairo.Format.Argb32, PintaCore.Workspace.ImageSize.Width, PintaCore.Workspace.ImageSize.Height); // Handle selection path. PintaCore.Tools.Commit(); selection_path = (PintaCore.Layers.ShowSelection) ? PintaCore.Workspace.ActiveDocument.Selection.SelectionPath : null; render_bounds = (selection_path != null) ? selection_path.GetBounds() : live_preview_surface.GetBounds(); render_bounds = PintaCore.Workspace.ClampToImageSize(render_bounds); history_item = new SimpleHistoryItem(effect.Icon, effect.Name); history_item.TakeSnapshotOfLayer(PintaCore.Layers.CurrentLayerIndex); // Paint the pre-effect layer surface into into the working surface. using (var ctx = new Cairo.Context(live_preview_surface)) { layer.Draw(ctx, layer.Surface, 1); } if (effect.EffectData != null) { effect.EffectData.PropertyChanged += EffectData_PropertyChanged; } if (Started != null) { Started(this, new LivePreviewStartedEventArgs()); } var settings = new AsyncEffectRenderer.Settings() { ThreadCount = PintaCore.System.RenderThreads, TileWidth = render_bounds.Width, TileHeight = 1, ThreadPriority = ThreadPriority.BelowNormal }; Debug.WriteLine(DateTime.Now.ToString("HH:mm:ss:ffff") + "Start Live preview."); renderer = new Renderer(this, settings); renderer.Start(effect, layer.Surface, live_preview_surface, render_bounds); if (effect.IsConfigurable) { if (!effect.LaunchConfiguration()) { PintaCore.Chrome.MainWindowBusy = true; Cancel(); } else { PintaCore.Chrome.MainWindowBusy = true; Apply(); } } else { PintaCore.Chrome.MainWindowBusy = true; Apply(); } }
private unsafe void RenderZoomOut(Cairo.ImageSurface src, Cairo.ImageSurface dst, Gdk.Point offset, Gdk.Size destinationSize, bool checker) { const int fpShift = 12; const int fpFactor = (1 << fpShift); Gdk.Size sourceSize = src.GetBounds().Size; long fDstLeftLong = ((long)offset.X * fpFactor * (long)sourceSize.Width) / (long)destinationSize.Width; long fDstTopLong = ((long)offset.Y * fpFactor * (long)sourceSize.Height) / (long)destinationSize.Height; long fDstRightLong = ((long)(offset.X + dst.Width) * fpFactor * (long)sourceSize.Width) / (long)destinationSize.Width; long fDstBottomLong = ((long)(offset.Y + dst.Height) * fpFactor * (long)sourceSize.Height) / (long)destinationSize.Height; int fDstLeft = (int)fDstLeftLong; int fDstTop = (int)fDstTopLong; int fDstRight = (int)fDstRightLong; int fDstBottom = (int)fDstBottomLong; int dx = (fDstRight - fDstLeft) / dst.Width; int dy = (fDstBottom - fDstTop) / dst.Height; ColorBgra *src_ptr = (ColorBgra *)src.DataPtr; ColorBgra *dst_ptr = (ColorBgra *)dst.DataPtr; int src_width = src.Width; int dst_width = dst.Width; for (int dstRow = 0, fDstY = fDstTop; dstRow < dst.Height && fDstY < fDstBottom; ++dstRow, fDstY += dy) { int srcY1 = fDstY >> fpShift; // y int srcY2 = (fDstY + (dy >> 2)) >> fpShift; // y + 0.25 int srcY3 = (fDstY + (dy >> 1)) >> fpShift; // y + 0.50 int srcY4 = (fDstY + (dy >> 1) + (dy >> 2)) >> fpShift; // y + 0.75 ColorBgra *src1 = src.GetRowAddressUnchecked(src_ptr, src_width, srcY1); ColorBgra *src2 = src.GetRowAddressUnchecked(src_ptr, src_width, srcY2); ColorBgra *src3 = src.GetRowAddressUnchecked(src_ptr, src_width, srcY3); ColorBgra *src4 = src.GetRowAddressUnchecked(src_ptr, src_width, srcY4); ColorBgra *dstPtr = dst.GetRowAddressUnchecked(dst_ptr, dst_width, dstRow); int checkerY = dstRow + offset.Y; int checkerX = offset.X; int maxCheckerX = checkerX + dst.Width; for (int fDstX = fDstLeft; checkerX < maxCheckerX && fDstX < fDstRight; ++checkerX, fDstX += dx) { int srcX1 = (fDstX + (dx >> 2)) >> fpShift; // x + 0.25 int srcX2 = (fDstX + (dx >> 1) + (dx >> 2)) >> fpShift; // x + 0.75 int srcX3 = fDstX >> fpShift; // x int srcX4 = (fDstX + (dx >> 1)) >> fpShift; // x + 0.50 ColorBgra *p1 = src1 + srcX1; ColorBgra *p2 = src2 + srcX2; ColorBgra *p3 = src3 + srcX3; ColorBgra *p4 = src4 + srcX4; int r = (2 + p1->R + p2->R + p3->R + p4->R) >> 2; int g = (2 + p1->G + p2->G + p3->G + p4->G) >> 2; int b = (2 + p1->B + p2->B + p3->B + p4->B) >> 2; int a = (2 + p1->A + p2->A + p3->A + p4->A) >> 2; if (checker) { // Blend it over the checkerboard background int v = ((checkerX ^ checkerY) & 8) * 8 + 191; a = a + (a >> 7); int vmia = v * (256 - a); r = ((r * a) + vmia) >> 8; g = ((g * a) + vmia) >> 8; b = ((b * a) + vmia) >> 8; dstPtr->Bgra = (uint)b + ((uint)g << 8) + ((uint)r << 16) + 0xff000000; } else { dstPtr->Bgra = (uint)b + ((uint)g << 8) + ((uint)r << 16) + ((uint)a << 24); } ++dstPtr; } } }