/// <inheritdoc/> public void Apply(ImageBase target, ImageBase source, int width, int height, Rectangle targetRectangle = default(Rectangle), Rectangle sourceRectangle = default(Rectangle)) { try { float[] pixels = new float[width * height * 4]; target.SetPixels(width, height, pixels); if (sourceRectangle == Rectangle.Empty) { sourceRectangle = source.Bounds; } // We don't want to affect the original source pixels so we make clone here. ImageFrame frame = source as ImageFrame; Image temp = frame != null ? new Image(frame) : new Image((Image)source); this.OnApply(temp, target, target.Bounds, sourceRectangle); targetRectangle = target.Bounds; this.numRowsProcessed = 0; this.totalRows = targetRectangle.Bottom; if (this.Parallelism > 1) { int partitionCount = this.Parallelism; Task[] tasks = new Task[partitionCount]; for (int p = 0; p < partitionCount; p++) { int current = p; tasks[p] = Task.Run(() => { int batchSize = targetRectangle.Bottom / partitionCount; int yStart = current * batchSize; int yEnd = current == partitionCount - 1 ? targetRectangle.Bottom : yStart + batchSize; this.Apply(target, temp, targetRectangle, sourceRectangle, yStart, yEnd); }); } Task.WaitAll(tasks); } else { this.Apply(target, temp, targetRectangle, sourceRectangle, targetRectangle.Y, targetRectangle.Bottom); } this.AfterApply(temp, target, target.Bounds, sourceRectangle); } catch (Exception ex) { throw new ImageProcessingException($"An error occured when processing the image using {this.GetType().Name}. See the inner exception for more detail.", ex); } }
/// <summary> /// Finds the bounding rectangle based on the first instance of any color component other /// than the given one. /// </summary> /// <param name="bitmap"> /// The <see cref="Image"/> to search within. /// </param> /// <param name="componentValue"> /// The color component value to remove. /// </param> /// <param name="channel"> /// The <see cref="RgbaComponent"/> channel to test against. /// </param> /// <returns> /// The <see cref="Rectangle"/>. /// </returns> public static Rectangle GetFilteredBoundingRectangle(ImageBase bitmap, float componentValue, RgbaComponent channel = RgbaComponent.B) { const float Epsilon = .00001f; int width = bitmap.Width; int height = bitmap.Height; Point topLeft = new Point(); Point bottomRight = new Point(); Func<ImageBase, int, int, float, bool> delegateFunc; // Determine which channel to check against switch (channel) { case RgbaComponent.R: delegateFunc = (imageBase, x, y, b) => Math.Abs(imageBase[x, y].R - b) > Epsilon; break; case RgbaComponent.G: delegateFunc = (imageBase, x, y, b) => Math.Abs(imageBase[x, y].G - b) > Epsilon; break; case RgbaComponent.A: delegateFunc = (imageBase, x, y, b) => Math.Abs(imageBase[x, y].A - b) > Epsilon; break; default: delegateFunc = (imageBase, x, y, b) => Math.Abs(imageBase[x, y].B - b) > Epsilon; break; } Func<ImageBase, int> getMinY = imageBase => { for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { if (delegateFunc(imageBase, x, y, componentValue)) { return y; } } } return 0; }; Func<ImageBase, int> getMaxY = imageBase => { for (int y = height - 1; y > -1; y--) { for (int x = 0; x < width; x++) { if (delegateFunc(imageBase, x, y, componentValue)) { return y; } } } return height; }; Func<ImageBase, int> getMinX = imageBase => { for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { if (delegateFunc(imageBase, x, y, componentValue)) { return x; } } } return 0; }; Func<ImageBase, int> getMaxX = imageBase => { for (int x = width - 1; x > -1; x--) { for (int y = 0; y < height; y++) { if (delegateFunc(imageBase, x, y, componentValue)) { return x; } } } return height; }; topLeft.Y = getMinY(bitmap); topLeft.X = getMinX(bitmap); bottomRight.Y = (getMaxY(bitmap) + 1).Clamp(0, height); bottomRight.X = (getMaxX(bitmap) + 1).Clamp(0, width); return GetBoundingRectangle(topLeft, bottomRight); }
/// <summary> /// Copies the properties from the other <see cref="ImageBase{T,TP}"/>. /// </summary> /// <param name="other"> /// The other <see cref="ImageBase{T,TP}"/> to copy the properties from. /// </param> protected void CopyProperties(ImageBase <T, TP> other) { this.Quality = other.Quality; this.FrameDelay = other.FrameDelay; }
/// <summary> /// Calculates the target rectangle for crop mode. /// </summary> /// <typeparam name="T">The type of pixels contained within the image.</typeparam> /// <param name="source">The source image.</param> /// <param name="options">The resize options.</param> /// <returns> /// The <see cref="Rectangle"/>. /// </returns> private static Rectangle CalculateCropRectangle <T, TP>(ImageBase <T, TP> source, ResizeOptions options) where T : IPackedVector <TP> where TP : struct { int width = options.Size.Width; int height = options.Size.Height; if (width <= 0 || height <= 0) { return(new Rectangle(0, 0, source.Width, source.Height)); } double ratio; int sourceWidth = source.Width; int sourceHeight = source.Height; int destinationX = 0; int destinationY = 0; int destinationWidth = width; int destinationHeight = height; // Fractional variants for preserving aspect ratio. double percentHeight = Math.Abs(height / (double)sourceHeight); double percentWidth = Math.Abs(width / (double)sourceWidth); if (percentHeight < percentWidth) { ratio = percentWidth; if (options.CenterCoordinates.Any()) { double center = -(ratio * sourceHeight) * options.CenterCoordinates.First(); destinationY = (int)center + (height / 2); if (destinationY > 0) { destinationY = 0; } if (destinationY < (int)(height - (sourceHeight * ratio))) { destinationY = (int)(height - (sourceHeight * ratio)); } } else { switch (options.Position) { case AnchorPosition.Top: case AnchorPosition.TopLeft: case AnchorPosition.TopRight: destinationY = 0; break; case AnchorPosition.Bottom: case AnchorPosition.BottomLeft: case AnchorPosition.BottomRight: destinationY = (int)(height - (sourceHeight * ratio)); break; default: destinationY = (int)((height - (sourceHeight * ratio)) / 2); break; } } destinationHeight = (int)Math.Ceiling(sourceHeight * percentWidth); } else { ratio = percentHeight; if (options.CenterCoordinates.Any()) { double center = -(ratio * sourceWidth) * options.CenterCoordinates.ToArray()[1]; destinationX = (int)center + (width / 2); if (destinationX > 0) { destinationX = 0; } if (destinationX < (int)(width - (sourceWidth * ratio))) { destinationX = (int)(width - (sourceWidth * ratio)); } } else { switch (options.Position) { case AnchorPosition.Left: case AnchorPosition.TopLeft: case AnchorPosition.BottomLeft: destinationX = 0; break; case AnchorPosition.Right: case AnchorPosition.TopRight: case AnchorPosition.BottomRight: destinationX = (int)(width - (sourceWidth * ratio)); break; default: destinationX = (int)((width - (sourceWidth * ratio)) / 2); break; } } destinationWidth = (int)Math.Ceiling(sourceWidth * percentHeight); } return(new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight)); }
/// <summary> /// Calculates the target rectangle for box pad mode. /// </summary> /// <typeparam name="T">The type of pixels contained within the image.</typeparam> /// <param name="source">The source image.</param> /// <param name="options">The resize options.</param> /// <returns> /// The <see cref="Rectangle"/>. /// </returns> private static Rectangle CalculateBoxPadRectangle <T, TP>(ImageBase <T, TP> source, ResizeOptions options) where T : IPackedVector <TP> where TP : struct { int width = options.Size.Width; int height = options.Size.Height; if (width <= 0 || height <= 0) { return(new Rectangle(0, 0, source.Width, source.Height)); } int sourceWidth = source.Width; int sourceHeight = source.Height; // Fractional variants for preserving aspect ratio. double percentHeight = Math.Abs(height / (double)sourceHeight); double percentWidth = Math.Abs(width / (double)sourceWidth); int boxPadHeight = height > 0 ? height : Convert.ToInt32(sourceHeight * percentWidth); int boxPadWidth = width > 0 ? width : Convert.ToInt32(sourceWidth * percentHeight); // Only calculate if upscaling. if (sourceWidth < boxPadWidth && sourceHeight < boxPadHeight) { int destinationX; int destinationY; int destinationWidth = sourceWidth; int destinationHeight = sourceHeight; width = boxPadWidth; height = boxPadHeight; switch (options.Position) { case AnchorPosition.Left: destinationY = (height - sourceHeight) / 2; destinationX = 0; break; case AnchorPosition.Right: destinationY = (height - sourceHeight) / 2; destinationX = width - sourceWidth; break; case AnchorPosition.TopRight: destinationY = 0; destinationX = width - sourceWidth; break; case AnchorPosition.Top: destinationY = 0; destinationX = (width - sourceWidth) / 2; break; case AnchorPosition.TopLeft: destinationY = 0; destinationX = 0; break; case AnchorPosition.BottomRight: destinationY = height - sourceHeight; destinationX = width - sourceWidth; break; case AnchorPosition.Bottom: destinationY = height - sourceHeight; destinationX = (width - sourceWidth) / 2; break; case AnchorPosition.BottomLeft: destinationY = height - sourceHeight; destinationX = 0; break; default: destinationY = (height - sourceHeight) / 2; destinationX = (width - sourceWidth) / 2; break; } return(new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight)); } // Switch to pad mode to downscale and calculate from there. return(CalculatePadRectangle(source, options)); }
/// <summary> /// Calculates the target rectangle for pad mode. /// </summary> /// <typeparam name="T">The type of pixels contained within the image.</typeparam> /// <param name="source">The source image.</param> /// <param name="options">The resize options.</param> /// <returns> /// The <see cref="Rectangle"/>. /// </returns> private static Rectangle CalculatePadRectangle <T, TP>(ImageBase <T, TP> source, ResizeOptions options) where T : IPackedVector <TP> where TP : struct { int width = options.Size.Width; int height = options.Size.Height; if (width <= 0 || height <= 0) { return(new Rectangle(0, 0, source.Width, source.Height)); } double ratio; int sourceWidth = source.Width; int sourceHeight = source.Height; int destinationX = 0; int destinationY = 0; int destinationWidth = width; int destinationHeight = height; // Fractional variants for preserving aspect ratio. double percentHeight = Math.Abs(height / (double)sourceHeight); double percentWidth = Math.Abs(width / (double)sourceWidth); if (percentHeight < percentWidth) { ratio = percentHeight; destinationWidth = Convert.ToInt32(sourceWidth * percentHeight); switch (options.Position) { case AnchorPosition.Left: case AnchorPosition.TopLeft: case AnchorPosition.BottomLeft: destinationX = 0; break; case AnchorPosition.Right: case AnchorPosition.TopRight: case AnchorPosition.BottomRight: destinationX = (int)(width - (sourceWidth * ratio)); break; default: destinationX = Convert.ToInt32((width - (sourceWidth * ratio)) / 2); break; } } else { ratio = percentWidth; destinationHeight = Convert.ToInt32(sourceHeight * percentWidth); switch (options.Position) { case AnchorPosition.Top: case AnchorPosition.TopLeft: case AnchorPosition.TopRight: destinationY = 0; break; case AnchorPosition.Bottom: case AnchorPosition.BottomLeft: case AnchorPosition.BottomRight: destinationY = (int)(height - (sourceHeight * ratio)); break; default: destinationY = (int)((height - (sourceHeight * ratio)) / 2); break; } } return(new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight)); }
/// <summary> /// This method is called after the process is applied to prepare the processor. /// </summary> /// <param name="source">The source image. Cannot be null.</param> /// <param name="target">Target image to apply the process to.</param> /// <param name="targetRectangle"> /// The <see cref="Rectangle"/> structure that specifies the location and size of the drawn image. /// The image is scaled to fit the rectangle. /// </param> /// <param name="sourceRectangle"> /// The <see cref="Rectangle"/> structure that specifies the portion of the image object to draw. /// </param> protected virtual void AfterApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle) { }
/// <summary> /// Applies the process to the specified portion of the specified <see cref="ImageBase"/> at the specified location /// and with the specified size. /// </summary> /// <param name="target">Target image to apply the process to.</param> /// <param name="source">The source image. Cannot be null.</param> /// <param name="targetRectangle"> /// The <see cref="Rectangle"/> structure that specifies the location and size of the drawn image. /// The image is scaled to fit the rectangle. /// </param> /// <param name="sourceRectangle"> /// The <see cref="Rectangle"/> structure that specifies the portion of the image object to draw. /// </param> /// <param name="startY">The index of the row within the source image to start processing.</param> /// <param name="endY">The index of the row within the source image to end processing.</param> /// <remarks> /// The method keeps the source image unchanged and returns the /// the result of image process as new image. /// </remarks> protected abstract void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY);
/// <summary> /// Combines the given image together with the current one by blending their pixels. /// </summary> /// <param name="source">The image this method extends.</param> /// <param name="image">The image to blend with the currently processing image.</param> /// <typeparam name="T">The pixel format.</typeparam> /// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam> /// <param name="percent">The opacity of the image image to blend. Must be between 0 and 100.</param> /// <param name="progressHandler">A delegate which is called as progress is made processing the image.</param> /// <returns>The <see cref="Image{T,TP}"/>.</returns> public static Image <T, TP> Blend <T, TP>(this Image <T, TP> source, ImageBase <T, TP> image, int percent = 50, ProgressEventHandler progressHandler = null) where T : IPackedVector <TP> where TP : struct { return(Blend(source, image, percent, source.Bounds, progressHandler)); }
/// <summary> /// Finds the bounding rectangle based on the first instance of any color component other /// than the given one. /// </summary> /// <typeparam name="T">The pixel format.</typeparam> /// <typeparam name="TP">The packed format. <example>long, float.</example></typeparam> /// <param name="bitmap">The <see cref="Image"/> to search within.</param> /// <param name="componentValue">The color component value to remove.</param> /// <param name="channel">The <see cref="RgbaComponent"/> channel to test against.</param> /// <returns> /// The <see cref="Rectangle"/>. /// </returns> public static Rectangle GetFilteredBoundingRectangle <T, TP>(ImageBase <T, TP> bitmap, float componentValue, RgbaComponent channel = RgbaComponent.B) where T : IPackedVector <TP> where TP : struct { const float Epsilon = .00001f; int width = bitmap.Width; int height = bitmap.Height; Point topLeft = new Point(); Point bottomRight = new Point(); Func <IPixelAccessor <T, TP>, int, int, float, bool> delegateFunc; // Determine which channel to check against switch (channel) { case RgbaComponent.R: delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].ToBytes()[0] - b) > Epsilon; break; case RgbaComponent.G: delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].ToBytes()[1] - b) > Epsilon; break; case RgbaComponent.B: delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].ToBytes()[2] - b) > Epsilon; break; default: delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].ToBytes()[3] - b) > Epsilon; break; } Func <IPixelAccessor <T, TP>, int> getMinY = pixels => { for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { if (delegateFunc(pixels, x, y, componentValue)) { return(y); } } } return(0); }; Func <IPixelAccessor <T, TP>, int> getMaxY = pixels => { for (int y = height - 1; y > -1; y--) { for (int x = 0; x < width; x++) { if (delegateFunc(pixels, x, y, componentValue)) { return(y); } } } return(height); }; Func <IPixelAccessor <T, TP>, int> getMinX = pixels => { for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { if (delegateFunc(pixels, x, y, componentValue)) { return(x); } } } return(0); }; Func <IPixelAccessor <T, TP>, int> getMaxX = pixels => { for (int x = width - 1; x > -1; x--) { for (int y = 0; y < height; y++) { if (delegateFunc(pixels, x, y, componentValue)) { return(x); } } } return(height); }; using (IPixelAccessor <T, TP> bitmapPixels = bitmap.Lock()) { topLeft.Y = getMinY(bitmapPixels); topLeft.X = getMinX(bitmapPixels); bottomRight.Y = (getMaxY(bitmapPixels) + 1).Clamp(0, height); bottomRight.X = (getMaxX(bitmapPixels) + 1).Clamp(0, width); } return(GetBoundingRectangle(topLeft, bottomRight)); }