///////////////////////////////////////////////////////////////////////////////// // 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. // // // // Ported to Pinta by: Jonathan Pobst // ///////////////////////////////////////////////////////////////////////////////// using System; using Gdk; using Pinta.Core; namespace Pinta.Gui.Widgets { class CanvasRenderer { private Size source_size; private Size destination_size; 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 (Cairo.ImageSurface src, Cairo.ImageSurface dst, Gdk.Point offset, bool checker) { dst.Flush (); if (scale_factor.Ratio == 1) RenderOneToOne (src, dst, offset, checker); else if (scale_factor.Ratio < 1) RenderZoomIn (src, dst, offset, checker); else RenderZoomOut (src, dst, offset, destination_size, checker); dst.MarkDirty (); } #region Algorithms ported from PDN 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; } } } private unsafe void RenderZoomIn (Cairo.ImageSurface src, Cairo.ImageSurface dst, Gdk.Point offset, bool checker) { 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 = *(srcRow + srcX); } ++dstPtr; } } } 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; } } } private int[] CreateLookupX (int srcWidth, int dstWidth, ScaleFactor scaleFactor) { var lookup = new int[dstWidth + 1]; for (int x = 0; x < lookup.Length; ++x) { Gdk.Point pt = new Gdk.Point (x, 0); Gdk.Point clientPt = scaleFactor.ScalePoint (pt); // Sometimes the scale factor is slightly different on one axis than // on another, simply due to accuracy. So we have to clamp this value to // be within bounds. lookup[x] = Utility.Clamp (clientPt.X, 0, srcWidth - 1); } return lookup; } private int[] CreateLookupY (int srcHeight, int dstHeight, ScaleFactor scaleFactor) { var lookup = new int[dstHeight + 1]; for (int y = 0; y < lookup.Length; ++y) { Gdk.Point pt = new Gdk.Point (0, y); Gdk.Point clientPt = scaleFactor.ScalePoint (pt); // Sometimes the scale factor is slightly different on one axis than // on another, simply due to accuracy. So we have to clamp this value to // be within bounds. lookup[y] = Utility.Clamp (clientPt.Y, 0, srcHeight - 1); } return lookup; } private int[] CreateS2DLookupX(int srcWidth, int dstWidth, ScaleFactor scaleFactor) { var lookup = new int[srcWidth + 1]; for (int x = 0; x < lookup.Length; ++x) { Gdk.Point pt = new Gdk.Point (x, 0); Gdk.Point clientPt = scaleFactor.UnscalePoint (pt); // Sometimes the scale factor is slightly different on one axis than // on another, simply due to accuracy. So we have to clamp this value to // be within bounds. lookup[x] = Utility.Clamp (clientPt.X, 0, dstWidth - 1); } return lookup; } private int[] CreateS2DLookupY (int srcHeight, int dstHeight, ScaleFactor scaleFactor) { var lookup = new int[srcHeight + 1]; for (int y = 0; y < lookup.Length; ++y) { Gdk.Point pt = new Gdk.Point (0, y); Gdk.Point clientPt = scaleFactor.UnscalePoint (pt); // Sometimes the scale factor is slightly different on one axis than // on another, simply due to accuracy. So we have to clamp this value to // be within bounds. lookup[y] = Utility.Clamp (clientPt.Y, 0, dstHeight - 1); } return lookup; } #endregion } }