<commit_before>/////////////////////////////////////////////////////////////////////////////////
// Paint.NET                                                                   //
// Copyright (C) dotPDN LLC, <NAME>, <NAME>, and contributors.     //
// Portions Copyright (C) Microsoft Corporation. All Rights Reserved.          //
// See license-pdn.txt for full licensing and attribution details.             //
//                                                                             //
// Ported to Pinta by: <NAME> <<EMAIL>>                      //
/////////////////////////////////////////////////////////////////////////////////

using System;
using Gdk;
using Pinta.Core;
using System.Collections.Generic;

namespace Pinta.Gui.Widgets
{
	class NewCanvasRenderer
	{
		private Size source_size;
		private Size destination_size;
		private Layer scratch_layer;
		private Layer offset_layer;

		private ScaleFactor scale_factor;
		private bool generated;
		private int[] d2sLookupX;
		private int[] d2sLookupY;
		private int[] s2dLookupX;
		private int[] s2dLookupY;

		public ScaleFactor ScaleFactor { get {return scale_factor;}}
		public int[] Dst2SrcLookupX { get {return d2sLookupX;}}
		public int[] Dst2SrcLookupY { get {return d2sLookupY;}}
		public int[] Src2DstLookupX { get {return s2dLookupX;}}
		public int[] Src2DstLookupY { get {return s2dLookupY;}}

		public void Initialize (Size sourceSize, Size destinationSize)
		{
			if (sourceSize == source_size && destinationSize == destination_size)
				return;
				
			source_size = sourceSize;
			destination_size = destinationSize;
			
			scale_factor = new ScaleFactor (source_size.Width, destination_size.Width);
			generated = false;
		}

		public void Render (List<Layer> layers, Cairo.ImageSurface dst, Gdk.Point offset, bool checker)
		{
			dst.Flush ();
		
			if (scale_factor.Ratio == 1)
				RenderOneToOne (layers, dst, offset, checker);
			else if (scale_factor.Ratio < 1)
				RenderZoomIn (layers, dst, offset, checker);
			else
				RenderZoomOut (layers, dst, offset, destination_size, checker);
			
			dst.MarkDirty ();
		}

		private Layer ScratchLayer {
			get {
				// Create one if we don't have one
				if (scratch_layer == null)
					scratch_layer = new Layer (new Cairo.ImageSurface (Cairo.Format.ARGB32, source_size.Width, source_size.Height));

				// If we have the wrong size one, dispose it and create the correct size
				if (scratch_layer.Surface.Width != source_size.Width || scratch_layer.Surface.Height != source_size.Height) {
					(scratch_layer.Surface as IDisposable).Dispose ();
					scratch_layer = new Layer (new Cairo.ImageSurface (Cairo.Format.ARGB32, source_size.Width, source_size.Height));
				}

				return scratch_layer;
			}
		}

		private Layer OffsetLayer {
			get {
				// Create one if we don't have one
				if (offset_layer == null)
					offset_layer = new Layer (new Cairo.ImageSurface (Cairo.Format.ARGB32, source_size.Width, source_size.Height));

				// If we have the wrong size one, dispose it and create the correct size
				if (offset_layer.Surface.Width != source_size.Width || offset_layer.Surface.Height != source_size.Height) {
					(offset_layer.Surface as IDisposable).Dispose ();
					offset_layer = new Layer (new Cairo.ImageSurface (Cairo.Format.ARGB32, source_size.Width, source_size.Height));
				}

				return offset_layer;
			}
		}

		private Layer CreateLivePreviewLayer (Layer original)
		{
			var scratch = ScratchLayer;
			scratch.Surface.Clear ();

			using (var g = new Cairo.Context (scratch.Surface)) {
				g.SetSource (original.Surface);
				g.Paint ();

				g.Save ();

				g.AppendPath (PintaCore.Layers.SelectionPath);
				g.Clip ();
				g.SetSource (PintaCore.LivePreview.LivePreviewSurface);
				g.Paint ();

				g.Restore ();
			}

			scratch.BlendMode = original.BlendMode;
			scratch.Offset = original.Offset;
			scratch.Opacity = original.Opacity;

			return scratch;
		}

		private Layer CreateOffsetLayer (Layer original, Point canvas_offset)
		{
			var offset = OffsetLayer;
			offset.Surface.Clear ();

			using (var g = new Cairo.Context (offset.Surface)) {
				g.SetSourceSurface (original.Surface, canvas_offset.X + (int)original.Offset.X, canvas_offset.Y + (int)original.Offset.Y);
				g.Paint ();
			}

			offset.BlendMode = original.BlendMode;
			offset.Offset = original.Offset;
			offset.Opacity = original.Opacity;

			return offset;
		}

		#region Algorithms ported from PDN
		private unsafe void RenderOneToOne (List<Layer> layers, Cairo.ImageSurface dst, Gdk.Point offset, bool checker)
		{
			checker = true;

			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, offset);

				var src = layer.Surface;

				// Get the blend mode for this layer and opacity
				var blend_op = UserBlendOps.GetBlendOp (layer.BlendMode, 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 and heights
				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 = blend_op.Apply (*dstRowPtr, *srcRowPtr);
						}
					
