/// <summary> /// Copies the contents of the given surface to the upper left corner of this surface. /// </summary> /// <param name="source">The surface to copy pixels from.</param> /// <remarks> /// The source surface does not need to have the same dimensions as this surface. Clipping /// will be handled automatically. No resizing will be done. /// </remarks> private void CopySurface(MaskSurface source) { if (disposed) { throw new ObjectDisposedException("Surface"); } if (stride == source.stride && width == source.width && height == source.height) { unsafe { Memory.Copy(scan0.VoidStar, source.scan0.VoidStar, ((ulong)(height - 1) * (ulong)stride) + (ulong)width); } } else { int copyWidth = Math.Min(width, source.width); int copyHeight = Math.Min(height, source.height); unsafe { for (int y = 0; y < copyHeight; ++y) { Memory.Copy(GetRowAddressUnchecked(y), source.GetRowAddressUnchecked(y), (ulong)copyWidth); } } } }
public MaskSurface Clone() { MaskSurface surface = new MaskSurface(width, height); surface.CopySurface(this); return(surface); }
/// <summary> /// Initializes a new instance of the <see cref="ChannelPortsSuite"/> class. /// </summary> /// <param name="filterImageProvider">The filter image provider.</param> /// <exception cref="ArgumentNullException"><paramref name="filterImageProvider"/> is null.</exception> public unsafe ChannelPortsSuite(IFilterImageProvider filterImageProvider) { if (filterImageProvider == null) { throw new ArgumentNullException(nameof(filterImageProvider)); } this.filterImageProvider = filterImageProvider; readPixelsProc = new ReadPixelsProc(ReadPixelsProc); writeBasePixelsProc = new WriteBasePixelsProc(WriteBasePixels); readPortForWritePortProc = new ReadPortForWritePortProc(ReadPortForWritePort); scaledChannelSurface = null; scaledSelectionMask = null; disposed = false; }
public void Dispose() { if (!disposed) { disposed = true; if (scaledChannelSurface != null) { scaledChannelSurface.Dispose(); scaledChannelSurface = null; } if (scaledSelectionMask != null) { scaledSelectionMask.Dispose(); scaledSelectionMask = null; } } }
private static unsafe void FillSelectionMask(PixelMemoryDesc *destiniation, MaskSurface source, VRect srcRect) { byte *dstPtr = (byte *)destiniation->data.ToPointer(); int stride = destiniation->rowBits / 8; int bpp = destiniation->colBits / 8; int offset = destiniation->bitOffset / 8; for (int y = srcRect.top; y < srcRect.bottom; y++) { byte *src = source.GetPointAddressUnchecked(srcRect.left, y); byte *dst = dstPtr + (y * stride) + offset; for (int x = srcRect.left; x < srcRect.right; x++) { *dst = *src; src++; dst += bpp; } } }
/// <summary> /// Fits the source surface to this surface using bicubic interpolation. /// </summary> /// <param name="source">The Surface to read pixels from.</param> /// <remarks> /// This method was implemented with correctness, not performance, in mind. /// Based on: "Bicubic Interpolation for Image Scaling" by Paul Bourke, /// http://astronomy.swin.edu.au/%7Epbourke/colour/bicubic/ /// </remarks> public void BicubicFitSurface(MaskSurface source) { float leftF = (1 * (float)(width - 1)) / (float)(source.width - 1); float topF = (1 * (height - 1)) / (float)(source.height - 1); float rightF = ((float)(source.width - 3) * (float)(width - 1)) / (float)(source.width - 1); float bottomF = ((float)(source.Height - 3) * (float)(height - 1)) / (float)(source.height - 1); int left = (int)Math.Ceiling((double)leftF); int top = (int)Math.Ceiling((double)topF); int right = (int)Math.Floor((double)rightF); int bottom = (int)Math.Floor((double)bottomF); Rectangle[] rois = new Rectangle[] { Rectangle.FromLTRB(left, top, right, bottom), new Rectangle(0, 0, width, top), new Rectangle(0, top, left, height - top), new Rectangle(right, top, width - right, height - top), new Rectangle(left, bottom, right - left, height - bottom) }; Rectangle dstRoi = Bounds; for (int i = 0; i < rois.Length; ++i) { rois[i].Intersect(dstRoi); if (rois[i].Width > 0 && rois[i].Height > 0) { if (i == 0) { BicubicFitSurfaceUnchecked(source, rois[i]); } else { BicubicFitSurfaceChecked(source, rois[i]); } } } }
/// <summary> /// Implements bicubic filtering with NO bounds checking at any pixel. /// </summary> private unsafe void BicubicFitSurfaceUnchecked(MaskSurface source, Rectangle dstRoi) { Rectangle roi = Rectangle.Intersect(dstRoi, Bounds); Rectangle roiIn = Rectangle.Intersect(dstRoi, new Rectangle(1, 1, width - 1, height - 1)); IntPtr rColCacheIP = Memory.Allocate(4 * (long)roi.Width * (long)sizeof(double), false); double *rColCache = (double *)rColCacheIP.ToPointer(); // Precompute and then cache the value of R() for each column for (int dstX = roi.Left; dstX < roi.Right; ++dstX) { double srcColumn = (double)(dstX * (source.width - 1)) / (double)(width - 1); double srcColumnFloor = Math.Floor(srcColumn); double srcColumnFrac = srcColumn - srcColumnFloor; for (int m = -1; m <= 2; ++m) { int index = (m + 1) + ((dstX - roi.Left) * 4); double x = m - srcColumnFrac; rColCache[index] = R(x); } } // Set this up so we can cache the R()'s for every row double *rRowCache = stackalloc double[4]; for (int dstY = roi.Top; dstY < roi.Bottom; ++dstY) { double srcRow = (double)(dstY * (source.height - 1)) / (double)(height - 1); double srcRowFloor = Math.Floor(srcRow); double srcRowFrac = srcRow - srcRowFloor; int srcRowInt = (int)srcRow; byte * dstPtr = GetPointAddressUnchecked(roi.Left, dstY); // Compute the R() values for this row for (int n = -1; n <= 2; ++n) { double x = srcRowFrac - n; rRowCache[n + 1] = R(x); } rColCache = (double *)rColCacheIP.ToPointer(); byte *srcRowPtr = source.GetRowAddressUnchecked(srcRowInt - 1); for (int dstX = roi.Left; dstX < roi.Right; dstX++) { double srcColumn = (double)(dstX * (source.width - 1)) / (double)(width - 1); double srcColumnFloor = Math.Floor(srcColumn); double srcColumnFrac = srcColumn - srcColumnFloor; int srcColumnInt = (int)srcColumn; double graySum = 0; double alphaSum = 0; double totalWeight = 0; byte *srcPtr = srcRowPtr + srcColumnInt - 1; for (int n = 0; n <= 3; ++n) { double w0 = rColCache[0] * rRowCache[n]; double w1 = rColCache[1] * rRowCache[n]; double w2 = rColCache[2] * rRowCache[n]; double w3 = rColCache[3] * rRowCache[n]; double a0 = 255.0; double a1 = 255.0; double a2 = 255.0; double a3 = 255.0; alphaSum += (a0 * w0) + (a1 * w1) + (a2 * w2) + (a3 * w3); totalWeight += w0 + w1 + w2 + w3; graySum += (a0 * srcPtr[0] * w0) + (a1 * srcPtr[1] * w1) + (a2 * srcPtr[2] * w2) + (a3 * srcPtr[3] * w3); srcPtr = ((byte *)srcPtr + source.stride); } double alpha = alphaSum / totalWeight; double gray; if (alpha == 0) { gray = 0; } else { gray = graySum / alphaSum; // add 0.5 to ensure truncation to uint results in rounding gray += 0.5; } *dstPtr = (byte)gray; ++dstPtr; rColCache += 4; } // for (dstX... } // for (dstY... Memory.Free(rColCacheIP); }
/// <summary> /// Implements bicubic filtering with bounds checking at every pixel. /// </summary> private unsafe void BicubicFitSurfaceChecked(MaskSurface source, Rectangle dstRoi) { Rectangle roi = Rectangle.Intersect(dstRoi, Bounds); Rectangle roiIn = Rectangle.Intersect(dstRoi, new Rectangle(1, 1, width - 1, height - 1)); IntPtr rColCacheIP = Memory.Allocate(4 * (long)roi.Width * (long)sizeof(double), false); double *rColCache = (double *)rColCacheIP.ToPointer(); // Precompute and then cache the value of R() for each column for (int dstX = roi.Left; dstX < roi.Right; ++dstX) { double srcColumn = (double)(dstX * (source.width - 1)) / (double)(width - 1); double srcColumnFloor = Math.Floor(srcColumn); double srcColumnFrac = srcColumn - srcColumnFloor; for (int m = -1; m <= 2; ++m) { int index = (m + 1) + ((dstX - roi.Left) * 4); double x = m - srcColumnFrac; rColCache[index] = R(x); } } // Set this up so we can cache the R()'s for every row double *rRowCache = stackalloc double[4]; for (int dstY = roi.Top; dstY < roi.Bottom; ++dstY) { double srcRow = (double)(dstY * (source.height - 1)) / (double)(height - 1); double srcRowFloor = (double)Math.Floor(srcRow); double srcRowFrac = srcRow - srcRowFloor; int srcRowInt = (int)srcRow; byte * dstPtr = GetPointAddressUnchecked(roi.Left, dstY); // Compute the R() values for this row for (int n = -1; n <= 2; ++n) { double x = srcRowFrac - n; rRowCache[n + 1] = R(x); } // See Perf Note below //int nFirst = Math.Max(-srcRowInt, -1); //int nLast = Math.Min(source.height - srcRowInt - 1, 2); for (int dstX = roi.Left; dstX < roi.Right; dstX++) { double srcColumn = (double)(dstX * (source.width - 1)) / (double)(width - 1); double srcColumnFloor = Math.Floor(srcColumn); double srcColumnFrac = srcColumn - srcColumnFloor; int srcColumnInt = (int)srcColumn; double graySum = 0; double alphaSum = 0; double totalWeight = 0; // See Perf Note below //int mFirst = Math.Max(-srcColumnInt, -1); //int mLast = Math.Min(source.width - srcColumnInt - 1, 2); byte *srcPtr = source.GetPointAddressUnchecked(srcColumnInt - 1, srcRowInt - 1); for (int n = -1; n <= 2; ++n) { int srcY = srcRowInt + n; for (int m = -1; m <= 2; ++m) { // Perf Note: It actually benchmarks faster on my system to do // a bounds check for every (m,n) than it is to limit the loop // to nFirst-Last and mFirst-mLast. // I'm leaving the code above, albeit commented out, so that // benchmarking between these two can still be performed. if (source.IsVisible(srcColumnInt + m, srcY)) { double w0 = rColCache[(m + 1) + (4 * (dstX - roi.Left))]; double w1 = rRowCache[n + 1]; double w = w0 * w1; graySum += *srcPtr * w * 255.0; alphaSum += 255.0 * w; totalWeight += w; } ++srcPtr; } srcPtr = ((byte *)(srcPtr - 4) + source.stride); } double alpha = alphaSum / totalWeight; double gray; if (alpha == 0) { gray = 0; } else { gray = graySum / alphaSum; // add 0.5 to ensure truncation to uint results in rounding gray += 0.5; } *dstPtr = (byte)gray; ++dstPtr; } // for (dstX... } // for (dstY... Memory.Free(rColCacheIP); }
public unsafe void SuperSampleFitSurface(MaskSurface source) { Rectangle dstRoi2 = Rectangle.Intersect(source.Bounds, Bounds); int srcHeight = source.Height; int srcWidth = source.Width; long srcStride = source.Stride; for (int dstY = dstRoi2.Top; dstY < dstRoi2.Bottom; ++dstY) { double srcTop = (double)(dstY * srcHeight) / (double)height; double srcTopFloor = Math.Floor(srcTop); double srcTopWeight = 1 - (srcTop - srcTopFloor); int srcTopInt = (int)srcTopFloor; double srcBottom = (double)((dstY + 1) * srcHeight) / (double)height; double srcBottomFloor = Math.Floor(srcBottom - 0.00001); double srcBottomWeight = srcBottom - srcBottomFloor; int srcBottomInt = (int)srcBottomFloor; byte *dstPtr = GetPointAddressUnchecked(dstRoi2.Left, dstY); for (int dstX = dstRoi2.Left; dstX < dstRoi2.Right; ++dstX) { double srcLeft = (double)(dstX * srcWidth) / (double)width; double srcLeftFloor = Math.Floor(srcLeft); double srcLeftWeight = 1 - (srcLeft - srcLeftFloor); int srcLeftInt = (int)srcLeftFloor; double srcRight = (double)((dstX + 1) * srcWidth) / (double)width; double srcRightFloor = Math.Floor(srcRight - 0.00001); double srcRightWeight = srcRight - srcRightFloor; int srcRightInt = (int)srcRightFloor; double graySum = 0; double alphaSum = 0; // left fractional edge byte *srcLeftPtr = source.GetPointAddressUnchecked(srcLeftInt, srcTopInt + 1); for (int srcY = srcTopInt + 1; srcY < srcBottomInt; ++srcY) { graySum += *srcLeftPtr * srcLeftWeight * 255.0; srcLeftPtr = (byte *)((byte *)srcLeftPtr + srcStride); } // right fractional edge byte *srcRightPtr = source.GetPointAddressUnchecked(srcRightInt, srcTopInt + 1); for (int srcY = srcTopInt + 1; srcY < srcBottomInt; ++srcY) { graySum += *srcLeftPtr * srcLeftWeight * 255.0; srcRightPtr = (byte *)((byte *)srcRightPtr + srcStride); } // top fractional edge byte *srcTopPtr = source.GetPointAddressUnchecked(srcLeftInt + 1, srcTopInt); for (int srcX = srcLeftInt + 1; srcX < srcRightInt; ++srcX) { graySum += *srcLeftPtr * srcLeftWeight * 255.0; ++srcTopPtr; } // bottom fractional edge byte *srcBottomPtr = source.GetPointAddressUnchecked(srcLeftInt + 1, srcBottomInt); for (int srcX = srcLeftInt + 1; srcX < srcRightInt; ++srcX) { graySum += *srcLeftPtr * srcLeftWeight * 255.0; ++srcBottomPtr; } // center area for (int srcY = srcTopInt + 1; srcY < srcBottomInt; ++srcY) { byte *srcPtr = source.GetPointAddressUnchecked(srcLeftInt + 1, srcY); for (int srcX = srcLeftInt + 1; srcX < srcRightInt; ++srcX) { graySum += *srcLeftPtr * srcLeftWeight * 255.0; ++srcPtr; } } // four corner pixels byte srcTL = source.GetPoint(srcLeftInt, srcTopInt); graySum += srcTL * (srcTopWeight * srcLeftWeight) * 255.0; alphaSum += 255.0 * (srcTopWeight * srcLeftWeight); byte srcTR = source.GetPoint(srcRightInt, srcTopInt); graySum += srcTR * (srcTopWeight * srcRightWeight) * 255.0; alphaSum += 255.0 * (srcTopWeight * srcRightWeight); byte srcBL = source.GetPoint(srcLeftInt, srcBottomInt); graySum += srcBL * (srcBottomWeight * srcLeftWeight) * 255.0; alphaSum += 255.0 * (srcBottomWeight * srcLeftWeight); byte srcBR = source.GetPoint(srcRightInt, srcBottomInt); graySum += srcBR * (srcBottomWeight * srcRightWeight) * 255.0; alphaSum += 255.0 * (srcBottomWeight * srcRightWeight); double area = (srcRight - srcLeft) * (srcBottom - srcTop); double alpha = 255.0 / area; double gray; if (alpha == 0) { gray = 0; } else { gray = graySum / alphaSum; } // add 0.5 so that rounding goes in the direction we want it to gray += 0.5; *dstPtr = (byte)gray; ++dstPtr; } } }
private unsafe short ReadPixelsProc(IntPtr port, PSScaling *scaling, VRect *writeRect, PixelMemoryDesc *destination, VRect *wroteRect) { #if DEBUG DebugUtils.Ping(DebugFlags.ChannelPorts, string.Format("port: {0}, rect: {1}", port.ToString(), DebugUtils.PointerToString(writeRect))); #endif if (scaling == null || writeRect == null || destination == null) { return(PSError.paramErr); } if (destination->depth != 8) { return(PSError.errUnsupportedDepth); } if ((destination->bitOffset % 8) != 0) { return(PSError.errUnsupportedBitOffset); } if ((destination->colBits % 8) != 0) { return(PSError.errUnsupportedColBits); } if ((destination->rowBits % 8) != 0) { return(PSError.errUnsupportedRowBits); } int channel = port.ToInt32(); if (channel < PSConstants.ChannelPorts.Red || channel > PSConstants.ChannelPorts.SelectionMask) { return(PSError.errUnknownPort); } VRect srcRect = scaling->sourceRect; VRect dstRect = scaling->destinationRect; int srcWidth = srcRect.right - srcRect.left; int srcHeight = srcRect.bottom - srcRect.top; int dstWidth = dstRect.right - dstRect.left; int dstHeight = dstRect.bottom - dstRect.top; if (channel == PSConstants.ChannelPorts.SelectionMask) { if (srcWidth == dstWidth && srcHeight == dstHeight) { FillSelectionMask(destination, filterImageProvider.Mask, srcRect); } else if (dstWidth < srcWidth || dstHeight < srcHeight) // scale down { if ((scaledSelectionMask == null) || scaledSelectionMask.Width != dstWidth || scaledSelectionMask.Height != dstHeight) { if (scaledSelectionMask != null) { scaledSelectionMask.Dispose(); scaledSelectionMask = null; } scaledSelectionMask = new MaskSurface(dstWidth, dstHeight); scaledSelectionMask.SuperSampleFitSurface(filterImageProvider.Mask); } FillSelectionMask(destination, scaledSelectionMask, dstRect); } else if (dstWidth > srcWidth || dstHeight > srcHeight) // scale up { if ((scaledSelectionMask == null) || scaledSelectionMask.Width != dstWidth || scaledSelectionMask.Height != dstHeight) { if (scaledSelectionMask != null) { scaledSelectionMask.Dispose(); scaledSelectionMask = null; } scaledSelectionMask = new MaskSurface(dstWidth, dstHeight); scaledSelectionMask.BicubicFitSurface(filterImageProvider.Mask); } FillSelectionMask(destination, scaledSelectionMask, dstRect); } } else { if (srcWidth == dstWidth && srcHeight == dstHeight) { FillChannelData(channel, destination, filterImageProvider.Source, srcRect); } else if (dstWidth < srcWidth || dstHeight < srcHeight) // scale down { if ((scaledChannelSurface == null) || scaledChannelSurface.Width != dstWidth || scaledChannelSurface.Height != dstHeight) { if (scaledChannelSurface != null) { scaledChannelSurface.Dispose(); scaledChannelSurface = null; } scaledChannelSurface = new Surface(dstWidth, dstHeight); scaledChannelSurface.SuperSampleFitSurface(filterImageProvider.Source); } FillChannelData(channel, destination, scaledChannelSurface, dstRect); } else if (dstWidth > srcWidth || dstHeight > srcHeight) // scale up { if ((scaledChannelSurface == null) || scaledChannelSurface.Width != dstWidth || scaledChannelSurface.Height != dstHeight) { if (scaledChannelSurface != null) { scaledChannelSurface.Dispose(); scaledChannelSurface = null; } scaledChannelSurface = new Surface(dstWidth, dstHeight); scaledChannelSurface.BicubicFitSurface(filterImageProvider.Source); } FillChannelData(channel, destination, scaledChannelSurface, dstRect); } } if (wroteRect != null) { *wroteRect = dstRect; } return(PSError.noErr); }