-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add image processing features and UI updates
- Removed unnecessary target frameworks from project file. - Added new ImageProcessing page with effects like blur, grayscale, etc. - Implemented event handlers for effect adjustments in the UI. - Introduced a collection to manage multiple image processing effects. - Updated main page to navigate to the new ImageProcessing page.
1 parent
6166515
commit ee5e377
Showing
21 changed files
with
1,696 additions
and
125 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" | ||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" | ||
xmlns:effects="clr-namespace:AuroraControls.Effects;assembly=AuroraControls" | ||
x:Class="AuroraControls.TestApp.ImageProcessing"> | ||
<ContentPage.Content> | ||
<StackLayout HorizontalOptions="FillAndExpand" | ||
VerticalOptions="FillAndExpand"> | ||
<Button Text="Change Effects" | ||
Clicked="Handle_Clicked" /> | ||
<Label Text="Blur Amount" /> | ||
<Slider Minimum="0" | ||
Maximum="10" | ||
ValueChanged="Handle_ValueChanged" /> | ||
<Image x:Name="image" | ||
Source="https://api.floodmagazine.com/wp-content/uploads/2016/07/Steve_Brule-2016-Marc_Lemoine-2.png" | ||
HorizontalOptions="FillAndExpand" | ||
VerticalOptions="FillAndExpand"> | ||
<Image.Effects> | ||
<effects:ImageProcessingEffect x:Name="ImageProcessingEffect" /> | ||
</Image.Effects> | ||
</Image> | ||
</StackLayout> | ||
</ContentPage.Content> | ||
</ContentPage> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
|
||
namespace AuroraControls.TestApp; | ||
|
||
public partial class ImageProcessing : ContentPage | ||
{ | ||
private List<AuroraControls.ImageProcessing.ImageProcessingBase> _imageProcessing = new(); | ||
|
||
private int _index = 0; | ||
|
||
private AuroraControls.ImageProcessing.Blur _blur; | ||
private AuroraControls.ImageProcessing.Circular _circular; | ||
private AuroraControls.ImageProcessing.Grayscale _grayscale; | ||
private AuroraControls.ImageProcessing.Invert _invert; | ||
private AuroraControls.ImageProcessing.Scale _scale; | ||
private AuroraControls.ImageProcessing.Sepia _sepia; | ||
|
||
private Random _rngesus = new Random(Guid.NewGuid().GetHashCode()); | ||
|
||
public ImageProcessing() | ||
{ | ||
InitializeComponent(); | ||
|
||
_blur = new AuroraControls.ImageProcessing.Blur { }; | ||
_circular = new AuroraControls.ImageProcessing.Circular(); | ||
_grayscale = new AuroraControls.ImageProcessing.Grayscale(); | ||
_invert = new AuroraControls.ImageProcessing.Invert(); | ||
_scale = new AuroraControls.ImageProcessing.Scale(); | ||
_sepia = new AuroraControls.ImageProcessing.Sepia(); | ||
|
||
this._imageProcessing.AddRange([_blur, _circular, _grayscale, _invert, _scale, _sepia]); | ||
} | ||
|
||
private void Handle_ValueChanged(object sender, ValueChangedEventArgs e) | ||
{ | ||
_blur.BlurAmount = e.NewValue; | ||
} | ||
|
||
private void Handle_Clicked(object sender, System.EventArgs e) | ||
{ | ||
if (_index > _imageProcessing.Count - 1) | ||
{ | ||
_index = 0; | ||
} | ||
|
||
var processingEffect = this._imageProcessing.ElementAt(_index); | ||
|
||
if (ImageProcessingEffect.ImageProcessingEffects.Contains(processingEffect)) | ||
{ | ||
ImageProcessingEffect.ImageProcessingEffects.Remove(processingEffect); | ||
} | ||
|
||
ImageProcessingEffect.ImageProcessingEffects.Add(processingEffect); | ||
|
||
_index++; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,19 @@ | ||
using AuroraControls; | ||
|
||
[assembly: Microsoft.Maui.Controls.XmlnsPrefix("http://auroracontrols.maui/schemas/controls", "AuroraControls")] | ||
[assembly: Microsoft.Maui.Controls.XmlnsPrefix("http://auroracontrols.maui/schemas/controls", "AuroraControls.Gauges")] | ||
[assembly: Microsoft.Maui.Controls.XmlnsPrefix("http://auroracontrols.maui/schemas/controls", "AuroraControls.Loading")] | ||
[assembly: Microsoft.Maui.Controls.XmlnsDefinition("http://auroracontrols.maui/schemas/controls", "AuroraControls")] | ||
[assembly: Microsoft.Maui.Controls.XmlnsPrefix(Constants.XamlNamespace, Constants.CommunityToolkitNamespacePrefix + nameof(AuroraControls.Gauges))] | ||
[assembly: Microsoft.Maui.Controls.XmlnsPrefix(Constants.XamlNamespace, Constants.CommunityToolkitNamespacePrefix + nameof(AuroraControls.Loading))] | ||
[assembly: Microsoft.Maui.Controls.XmlnsPrefix(Constants.XamlNamespace, Constants.CommunityToolkitNamespacePrefix + nameof(AuroraControls.VisualEffects))] | ||
[assembly: Microsoft.Maui.Controls.XmlnsPrefix(Constants.XamlNamespace, Constants.CommunityToolkitNamespace)] | ||
|
||
[assembly: Microsoft.Maui.Controls.XmlnsDefinition(Constants.XamlNamespace, "aurora")] | ||
|
||
namespace AuroraControls; | ||
|
||
#pragma warning disable SA1649 | ||
internal static class Constants | ||
#pragma warning restore SA1649 | ||
{ | ||
public const string XamlNamespace = "http://auroracontrols.maui/schemas/controls"; | ||
public const string CommunityToolkitNamespace = $"{nameof(AuroraControls)}"; | ||
public const string CommunityToolkitNamespacePrefix = $"{CommunityToolkitNamespace}."; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,345 @@ | ||
using Microsoft.Maui.Controls.Platform; | ||
|
||
#if ANDROID | ||
using Android.Graphics.Drawables; | ||
using Android.Widget; | ||
using SkiaSharp.Views.Android; | ||
#endif | ||
|
||
#if IOS || MACCATALYST | ||
using SkiaSharp.Views.iOS; | ||
using UIKit; | ||
#endif | ||
|
||
namespace AuroraControls.Effects; | ||
|
||
/// <summary> | ||
/// Image processing effect. | ||
/// </summary> | ||
public class ImageProcessingEffect : RoutingEffect | ||
{ | ||
/// <summary> | ||
/// Gets the image processing effects. | ||
/// </summary> | ||
/// <value>The image processing effects.</value> | ||
public ImageProcessing.ImageProcessingCollection ImageProcessingEffects { get; private set; } | ||
= new ImageProcessing.ImageProcessingCollection(); | ||
|
||
/// <summary> | ||
/// The processor changed property. | ||
/// </summary> | ||
public static readonly BindablePropertyKey ProcessorChangedProperty = | ||
BindableProperty.CreateReadOnly("ProcessorChanged", typeof(object), typeof(ImageProcessingEffect), null); | ||
|
||
/// <summary> | ||
/// Initializes a new instance of the <see cref="ImageProcessingEffect"/> class. | ||
/// </summary> | ||
public ImageProcessingEffect() | ||
{ | ||
} | ||
} | ||
|
||
#if ANDROID | ||
public class ImagePlatformProcessingEffect : PlatformEffect | ||
{ | ||
private SKBitmap _image; | ||
|
||
private bool _processing; | ||
|
||
protected override async void OnAttached() | ||
{ | ||
var view = Control as ImageView; | ||
|
||
if (view == null) | ||
{ | ||
return; | ||
} | ||
|
||
if (this.Element?.Effects?.FirstOrDefault(e => e is ImageProcessingEffect) is ImageProcessingEffect effect) | ||
{ | ||
effect.ImageProcessingEffects.PropertyChanged += ImageProcessingEffects_PropertyChanged; | ||
} | ||
|
||
var drawable = view?.Drawable as BitmapDrawable; | ||
|
||
var androidBitmap = drawable?.Bitmap; | ||
|
||
_image = androidBitmap?.ToSKBitmap(); | ||
|
||
await ProcessImage(view, Element); | ||
} | ||
|
||
protected override void OnDetached() | ||
{ | ||
if (this.Element?.Effects?.FirstOrDefault(e => e is ImageProcessingEffect) is ImageProcessingEffect effect) | ||
{ | ||
effect.ImageProcessingEffects.PropertyChanged -= ImageProcessingEffects_PropertyChanged; | ||
} | ||
} | ||
|
||
private async void ImageProcessingEffects_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs args) | ||
{ | ||
var view = Control as ImageView; | ||
|
||
if (view == null) | ||
{ | ||
return; | ||
} | ||
|
||
if (args.PropertyName.Equals(ImageProcessingEffect.ProcessorChangedProperty.BindableProperty.PropertyName)) | ||
{ | ||
await ProcessImage(view, Element); | ||
} | ||
} | ||
|
||
protected override async void OnElementPropertyChanged(System.ComponentModel.PropertyChangedEventArgs args) | ||
{ | ||
var view = Control as ImageView; | ||
|
||
if (view == null) | ||
{ | ||
return; | ||
} | ||
|
||
if (args.PropertyName.Equals(Image.SourceProperty.PropertyName) || | ||
args.PropertyName.Equals(Image.IsLoadingProperty.PropertyName)) | ||
{ | ||
var drawable = view?.Drawable as BitmapDrawable; | ||
|
||
var androidBitmap = drawable?.Bitmap; | ||
|
||
_image = androidBitmap?.ToSKBitmap(); | ||
|
||
await ProcessImage(view, Element); | ||
} | ||
|
||
base.OnElementPropertyChanged(args); | ||
} | ||
|
||
private async Task ProcessImage(ImageView view, Element element) | ||
{ | ||
if (_processing) | ||
{ | ||
return; | ||
} | ||
|
||
_processing = true; | ||
|
||
try | ||
{ | ||
if (element == null) | ||
{ | ||
return; | ||
} | ||
|
||
if (_image == null) | ||
{ | ||
view.SetImageBitmap(null); | ||
return; | ||
} | ||
|
||
var effect = Element?.Effects?.FirstOrDefault(e => e is ImageProcessingEffect) as ImageProcessingEffect; | ||
|
||
var processingEffects = effect?.ImageProcessingEffects; | ||
|
||
if (!processingEffects?.Any() ?? true) | ||
{ | ||
return; | ||
} | ||
|
||
SKBitmap processingImage = null; | ||
|
||
try | ||
{ | ||
processingImage = _image.Copy(); | ||
|
||
await Task.Run(() => | ||
{ | ||
foreach (var processingEffect in processingEffects) | ||
{ | ||
var imageProcessor = ImageProcessing.RegisteredImageProcessors.GetProcessor(processingEffect.Key); | ||
|
||
if (imageProcessor != null) | ||
{ | ||
var tempImage = imageProcessor.ProcessImage(processingImage, processingEffect); | ||
|
||
if (tempImage != processingImage) | ||
{ | ||
processingImage?.Dispose(); | ||
processingImage = null; | ||
} | ||
|
||
processingImage = tempImage; | ||
} | ||
} | ||
}); | ||
|
||
using var native = processingImage.ToBitmap(); | ||
(view?.Drawable as BitmapDrawable)?.Bitmap?.Recycle(); | ||
view.SetImageBitmap(native); | ||
} | ||
finally | ||
{ | ||
processingImage?.Dispose(); | ||
processingImage = null; | ||
} | ||
} | ||
finally | ||
{ | ||
_processing = false; | ||
} | ||
} | ||
} | ||
#endif | ||
|
||
#if IOS || MACCATALYST | ||
public class ImagePlatformProcessingEffect : PlatformEffect | ||
{ | ||
private SKBitmap _image; | ||
|
||
private bool _processing; | ||
|
||
private long _lastProcessingTime; | ||
|
||
protected override async void OnAttached() | ||
{ | ||
var view = Control as UIImageView; | ||
|
||
if (view == null) | ||
{ | ||
return; | ||
} | ||
|
||
if (this.Element?.Effects?.FirstOrDefault(e => e is Effects.ImageProcessingEffect) is ImageProcessingEffect effect) | ||
{ | ||
effect.ImageProcessingEffects.PropertyChanged += ImageProcessingEffects_PropertyChanged; | ||
} | ||
|
||
_image = view?.Image?.ToSKBitmap(); | ||
await ProcessImage(view, Element); | ||
} | ||
|
||
protected override void OnDetached() | ||
{ | ||
if (this.Element?.Effects?.FirstOrDefault(e => e is ImageProcessingEffect) is ImageProcessingEffect effect) | ||
{ | ||
effect.ImageProcessingEffects.PropertyChanged -= ImageProcessingEffects_PropertyChanged; | ||
} | ||
} | ||
|
||
private async void ImageProcessingEffects_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs args) | ||
{ | ||
var view = Control as UIImageView; | ||
|
||
if (view == null) | ||
{ | ||
return; | ||
} | ||
|
||
if (args.PropertyName.Equals(ImageProcessingEffect.ProcessorChangedProperty.BindableProperty.PropertyName)) | ||
{ | ||
_lastProcessingTime = DateTime.UtcNow.Ticks; | ||
await ProcessImage(view, Element); | ||
} | ||
} | ||
|
||
protected override async void OnElementPropertyChanged(System.ComponentModel.PropertyChangedEventArgs args) | ||
{ | ||
var view = Control as UIImageView; | ||
|
||
if (view == null) | ||
{ | ||
return; | ||
} | ||
|
||
if (args.PropertyName.Equals(Image.SourceProperty.PropertyName) || | ||
args.PropertyName.Equals(Image.IsLoadingProperty.PropertyName)) | ||
{ | ||
_image = view?.Image?.ToSKBitmap(); | ||
|
||
await ProcessImage(view, Element); | ||
} | ||
|
||
base.OnElementPropertyChanged(args); | ||
} | ||
|
||
private async Task ProcessImage(UIImageView view, Element element) | ||
{ | ||
if (_processing) | ||
{ | ||
return; | ||
} | ||
|
||
_processing = true; | ||
var currentProcessingTime = _lastProcessingTime; | ||
|
||
try | ||
{ | ||
if (element == null) | ||
{ | ||
return; | ||
} | ||
|
||
if (_image == null) | ||
{ | ||
view.Image = null; | ||
return; | ||
} | ||
|
||
var effect = Element?.Effects?.FirstOrDefault(e => e is ImageProcessingEffect) as ImageProcessingEffect; | ||
|
||
var processingEffects = effect?.ImageProcessingEffects; | ||
|
||
if (!processingEffects?.Any() ?? true) | ||
{ | ||
return; | ||
} | ||
|
||
SKBitmap processingImage = null; | ||
try | ||
{ | ||
processingImage = _image.Copy(); | ||
|
||
await Task.Run(() => | ||
{ | ||
foreach (var processingEffect in processingEffects) | ||
{ | ||
var imageProcessor = ImageProcessing.RegisteredImageProcessors.GetProcessor(processingEffect.Key); | ||
|
||
if (imageProcessor != null) | ||
{ | ||
var tempImage = imageProcessor.ProcessImage(processingImage, processingEffect); | ||
|
||
if (tempImage != processingImage) | ||
{ | ||
processingImage?.Dispose(); | ||
processingImage = null; | ||
} | ||
|
||
processingImage = tempImage; | ||
} | ||
} | ||
}); | ||
|
||
view.Image?.Dispose(); | ||
|
||
using var native = processingImage?.ToUIImage(); | ||
view.Image = native; | ||
} | ||
finally | ||
{ | ||
processingImage?.Dispose(); | ||
processingImage = null; | ||
} | ||
} | ||
finally | ||
{ | ||
_processing = false; | ||
if (_lastProcessingTime > currentProcessingTime) | ||
{ | ||
await ProcessImage(view, element); | ||
} | ||
} | ||
} | ||
} | ||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
using System; | ||
using SkiaSharp; | ||
|
||
namespace AuroraControls.ImageProcessing; | ||
|
||
/// <summary> | ||
/// Blur effect for images. | ||
/// </summary> | ||
public class Blur : ImageProcessingBase, IImageProcessor | ||
{ | ||
/// <summary> | ||
/// Gets the key. | ||
/// </summary> | ||
/// <value>The "Blur" key.</value> | ||
public override string Key => nameof(Blur); | ||
|
||
/// <summary> | ||
/// Blur location options. | ||
/// </summary> | ||
public enum BlurLocation | ||
{ | ||
Full, | ||
Inside, | ||
} | ||
|
||
/// <summary> | ||
/// The blur amount property. | ||
/// </summary> | ||
public static BindableProperty BlurAmountProperty = | ||
BindableProperty.Create(nameof(BlurAmount), typeof(double), typeof(Blur), default(double)); | ||
|
||
/// <summary> | ||
/// Gets or sets the blur amount. | ||
/// </summary> | ||
/// <value>A double value representing the blur amount. Default value is default(double).</value> | ||
public double BlurAmount | ||
{ | ||
get { return (double)GetValue(BlurAmountProperty); } | ||
set { SetValue(BlurAmountProperty, value); } | ||
} | ||
|
||
/// <summary> | ||
/// The blurring location property. | ||
/// </summary> | ||
public static BindableProperty BlurringLocationProperty = | ||
BindableProperty.Create(nameof(BlurringLocation), typeof(object), typeof(BlurLocation), BlurLocation.Full); | ||
|
||
/// <summary> | ||
/// Gets or sets the blurring location. | ||
/// </summary> | ||
/// <value>Takes a BlurLocation enum. Default value is BlurLocation.Full.</value> | ||
public BlurLocation BlurringLocation | ||
{ | ||
get { return (BlurLocation)GetValue(BlurringLocationProperty); } | ||
set { SetValue(BlurringLocationProperty, value); } | ||
} | ||
|
||
/// <summary> | ||
/// Processes the image and apply blur. | ||
/// </summary> | ||
/// <returns>The an SKBitmap image.</returns> | ||
/// <param name="processingImage">Processing SKBitmap image.</param> | ||
/// <param name="imageProcessor">Image processor.</param> | ||
public SKBitmap ProcessImage(SKBitmap processingImage, ImageProcessingBase imageProcessor) | ||
{ | ||
if (imageProcessor is not AuroraControls.ImageProcessing.Blur) | ||
{ | ||
return processingImage; | ||
} | ||
|
||
var blur = imageProcessor as AuroraControls.ImageProcessing.Blur; | ||
|
||
var blurAmount = (float)blur.BlurAmount; | ||
var blurLocation = blur.BlurringLocation; | ||
|
||
var bitmap = new SKBitmap(processingImage.Info); | ||
|
||
using var canvas = new SKCanvas(bitmap); | ||
using var paint = new SKPaint(); | ||
paint.IsAntialias = true; | ||
paint.Style = SKPaintStyle.Fill; | ||
|
||
if (blurLocation == AuroraControls.ImageProcessing.Blur.BlurLocation.Inside) | ||
{ | ||
paint.BlendMode = SKBlendMode.SrcOver; | ||
} | ||
|
||
paint.ImageFilter = SKImageFilter.CreateBlur((int)blurAmount, (int)blurAmount); | ||
|
||
canvas.Clear(); | ||
|
||
if (blurLocation == AuroraControls.ImageProcessing.Blur.BlurLocation.Full) | ||
{ | ||
canvas.DrawBitmap(processingImage, processingImage.Info.Rect); | ||
} | ||
|
||
canvas.DrawBitmap(processingImage, processingImage.Info.Rect, paint); | ||
|
||
canvas.Flush(); | ||
|
||
return bitmap; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
using System; | ||
using SkiaSharp; | ||
|
||
namespace AuroraControls.ImageProcessing; | ||
|
||
/// <summary> | ||
/// Circular image mask. | ||
/// </summary> | ||
public class Circular : ImageProcessingBase, IImageProcessor | ||
{ | ||
/// <summary> | ||
/// Gets the key. | ||
/// </summary> | ||
/// <value>The "Circular" key.</value> | ||
public override string Key => nameof(Circular); | ||
|
||
/// <summary> | ||
/// Processes the image and apply circular mask. | ||
/// </summary> | ||
/// <returns>The an SKBitmap image.</returns> | ||
/// <param name="processingImage">Processing SKBitmap image.</param> | ||
/// <param name="imageProcessor">Image processor.</param> | ||
public SKBitmap ProcessImage(SKBitmap processingImage, ImageProcessingBase imageProcessor) | ||
{ | ||
if (imageProcessor is not AuroraControls.ImageProcessing.Circular) | ||
{ | ||
return processingImage; | ||
} | ||
|
||
using var canvas = new SKCanvas(processingImage); | ||
using var paint = new SKPaint(); | ||
paint.BlendMode = SKBlendMode.SrcIn; | ||
paint.IsAntialias = true; | ||
paint.Color = SKColors.Transparent; | ||
|
||
var size = Math.Min(processingImage.Info.Width, processingImage.Info.Height); | ||
|
||
var left = (processingImage.Info.Width - size) / 2f; | ||
var top = (processingImage.Info.Height - size) / 2f; | ||
var right = left + size; | ||
var bottom = top + size; | ||
|
||
var rect = new SKRect(left, top, right, bottom); | ||
|
||
using (var outer = new SKPath()) | ||
using (var cutout = new SKPath()) | ||
{ | ||
outer.AddRect(processingImage.Info.Rect); | ||
cutout.AddOval(rect); | ||
using (var finalPath = outer.Op(cutout, SKPathOp.Difference)) | ||
{ | ||
canvas.DrawPath(finalPath, paint); | ||
canvas.Flush(); | ||
} | ||
} | ||
|
||
return processingImage; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
namespace AuroraControls.ImageProcessing; | ||
|
||
/// <summary> | ||
/// Grayscale image effect. | ||
/// </summary> | ||
public class Grayscale : ImageProcessingBase, IImageProcessor | ||
{ | ||
/// <summary> | ||
/// Gets the key. | ||
/// </summary> | ||
/// <value>The "Grayscale" key.</value> | ||
public override string Key => nameof(Grayscale); | ||
|
||
/// <summary> | ||
/// Apply grayscale filter. | ||
/// </summary> | ||
/// <returns>The an SKBitmap image.</returns> | ||
/// <param name="processingImage">Processing SKBitmap image.</param> | ||
/// <param name="imageProcessor">Image processor.</param> | ||
public SKBitmap ProcessImage(SKBitmap processingImage, ImageProcessingBase imageProcessor) | ||
{ | ||
var bitmap = new SKBitmap(processingImage.Info); | ||
|
||
using var canvas = new SKCanvas(bitmap); | ||
using var paint = new SKPaint(); | ||
|
||
paint.ColorFilter = SKColorFilter.CreateColorMatrix( | ||
[ | ||
0.21f, 0.72f, 0.07f, 0.0f, 0.0f, | ||
0.21f, 0.72f, 0.07f, 0.0f, 0.0f, | ||
0.21f, 0.72f, 0.07f, 0.0f, 0.0f, | ||
0.0f, 0.0f, 0.0f, 1.0f, 0.0f | ||
]); | ||
|
||
canvas.Clear(); | ||
canvas.DrawBitmap(processingImage, processingImage.Info.Rect, paint); | ||
canvas.Flush(); | ||
|
||
return bitmap; | ||
} | ||
} |
18 changes: 18 additions & 0 deletions
18
AuroraControlsMaui/ImageProcessing/IImageProcessor.core.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
using System; | ||
using SkiaSharp; | ||
|
||
namespace AuroraControls.ImageProcessing; | ||
|
||
/// <summary> | ||
/// Image processor interface. | ||
/// </summary> | ||
public interface IImageProcessor | ||
{ | ||
/// <summary> | ||
/// Processes the image. | ||
/// </summary> | ||
/// <returns>The image.</returns> | ||
/// <param name="processingImage">Processing image.</param> | ||
/// <param name="imageProcessor">Image processor.</param> | ||
SKBitmap ProcessImage(SKBitmap processingImage, ImageProcessingBase imageProcessor); | ||
} |
13 changes: 13 additions & 0 deletions
13
AuroraControlsMaui/ImageProcessing/ImageProcessingBase.core.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
namespace AuroraControls.ImageProcessing; | ||
|
||
/// <summary> | ||
/// Image processing base class. | ||
/// </summary> | ||
public abstract class ImageProcessingBase : BindableObject | ||
{ | ||
/// <summary> | ||
/// Gets the key. | ||
/// </summary> | ||
/// <value>The key for processor.</value> | ||
public abstract string Key { get; } | ||
} |
238 changes: 238 additions & 0 deletions
238
AuroraControlsMaui/ImageProcessing/ImageProcessingCollection.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,238 @@ | ||
using System; | ||
using System.Collections; | ||
using System.Collections.Generic; | ||
using System.Collections.Specialized; | ||
|
||
namespace AuroraControls.ImageProcessing; | ||
|
||
/// <summary> | ||
/// Image processing collection. | ||
/// </summary> | ||
public class ImageProcessingCollection : BindableObject, IList<ImageProcessingBase>, INotifyCollectionChanged | ||
{ | ||
/// <summary> | ||
/// The collection of image processors. | ||
/// </summary> | ||
private readonly List<ImageProcessingBase> _items = new List<ImageProcessingBase>(); | ||
|
||
/// <summary> | ||
/// Occurs when collection changed. | ||
/// </summary> | ||
public event NotifyCollectionChangedEventHandler CollectionChanged; | ||
|
||
/// <summary> | ||
/// Finalizes an instance of the <see cref="ImageProcessingCollection"/> class. | ||
/// Releases unmanaged resources and performs other cleanup operations before the | ||
/// <see cref="T:AuroraControls.ImageProcessing.ImageProcessingCollection"/> is reclaimed by garbage collection. | ||
/// </summary> | ||
~ImageProcessingCollection() | ||
{ | ||
if (this._items == null) | ||
{ | ||
return; | ||
} | ||
|
||
foreach (var item in this._items) | ||
{ | ||
item.PropertyChanged -= this.HandlePropertyChangedEventHandler; | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Gets the index in the collection of a given processor. | ||
/// </summary> | ||
/// <returns>The index of the processor.</returns> | ||
/// <param name="item">Item.</param> | ||
public int IndexOf(ImageProcessingBase item) | ||
{ | ||
return this._items.IndexOf(item); | ||
} | ||
|
||
/// <summary> | ||
/// Inserts the processor at the specified index. | ||
/// </summary> | ||
/// <param name="index">Index to insert at.</param> | ||
/// <param name="item">Image processor.</param> | ||
public void Insert(int index, ImageProcessingBase item) | ||
{ | ||
this._items.Insert(index, item); | ||
|
||
item.PropertyChanged -= HandlePropertyChangedEventHandler; | ||
item.PropertyChanged += HandlePropertyChangedEventHandler; | ||
|
||
SetValue(Effects.ImageProcessingEffect.ProcessorChangedProperty, item); | ||
CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index)); | ||
} | ||
|
||
/// <summary> | ||
/// Removes processor at index. | ||
/// </summary> | ||
/// <param name="index">Index.</param> | ||
public void RemoveAt(int index) | ||
{ | ||
var oldItem = this[index]; | ||
this._items.RemoveAt(index); | ||
oldItem.PropertyChanged -= HandlePropertyChangedEventHandler; | ||
SetValue(Effects.ImageProcessingEffect.ProcessorChangedProperty, oldItem); | ||
CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, oldItem, index)); | ||
} | ||
|
||
/// <summary> | ||
/// Gets or sets the <see cref="T:AuroraControls.ImageProcessing.ImageProcessingCollection"/> at the specified index. | ||
/// </summary> | ||
/// <param name="index">Index.</param> | ||
public ImageProcessingBase this[int index] | ||
{ | ||
get | ||
{ | ||
return this._items[index]; | ||
} | ||
|
||
set | ||
{ | ||
var oldItem = this[index]; | ||
|
||
var imageProcessingBase = (ImageProcessingBase)value; | ||
|
||
this._items[index] = imageProcessingBase; | ||
|
||
imageProcessingBase.PropertyChanged -= HandlePropertyChangedEventHandler; | ||
imageProcessingBase.PropertyChanged += HandlePropertyChangedEventHandler; | ||
|
||
oldItem.PropertyChanged -= HandlePropertyChangedEventHandler; | ||
SetValue(Effects.ImageProcessingEffect.ProcessorChangedProperty, oldItem); | ||
CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, value, oldItem)); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Add the specified processor to the list. | ||
/// </summary> | ||
/// <param name="item">Image processor.</param> | ||
public void Add(ImageProcessingBase item) | ||
{ | ||
this._items.Add(item); | ||
|
||
item.PropertyChanged -= HandlePropertyChangedEventHandler; | ||
item.PropertyChanged += HandlePropertyChangedEventHandler; | ||
|
||
SetValue(Effects.ImageProcessingEffect.ProcessorChangedProperty, item); | ||
CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, Count - 1)); | ||
} | ||
|
||
/// <summary> | ||
/// Clears the list. | ||
/// </summary> | ||
public void Clear() | ||
{ | ||
foreach (var item in this._items) | ||
{ | ||
item.PropertyChanged -= HandlePropertyChangedEventHandler; | ||
} | ||
|
||
this._items.Clear(); | ||
|
||
SetValue(Effects.ImageProcessingEffect.ProcessorChangedProperty, null); | ||
CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); | ||
} | ||
|
||
/// <summary> | ||
/// Checks if the processor passed in already exists in the list. | ||
/// </summary> | ||
/// <returns>Returns <c>true</c> if the item exists in the list, otherwise <c>false</c>.</returns> | ||
/// <param name="item">Item.</param> | ||
public bool Contains(ImageProcessingBase item) | ||
{ | ||
return this._items.Contains(item); | ||
} | ||
|
||
/// <summary> | ||
/// Copies items to list. | ||
/// </summary> | ||
/// <param name="array">Array of processors.</param> | ||
/// <param name="arrayIndex">Array index.</param> | ||
public void CopyTo(ImageProcessingBase[] array, int arrayIndex) | ||
{ | ||
foreach (var item in array) | ||
{ | ||
item.PropertyChanged -= HandlePropertyChangedEventHandler; | ||
item.PropertyChanged += HandlePropertyChangedEventHandler; | ||
} | ||
|
||
this._items.CopyTo(array, arrayIndex); | ||
} | ||
|
||
/// <summary> | ||
/// Remove the specified item. | ||
/// </summary> | ||
/// <returns>The remove.</returns> | ||
/// <param name="item">Item.</param> | ||
public bool Remove(ImageProcessingBase item) | ||
{ | ||
var oldIndex = IndexOf(item); | ||
|
||
if (!this._items.Remove(item)) | ||
{ | ||
return false; | ||
} | ||
|
||
item.PropertyChanged -= this.HandlePropertyChangedEventHandler; | ||
this.SetValue(Effects.ImageProcessingEffect.ProcessorChangedProperty, item); | ||
this.CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, oldIndex)); | ||
return true; | ||
} | ||
|
||
/// <summary> | ||
/// Gets the count. | ||
/// </summary> | ||
/// <value>The count.</value> | ||
public int Count | ||
{ | ||
get | ||
{ | ||
return this._items.Count; | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Gets a value indicating whether this <see cref="T:AuroraControls.ImageProcessing.ImageProcessingCollection"/> is | ||
/// read only. | ||
/// </summary> | ||
/// <value><c>true</c> if is read only; otherwise, <c>false</c>.</value> | ||
public bool IsReadOnly | ||
{ | ||
get | ||
{ | ||
return false; | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Gets the enumerator. | ||
/// </summary> | ||
/// <returns>The enumerator.</returns> | ||
public IEnumerator<ImageProcessingBase> GetEnumerator() | ||
{ | ||
return this._items.GetEnumerator(); | ||
} | ||
|
||
/// <summary> | ||
/// System.s the collections. IE numerable. get enumerator. | ||
/// </summary> | ||
/// <returns>The collections. IE numerable. get enumerator.</returns> | ||
IEnumerator IEnumerable.GetEnumerator() | ||
{ | ||
return GetEnumerator(); | ||
} | ||
|
||
/// <summary> | ||
/// Handles the property changed event handler. | ||
/// </summary> | ||
/// <param name="sender">Sender.</param> | ||
/// <param name="e"><c>PropertyChangedEventArgs</c> provides the <see cref="T:PropertyChangedEventArgs.PropertyName"/> property to get the name of the property that changed.</param> | ||
private void HandlePropertyChangedEventHandler(object sender, System.ComponentModel.PropertyChangedEventArgs e) | ||
{ | ||
ClearValue(Effects.ImageProcessingEffect.ProcessorChangedProperty); | ||
SetValue(Effects.ImageProcessingEffect.ProcessorChangedProperty, sender); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
using System; | ||
using SkiaSharp; | ||
|
||
namespace AuroraControls.ImageProcessing; | ||
|
||
/// <summary> | ||
/// Invert image processing effect. | ||
/// </summary> | ||
public class Invert : ImageProcessingBase, IImageProcessor | ||
{ | ||
/// <summary> | ||
/// Gets the key. | ||
/// </summary> | ||
/// <value>The "Invert" key.</value> | ||
public override string Key => nameof(Invert); | ||
|
||
/// <summary> | ||
/// Processes the inversion image. | ||
/// </summary> | ||
/// <returns>An SKBitmap image.</returns> | ||
/// <param name="processingImage">Image to process.</param> | ||
/// <param name="imageProcessor">Image processor.</param> | ||
public SKBitmap ProcessImage(SKBitmap processingImage, ImageProcessingBase imageProcessor) | ||
{ | ||
using (var canvas = new SKCanvas(processingImage)) | ||
using (var paint = new SKPaint()) | ||
{ | ||
paint.IsAntialias = true; | ||
paint.Style = SKPaintStyle.Fill; | ||
paint.ColorFilter = SKColorFilter.CreateColorMatrix( | ||
[ | ||
-1f, 0f, 0f, 0f, 255f, | ||
0f, -1f, 0f, 0f, 255f, | ||
0f, 0f, -1f, 0f, 255f, | ||
0f, 0f, 0f, 1f, 0f | ||
]); | ||
|
||
canvas.Clear(); | ||
canvas.DrawBitmap(processingImage, processingImage.Info.Rect, paint); | ||
canvas.Flush(); | ||
} | ||
|
||
return processingImage; | ||
} | ||
} |
47 changes: 47 additions & 0 deletions
47
AuroraControlsMaui/ImageProcessing/RegisteredImageProcessors.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
|
||
namespace AuroraControls.ImageProcessing; | ||
|
||
/// <summary> | ||
/// Registered image processors. | ||
/// </summary> | ||
public static class RegisteredImageProcessors | ||
{ | ||
/// <summary> | ||
/// A static dictionary of registered Image processors and their adjacent keys. | ||
/// </summary> | ||
private static readonly Dictionary<string, IImageProcessor> _imageProcessors = new Dictionary<string, IImageProcessor>(); | ||
|
||
static RegisteredImageProcessors() | ||
{ | ||
SetProcessor(nameof(Grayscale), new Grayscale()); | ||
SetProcessor(nameof(Sepia), new Sepia()); | ||
SetProcessor(nameof(Invert), new Invert()); | ||
SetProcessor(nameof(Grayscale), new Grayscale()); | ||
SetProcessor(nameof(Blur), new Blur()); | ||
SetProcessor(nameof(Circular), new Circular()); | ||
SetProcessor(nameof(Scale), new Scale()); | ||
SetProcessor(nameof(ResizeImage), new ResizeImage()); | ||
} | ||
|
||
/// <summary> | ||
/// Gets the processor by key. | ||
/// </summary> | ||
/// <returns>result as an IImageProcessor.</returns> | ||
/// <param name="key">Key/name of processor.</param> | ||
public static IImageProcessor GetProcessor(string key) | ||
{ | ||
return _imageProcessors.ContainsKey(key) ? _imageProcessors[key] : null; | ||
} | ||
|
||
/// <summary> | ||
/// Sets the processor. | ||
/// </summary> | ||
/// <param name="key">Key.</param> | ||
/// <param name="imageProcessor">Image processor.</param> | ||
public static void SetProcessor(string key, IImageProcessor imageProcessor) | ||
{ | ||
_imageProcessors[key] = imageProcessor; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
using System; | ||
using System.IO; | ||
using SkiaSharp; | ||
|
||
namespace AuroraControls.ImageProcessing; | ||
|
||
/// <summary> | ||
/// Resize image processor. | ||
/// </summary> | ||
public class ResizeImage : ImageProcessingBase, IImageProcessor | ||
{ | ||
/// <summary> | ||
/// Gets the key. | ||
/// </summary> | ||
/// <value>The "ResizeImage" key.</value> | ||
public override string Key => nameof(ResizeImage); | ||
|
||
/// <summary> | ||
/// The max height property. | ||
/// </summary> | ||
public static BindableProperty MaxHeightProperty = | ||
BindableProperty.Create(nameof(MaxHeight), typeof(int), typeof(ResizeImage), 100); | ||
|
||
/// <summary> | ||
/// Gets or sets the height of the max. | ||
/// </summary> | ||
/// <value>int value representing the desired max height.</value> | ||
public int MaxHeight | ||
{ | ||
get { return (int)GetValue(MaxHeightProperty); } | ||
set { SetValue(MaxHeightProperty, value); } | ||
} | ||
|
||
/// <summary> | ||
/// The max width property. | ||
/// </summary> | ||
public static BindableProperty MaxWidthProperty = | ||
BindableProperty.Create(nameof(MaxWidth), typeof(int), typeof(ResizeImage), 100); | ||
|
||
/// <summary> | ||
/// Gets or sets the width of the max. | ||
/// </summary> | ||
/// <value>int value processing the desired max width.</value> | ||
public int MaxWidth | ||
{ | ||
get { return (int)GetValue(MaxWidthProperty); } | ||
set { SetValue(MaxWidthProperty, value); } | ||
} | ||
|
||
/// <summary> | ||
/// Processes the image. | ||
/// </summary> | ||
/// <returns>The SKBitmap image.</returns> | ||
/// <param name="processingImage">Processing image.</param> | ||
/// <param name="imageProcessor">Image processor.</param> | ||
public SKBitmap ProcessImage(SKBitmap processingImage, ImageProcessingBase imageProcessor) | ||
{ | ||
if (imageProcessor is AuroraControls.ImageProcessing.ResizeImage) | ||
{ | ||
var resizeImageProcessor = imageProcessor as AuroraControls.ImageProcessing.ResizeImage; | ||
var maxHeight = resizeImageProcessor.MaxHeight; | ||
var maxWidth = resizeImageProcessor.MaxWidth; | ||
|
||
var info = processingImage.Info; | ||
|
||
var maxSize = Math.Max(maxHeight, maxWidth); | ||
|
||
var supportedScale = | ||
info.Height > info.Width | ||
? (float)maxHeight / info.Height | ||
: (float)maxWidth / info.Width; | ||
|
||
var scaledWidth = (int)(info.Width * supportedScale); | ||
var scaledHeight = (int)(info.Height * supportedScale); | ||
|
||
var newImageInfo = new SKImageInfo(scaledWidth, scaledHeight, processingImage.Info.ColorType); | ||
|
||
return processingImage.Resize(newImageInfo, SKFilterQuality.High); | ||
} | ||
|
||
return processingImage; | ||
} | ||
|
||
/// <summary> | ||
/// Resizes the image for export as stream. | ||
/// </summary> | ||
/// <returns>The image exported as a stream.</returns> | ||
/// <param name="imageBytes">Image bytes.</param> | ||
/// <param name="maxHeight">Max height.</param> | ||
/// <param name="maxWidth">Max width.</param> | ||
/// <param name="quality">Quality.</param> | ||
/// <param name="imageFormat">Image format; default is PNG.</param> | ||
/// <param name="streamDisposesData">If set to <c>true</c> stream disposes data.</param> | ||
public static Stream ResizeImageForExportAsStream(byte[] imageBytes, int maxHeight = 100, int maxWidth = 100, int quality = 80, SKEncodedImageFormat imageFormat = SKEncodedImageFormat.Png, bool streamDisposesData = true) | ||
{ | ||
return ResizeImageInternal(imageBytes, maxHeight, maxWidth, quality, imageFormat).AsStream(streamDisposesData); | ||
} | ||
|
||
/// <summary> | ||
/// Resizes the image for export as byte[]. | ||
/// </summary> | ||
/// <returns>The image exported as byte[].</returns> | ||
/// <param name="imageBytes">Image bytes.</param> | ||
/// <param name="maxHeight">Max height.</param> | ||
/// <param name="maxWidth">Max width.</param> | ||
/// <param name="quality">Quality.</param> | ||
/// <param name="imageFormat">Image format.</param> | ||
public static byte[] ResizeImageForExport(byte[] imageBytes, int maxHeight = 100, int maxWidth = 100, int quality = 80, SKEncodedImageFormat imageFormat = SKEncodedImageFormat.Png) | ||
{ | ||
return ResizeImageInternal(imageBytes, maxHeight, maxWidth, quality, imageFormat).ToArray(); | ||
} | ||
|
||
/// <summary> | ||
/// Internal method used for resizeing the image. | ||
/// </summary> | ||
/// <returns>The image as SKData.</returns> | ||
/// <param name="imageBytes">Image bytes.</param> | ||
/// <param name="maxHeight">Max height.</param> | ||
/// <param name="maxWidth">Max width.</param> | ||
/// <param name="quality">Quality.</param> | ||
/// <param name="imageFormat">Image format.</param> | ||
private static SKData ResizeImageInternal(byte[] imageBytes, int maxHeight = 100, int maxWidth = 100, int quality = 80, SKEncodedImageFormat imageFormat = SKEncodedImageFormat.Png) | ||
{ | ||
using (SKData data = SKData.CreateCopy(imageBytes)) | ||
{ | ||
using (SKCodec codec = SKCodec.Create(data)) | ||
{ | ||
var info = codec.Info; | ||
|
||
var maxSize = Math.Max(maxHeight, maxWidth); | ||
|
||
var supportedScale = | ||
info.Height > info.Width | ||
? (float)maxHeight / info.Height | ||
: (float)maxWidth / info.Width; | ||
|
||
var scaledWidth = (int)(info.Width * supportedScale); | ||
var scaledHeight = (int)(info.Height * supportedScale); | ||
|
||
// decode the bitmap at the nearest size | ||
var nearest = new SKImageInfo(scaledWidth, scaledHeight); | ||
|
||
SKBitmap bmp = null; | ||
try | ||
{ | ||
bmp = SKBitmap.Decode(codec); | ||
|
||
SKImageInfo desired = new SKImageInfo(scaledWidth, scaledHeight); | ||
bmp = bmp.Resize(desired, SKFilterQuality.High); | ||
|
||
using (var image = SKImage.FromBitmap(bmp)) | ||
{ | ||
return image.Encode(imageFormat, quality); | ||
} | ||
} | ||
finally | ||
{ | ||
bmp?.Dispose(); | ||
bmp = null; | ||
} | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
using System; | ||
using System.IO; | ||
using SkiaSharp; | ||
|
||
namespace AuroraControls.ImageProcessing; | ||
|
||
public class Rotate : ImageProcessingBase, IImageProcessor | ||
{ | ||
public enum RotationDegrees | ||
{ | ||
Zero = 0, | ||
Ninety = 90, | ||
OneHundredAndEighty = 180, | ||
TwoHundredAndSeventy = 270, | ||
NegativeNinety = -90, | ||
NegativeOneHundredAndEighty = -180, | ||
NegativeTwoHundredAndSeventy = -270, | ||
} | ||
|
||
/// <summary> | ||
/// Gets the key. | ||
/// </summary> | ||
/// <value>The "ResizeImage" key.</value> | ||
public override string Key => nameof(Rotate); | ||
|
||
public static BindableProperty RotationAmountProperty = | ||
BindableProperty.Create(nameof(RotationDegrees), typeof(object), typeof(Rotate), RotationDegrees.Zero); | ||
|
||
public RotationDegrees RotationAmount | ||
{ | ||
get => (RotationDegrees)GetValue(RotationAmountProperty); | ||
set => SetValue(RotationAmountProperty, value); | ||
} | ||
|
||
/// <summary> | ||
/// Processes the image. | ||
/// </summary> | ||
/// <returns>The SKBitmap image.</returns> | ||
/// <param name="processingImage">Processing image.</param> | ||
/// <param name="imageProcessor">Image processor.</param> | ||
public SKBitmap ProcessImage(SKBitmap processingImage, ImageProcessingBase imageProcessor) | ||
{ | ||
if (imageProcessor is AuroraControls.ImageProcessing.Rotate processor) | ||
{ | ||
var width = 0; | ||
var height = 0; | ||
|
||
switch (processor.RotationAmount) | ||
{ | ||
case RotationDegrees.Ninety: | ||
case RotationDegrees.TwoHundredAndSeventy: | ||
case RotationDegrees.NegativeNinety: | ||
case RotationDegrees.NegativeTwoHundredAndSeventy: | ||
width = processingImage.Height; | ||
height = processingImage.Width; | ||
break; | ||
default: | ||
height = processingImage.Height; | ||
width = processingImage.Width; | ||
break; | ||
} | ||
|
||
var bitmap = new SKBitmap(width, height, processingImage.AlphaType == SKAlphaType.Opaque); | ||
using (var canvas = new SKCanvas(bitmap)) | ||
{ | ||
canvas.Translate(width, 0); | ||
canvas.RotateDegrees((float)processor.RotationAmount); | ||
canvas.DrawBitmap(processingImage, 0, 0); | ||
canvas.Flush(); | ||
} | ||
|
||
return bitmap; | ||
} | ||
|
||
return processingImage; | ||
} | ||
|
||
/// <summary> | ||
/// Resizes the image for export as stream. | ||
/// </summary> | ||
/// <returns>The image exported as a stream.</returns> | ||
/// <param name="imageBytes">Image bytes.</param> | ||
/// <param name="maxHeight">Max height.</param> | ||
/// <param name="maxWidth">Max width.</param> | ||
/// <param name="quality">Quality.</param> | ||
/// <param name="imageFormat">Image format; default is PNG.</param> | ||
/// <param name="streamDisposesData">If set to <c>true</c> stream disposes data.</param> | ||
public static Stream ResizeImageForExportAsStream(byte[] imageBytes, int maxHeight = 100, int maxWidth = 100, int quality = 80, SKEncodedImageFormat imageFormat = SKEncodedImageFormat.Png, bool streamDisposesData = true) | ||
{ | ||
return ResizeImageInternal(imageBytes, maxHeight, maxWidth, quality, imageFormat).AsStream(streamDisposesData); | ||
} | ||
|
||
/// <summary> | ||
/// Resizes the image for export as byte[]. | ||
/// </summary> | ||
/// <returns>The image exported as byte[].</returns> | ||
/// <param name="imageBytes">Image bytes.</param> | ||
/// <param name="maxHeight">Max height.</param> | ||
/// <param name="maxWidth">Max width.</param> | ||
/// <param name="quality">Quality.</param> | ||
/// <param name="imageFormat">Image format.</param> | ||
public static byte[] ResizeImageForExport(byte[] imageBytes, int maxHeight = 100, int maxWidth = 100, int quality = 80, SKEncodedImageFormat imageFormat = SKEncodedImageFormat.Png) | ||
{ | ||
return ResizeImageInternal(imageBytes, maxHeight, maxWidth, quality, imageFormat).ToArray(); | ||
} | ||
|
||
/// <summary> | ||
/// Internal method used for resizeing the image. | ||
/// </summary> | ||
/// <returns>The image as SKData.</returns> | ||
/// <param name="imageBytes">Image bytes.</param> | ||
/// <param name="maxHeight">Max height.</param> | ||
/// <param name="maxWidth">Max width.</param> | ||
/// <param name="quality">Quality.</param> | ||
/// <param name="imageFormat">Image format.</param> | ||
private static SKData ResizeImageInternal(byte[] imageBytes, int maxHeight = 100, int maxWidth = 100, int quality = 80, SKEncodedImageFormat imageFormat = SKEncodedImageFormat.Png) | ||
{ | ||
using (SKData data = SKData.CreateCopy(imageBytes)) | ||
{ | ||
using (SKCodec codec = SKCodec.Create(data)) | ||
{ | ||
var info = codec.Info; | ||
|
||
var maxSize = Math.Max(maxHeight, maxWidth); | ||
|
||
var supportedScale = | ||
info.Height > info.Width | ||
? (float)maxHeight / info.Height | ||
: (float)maxWidth / info.Width; | ||
|
||
var scaledWidth = (int)(info.Width * supportedScale); | ||
var scaledHeight = (int)(info.Height * supportedScale); | ||
|
||
// decode the bitmap at the nearest size | ||
var nearest = new SKImageInfo(scaledWidth, scaledHeight); | ||
|
||
SKBitmap bmp = null; | ||
try | ||
{ | ||
bmp = SKBitmap.Decode(codec); | ||
|
||
SKImageInfo desired = new SKImageInfo(scaledWidth, scaledHeight); | ||
bmp = bmp.Resize(desired, SKFilterQuality.High); | ||
|
||
using (var image = SKImage.FromBitmap(bmp)) | ||
{ | ||
return image.Encode(imageFormat, quality); | ||
} | ||
} | ||
finally | ||
{ | ||
bmp?.Dispose(); | ||
bmp = null; | ||
} | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
using System; | ||
using SkiaSharp; | ||
|
||
namespace AuroraControls.ImageProcessing; | ||
|
||
/// <summary> | ||
/// Scale image. | ||
/// </summary> | ||
public class Scale : ImageProcessingBase, IImageProcessor | ||
{ | ||
/// <summary> | ||
/// Gets the key. | ||
/// </summary> | ||
/// <value>The "Scale" key.</value> | ||
public override string Key => nameof(Scale); | ||
|
||
/// <summary> | ||
/// The scale amount property. | ||
/// </summary> | ||
public static BindableProperty ScaleAmountProperty = | ||
BindableProperty.Create(nameof(ScaleAmount), typeof(double), typeof(Scale), 1d); | ||
|
||
/// <summary> | ||
/// Gets or sets the scale amount. | ||
/// </summary> | ||
/// <value>Expects a double. Default value is 1d.</value> | ||
public double ScaleAmount | ||
{ | ||
get { return (double)GetValue(ScaleAmountProperty); } | ||
set { SetValue(ScaleAmountProperty, value); } | ||
} | ||
|
||
/// <summary> | ||
/// Apply Scale process. | ||
/// </summary> | ||
/// <returns>The an SKBitmap image.</returns> | ||
/// <param name="processingImage">Processing SKBitmap image.</param> | ||
/// <param name="imageProcessor">Image processor.</param> | ||
public SKBitmap ProcessImage(SKBitmap processingImage, ImageProcessingBase imageProcessor) | ||
{ | ||
if (imageProcessor is AuroraControls.ImageProcessing.Scale) | ||
{ | ||
var scaleProcessor = imageProcessor as AuroraControls.ImageProcessing.Scale; | ||
|
||
var scaleAmount = (float)scaleProcessor.ScaleAmount; | ||
|
||
var info = | ||
new SKImageInfo( | ||
(int)(processingImage.Info.Rect.Width * scaleAmount), | ||
(int)(processingImage.Info.Rect.Height * scaleAmount), | ||
processingImage.Info.ColorType); | ||
|
||
return processingImage.Resize(info, SKFilterQuality.High); | ||
} | ||
|
||
return processingImage; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
using System; | ||
using SkiaSharp; | ||
|
||
namespace AuroraControls.ImageProcessing; | ||
|
||
/// <summary> | ||
/// Sepia effect. | ||
/// </summary> | ||
public class Sepia : ImageProcessingBase, IImageProcessor | ||
{ | ||
/// <summary> | ||
/// Gets the key. | ||
/// </summary> | ||
/// <value>The "Sepia" key.</value> | ||
public override string Key => nameof(Sepia); | ||
|
||
/// <summary> | ||
/// Apply Scale process. | ||
/// </summary> | ||
/// <returns>The an SKBitmap image.</returns> | ||
/// <param name="processingImage">Processing SKBitmap image.</param> | ||
/// <param name="imageProcessor">Image processor.</param> | ||
public SKBitmap ProcessImage(SKBitmap processingImage, ImageProcessingBase imageProcessor) | ||
{ | ||
var bitmap = new SKBitmap(processingImage.Info); | ||
|
||
using (var canvas = new SKCanvas(bitmap)) | ||
using (var paint = new SKPaint()) | ||
{ | ||
paint.ColorFilter = SKColorFilter.CreateColorMatrix( | ||
[ | ||
0.393f, 0.769f, 0.189f, 0.0f, 0.0f, | ||
0.349f, 0.686f, 0.168f, 0.0f, 0.0f, | ||
0.272f, 0.534f, 0.131f, 0.0f, 0.0f, | ||
0.0f, 0.0f, 0.0f, 1.0f, 0.0f | ||
]); | ||
|
||
canvas.Clear(); | ||
|
||
canvas.DrawBitmap(processingImage, processingImage.Info.Rect, paint); | ||
|
||
canvas.Flush(); | ||
} | ||
|
||
return bitmap; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
namespace AuroraControls.ImageProcessing; | ||
|
||
/// <summary> | ||
/// Watermark effect for images. | ||
/// </summary> | ||
public class Watermark : ImageProcessingBase, IImageProcessor | ||
{ | ||
public enum WatermarkLocation | ||
{ | ||
Start, | ||
Center, | ||
End, | ||
} | ||
|
||
public static BindableProperty BackgroundColorProperty = | ||
BindableProperty.Create(nameof(BackgroundColor), typeof(Color), typeof(Watermark)); | ||
|
||
public static BindableProperty BackgroundCornerRadiusProperty = | ||
BindableProperty.Create(nameof(BackgroundCornerRadius), typeof(double), typeof(Watermark), 4d); | ||
|
||
public static BindableProperty FontSizeProperty = | ||
BindableProperty.Create(nameof(FontSize), typeof(double), typeof(Watermark), 24d); | ||
|
||
public static BindableProperty ForegroundColorProperty = | ||
BindableProperty.Create(nameof(ForegroundColor), typeof(Color), typeof(Watermark), Colors.White.MultiplyAlpha(.5f)); | ||
|
||
public static BindableProperty HorizontalWatermarkLocationProperty = | ||
BindableProperty.Create(nameof(HorizontalWatermarkLocation), typeof(WatermarkLocation), typeof(Watermark), WatermarkLocation.End); | ||
|
||
public static BindableProperty TypefaceProperty = | ||
BindableProperty.Create(nameof(Typeface), typeof(SKTypeface), typeof(Watermark)); | ||
|
||
public static BindableProperty VerticalWatermarkLocationProperty = | ||
BindableProperty.Create(nameof(VerticalWatermarkLocation), typeof(WatermarkLocation), typeof(Watermark), WatermarkLocation.End); | ||
|
||
public static BindableProperty WatermarkPaddingProperty = | ||
BindableProperty.Create(nameof(WatermarkPadding), typeof(double), typeof(Watermark), 8d); | ||
|
||
public static BindableProperty WatermarkTextProperty = | ||
BindableProperty.Create(nameof(WatermarkText), typeof(string), typeof(Watermark)); | ||
|
||
/// <summary> | ||
/// Gets the key. | ||
/// </summary> | ||
/// <value>The "Watermark" key.</value> | ||
public override string Key => nameof(Watermark); | ||
|
||
public WatermarkLocation HorizontalWatermarkLocation | ||
{ | ||
get => (WatermarkLocation)this.GetValue(HorizontalWatermarkLocationProperty); | ||
set => this.SetValue(HorizontalWatermarkLocationProperty, value); | ||
} | ||
|
||
public WatermarkLocation VerticalWatermarkLocation | ||
{ | ||
get => (WatermarkLocation)this.GetValue(VerticalWatermarkLocationProperty); | ||
set => this.SetValue(VerticalWatermarkLocationProperty, value); | ||
} | ||
|
||
public double WatermarkPadding | ||
{ | ||
get => (double)this.GetValue(WatermarkPaddingProperty); | ||
set => this.SetValue(WatermarkPaddingProperty, value); | ||
} | ||
|
||
public string WatermarkText | ||
{ | ||
get => (string)this.GetValue(WatermarkTextProperty); | ||
set => this.SetValue(WatermarkTextProperty, value); | ||
} | ||
|
||
public double FontSize | ||
{ | ||
get => (double)this.GetValue(FontSizeProperty); | ||
set => this.SetValue(FontSizeProperty, value); | ||
} | ||
|
||
public SKTypeface Typeface | ||
{ | ||
get => (SKTypeface)this.GetValue(TypefaceProperty); | ||
set => this.SetValue(TypefaceProperty, value); | ||
} | ||
|
||
public Color ForegroundColor | ||
{ | ||
get => (Color)this.GetValue(ForegroundColorProperty); | ||
set => this.SetValue(ForegroundColorProperty, value); | ||
} | ||
|
||
public Color BackgroundColor | ||
{ | ||
get => (Color)this.GetValue(BackgroundColorProperty); | ||
set => this.SetValue(BackgroundColorProperty, value); | ||
} | ||
|
||
public double BackgroundCornerRadius | ||
{ | ||
get => (double)this.GetValue(BackgroundCornerRadiusProperty); | ||
set => this.SetValue(BackgroundCornerRadiusProperty, value); | ||
} | ||
|
||
public SKBitmap ProcessImage(SKBitmap processingImage, ImageProcessingBase imageProcessor) | ||
{ | ||
string text = this.WatermarkText; | ||
|
||
if (processingImage == null || string.IsNullOrEmpty(text)) | ||
{ | ||
return processingImage; | ||
} | ||
|
||
if (imageProcessor is Watermark watermark) | ||
{ | ||
using (SKCanvas canvas = new(processingImage)) | ||
using (SKPaint paint = new()) | ||
{ | ||
paint.IsAntialias = true; | ||
paint.Style = SKPaintStyle.Fill; | ||
paint.TextSize = (float)this.FontSize; | ||
paint.Typeface = this.Typeface ?? PlatformInfo.DefaultTypeface; | ||
|
||
paint.EnsureHasValidFont(text); | ||
|
||
SKRect measuredText = SKRect.Empty; | ||
paint.MeasureText(text, ref measuredText); | ||
float x = 0f; | ||
float y = 0f; | ||
|
||
SKRect rect = new(0f, 0f, processingImage.Width, processingImage.Height); | ||
|
||
switch (this.HorizontalWatermarkLocation) | ||
{ | ||
case WatermarkLocation.Center: | ||
x = rect.MidX - measuredText.MidX; | ||
break; | ||
case WatermarkLocation.End: | ||
x = rect.Left + rect.Width - measuredText.Width - (float)this.WatermarkPadding; | ||
break; | ||
case WatermarkLocation.Start: | ||
default: | ||
x = rect.Left + (float)this.WatermarkPadding; | ||
break; | ||
} | ||
|
||
switch (this.VerticalWatermarkLocation) | ||
{ | ||
case WatermarkLocation.Center: | ||
y = rect.MidY - (measuredText.Height / 2f); | ||
break; | ||
case WatermarkLocation.End: | ||
y = rect.Top + rect.Height - (measuredText.Height / 2f) - (float)this.WatermarkPadding; | ||
break; | ||
case WatermarkLocation.Start: | ||
default: | ||
y = rect.Top + (measuredText.Height / 2f) + (float)this.WatermarkPadding; | ||
break; | ||
} | ||
|
||
paint.Color = this.BackgroundColor.ToSKColor(); | ||
float halfPadding = (float)this.WatermarkPadding / 2f; | ||
canvas.DrawRoundRect( | ||
x - halfPadding, y - (measuredText.Height / 2f) - halfPadding, | ||
measuredText.Width + (float)this.WatermarkPadding, measuredText.Height + (float)this.WatermarkPadding, | ||
(float)this.BackgroundCornerRadius, (float)this.BackgroundCornerRadius, | ||
paint); | ||
|
||
paint.Color = this.ForegroundColor.ToSKColor(); | ||
canvas.DrawTextCenteredVertically(text, new SKPoint(x, y), paint); | ||
|
||
canvas.Flush(); | ||
|
||
return processingImage; | ||
} | ||
} | ||
|
||
return processingImage; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters