/// <summary> /// Reads a run length encoded TGA image with a palette. /// </summary> /// <typeparam name="TPixel">The pixel type.</typeparam> /// <param name="width">The width of the image.</param> /// <param name="height">The height of the image.</param> /// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param> /// <param name="palette">The color palette.</param> /// <param name="colorMapPixelSizeInBytes">Color map size of one entry in bytes.</param> /// <param name="origin">The image origin.</param> private void ReadPalettedRle <TPixel>(int width, int height, Buffer2D <TPixel> pixels, byte[] palette, int colorMapPixelSizeInBytes, TgaImageOrigin origin) where TPixel : unmanaged, IPixel <TPixel> { int bytesPerPixel = 1; using (IMemoryOwner <byte> buffer = this.memoryAllocator.Allocate <byte>(width * height * bytesPerPixel, AllocationOptions.Clean)) { TPixel color = default; var alphaBits = this.tgaMetadata.AlphaChannelBits; Span <byte> bufferSpan = buffer.GetSpan(); this.UncompressRle(width, height, bufferSpan, bytesPerPixel: 1); for (int y = 0; y < height; y++) { int newY = InvertY(y, height, origin); Span <TPixel> pixelRow = pixels.GetRowSpan(newY); int rowStartIdx = y * width * bytesPerPixel; for (int x = 0; x < width; x++) { int idx = rowStartIdx + x; switch (colorMapPixelSizeInBytes) { case 1: color.FromL8(Unsafe.As <byte, L8>(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes])); break; case 2: Bgra5551 bgra = Unsafe.As <byte, Bgra5551>(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes]); if (!this.hasAlpha) { // Set alpha value to 1, to treat it as opaque for Bgra5551. bgra.PackedValue = (ushort)(bgra.PackedValue | 0x8000); } color.FromBgra5551(bgra); break; case 3: color.FromBgr24(Unsafe.As <byte, Bgr24>(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes])); break; case 4: if (this.hasAlpha) { color.FromBgra32(Unsafe.As <byte, Bgra32>(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes])); } else { var alpha = alphaBits == 0 ? byte.MaxValue : bufferSpan[idx + 3]; color.FromBgra32(new Bgra32(bufferSpan[idx + 2], bufferSpan[idx + 1], bufferSpan[idx], (byte)alpha)); } break; } int newX = InvertX(x, width, origin); pixelRow[newX] = color; } } } }
/// <summary> /// Reads a uncompressed TGA image where each pixels has 16 bit. /// </summary> /// <typeparam name="TPixel">The pixel type.</typeparam> /// <param name="width">The width of the image.</param> /// <param name="height">The height of the image.</param> /// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param> /// <param name="origin">The image origin.</param> private void ReadBgra16 <TPixel>(int width, int height, Buffer2D <TPixel> pixels, TgaImageOrigin origin) where TPixel : unmanaged, IPixel <TPixel> { TPixel color = default; bool invertX = InvertX(origin); using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 2, 0)) { for (int y = 0; y < height; y++) { int newY = InvertY(y, height, origin); Span <TPixel> pixelSpan = pixels.GetRowSpan(newY); if (invertX) { for (int x = width - 1; x >= 0; x--) { this.currentStream.Read(this.scratchBuffer, 0, 2); if (!this.hasAlpha) { this.scratchBuffer[1] |= 1 << 7; } if (this.fileHeader.ImageType == TgaImageType.BlackAndWhite) { color.FromLa16(Unsafe.As <byte, La16>(ref this.scratchBuffer[0])); } else { color.FromBgra5551(Unsafe.As <byte, Bgra5551>(ref this.scratchBuffer[0])); } pixelSpan[x] = color; } } else { this.currentStream.Read(row); Span <byte> rowSpan = row.GetSpan(); if (!this.hasAlpha) { // We need to set the alpha component value to fully opaque. for (int x = 1; x < rowSpan.Length; x += 2) { rowSpan[x] |= 1 << 7; } } if (this.fileHeader.ImageType == TgaImageType.BlackAndWhite) { PixelOperations <TPixel> .Instance.FromLa16Bytes(this.configuration, rowSpan, pixelSpan, width); } else { PixelOperations <TPixel> .Instance.FromBgra5551Bytes(this.configuration, rowSpan, pixelSpan, width); } } } } }
/// <inheritdoc/> protected override void OnApply(ImageFrame <TPixel> source, ImageFrame <TPixel> destination, Rectangle sourceRectangle, Configuration configuration) { int height = this.TargetDimensions.Height; int width = this.TargetDimensions.Width; Rectangle sourceBounds = source.Bounds(); var targetBounds = new Rectangle(0, 0, width, height); // Since could potentially be resizing the canvas we might need to re-calculate the matrix Matrix4x4 matrix = this.GetProcessingMatrix(sourceBounds, targetBounds); // Convert from screen to world space. Matrix4x4.Invert(matrix, out matrix); if (this.Sampler is NearestNeighborResampler) { Parallel.For( 0, height, configuration.ParallelOptions, y => { Span <TPixel> destRow = destination.GetPixelRowSpan(y); for (int x = 0; x < width; x++) { var point = Point.Round(Vector2.Transform(new Vector2(x, y), matrix)); if (sourceBounds.Contains(point.X, point.Y)) { destRow[x] = source[point.X, point.Y]; } } }); return; } int maxSourceX = source.Width - 1; int maxSourceY = source.Height - 1; (float radius, float scale, float ratio)xRadiusScale = this.GetSamplingRadius(source.Width, destination.Width); (float radius, float scale, float ratio)yRadiusScale = this.GetSamplingRadius(source.Height, destination.Height); float xScale = xRadiusScale.scale; float yScale = yRadiusScale.scale; var radius = new Vector2(xRadiusScale.radius, yRadiusScale.radius); IResampler sampler = this.Sampler; var maxSource = new Vector4(maxSourceX, maxSourceY, maxSourceX, maxSourceY); int xLength = (int)MathF.Ceiling((radius.X * 2) + 2); int yLength = (int)MathF.Ceiling((radius.Y * 2) + 2); MemoryManager memoryManager = configuration.MemoryManager; using (Buffer2D <float> yBuffer = memoryManager.Allocate2D <float>(yLength, height)) using (Buffer2D <float> xBuffer = memoryManager.Allocate2D <float>(xLength, height)) { Parallel.For( 0, height, configuration.ParallelOptions, y => { Span <TPixel> destRow = destination.GetPixelRowSpan(y); Span <float> ySpan = yBuffer.GetRowSpan(y); Span <float> xSpan = xBuffer.GetRowSpan(y); for (int x = 0; x < width; x++) { // Use the single precision position to calculate correct bounding pixels // otherwise we get rogue pixels outside of the bounds. var point = Vector2.Transform(new Vector2(x, y), matrix); // Clamp sampling pixel radial extents to the source image edges Vector2 maxXY = point + radius; Vector2 minXY = point - radius; // max, maxY, minX, minY var extents = new Vector4( MathF.Floor(maxXY.X + .5F), MathF.Floor(maxXY.Y + .5F), MathF.Ceiling(minXY.X - .5F), MathF.Ceiling(minXY.Y - .5F)); int right = (int)extents.X; int bottom = (int)extents.Y; int left = (int)extents.Z; int top = (int)extents.W; extents = Vector4.Clamp(extents, Vector4.Zero, maxSource); int maxX = (int)extents.X; int maxY = (int)extents.Y; int minX = (int)extents.Z; int minY = (int)extents.W; if (minX == maxX || minY == maxY) { continue; } // It appears these have to be calculated on-the-fly. // Precalulating transformed weights would require prior knowledge of every transformed pixel location // since they can be at sub-pixel positions on both axis. // I've optimized where I can but am always open to suggestions. if (yScale > 1 && xScale > 1) { CalculateWeightsDown(top, bottom, minY, maxY, point.Y, sampler, yScale, ySpan); CalculateWeightsDown(left, right, minX, maxX, point.X, sampler, xScale, xSpan); } else { CalculateWeightsScaleUp(minY, maxY, point.Y, sampler, ySpan); CalculateWeightsScaleUp(minX, maxX, point.X, sampler, xSpan); } // Now multiply the results against the offsets Vector4 sum = Vector4.Zero; for (int yy = 0, j = minY; j <= maxY; j++, yy++) { float yWeight = ySpan[yy]; for (int xx = 0, i = minX; i <= maxX; i++, xx++) { float xWeight = xSpan[xx]; var vector = source[i, j].ToVector4(); // Values are first premultiplied to prevent darkening of edge pixels Vector4 multiplied = vector.Premultiply(); sum += multiplied * xWeight * yWeight; } } ref TPixel dest = ref destRow[x]; // Reverse the premultiplication dest.PackFromVector4(sum.UnPremultiply()); } }); } }
/// <summary> /// Reads a uncompressed TGA image with a palette. /// </summary> /// <typeparam name="TPixel">The pixel type.</typeparam> /// <param name="width">The width of the image.</param> /// <param name="height">The height of the image.</param> /// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param> /// <param name="palette">The color palette.</param> /// <param name="colorMapPixelSizeInBytes">Color map size of one entry in bytes.</param> /// <param name="origin">The image origin.</param> private void ReadPaletted <TPixel>(int width, int height, Buffer2D <TPixel> pixels, byte[] palette, int colorMapPixelSizeInBytes, TgaImageOrigin origin) where TPixel : unmanaged, IPixel <TPixel> { TPixel color = default; bool invertX = InvertX(origin); for (int y = 0; y < height; y++) { int newY = InvertY(y, height, origin); Span <TPixel> pixelRow = pixels.GetRowSpan(newY); switch (colorMapPixelSizeInBytes) { case 2: if (invertX) { for (int x = width - 1; x >= 0; x--) { this.ReadPalettedBgr16Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow); } } else { for (int x = 0; x < width; x++) { this.ReadPalettedBgr16Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow); } } break; case 3: if (invertX) { for (int x = width - 1; x >= 0; x--) { this.ReadPalettedBgr24Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow); } } else { for (int x = 0; x < width; x++) { this.ReadPalettedBgr24Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow); } } break; case 4: if (invertX) { for (int x = width - 1; x >= 0; x--) { this.ReadPalettedBgra32Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow); } } else { for (int x = 0; x < width; x++) { this.ReadPalettedBgra32Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow); } } break; } } }
private Buffer2D <float> Render(IPath path) { Size size = Rectangle.Ceiling(path.Bounds).Size; size = new Size(size.Width + (this.offset * 2), size.Height + (this.offset * 2)); float subpixelCount = 4; float offset = 0.5f; if (this.Options.Antialias) { offset = 0f; // we are antialising skip offsetting as real antalising should take care of offset. subpixelCount = this.Options.AntialiasSubpixelDepth; if (subpixelCount < 4) { subpixelCount = 4; } } // take the path inside the path builder, scan thing and generate a Buffer2d representing the glyph and cache it. Buffer2D <float> fullBuffer = this.MemoryAllocator.Allocate2D <float>(size.Width + 1, size.Height + 1, AllocationOptions.Clean); using (IMemoryOwner <float> bufferBacking = this.MemoryAllocator.Allocate <float>(path.MaxIntersections)) using (IMemoryOwner <PointF> rowIntersectionBuffer = this.MemoryAllocator.Allocate <PointF>(size.Width)) { float subpixelFraction = 1f / subpixelCount; float subpixelFractionPoint = subpixelFraction / subpixelCount; for (int y = 0; y <= size.Height; y++) { Span <float> scanline = fullBuffer.GetRowSpan(y); bool scanlineDirty = false; float yPlusOne = y + 1; for (float subPixel = (float)y; subPixel < yPlusOne; subPixel += subpixelFraction) { var start = new PointF(path.Bounds.Left - 1, subPixel); var end = new PointF(path.Bounds.Right + 1, subPixel); Span <PointF> intersectionSpan = rowIntersectionBuffer.GetSpan(); Span <float> buffer = bufferBacking.GetSpan(); int pointsFound = path.FindIntersections(start, end, intersectionSpan); if (pointsFound == 0) { // nothing on this line skip continue; } for (int i = 0; i < pointsFound && i < intersectionSpan.Length; i++) { buffer[i] = intersectionSpan[i].X; } QuickSort.Sort(buffer.Slice(0, pointsFound)); for (int point = 0; point < pointsFound; point += 2) { // points will be paired up float scanStart = buffer[point]; float scanEnd = buffer[point + 1]; int startX = (int)MathF.Floor(scanStart + offset); int endX = (int)MathF.Floor(scanEnd + offset); if (startX >= 0 && startX < scanline.Length) { for (float x = scanStart; x < startX + 1; x += subpixelFraction) { scanline[startX] += subpixelFractionPoint; scanlineDirty = true; } } if (endX >= 0 && endX < scanline.Length) { for (float x = endX; x < scanEnd; x += subpixelFraction) { scanline[endX] += subpixelFractionPoint; scanlineDirty = true; } } int nextX = startX + 1; endX = Math.Min(endX, scanline.Length); // reduce to end to the right edge nextX = Math.Max(nextX, 0); for (int x = nextX; x < endX; x++) { scanline[x] += subpixelFraction; scanlineDirty = true; } } } if (scanlineDirty) { if (!this.Options.Antialias) { for (int x = 0; x < size.Width; x++) { if (scanline[x] >= 0.5) { scanline[x] = 1; } else { scanline[x] = 0; } } } } } } return(fullBuffer); }
/// <inheritdoc /> protected override void OnFrameApply(ImageFrame <TPixel> source, Rectangle sourceRectangle, Configuration configuration) { DenseMatrix <float>[] kernels = { this.North, this.NorthWest, this.West, this.SouthWest, this.South, this.SouthEast, this.East, this.NorthEast }; int startY = sourceRectangle.Y; int endY = sourceRectangle.Bottom; int startX = sourceRectangle.X; int endX = sourceRectangle.Right; // Align start/end positions. int minX = Math.Max(0, startX); int maxX = Math.Min(source.Width, endX); int minY = Math.Max(0, startY); int maxY = Math.Min(source.Height, endY); // we need a clean copy for each pass to start from using (ImageFrame <TPixel> cleanCopy = source.Clone()) { new ConvolutionProcessor <TPixel>(kernels[0]).Apply(source, sourceRectangle, configuration); if (kernels.Length == 1) { return; } int shiftY = startY; int shiftX = startX; // Reset offset if necessary. if (minX > 0) { shiftX = 0; } if (minY > 0) { shiftY = 0; } // Additional runs. // ReSharper disable once ForCanBeConvertedToForeach for (int i = 1; i < kernels.Length; i++) { using (ImageFrame <TPixel> pass = cleanCopy.Clone()) { new ConvolutionProcessor <TPixel>(kernels[i]).Apply(pass, sourceRectangle, configuration); Buffer2D <TPixel> passPixels = pass.PixelBuffer; Buffer2D <TPixel> targetPixels = source.PixelBuffer; Parallel.For( minY, maxY, configuration.ParallelOptions, y => { int offsetY = y - shiftY; ref TPixel passPixelsBase = ref MemoryMarshal.GetReference(passPixels.GetRowSpan(offsetY)); ref TPixel targetPixelsBase = ref MemoryMarshal.GetReference(targetPixels.GetRowSpan(offsetY)); for (int x = minX; x < maxX; x++) { int offsetX = x - shiftX; // Grab the max components of the two pixels ref TPixel currentPassPixel = ref Unsafe.Add(ref passPixelsBase, offsetX); ref TPixel currentTargetPixel = ref Unsafe.Add(ref targetPixelsBase, offsetX); var pixelValue = Vector4.Max( currentPassPixel.ToVector4(), currentTargetPixel.ToVector4()); currentTargetPixel.PackFromVector4(pixelValue); } });
/// <inheritdoc/> protected override unsafe void OnApply(ImageBase <TPixel> source, Rectangle sourceRectangle) { // Jump out, we'll deal with that later. if (source.Width == this.Width && source.Height == this.Height && sourceRectangle == this.ResizeRectangle) { return; } int width = this.Width; int height = this.Height; int sourceX = sourceRectangle.X; int sourceY = sourceRectangle.Y; int startY = this.ResizeRectangle.Y; int endY = this.ResizeRectangle.Bottom; int startX = this.ResizeRectangle.X; int endX = this.ResizeRectangle.Right; int minX = Math.Max(0, startX); int maxX = Math.Min(width, endX); int minY = Math.Max(0, startY); int maxY = Math.Min(height, endY); if (this.Sampler is NearestNeighborResampler) { // Scaling factors float widthFactor = sourceRectangle.Width / (float)this.ResizeRectangle.Width; float heightFactor = sourceRectangle.Height / (float)this.ResizeRectangle.Height; using (var targetPixels = new PixelAccessor <TPixel>(width, height)) { Parallel.For( minY, maxY, this.ParallelOptions, y => { // Y coordinates of source points Span <TPixel> sourceRow = source.GetRowSpan((int)(((y - startY) * heightFactor) + sourceY)); Span <TPixel> targetRow = targetPixels.GetRowSpan(y); for (int x = minX; x < maxX; x++) { // X coordinates of source points targetRow[x] = sourceRow[(int)(((x - startX) * widthFactor) + sourceX)]; } }); // Break out now. source.SwapPixelsBuffers(targetPixels); return; } } // Interpolate the image using the calculated weights. // A 2-pass 1D algorithm appears to be faster than splitting a 1-pass 2D algorithm // First process the columns. Since we are not using multiple threads startY and endY // are the upper and lower bounds of the source rectangle. // TODO: Using a transposed variant of 'firstPassPixels' could eliminate the need for the WeightsWindow.ComputeWeightedColumnSum() method, and improve speed! using (var targetPixels = new PixelAccessor <TPixel>(width, height)) { using (var firstPassPixels = new Buffer2D <Vector4>(width, source.Height)) { firstPassPixels.Clear(); Parallel.For( 0, sourceRectangle.Bottom, this.ParallelOptions, y => { // TODO: Without Parallel.For() this buffer object could be reused: using (var tempRowBuffer = new Buffer <Vector4>(source.Width)) { Span <Vector4> firstPassRow = firstPassPixels.GetRowSpan(y); Span <TPixel> sourceRow = source.GetRowSpan(y); PixelOperations <TPixel> .Instance.ToVector4(sourceRow, tempRowBuffer, sourceRow.Length); if (this.Compand) { for (int x = minX; x < maxX; x++) { WeightsWindow window = this.HorizontalWeights.Weights[x - startX]; firstPassRow[x] = window.ComputeExpandedWeightedRowSum(tempRowBuffer, sourceX); } } else { for (int x = minX; x < maxX; x++) { WeightsWindow window = this.HorizontalWeights.Weights[x - startX]; firstPassRow[x] = window.ComputeWeightedRowSum(tempRowBuffer, sourceX); } } } }); // Now process the rows. Parallel.For( minY, maxY, this.ParallelOptions, y => { // Ensure offsets are normalised for cropping and padding. WeightsWindow window = this.VerticalWeights.Weights[y - startY]; Span <TPixel> targetRow = targetPixels.GetRowSpan(y); if (this.Compand) { for (int x = 0; x < width; x++) { // Destination color components Vector4 destination = window.ComputeWeightedColumnSum(firstPassPixels, x, sourceY); destination = destination.Compress(); ref TPixel pixel = ref targetRow[x]; pixel.PackFromVector4(destination); } } else { for (int x = 0; x < width; x++) { // Destination color components Vector4 destination = window.ComputeWeightedColumnSum(firstPassPixels, x, sourceY); ref TPixel pixel = ref targetRow[x]; pixel.PackFromVector4(destination); } } });
/// <inheritdoc/> protected override void OnFrameApply( ImageFrame <TPixel> source, ImageFrame <TPixel> destination, Rectangle sourceRectangle, Configuration configuration) { int height = this.TargetDimensions.Height; int width = this.TargetDimensions.Width; Rectangle sourceBounds = source.Bounds(); var targetBounds = new Rectangle(0, 0, width, height); // Since could potentially be resizing the canvas we might need to re-calculate the matrix Matrix3x2 matrix = this.GetProcessingMatrix(sourceBounds, targetBounds); // Convert from screen to world space. Matrix3x2.Invert(matrix, out matrix); if (this.Sampler is NearestNeighborResampler) { Parallel.For( 0, height, configuration.ParallelOptions, y => { Span <TPixel> destRow = destination.GetPixelRowSpan(y); for (int x = 0; x < width; x++) { var point = Point.Transform(new Point(x, y), matrix); if (sourceBounds.Contains(point.X, point.Y)) { destRow[x] = source[point.X, point.Y]; } } }); return; } int maxSourceX = source.Width - 1; int maxSourceY = source.Height - 1; (float radius, float scale, float ratio)xRadiusScale = this.GetSamplingRadius(source.Width, destination.Width); (float radius, float scale, float ratio)yRadiusScale = this.GetSamplingRadius(source.Height, destination.Height); float xScale = xRadiusScale.scale; float yScale = yRadiusScale.scale; var radius = new Vector2(xRadiusScale.radius, yRadiusScale.radius); IResampler sampler = this.Sampler; var maxSource = new Vector4(maxSourceX, maxSourceY, maxSourceX, maxSourceY); int xLength = (int)MathF.Ceiling((radius.X * 2) + 2); int yLength = (int)MathF.Ceiling((radius.Y * 2) + 2); MemoryManager memoryManager = configuration.MemoryManager; using (Buffer2D <float> yBuffer = memoryManager.Allocate2D <float>(yLength, height)) using (Buffer2D <float> xBuffer = memoryManager.Allocate2D <float>(xLength, height)) { Parallel.For( 0, height, configuration.ParallelOptions, y => { ref TPixel destRowRef = ref MemoryMarshal.GetReference(destination.GetPixelRowSpan(y)); ref float ySpanRef = ref MemoryMarshal.GetReference(yBuffer.GetRowSpan(y)); ref float xSpanRef = ref MemoryMarshal.GetReference(xBuffer.GetRowSpan(y));
/// <inheritdoc /> protected override void OnFrameApply(ImageFrame <TPixel> source) { DenseMatrix <float>[] kernels = this.Kernels.Flatten(); int startY = this.SourceRectangle.Y; int endY = this.SourceRectangle.Bottom; int startX = this.SourceRectangle.X; int endX = this.SourceRectangle.Right; // Align start/end positions. int minX = Math.Max(0, startX); int maxX = Math.Min(source.Width, endX); int minY = Math.Max(0, startY); int maxY = Math.Min(source.Height, endY); // we need a clean copy for each pass to start from using (ImageFrame <TPixel> cleanCopy = source.Clone()) { using (var processor = new ConvolutionProcessor <TPixel>(kernels[0], true, this.Source, this.SourceRectangle)) { processor.Apply(source); } if (kernels.Length == 1) { return; } int shiftY = startY; int shiftX = startX; // Reset offset if necessary. if (minX > 0) { shiftX = 0; } if (minY > 0) { shiftY = 0; } var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY); // Additional runs. // ReSharper disable once ForCanBeConvertedToForeach for (int i = 1; i < kernels.Length; i++) { using (ImageFrame <TPixel> pass = cleanCopy.Clone()) { using (var processor = new ConvolutionProcessor <TPixel>(kernels[i], true, this.Source, this.SourceRectangle)) { processor.Apply(pass); } Buffer2D <TPixel> passPixels = pass.PixelBuffer; Buffer2D <TPixel> targetPixels = source.PixelBuffer; ParallelHelper.IterateRows( workingRect, this.Configuration, rows => { for (int y = rows.Min; y < rows.Max; y++) { int offsetY = y - shiftY; ref TPixel passPixelsBase = ref MemoryMarshal.GetReference(passPixels.GetRowSpan(offsetY)); ref TPixel targetPixelsBase = ref MemoryMarshal.GetReference(targetPixels.GetRowSpan(offsetY)); for (int x = minX; x < maxX; x++) { int offsetX = x - shiftX; // Grab the max components of the two pixels ref TPixel currentPassPixel = ref Unsafe.Add(ref passPixelsBase, offsetX); ref TPixel currentTargetPixel = ref Unsafe.Add(ref targetPixelsBase, offsetX); var pixelValue = Vector4.Max( currentPassPixel.ToVector4(), currentTargetPixel.ToVector4()); currentTargetPixel.FromVector4(pixelValue); } } });
/// <inheritdoc/> protected override void OnFrameApply(ImageFrame <TPixel> source, Rectangle sourceRectangle, Configuration configuration) { int kernelYHeight = this.KernelY.Rows; int kernelYWidth = this.KernelY.Columns; int kernelXHeight = this.KernelX.Rows; int kernelXWidth = this.KernelX.Columns; int radiusY = kernelYHeight >> 1; int radiusX = kernelXWidth >> 1; int startY = sourceRectangle.Y; int endY = sourceRectangle.Bottom; int startX = sourceRectangle.X; int endX = sourceRectangle.Right; int maxY = endY - 1; int maxX = endX - 1; using (Buffer2D <TPixel> targetPixels = configuration.MemoryManager.Allocate2D <TPixel>(source.Width, source.Height)) { source.CopyTo(targetPixels); Parallel.For( startY, endY, configuration.ParallelOptions, y => { Span <TPixel> sourceRow = source.GetPixelRowSpan(y); Span <TPixel> targetRow = targetPixels.GetRowSpan(y); for (int x = startX; x < endX; x++) { float rX = 0; float gX = 0; float bX = 0; float rY = 0; float gY = 0; float bY = 0; // Apply each matrix multiplier to the color components for each pixel. for (int fy = 0; fy < kernelYHeight; fy++) { int fyr = fy - radiusY; int offsetY = y + fyr; offsetY = offsetY.Clamp(0, maxY); Span <TPixel> sourceOffsetRow = source.GetPixelRowSpan(offsetY); for (int fx = 0; fx < kernelXWidth; fx++) { int fxr = fx - radiusX; int offsetX = x + fxr; offsetX = offsetX.Clamp(0, maxX); Vector4 currentColor = sourceOffsetRow[offsetX].ToVector4().Premultiply(); if (fy < kernelXHeight) { Vector4 kx = this.KernelX[fy, fx] * currentColor; rX += kx.X; gX += kx.Y; bX += kx.Z; } if (fx < kernelYWidth) { Vector4 ky = this.KernelY[fy, fx] * currentColor; rY += ky.X; gY += ky.Y; bY += ky.Z; } } } float red = MathF.Sqrt((rX * rX) + (rY * rY)); float green = MathF.Sqrt((gX * gX) + (gY * gY)); float blue = MathF.Sqrt((bX * bX) + (bY * bY)); ref TPixel pixel = ref targetRow[x]; pixel.PackFromVector4(new Vector4(red, green, blue, sourceRow[x].ToVector4().W).UnPremultiply()); } }); Buffer2D <TPixel> .SwapContents(source.PixelBuffer, targetPixels); } }
/// <inheritdoc/> protected override void OnFrameApply(ImageFrame <TPixel> source, ImageFrame <TPixel> destination, Rectangle sourceRectangle, Configuration configuration) { // Handle resize dimensions identical to the original if (source.Width == destination.Width && source.Height == destination.Height && sourceRectangle == this.ResizeRectangle) { // The cloned will be blank here copy all the pixel data over source.GetPixelSpan().CopyTo(destination.GetPixelSpan()); return; } int width = this.Width; int height = this.Height; int sourceX = sourceRectangle.X; int sourceY = sourceRectangle.Y; int startY = this.ResizeRectangle.Y; int endY = this.ResizeRectangle.Bottom; int startX = this.ResizeRectangle.X; int endX = this.ResizeRectangle.Right; int minX = Math.Max(0, startX); int maxX = Math.Min(width, endX); int minY = Math.Max(0, startY); int maxY = Math.Min(height, endY); if (this.Sampler is NearestNeighborResampler) { var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY); // Scaling factors float widthFactor = sourceRectangle.Width / (float)this.ResizeRectangle.Width; float heightFactor = sourceRectangle.Height / (float)this.ResizeRectangle.Height; ParallelHelper.IterateRows( workingRect, configuration, rows => { for (int y = rows.Min; y < rows.Max; y++) { // Y coordinates of source points Span <TPixel> sourceRow = source.GetPixelRowSpan((int)(((y - startY) * heightFactor) + sourceY)); Span <TPixel> targetRow = destination.GetPixelRowSpan(y); for (int x = minX; x < maxX; x++) { // X coordinates of source points targetRow[x] = sourceRow[(int)(((x - startX) * widthFactor) + sourceX)]; } } }); return; } int sourceHeight = source.Height; PixelConversionModifiers conversionModifiers = PixelConversionModifiers.Premultiply; if (this.Compand) { conversionModifiers |= PixelConversionModifiers.Scale | PixelConversionModifiers.SRgbCompand; } // Interpolate the image using the calculated weights. // A 2-pass 1D algorithm appears to be faster than splitting a 1-pass 2D algorithm // First process the columns. Since we are not using multiple threads startY and endY // are the upper and lower bounds of the source rectangle. using (Buffer2D <Vector4> firstPassPixelsTransposed = source.MemoryAllocator.Allocate2D <Vector4>(sourceHeight, width)) { firstPassPixelsTransposed.MemorySource.Clear(); var processColsRect = new Rectangle(0, 0, source.Width, sourceRectangle.Bottom); ParallelHelper.IterateRowsWithTempBuffer <Vector4>( processColsRect, configuration, (rows, tempRowBuffer) => { for (int y = rows.Min; y < rows.Max; y++) { Span <TPixel> sourceRow = source.GetPixelRowSpan(y).Slice(sourceX); Span <Vector4> tempRowSpan = tempRowBuffer.Span.Slice(sourceX); PixelOperations <TPixel> .Instance.ToVector4(configuration, sourceRow, tempRowSpan, conversionModifiers); ref Vector4 firstPassBaseRef = ref firstPassPixelsTransposed.Span[y]; for (int x = minX; x < maxX; x++) { ResizeKernel kernel = this.horizontalKernelMap.GetKernel(x - startX); Unsafe.Add(ref firstPassBaseRef, x * sourceHeight) = kernel.Convolve(tempRowSpan); } } }); var processRowsRect = Rectangle.FromLTRB(0, minY, width, maxY); // Now process the rows. ParallelHelper.IterateRowsWithTempBuffer <Vector4>( processRowsRect, configuration, (rows, tempRowBuffer) => { Span <Vector4> tempRowSpan = tempRowBuffer.Span; for (int y = rows.Min; y < rows.Max; y++) { // Ensure offsets are normalized for cropping and padding. ResizeKernel kernel = this.verticalKernelMap.GetKernel(y - startY); ref Vector4 tempRowBase = ref MemoryMarshal.GetReference(tempRowSpan); for (int x = 0; x < width; x++) { Span <Vector4> firstPassColumn = firstPassPixelsTransposed.GetRowSpan(x).Slice(sourceY); // Destination color components Unsafe.Add(ref tempRowBase, x) = kernel.Convolve(firstPassColumn); } Span <TPixel> targetRowSpan = destination.GetPixelRowSpan(y); PixelOperations <TPixel> .Instance.FromVector4Destructive(configuration, tempRowSpan, targetRowSpan, conversionModifiers); } }); } }
/// <summary> /// Reads a run length encoded TGA image. /// </summary> /// <typeparam name="TPixel">The pixel type.</typeparam> /// <param name="width">The width of the image.</param> /// <param name="height">The height of the image.</param> /// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param> /// <param name="bytesPerPixel">The bytes per pixel.</param> /// <param name="origin">The image origin.</param> private void ReadRle <TPixel>(int width, int height, Buffer2D <TPixel> pixels, int bytesPerPixel, TgaImageOrigin origin) where TPixel : unmanaged, IPixel <TPixel> { TPixel color = default; var alphaBits = this.tgaMetadata.AlphaChannelBits; using (IMemoryOwner <byte> buffer = this.memoryAllocator.Allocate <byte>(width * height * bytesPerPixel, AllocationOptions.Clean)) { Span <byte> bufferSpan = buffer.GetSpan(); this.UncompressRle(width, height, bufferSpan, bytesPerPixel); for (int y = 0; y < height; y++) { int newY = InvertY(y, height, origin); Span <TPixel> pixelRow = pixels.GetRowSpan(newY); int rowStartIdx = y * width * bytesPerPixel; for (int x = 0; x < width; x++) { int idx = rowStartIdx + (x * bytesPerPixel); switch (bytesPerPixel) { case 1: color.FromL8(Unsafe.As <byte, L8>(ref bufferSpan[idx])); break; case 2: if (!this.hasAlpha) { // Set alpha value to 1, to treat it as opaque for Bgra5551. bufferSpan[idx + 1] = (byte)(bufferSpan[idx + 1] | 128); } if (this.fileHeader.ImageType == TgaImageType.RleBlackAndWhite) { color.FromLa16(Unsafe.As <byte, La16>(ref bufferSpan[idx])); } else { color.FromBgra5551(Unsafe.As <byte, Bgra5551>(ref bufferSpan[idx])); } break; case 3: color.FromBgr24(Unsafe.As <byte, Bgr24>(ref bufferSpan[idx])); break; case 4: if (this.hasAlpha) { color.FromBgra32(Unsafe.As <byte, Bgra32>(ref bufferSpan[idx])); } else { var alpha = alphaBits == 0 ? byte.MaxValue : bufferSpan[idx + 3]; color.FromBgra32(new Bgra32(bufferSpan[idx + 2], bufferSpan[idx + 1], bufferSpan[idx], (byte)alpha)); } break; } int newX = InvertX(x, width, origin); pixelRow[newX] = color; } } } }
/// <inheritdoc/> protected override void OnFrameApply( ImageFrame <TPixel> source, Rectangle sourceRectangle, Configuration configuration) { if (this.BrushSize <= 0 || this.BrushSize > source.Height || this.BrushSize > source.Width) { throw new ArgumentOutOfRangeException(nameof(this.BrushSize)); } int startY = sourceRectangle.Y; int endY = sourceRectangle.Bottom; int startX = sourceRectangle.X; int endX = sourceRectangle.Right; int maxY = endY - 1; int maxX = endX - 1; int radius = this.BrushSize >> 1; int levels = this.Levels; using (Buffer2D <TPixel> targetPixels = configuration.MemoryAllocator.Allocate2D <TPixel>(source.Size())) { source.CopyTo(targetPixels); var workingRect = Rectangle.FromLTRB(startX, startY, endX, endY); ParallelHelper.IterateRows( workingRect, configuration, rows => { for (int y = rows.Min; y < rows.Max; y++) { Span <TPixel> sourceRow = source.GetPixelRowSpan(y); Span <TPixel> targetRow = targetPixels.GetRowSpan(y); for (int x = startX; x < endX; x++) { int maxIntensity = 0; int maxIndex = 0; int[] intensityBin = new int[levels]; float[] redBin = new float[levels]; float[] blueBin = new float[levels]; float[] greenBin = new float[levels]; for (int fy = 0; fy <= radius; fy++) { int fyr = fy - radius; int offsetY = y + fyr; offsetY = offsetY.Clamp(0, maxY); Span <TPixel> sourceOffsetRow = source.GetPixelRowSpan(offsetY); for (int fx = 0; fx <= radius; fx++) { int fxr = fx - radius; int offsetX = x + fxr; offsetX = offsetX.Clamp(0, maxX); var vector = sourceOffsetRow[offsetX].ToVector4(); float sourceRed = vector.X; float sourceBlue = vector.Z; float sourceGreen = vector.Y; int currentIntensity = (int)MathF.Round( (sourceBlue + sourceGreen + sourceRed) / 3F * (levels - 1)); intensityBin[currentIntensity]++; blueBin[currentIntensity] += sourceBlue; greenBin[currentIntensity] += sourceGreen; redBin[currentIntensity] += sourceRed; if (intensityBin[currentIntensity] > maxIntensity) { maxIntensity = intensityBin[currentIntensity]; maxIndex = currentIntensity; } } float red = MathF.Abs(redBin[maxIndex] / maxIntensity); float green = MathF.Abs(greenBin[maxIndex] / maxIntensity); float blue = MathF.Abs(blueBin[maxIndex] / maxIntensity); ref TPixel pixel = ref targetRow[x]; pixel.FromVector4( new Vector4(red, green, blue, sourceRow[x].ToVector4().W)); } } } }); Buffer2D <TPixel> .SwapOrCopyContent(source.PixelBuffer, targetPixels); } }
/// <inheritdoc/> protected override void OnFrameApply(ImageFrame <TPixel> source, ImageFrame <TPixel> destination, Rectangle sourceRectangle, Configuration configuration) { int height = this.TargetDimensions.Height; int width = this.TargetDimensions.Width; Rectangle sourceBounds = source.Bounds(); var targetBounds = new Rectangle(0, 0, width, height); // Since could potentially be resizing the canvas we might need to re-calculate the matrix Matrix4x4 matrix = this.GetProcessingMatrix(sourceBounds, targetBounds); // Convert from screen to world space. Matrix4x4.Invert(matrix, out matrix); const float Epsilon = 0.0000001F; if (this.Sampler is NearestNeighborResampler) { ParallelFor.WithConfiguration( 0, height, configuration, y => { Span <TPixel> destRow = destination.GetPixelRowSpan(y); for (int x = 0; x < width; x++) { var v3 = Vector3.Transform(new Vector3(x, y, 1), matrix); float z = MathF.Max(v3.Z, Epsilon); int px = (int)MathF.Round(v3.X / z); int py = (int)MathF.Round(v3.Y / z); if (sourceBounds.Contains(px, py)) { destRow[x] = source[px, py]; } } }); return; } int maxSourceX = source.Width - 1; int maxSourceY = source.Height - 1; (float radius, float scale, float ratio)xRadiusScale = this.GetSamplingRadius(source.Width, destination.Width); (float radius, float scale, float ratio)yRadiusScale = this.GetSamplingRadius(source.Height, destination.Height); float xScale = xRadiusScale.scale; float yScale = yRadiusScale.scale; // Using Vector4 with dummy 0-s, because Vector2 SIMD implementation is not reliable: var radius = new Vector4(xRadiusScale.radius, yRadiusScale.radius, 0, 0); IResampler sampler = this.Sampler; var maxSource = new Vector4(maxSourceX, maxSourceY, maxSourceX, maxSourceY); int xLength = (int)MathF.Ceiling((radius.X * 2) + 2); int yLength = (int)MathF.Ceiling((radius.Y * 2) + 2); MemoryAllocator memoryAllocator = configuration.MemoryAllocator; using (Buffer2D <float> yBuffer = memoryAllocator.Allocate2D <float>(yLength, height)) using (Buffer2D <float> xBuffer = memoryAllocator.Allocate2D <float>(xLength, height)) { ParallelFor.WithConfiguration( 0, height, configuration, y => { ref TPixel destRowRef = ref MemoryMarshal.GetReference(destination.GetPixelRowSpan(y)); ref float ySpanRef = ref MemoryMarshal.GetReference(yBuffer.GetRowSpan(y)); ref float xSpanRef = ref MemoryMarshal.GetReference(xBuffer.GetRowSpan(y));
/// <summary> /// Reads the 32 bit color palette from the stream, checking the alpha component of each pixel. /// This is a special case only used for 32bpp WinBMPv3 files, which could be in either BGR0 or BGRA format. /// </summary> /// <typeparam name="TPixel">The pixel format.</typeparam> /// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param> /// <param name="width">The width of the bitmap.</param> /// <param name="height">The height of the bitmap.</param> /// <param name="inverted">Whether the bitmap is inverted.</param> private void ReadRgb32Slow <TPixel>(Buffer2D <TPixel> pixels, int width, int height, bool inverted) where TPixel : struct, IPixel <TPixel> { int padding = CalculatePadding(width, 4); using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 4, padding)) using (IMemoryOwner <Bgra32> bgraRow = this.memoryAllocator.Allocate <Bgra32>(width)) { Span <Bgra32> bgraRowSpan = bgraRow.GetSpan(); long currentPosition = this.stream.Position; bool hasAlpha = false; // Loop though the rows checking each pixel. We start by assuming it's // an BGR0 image. If we hit a non-zero alpha value, then we know it's // actually a BGRA image, and change tactics accordingly. for (int y = 0; y < height; y++) { this.stream.Read(row); PixelOperations <Bgra32> .Instance.FromBgra32Bytes( this.configuration, row.GetSpan(), bgraRowSpan, width); // Check each pixel in the row to see if it has an alpha value. for (int x = 0; x < width; x++) { Bgra32 bgra = bgraRowSpan[x]; if (bgra.A > 0) { hasAlpha = true; break; } } if (hasAlpha) { break; } } // Reset our stream for a second pass. this.stream.Position = currentPosition; // Process the pixels in bulk taking the raw alpha component value. if (hasAlpha) { for (int y = 0; y < height; y++) { this.stream.Read(row); int newY = Invert(y, height, inverted); Span <TPixel> pixelSpan = pixels.GetRowSpan(newY); PixelOperations <TPixel> .Instance.FromBgra32Bytes( this.configuration, row.GetSpan(), pixelSpan, width); } return; } // Slow path. We need to set each alpha component value to fully opaque. for (int y = 0; y < height; y++) { this.stream.Read(row); PixelOperations <Bgra32> .Instance.FromBgra32Bytes( this.configuration, row.GetSpan(), bgraRowSpan, width); int newY = Invert(y, height, inverted); Span <TPixel> pixelSpan = pixels.GetRowSpan(newY); for (int x = 0; x < width; x++) { Bgra32 bgra = bgraRowSpan[x]; bgra.A = byte.MaxValue; ref TPixel pixel = ref pixelSpan[x]; pixel.FromBgra32(bgra); } } }