/// <inheritdoc/> protected override void OnFrameApply(ImageFrame <TPixel> source) { // Preliminary gamma highlight pass var gammaOperation = new ApplyGammaExposureRowOperation(this.SourceRectangle, source.PixelBuffer, this.Configuration, this.gamma); ParallelRowIterator.IterateRows <ApplyGammaExposureRowOperation, Vector4>( this.Configuration, this.SourceRectangle, in gammaOperation); // Create a 0-filled buffer to use to store the result of the component convolutions using Buffer2D <Vector4> processingBuffer = this.Configuration.MemoryAllocator.Allocate2D <Vector4>(source.Size(), AllocationOptions.Clean); // Perform the 1D convolutions on all the kernel components and accumulate the results this.OnFrameApplyCore(source, this.SourceRectangle, this.Configuration, processingBuffer); float inverseGamma = 1 / this.gamma; // Apply the inverse gamma exposure pass, and write the final pixel data var operation = new ApplyInverseGammaExposureRowOperation(this.SourceRectangle, source.PixelBuffer, processingBuffer, this.Configuration, inverseGamma); ParallelRowIterator.IterateRows( this.Configuration, this.SourceRectangle, in operation); }
/// <inheritdoc /> protected override void OnFrameApply(ImageFrame <TPixel> source) { var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); // We need a clean copy for each pass to start from using ImageFrame <TPixel> cleanCopy = source.Clone(); using (var processor = new ConvolutionProcessor <TPixel>(this.Configuration, in this.kernels[0], true, this.Source, interest)) { processor.Apply(source); } if (this.kernels.Length == 1) { return; } // Additional runs for (int i = 1; i < this.kernels.Length; i++) { using ImageFrame <TPixel> pass = cleanCopy.Clone(); using (var processor = new ConvolutionProcessor <TPixel>(this.Configuration, in this.kernels[i], true, this.Source, interest)) { processor.Apply(pass); } var operation = new RowOperation(source.PixelBuffer, pass.PixelBuffer, interest); ParallelRowIterator.IterateRows( this.Configuration, interest, in operation); } }
/// <inheritdoc/> protected override void OnFrameApply(ImageFrame <TPixel> source) { int sourceWidth = source.Width; int sourceHeight = source.Height; int tileWidth = (int)MathF.Ceiling(sourceWidth / (float)this.Tiles); int tileHeight = (int)MathF.Ceiling(sourceHeight / (float)this.Tiles); int tileCount = this.Tiles; int halfTileWidth = tileWidth / 2; int halfTileHeight = tileHeight / 2; int luminanceLevels = this.LuminanceLevels; // The image is split up into tiles. For each tile the cumulative distribution function will be calculated. using (var cdfData = new CdfTileData(this.Configuration, sourceWidth, sourceHeight, this.Tiles, this.Tiles, tileWidth, tileHeight, luminanceLevels)) { cdfData.CalculateLookupTables(source, this); var tileYStartPositions = new List <(int y, int cdfY)>(); int cdfY = 0; int yStart = halfTileHeight; for (int tile = 0; tile < tileCount - 1; tile++) { tileYStartPositions.Add((yStart, cdfY)); cdfY++; yStart += tileHeight; } var operation = new RowIntervalOperation(cdfData, tileYStartPositions, tileWidth, tileHeight, tileCount, halfTileWidth, luminanceLevels, source); ParallelRowIterator.IterateRowIntervals( this.Configuration, new Rectangle(0, 0, sourceWidth, tileYStartPositions.Count), in operation); ref TPixel pixelsBase = ref source.GetPixelReference(0, 0); // Fix left column ProcessBorderColumn(ref pixelsBase, cdfData, 0, sourceWidth, sourceHeight, this.Tiles, tileHeight, xStart: 0, xEnd: halfTileWidth, luminanceLevels); // Fix right column int rightBorderStartX = ((this.Tiles - 1) * tileWidth) + halfTileWidth; ProcessBorderColumn(ref pixelsBase, cdfData, this.Tiles - 1, sourceWidth, sourceHeight, this.Tiles, tileHeight, xStart: rightBorderStartX, xEnd: sourceWidth, luminanceLevels); // Fix top row ProcessBorderRow(ref pixelsBase, cdfData, 0, sourceWidth, this.Tiles, tileWidth, yStart: 0, yEnd: halfTileHeight, luminanceLevels); // Fix bottom row int bottomBorderStartY = ((this.Tiles - 1) * tileHeight) + halfTileHeight; ProcessBorderRow(ref pixelsBase, cdfData, this.Tiles - 1, sourceWidth, this.Tiles, tileWidth, yStart: bottomBorderStartY, yEnd: sourceHeight, luminanceLevels); // Left top corner ProcessCornerTile(ref pixelsBase, cdfData, sourceWidth, 0, 0, xStart: 0, xEnd: halfTileWidth, yStart: 0, yEnd: halfTileHeight, luminanceLevels); // Left bottom corner ProcessCornerTile(ref pixelsBase, cdfData, sourceWidth, 0, this.Tiles - 1, xStart: 0, xEnd: halfTileWidth, yStart: bottomBorderStartY, yEnd: sourceHeight, luminanceLevels); // Right top corner ProcessCornerTile(ref pixelsBase, cdfData, sourceWidth, this.Tiles - 1, 0, xStart: rightBorderStartX, xEnd: sourceWidth, yStart: 0, yEnd: halfTileHeight, luminanceLevels); // Right bottom corner ProcessCornerTile(ref pixelsBase, cdfData, sourceWidth, this.Tiles - 1, this.Tiles - 1, xStart: rightBorderStartX, xEnd: sourceWidth, yStart: bottomBorderStartY, yEnd: sourceHeight, luminanceLevels); }
protected override void OnFrameApply(ImageFrame <TPixel> source) { var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); var operation = new OpaqueRowOperation(this.Configuration, source.PixelBuffer, interest); ParallelRowIterator.IterateRows <OpaqueRowOperation, Vector4>(this.Configuration, interest, in operation); }
/// <summary> /// Swaps the image at the Y-axis, which goes vertically through the middle at half of the width of the image. /// </summary> /// <param name="source">The source image to apply the process to.</param> /// <param name="configuration">The configuration.</param> private void FlipY(ImageFrame <TPixel> source, Configuration configuration) { var operation = new RowOperation(source); ParallelRowIterator.IterateRows( configuration, source.Bounds(), in operation); }
/// <summary> /// Applies a tint to an image. /// </summary> public static void Tint(this Image <Rgba32> image, Color colour) { var tint = colour.ToPixel <Rgba32>(); var lightness = 2 * (1 - (Converter.ToHsl(tint).L * 2 - 1)); var operation = new TintRowOperation(image, tint, lightness); ParallelRowIterator.IterateRows(Configuration.Default, image.Bounds(), in operation); }
public static void ApplyHaze(this Image <Rgba32> image, Color tint, float amount, float opacity) { var mask = new Image <Rgba32>(image.Width, image.Height); var operation = new HazeRowOperation(mask, tint, amount, opacity); ParallelRowIterator.IterateRows(Configuration.Default, mask.Bounds(), in operation); image.Mutate(context => context.DrawImage(mask, PixelColorBlendingMode.Screen, 1f)); }
public static void ApplyGaussianNoise(this Image <Rgba32> image) { var noiseImage = image.Clone(); var operation = new GaussianNoiseRowOperation(noiseImage); ParallelRowIterator.IterateRows(Configuration.Default, noiseImage.Bounds(), in operation); image.Mutate(c => c.DrawImage(noiseImage, PixelColorBlendingMode.Multiply, 0.1f)); }
/// <inheritdoc/> protected override void OnFrameApply(ImageFrame <TPixel> source) { var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); var operation = new RowIntervalOperation(interest.X, source, this.definition.Matrix, this.Configuration); ParallelRowIterator.IterateRows <RowIntervalOperation, Vector4>( this.Configuration, interest, in operation); }
/// <summary> /// Reprojects an image in an equirectangular projection, such as an underlay or composited underlay with IR imagery, /// into a geostationary projection. /// </summary> public static Image <Rgba32> ToGeostationaryProjection(this Image <Rgba32> source, double satelliteLongitude, double satelliteHeight, RenderOptions options) { // Projected image var target = new Image <Rgba32>(options.ImageSize, options.ImageSize); // Reproject image to match the given satellite var operation = new GeostationaryProjectionRowOperation(source, target, satelliteLongitude, satelliteHeight, options); ParallelRowIterator.IterateRows(Configuration.Default, target.Bounds(), in operation); return(target); }
private Image <Rgba32> Reproject(Registration registration) { var definition = registration.Definition; // Preserve 2:1 equirectangular aspect ratio var maxWidth = _options.ImageSize * 2; var maxHeight = _options.ImageSize; // Determine pixel ranges of projected image so we can limit our processing to longitudes visible to satellite // Unwrap the longitude range to simplify maths var longitudeRange = new Range( definition.LongitudeRange.Start, definition.LongitudeRange.End).UnwrapLongitude(); var latitudeRange = new Range(definition.LatitudeRange.Start, definition.LatitudeRange.End); _logger.LogInformation("{definition:l0} latitude range {startRange:F2} to {endRange:F2} degrees", definition.DisplayName, Angle.FromRadians(latitudeRange.Start).Degrees, Angle.FromRadians(latitudeRange.End).Degrees); _logger.LogInformation("{definition:l0} unwrapped longitude range {startRange:F2} to {endRange:F2} degrees", definition.DisplayName, Angle.FromRadians(longitudeRange.Start).Degrees, Angle.FromRadians(longitudeRange.End).Degrees); // Get size of projection in pixels var xRange = new PixelRange(longitudeRange, a => a.ScaleToWidth(maxWidth)); // Restrict height of image to the visible range if we are not performing explicit cropping or no cropping var yRange = _options.EquirectangularRender?.NoCrop == true || _options.EquirectangularRender?.ExplicitCrop == true ? new PixelRange(0, maxHeight) : new PixelRange(latitudeRange, a => a.ScaleToHeight(maxHeight)); _logger.LogInformation("{definition:l0} pixel range X: {minX} - {maxX} px", definition.DisplayName, xRange.Start, xRange.End); _logger.LogInformation("{definition:l0} pixel range Y: {minY} - {maxY} px", definition.DisplayName, yRange.Start, yRange.End); _logger.LogInformation("{definition:l0} width: {targetWidth} px", definition.DisplayName, xRange.Range); _logger.LogInformation("{definition:l0} height: {targetWidth} px", definition.DisplayName, yRange.Range); // Create target image with the correct dimensions for the projected satellite image var target = new Image <Rgba32>(xRange.Range, yRange.Range); _logger.LogInformation("{definition:l0} Reprojecting", definition.DisplayName); // Perform reprojection var operation = new ReprojectRowOperation(registration, SourceImage !, target, xRange.Start, yRange.Start, _options); ParallelRowIterator.IterateRows(Configuration.Default, target.Bounds(), in operation); return(target); }
/// <inheritdoc /> protected override void OnFrameApply(ImageFrame <TPixel> source) { Configuration configuration = this.Configuration; using IFrameQuantizer <TPixel> frameQuantizer = this.quantizer.CreateFrameQuantizer <TPixel>(configuration); using IQuantizedFrame <TPixel> quantized = frameQuantizer.QuantizeFrame(source); var operation = new RowIntervalOperation(this.SourceRectangle, source, quantized); ParallelRowIterator.IterateRows( configuration, this.SourceRectangle, in operation); }
protected override void OnFrameApply(ImageFrame <TPixelBg> source) { var targetBounds = this.TargetImage.Bounds(); var sourceBounds = this.Source.Bounds(); var maxWidth = Math.Min(targetBounds.Width, sourceBounds.Width); var operation = new RowIntervalOperation( source, this.TargetImage, this.Blender, this.Configuration, maxWidth); ParallelRowIterator.IterateRowIntervals(this.Configuration, this.SourceRectangle, in operation); }
/// <inheritdoc/> protected override void OnFrameApply(ImageFrame <TPixel> source) { using Buffer2D <TPixel> firstPassPixels = this.Configuration.MemoryAllocator.Allocate2D <TPixel>(source.Size()); var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); // We use a rectangle 2x the interest width to allocate a buffer big enough // for source and target bulk pixel conversion. var operationBounds = new Rectangle(interest.X, interest.Y, interest.Width * 2, interest.Height); // We can create a single sampling map with the size as if we were using the non separated 2D kernel // the two 1D kernels represent, and reuse it across both convolution steps, like in the bokeh blur. using var mapXY = new KernelSamplingMap(this.Configuration.MemoryAllocator); mapXY.BuildSamplingOffsetMap(this.Kernel.Length, this.Kernel.Length, interest); // Horizontal convolution var horizontalOperation = new HorizontalConvolutionRowOperation( interest, firstPassPixels, source.PixelBuffer, mapXY, this.Kernel, this.Configuration, this.PreserveAlpha); ParallelRowIterator.IterateRows <HorizontalConvolutionRowOperation, Vector4>( this.Configuration, operationBounds, in horizontalOperation); // Vertical convolution var verticalOperation = new VerticalConvolutionRowOperation( interest, source.PixelBuffer, firstPassPixels, mapXY, this.Kernel, this.Configuration, this.PreserveAlpha); ParallelRowIterator.IterateRows <VerticalConvolutionRowOperation, Vector4>( this.Configuration, operationBounds, in verticalOperation); }
/// <summary> /// Returns a copy of the image frame in the given pixel format. /// </summary> /// <typeparam name="TPixel2">The pixel format.</typeparam> /// <param name="configuration">The configuration providing initialization code which allows extending the library.</param> /// <returns>The <see cref="ImageFrame{TPixel2}"/></returns> internal ImageFrame <TPixel2> CloneAs <TPixel2>(Configuration configuration) where TPixel2 : unmanaged, IPixel <TPixel2> { if (typeof(TPixel2) == typeof(TPixel)) { return(this.Clone(configuration) as ImageFrame <TPixel2>); } var target = new ImageFrame <TPixel2>(configuration, this.Width, this.Height, this.Metadata.DeepClone()); var operation = new RowIntervalOperation <TPixel2>(this, target, configuration); ParallelRowIterator.IterateRowIntervals( configuration, this.Bounds(), in operation); return(target); }
/// <summary> /// EWS-G1 images either require cropping on the left edge or the right edge, depending on the time of day. The /// <c>Satellites.json</c> file contains crop values for a right-hand crop. This method identifies if a left /// crop is required and adjusts the crop bounds accordingly. /// </summary> private void NormaliseEwsCrop() { Guard.Against.Null(Registration?.Image, nameof(Registration.Image)); if (Registration.Definition.DisplayName != "EWS-G1" || Registration.Definition.Crop == null) { return; } var operation = new EwsAlignmentRowOperation(Registration.Image); ParallelRowIterator.IterateRows(Configuration.Default, Registration.Image.Bounds(), in operation); if (operation.IsRightCrop()) { return; } _logger.LogInformation("Swapping horizontal crop bounds for EWS-G1"); Registration.FlipHorizontalCrop = true; }
public void ApplyQuantizationDither <TFrameQuantizer, TPixel>( ref TFrameQuantizer quantizer, ImageFrame <TPixel> source, IndexedImageFrame <TPixel> destination, Rectangle bounds) where TFrameQuantizer : struct, IQuantizer <TPixel> where TPixel : unmanaged, IPixel <TPixel> { var ditherOperation = new QuantizeDitherRowOperation <TFrameQuantizer, TPixel>( ref quantizer, in Unsafe.AsRef(this), source, destination, bounds); ParallelRowIterator.IterateRows( quantizer.Configuration, bounds, in ditherOperation); }
public override ExecutionResult Run(IStepExecutionContext context) { var overlayOptions = _options.Overlay; if (!overlayOptions.ApplyOverlay) { return(ExecutionResult.Next()); } Guard.Against.Null(SourceImage, nameof(SourceImage)); var clut = _clutService.GetClut(); OverlayImage = new Image <Rgba32>(SourceImage.Width, SourceImage.Height); var operation = new ApplyClutRowOperation(SourceImage, OverlayImage, clut); ParallelRowIterator.IterateRows(Configuration.Default, SourceImage.Bounds(), in operation); return(ExecutionResult.Next()); }
/// <inheritdoc/> protected override void OnFrameApply(ImageFrame <TPixel> source) { MemoryAllocator memoryAllocator = this.Configuration.MemoryAllocator; int numberOfPixels = source.Width * source.Height; var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); using IMemoryOwner <int> histogramBuffer = memoryAllocator.Allocate <int>(this.LuminanceLevels, AllocationOptions.Clean); // Build the histogram of the grayscale levels var grayscaleOperation = new GrayscaleLevelsRowIntervalOperation(interest, histogramBuffer, source, this.LuminanceLevels); ParallelRowIterator.IterateRows( this.Configuration, interest, in grayscaleOperation); Span <int> histogram = histogramBuffer.GetSpan(); if (this.ClipHistogramEnabled) { this.ClipHistogram(histogram, this.ClipLimit); } using IMemoryOwner <int> cdfBuffer = memoryAllocator.Allocate <int>(this.LuminanceLevels, AllocationOptions.Clean); // Calculate the cumulative distribution function, which will map each input pixel to a new value. int cdfMin = this.CalculateCdf( ref MemoryMarshal.GetReference(cdfBuffer.GetSpan()), ref MemoryMarshal.GetReference(histogram), histogram.Length - 1); float numberOfPixelsMinusCdfMin = numberOfPixels - cdfMin; // Apply the cdf to each pixel of the image var cdfOperation = new CdfApplicationRowIntervalOperation(interest, cdfBuffer, source, this.LuminanceLevels, numberOfPixelsMinusCdfMin); ParallelRowIterator.IterateRows( this.Configuration, interest, in cdfOperation); }
/// <inheritdoc/> protected override void OnFrameApply(ImageFrame <TPixel> source) { int brushSize = this.definition.BrushSize; if (brushSize <= 0 || brushSize > source.Height || brushSize > source.Width) { throw new ArgumentOutOfRangeException(nameof(brushSize)); } using Buffer2D <TPixel> targetPixels = this.Configuration.MemoryAllocator.Allocate2D <TPixel>(source.Size()); source.CopyTo(targetPixels); var operation = new RowIntervalOperation(this.SourceRectangle, targetPixels, source, this.Configuration, brushSize >> 1, this.definition.Levels); ParallelRowIterator.IterateRowIntervals( this.Configuration, this.SourceRectangle, in operation); Buffer2D <TPixel> .SwapOrCopyContent(source.PixelBuffer, targetPixels); }
public void ApplyQuantizationDither <TFrameQuantizer, TPixel>( ref TFrameQuantizer quantizer, ReadOnlyMemory <TPixel> palette, ImageFrame <TPixel> source, Memory <byte> output, Rectangle bounds) where TFrameQuantizer : struct, IFrameQuantizer <TPixel> where TPixel : struct, IPixel <TPixel> { var ditherOperation = new QuantizeDitherRowIntervalOperation <TFrameQuantizer, TPixel>( ref quantizer, in Unsafe.AsRef(this), source, output, bounds, palette, ImageMaths.GetBitsNeededForColorDepth(palette.Span.Length)); ParallelRowIterator.IterateRows( quantizer.Configuration, bounds, in ditherOperation); }
private static void SecondPass <TFrameQuantizer, TPixel>( ref TFrameQuantizer quantizer, ImageFrame <TPixel> source, Rectangle bounds, Memory <byte> output, ReadOnlyMemory <TPixel> palette) where TFrameQuantizer : struct, IFrameQuantizer <TPixel> where TPixel : unmanaged, IPixel <TPixel> { IDither dither = quantizer.Options.Dither; if (dither is null) { var operation = new RowIntervalOperation <TFrameQuantizer, TPixel>(quantizer, source, output, bounds, palette); ParallelRowIterator.IterateRowIntervals( quantizer.Configuration, bounds, in operation); return; } dither.ApplyQuantizationDither(ref quantizer, palette, source, output, bounds); }
public static void HorizontalOffset(this Image <Rgba32> image, int amount) { var operation = new OffsetRowOperation(image, amount); ParallelRowIterator.IterateRows <OffsetRowOperation, Rgba32>(Configuration.Default, image.Bounds(), in operation); }
/// <summary> /// Sets the background outside the Earth on geostationary images to be transparent in order to facilitate blending. /// </summary> /// <param name="image">source image</param> public static void RemoveBackground(this Image <Rgba32> image) { var operation = new RemoveBackgroundRowOperation(image); ParallelRowIterator.IterateRows(Configuration.Default, image.Bounds(), in operation); }