Exemple #1
0
        /// <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);
            }
        }
Exemple #2
0
        /// <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);
        }
Exemple #3
0
        /// <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.IterateRows(
                    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);
            }
Exemple #4
0
        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);
        }
Exemple #5
0
        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));
        }
Exemple #6
0
        /// <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);
        }
Exemple #8
0
        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);
        }
Exemple #11
0
        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.IterateRows(this.Configuration, this.SourceRectangle, 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 : struct, 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.IterateRows(
                configuration,
                this.Bounds(),
                in operation);

            return(target);
        }
Exemple #16
0
        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);
        }
Exemple #17
0
        /// <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;
        }
Exemple #18
0
        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());
        }
Exemple #19
0
        /// <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);
        }
Exemple #20
0
        /// <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.IterateRows(
                this.Configuration,
                this.SourceRectangle,
                in operation);

            Buffer2D <TPixel> .SwapOrCopyContent(source.PixelBuffer, targetPixels);
        }
Exemple #21
0
        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.IterateRows(
                    quantizer.Configuration,
                    bounds,
                    in operation);

                return;
            }

            dither.ApplyQuantizationDither(ref quantizer, palette, source, output, bounds);
        }
Exemple #23
0
        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);
        }
Exemple #24
0
        /// <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);
        }