						++dstRowPtr;
						++srcRowPtr;
						++dstCol;
					}
				}

				// Only checker the first layer
				checker = false;
			}
		}

		private unsafe void RenderZoomIn (List<Layer> layers, Cairo.ImageSurface dst, Gdk.Point offset, bool checker)
		{
			checker = true;

			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, offset);

				var src = layer.Surface;

				// Get the blend mode for this layer and opacity
				var blend_op = UserBlendOps.GetBlendOp (layer.BlendMode, layer.Opacity);
				
				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;

				if (!generated) {
					d2sLookupX = CreateLookupX (src_width, destination_size.Width, scale_factor);
					d2sLookupY = CreateLookupY (src.Height, destination_size.Height, scale_factor);
					s2dLookupX = CreateS2DLookupX (src_width, destination_size.Width, scale_factor);
					s2dLookupY = CreateS2DLookupY (src.Height, destination_size.Height, scale_factor);

					generated = true;
				}

				for (int dstRow = 0; dstRow < dst_height; ++dstRow) {
					int nnY = dstRow + offset.Y;
					int srcY = d2sLookupY[nnY];

					ColorBgra* dstPtr = dst.GetRowAddressUnchecked (dst_ptr, dst_width, dstRow);
					ColorBgra* srcRow = src.GetRowAddressUnchecked (src_ptr, src_width, srcY);

					for (int dstCol = 0; dstCol < dst_width; ++dstCol) {
						int nnX = dstCol + offset.X;
						int srcX = d2sLookupX[nnX];
					
						if (checker) {
							ColorBgra src2 = *(srcRow + srcX);
							int b = src2.B;
							int g = src2.G;
							int r = src2.R;
							int a = src2.A;

							// Blend it over the checkerboard background
							int v = (((dstCol + offset.X) ^ (dstRow + offset.Y)) & 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) + ((uint)255 << 24);
						} else {
							*dstPtr = blend_op.Apply (*dstPtr, *(srcRow + srcX));
						}

						++dstPtr;
					}
				}

				// Only checker the first layer
				checker = false;
			}
		}

		private unsafe void RenderZoomOut (List<Layer> layers, Cairo.ImageSurface dst, Gdk.Point offset, Gdk.Size destinationSize, bool checker)
		{
			checker = true;<commit_msg>[WIP] Tidy up.<commit_after>/////////////////////////////////////////////////////////////////////////////////
// Paint.NET                                                                   //
// Copyright (C) dotPDN LLC, <NAME>, <NAME>, and contributors.     //
// Portions Copyright (C) Microsoft Corporation. All Rights Reserved.          //
// See license-pdn.txt for full licensing and attribution details.             //
//                                                                             //
// Ported to Pinta by: <NAME> <<EMAIL>>                      //
/////////////////////////////////////////////////////////////////////////////////

using System;
using System.Collections.Generic;
using Gdk;
using Pinta.Core;

namespace Pinta.Gui.Widgets
{
	class NewCanvasRenderer
	{
		private Size source_size;
		private Size destination_size;
		private Layer scratch_layer;
		private Layer offset_layer;

		private ScaleFactor scale_factor;
		private bool generated;
		private int[] d2sLookupX;
		private int[] d2sLookupY;
		private int[] s2dLookupX;
		private int[] s2dLookupY;

		public ScaleFactor ScaleFactor { get {return scale_factor;}}
		public int[] Dst2SrcLookupX { get {return d2sLookupX;}}
		public int[] Dst2SrcLookupY { get {return d2sLookupY;}}
		public int[] Src2DstLookupX { get {return s2dLookupX;}}
		public int[] Src2DstLookupY { get {return s2dLookupY;}}

		public void Initialize (Size sourceSize, Size destinationSize)
		{
			if (sourceSize == source_size && destinationSize == destination_size)
				return;
				
			source_size = sourceSize;
			destination_size = destinationSize;
			
			scale_factor = new ScaleFactor (source_size.Width, destination_size.Width);
			generated = false;
		}

		public void Render (List<Layer> layers, Cairo.ImageSurface dst, Gdk.Point offset)
		{
			dst.Flush ();
		
			if (scale_factor.Ratio == 1)
				RenderOneToOne (layers, dst, offset);
			else if (scale_factor.Ratio < 1)
				RenderZoomIn (layers, dst, offset);
			else
				RenderZoomOut (layers, dst, offset, destination_size);
			
			dst.MarkDirty ();
		}

		private Layer ScratchLayer {
			get {
				// Create one if we don't have one
				if (scratch_layer == null)
					scratch_layer = new Layer (new Cairo.ImageSurface (Cairo.Format.ARGB32, source_size.Width, source_size.Height));

				// If we have the wrong size one, dispose it and create the correct size
				if (scratch_layer.Surface.Width != source_size.Width || scratch_layer.Surface.Height != source_size.Height) {
					(scratch_layer.Surface as IDisposable).Dispose ();
					scratch_layer = new Layer (new Cairo.ImageSurface (Cairo.Format.ARGB32, source_size.Width, source_size.Height));
				}

				return scratch_layer;
			}
		}

		private Layer OffsetLayer {
			get {
				// Create one if we don't have one
				if (offset_layer == null)
					offset_layer = new Layer (new Cairo.ImageSurface (Cairo.Format.ARGB32, source_size.Width, source_size.Height));

				// If we have the wrong size one, dispose it and create the correct size
				if (offset_layer.Surface.Width != source_size.Width || offset_layer.Surface.Height != source_size.Height) {
					(offset_layer.Surface as IDisposable).Dispose ();
					offset_layer = new Layer (new Cairo.ImageSurface (Cairo.Format.ARGB32, source_size.Width, source_size.Height));
				}

				return offset_layer;
			}
		}

		private Layer CreateLivePreviewLayer (Layer original)
		{
			var scratch = ScratchLayer;
			scratch.Surface.Clear ();

			using (var g = new Cairo.Context (scratch.Surface)) {
				g.SetSource (original.Surface);
				g.Paint ();

				g.Save ();

				g.AppendPath (PintaCore.Layers.SelectionPath);
				g.Clip ();
				g.SetSource (PintaCore.LivePreview.LivePreviewSurface);
				g.Paint ();

				g.Restore ();
			}

			scratch.BlendMode = original.BlendMode;
			scratch.Offset = original.Offset;
			scratch.Opacity = original.Opacity;

			return scratch;
		}

		private Layer CreateOffsetLayer (Layer original, Point canvas_offset)
		{
			var offset = OffsetLayer;
			offset.Surface.Clear ();

			using (var g = new Cairo.Context (offset.Surface)) {
				g.SetSourceSurface (original.Surface, canvas_offset.X + (int)original.Offset.X, canvas_offset.Y + (int)original.Offset.Y);
				g.Paint ();
			}

			offset.BlendMode = original.BlendMode;
			offset.Offset = original.Offset;
			offset.Opacity = original.Opacity;

			return offset;
		}

		#region Algorithms ported from PDN
		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;

			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, offset);

				var src = layer.Surface;

				// Get the blend mode for this layer and opacity
				var blend_op = UserBlendOps.GetBlendOp (layer.BlendMode, 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 and heights
				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 = blend_op.Apply (*dstRowPtr, *srcRowPtr);
						}
					
						++dstRowPtr;
						++srcRowPtr;
						++dstCol;
					}
				}

				// Only checker the first layer
				checker = false;
			}
		}

		private unsafe void RenderZoomIn (List<Layer> layers, Cairo.ImageSurface dst, Gdk.Point offset)
		{
			// The first layer should be blended with the transparent checkerboard
			var checker = true;

			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, offset);

				var src = layer.Surface;

				// Get the blend mode for this layer and opacity
				var blend_op = UserBlendOps.GetBlendOp (layer.BlendMode, layer.Opacity);
				
				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;

				if (!generated) {
					d2sLookupX = CreateLookupX (src_width, destination_size.Width, scale_factor);
					d2sLookupY = CreateLookupY (src.Height, destination_size.Height, scale_factor);
					s2dLookupX = CreateS2DLookupX (src_width, destination_size.Width, scale_factor);
					s2dLookupY = CreateS2DLookupY (src.Height, destination_size.Height, scale_factor);

					generated = true;
				}

				for (int dstRow = 0; dstRow < dst_height; ++dstRow) {
					int nnY = dstRow + offset.Y;
					int srcY = d2sLookupY[nnY];

					ColorBgra* dstPtr = dst.GetRowAddressUnchecked (dst_ptr, dst_width, dstRow);
					ColorBgra* srcRow = src.GetRowAddressUnchecked (src_ptr, src_width, srcY);

					for (int dstCol = 0; dstCol < dst_width; ++dstCol) {
						int nnX = dstCol + offset.X;
						int srcX = d2sLookupX[nnX];
					
						if (checker) {
							ColorBgra src2 = *(srcRow + srcX);
							int b = src2.B;
							int g = src2.G;
							int r = src2.R;
							int a = src2.A;

							// Blend it over the checkerboard background
							int v = (((dstCol + offset.X) ^ (dstRow + offset.Y)) & 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) + ((uint)255 << 24);
						} else {
							*dstPtr = blend_op.Apply (*dstPtr, *(srcRow + srcX));
						}

						++dstPtr;
					}
				}

				// Only checker the first layer
				checker = false;
			}
		}

		private unsafe void RenderZoomOut (List<Layer> layers, Cairo.ImageSurface dst, Gdk.Point offset, Gdk.Size destinationSize)
		{
			// The first layer should be blended with the transparent checkerboard
			var checker = true;
{'fraction_non_alphanumeric': 0.13145290047897817, 'fraction_numerical': 0.008515167642362959, 'mean_word_length': 2.5481495468277946, 'pattern_counts': {'":': 0, '<': 63, '<?xml version=': 0, '>': 65, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 10, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6609338', 'n_tokens_mistral': 7521, 'n_tokens_neox': 5887, 'n_words': 1651}