/// <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); } } }
/// <summary> /// Initializes a new instance of the <see cref="WeightsBuffer"/> class. /// </summary> /// <param name="memoryAllocator">The <see cref="MemoryAllocator"/> to use for allocations.</param> /// <param name="sourceSize">The size of the source window</param> /// <param name="destinationSize">The size of the destination window</param> public WeightsBuffer(MemoryAllocator memoryAllocator, int sourceSize, int destinationSize) { this.dataBuffer = memoryAllocator.Allocate2D <float>(sourceSize, destinationSize, true); this.Weights = new WeightsWindow[destinationSize]; }
/// <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 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 => { 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> /// Decodes the image from the specified this._stream and sets /// the data to image. /// </summary> /// <typeparam name="TPixel">The pixel format.</typeparam> /// <param name="stream">The stream, where the image should be /// decoded from. Cannot be null (Nothing in Visual Basic).</param> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="stream"/> is null.</para> /// </exception> /// <returns>The decoded image.</returns> public Image <TPixel> Decode <TPixel>(Stream stream) where TPixel : struct, IPixel <TPixel> { try { int bytesPerColorMapEntry = this.ReadImageHeaders(stream, out bool inverted, out byte[] palette); var image = new Image <TPixel>(this.configuration, this.infoHeader.Width, this.infoHeader.Height, this.metaData); Buffer2D <TPixel> pixels = image.GetRootFramePixelBuffer(); switch (this.infoHeader.Compression) { case BmpCompression.RGB: if (this.infoHeader.BitsPerPixel == 32) { if (this.bmpMetaData.InfoHeaderType == BmpInfoHeaderType.WinVersion3) { this.ReadRgb32Slow(pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); } else { this.ReadRgb32Fast(pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); } } else if (this.infoHeader.BitsPerPixel == 24) { this.ReadRgb24(pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); } else if (this.infoHeader.BitsPerPixel == 16) { this.ReadRgb16(pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); } else if (this.infoHeader.BitsPerPixel <= 8) { this.ReadRgbPalette( pixels, palette, this.infoHeader.Width, this.infoHeader.Height, this.infoHeader.BitsPerPixel, bytesPerColorMapEntry, inverted); } break; case BmpCompression.RLE8: case BmpCompression.RLE4: this.ReadRle(this.infoHeader.Compression, pixels, palette, this.infoHeader.Width, this.infoHeader.Height, inverted); break; case BmpCompression.BitFields: this.ReadBitFields(pixels, inverted); break; default: BmpThrowHelper.ThrowNotSupportedException("Does not support this kind of bitmap files."); break; } return(image); } catch (IndexOutOfRangeException e) { throw new ImageFormatException("Bitmap does not have a valid format.", e); } }
/// <summary> /// Gets the span to the backing buffer at the given row. /// </summary> /// <typeparam name="TPixel">The type of the pixel.</typeparam> /// <param name="source">The source.</param> /// <param name="row">The row.</param> /// <returns> /// The span retuned from Pixel source /// </returns> private static Span <TPixel> GetSpan <TPixel>(Buffer2D <TPixel> source, int row) where TPixel : struct, IPixel <TPixel> => source.GetSpan().Slice(row * source.Width, source.Width);
/// <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; } 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()) { new ConvolutionProcessor <TPixel>(kernels[i]).Apply(pass, sourceRectangle, configuration); Buffer2D <TPixel> passPixels = pass.PixelBuffer; Buffer2D <TPixel> targetPixels = source.PixelBuffer; ParallelHelper.IterateRows( workingRect, 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.PackFromVector4(pixelValue); } } });
/// <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.DangerousGetRowSpan(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; } } } }
/// <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 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; // 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); Vector4Utils.Premultiply(tempRowSpan); ref Vector4 firstPassBaseRef = ref firstPassPixelsTransposed.Span[y]; if (this.Compand) { SRgbCompanding.Expand(tempRowSpan); } 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); } Vector4Utils.UnPremultiply(tempRowSpan); if (this.Compand) { SRgbCompanding.Compress(tempRowSpan); } Span <TPixel> targetRowSpan = destination.GetPixelRowSpan(y); PixelOperations <TPixel> .Instance.FromVector4(configuration, tempRowSpan, targetRowSpan); } }); } }
/// <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, Span <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.DangerousGetRowSpan(newY); switch (colorMapPixelSizeInBytes) { case 2: if (invertX) { for (int x = width - 1; x >= 0; x--) { this.ReadPalettedBgra16Pixel(palette, colorMapPixelSizeInBytes, x, color, pixelRow); } } else { for (int x = 0; x < width; x++) { this.ReadPalettedBgra16Pixel(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; } } }
/// <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) { Parallel.For( 0, height, configuration.ParallelOptions, 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)) { 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));
public void Setup() { this.buffer = Configuration.Default.MemoryAllocator.Allocate2D <float>(1000, 500); this.destRegion = this.buffer.GetRegion(200, 100, 128, 128); }
/// <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="inverted">Indicates, if the origin of the image is top left rather the bottom left (the default).</param> private void ReadPalettedRle <TPixel>(int width, int height, Buffer2D <TPixel> pixels, byte[] palette, int colorMapPixelSizeInBytes, bool inverted) 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 = Invert(y, height, inverted); 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; } pixelRow[x] = color; } } } }
/// <inheritdoc/> protected override unsafe void OnApply(ImageFrame <TPixel> source, ImageFrame <TPixel> cloned, Rectangle sourceRectangle, Configuration configuration) { // Jump out, we'll deal with that later. if (source.Width == cloned.Width && source.Height == cloned.Height && sourceRectangle == this.ResizeRectangle) { // the cloned will be blank here copy all the pixel data over source.GetPixelSpan().CopyTo(cloned.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) { // Scaling factors float widthFactor = sourceRectangle.Width / (float)this.ResizeRectangle.Width; float heightFactor = sourceRectangle.Height / (float)this.ResizeRectangle.Height; Parallel.For( minY, maxY, configuration.ParallelOptions, y => { // Y coordinates of source points Span <TPixel> sourceRow = source.GetPixelRowSpan((int)(((y - startY) * heightFactor) + sourceY)); Span <TPixel> targetRow = cloned.GetPixelRowSpan(y); for (int x = minX; x < maxX; x++) { // X coordinates of source points targetRow[x] = sourceRow[(int)(((x - startX) * widthFactor) + sourceX)]; } }); 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 firstPassPixels = new Buffer2D <Vector4>(width, source.Height)) { firstPassPixels.Clear(); Parallel.For( 0, sourceRectangle.Bottom, configuration.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.GetPixelRowSpan(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, configuration.ParallelOptions, y => { // Ensure offsets are normalised for cropping and padding. WeightsWindow window = this.VerticalWeights.Weights[y - startY]; Span <TPixel> targetRow = cloned.GetPixelRowSpan(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); } } });
/// <summary> /// Decodes the image from the specified stream. /// </summary> /// <typeparam name="TPixel">The pixel format.</typeparam> /// <param name="stream">The stream, where the image should be decoded from. Cannot be null.</param> /// <exception cref="System.ArgumentNullException"> /// <para><paramref name="stream"/> is null.</para> /// </exception> /// <returns>The decoded image.</returns> public Image <TPixel> Decode <TPixel>(Stream stream) where TPixel : struct, IPixel <TPixel> { try { bool inverted = this.ReadFileHeader(stream); this.currentStream.Skip(this.fileHeader.IdLength); // Parse the color map, if present. if (this.fileHeader.ColorMapType != 0 && this.fileHeader.ColorMapType != 1) { TgaThrowHelper.ThrowNotSupportedException($"Unknown tga colormap type {this.fileHeader.ColorMapType} found"); } if (this.fileHeader.Width == 0 || this.fileHeader.Height == 0) { throw new UnknownImageFormatException("Width or height cannot be 0"); } var image = new Image <TPixel>(this.configuration, this.fileHeader.Width, this.fileHeader.Height, this.metadata); Buffer2D <TPixel> pixels = image.GetRootFramePixelBuffer(); if (this.fileHeader.ColorMapType is 1) { if (this.fileHeader.CMapLength <= 0) { TgaThrowHelper.ThrowImageFormatException("Missing tga color map length"); } if (this.fileHeader.CMapDepth <= 0) { TgaThrowHelper.ThrowImageFormatException("Missing tga color map depth"); } int colorMapPixelSizeInBytes = this.fileHeader.CMapDepth / 8; int colorMapSizeInBytes = this.fileHeader.CMapLength * colorMapPixelSizeInBytes; using (IManagedByteBuffer palette = this.memoryAllocator.AllocateManagedByteBuffer(colorMapSizeInBytes, AllocationOptions.Clean)) { this.currentStream.Read(palette.Array, this.fileHeader.CMapStart, colorMapSizeInBytes); if (this.fileHeader.ImageType is TgaImageType.RleColorMapped) { this.ReadPalettedRle( this.fileHeader.Width, this.fileHeader.Height, pixels, palette.Array, colorMapPixelSizeInBytes, inverted); } else { this.ReadPaletted( this.fileHeader.Width, this.fileHeader.Height, pixels, palette.Array, colorMapPixelSizeInBytes, inverted); } } return(image); } // Even if the image type indicates it is not a paletted image, it can still contain a palette. Skip those bytes. if (this.fileHeader.CMapLength > 0) { int colorMapPixelSizeInBytes = this.fileHeader.CMapDepth / 8; this.currentStream.Skip(this.fileHeader.CMapLength * colorMapPixelSizeInBytes); } switch (this.fileHeader.PixelDepth) { case 8: if (this.fileHeader.ImageType.IsRunLengthEncoded()) { this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 1, inverted); } else { this.ReadMonoChrome(this.fileHeader.Width, this.fileHeader.Height, pixels, inverted); } break; case 15: case 16: if (this.fileHeader.ImageType.IsRunLengthEncoded()) { this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 2, inverted); } else { this.ReadBgra16(this.fileHeader.Width, this.fileHeader.Height, pixels, inverted); } break; case 24: if (this.fileHeader.ImageType.IsRunLengthEncoded()) { this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 3, inverted); } else { this.ReadBgr24(this.fileHeader.Width, this.fileHeader.Height, pixels, inverted); } break; case 32: if (this.fileHeader.ImageType.IsRunLengthEncoded()) { this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 4, inverted); } else { this.ReadBgra32(this.fileHeader.Width, this.fileHeader.Height, pixels, inverted); } break; default: TgaThrowHelper.ThrowNotSupportedException("Does not support this kind of tga files."); break; } return(image); } catch (IndexOutOfRangeException e) { throw new ImageFormatException("TGA image does not have a valid format.", e); } }
/// <summary> /// Reads the frames colors, mapping indices to colors. /// </summary> /// <typeparam name="TPixel">The pixel format.</typeparam> /// <param name="image">The image to decode the information to.</param> /// <param name="previousFrame">The previous frame.</param> /// <param name="indices">The indexed pixels.</param> /// <param name="colorTable">The color table containing the available colors.</param> /// <param name="descriptor">The <see cref="GifImageDescriptor"/></param> private void ReadFrameColors <TPixel>(ref Image <TPixel> image, ref ImageFrame <TPixel> previousFrame, Buffer2D <byte> indices, ReadOnlySpan <Rgb24> colorTable, in GifImageDescriptor descriptor)
public RowIntervalOperation(Configuration configuration, Rectangle bounds, Buffer2D <TPixel> source) { this.configuration = configuration; this.bounds = bounds; this.source = source; }