How use my SKBitmap or a Maui Resource png with SkiaImage? #96
-
GOAL: Define a custom SkiaButton with SkiaImage in left half, two line text in right half. Everything works, until I set image's ImageSource. custom button's xaml has: <draw:SkiaImage x:Name="BtnImage ... /> xaml.cs: public partial class BuyUnitButton : SkiaButton
{
...
private static void OnLookChanged(BindableObject bindable, object oldValue, object newValue)
{
if (bindable is BuyUnitButton it)
{
ImageSource? source = it.GetValue(ImageSourceProperty) as ImageSource;
it.BtnImage.SetImageSource(source);
}
}
... Verified
Result:
Questions:
--
|
Beta Was this translation helpful? Give feedback.
Replies: 7 comments
-
I will make a stab at the last bullet point. Clone SkiaImage, remove most of the code, give it an SKBitmap property. I don't currently need any "effects"; my version will simply do: |
Beta Was this translation helpful? Give feedback.
-
using DrawnUi.Maui.Draw;
using SkiaSharp;
using System.Diagnostics;
namespace DrawnUi.Maui.Draw;
public class SkiaBitmapControl : SkiaControl
{
public SkiaBitmapControl()
{
}
#region PROPERTIES
public static readonly BindableProperty AspectProperty = BindableProperty.Create(
nameof(Aspect),
typeof(TransformAspect),
typeof(SkiaImage),
TransformAspect.AspectFit,
propertyChanged: NeedInvalidateMeasure);
/// <summary> Aspect to render image with, default is AspectCover. </summary>
public TransformAspect Aspect
{
get { return (TransformAspect)GetValue(AspectProperty); }
set { SetValue(AspectProperty, value); }
}
public static readonly BindableProperty DrawWhenEmptyProperty = BindableProperty.Create(nameof(Tag),
typeof(bool),
typeof(SkiaImage),
true, propertyChanged: NeedInvalidateMeasure);
public bool DrawWhenEmpty
{
get { return (bool)GetValue(DrawWhenEmptyProperty); }
set { SetValue(DrawWhenEmptyProperty, value); }
}
public static readonly BindableProperty VerticalAlignmentProperty = BindableProperty.Create(
nameof(VerticalAlignment),
typeof(DrawImageAlignment),
typeof(SkiaBitmapControl),
DrawImageAlignment.Center,
propertyChanged: NeedDraw);
public DrawImageAlignment VerticalAlignment
{
get { return (DrawImageAlignment)GetValue(VerticalAlignmentProperty); }
set { SetValue(VerticalAlignmentProperty, value); }
}
public static readonly BindableProperty HorizontalAlignmentProperty = BindableProperty.Create(
nameof(HorizontalAlignment),
typeof(DrawImageAlignment),
typeof(SkiaBitmapControl),
DrawImageAlignment.Center,
propertyChanged: NeedDraw);
public DrawImageAlignment HorizontalAlignment
{
get { return (DrawImageAlignment)GetValue(HorizontalAlignmentProperty); }
set { SetValue(HorizontalAlignmentProperty, value); }
}
public static readonly BindableProperty BitmapProperty = BindableProperty.Create(
nameof(Bitmap),
typeof(SKBitmap),
typeof(SkiaBitmapControl),
defaultValue: null,
propertyChanged: OnSetBitmap);
private static void OnSetBitmap(BindableObject bindable, object oldvalue, object newvalue)
{
if (bindable is SkiaBitmapControl it)
{
it.Bitmap = newvalue as SKBitmap;
}
}
public SKBitmap Bitmap
{
get { return (SKBitmap)GetValue(BitmapProperty); }
set { SetValue(BitmapProperty, value); }
}
public static readonly BindableProperty RescalingQualityProperty = BindableProperty.Create(
nameof(RescalingQuality),
typeof(SKFilterQuality),
typeof(SkiaBitmapControl),
SKFilterQuality.Medium,
propertyChanged: NeedInvalidateMeasure);
/// <summary> Default value is Medium. </summary>
public SKFilterQuality RescalingQuality
{
get { return (SKFilterQuality)GetValue(RescalingQualityProperty); }
set { SetValue(RescalingQualityProperty, value); }
}
public static readonly BindableProperty ZoomXProperty = BindableProperty.Create(
nameof(ZoomX),
typeof(double),
typeof(SkiaBitmapControl),
1.0,
propertyChanged: NeedDraw);
public double ZoomX
{
get { return (double)GetValue(ZoomXProperty); }
set { SetValue(ZoomXProperty, value); }
}
public static readonly BindableProperty ZoomYProperty = BindableProperty.Create(
nameof(ZoomY),
typeof(double),
typeof(SkiaBitmapControl),
1.0,
propertyChanged: NeedDraw);
public double ZoomY
{
get { return (double)GetValue(ZoomYProperty); }
set { SetValue(ZoomYProperty, value); }
}
public static readonly BindableProperty HorizontalOffsetProperty = BindableProperty.Create(
nameof(HorizontalOffset),
typeof(double),
typeof(SkiaBitmapControl),
0.0,
propertyChanged: NeedDraw);
public double HorizontalOffset
{
get { return (double)GetValue(HorizontalOffsetProperty); }
set { SetValue(HorizontalOffsetProperty, value); }
}
public static readonly BindableProperty VerticalOffsetProperty = BindableProperty.Create(
nameof(VerticalOffset),
typeof(double),
typeof(SkiaBitmapControl),
0.0,
propertyChanged: NeedDraw);
public double VerticalOffset
{
get { return (double)GetValue(VerticalOffsetProperty); }
set { SetValue(VerticalOffsetProperty, value); }
}
public static readonly BindableProperty SpriteHeightProperty = BindableProperty.Create(
nameof(SpriteHeight),
typeof(double),
typeof(SkiaBitmapControl),
0.0);
public double SpriteHeight
{
get { return (double)GetValue(SpriteHeightProperty); }
set { SetValue(SpriteHeightProperty, value); }
}
public static readonly BindableProperty SpriteWidthProperty = BindableProperty.Create(
nameof(SpriteWidth),
typeof(double),
typeof(SkiaBitmapControl),
0.0);
public double SpriteWidth
{
get { return (double)GetValue(SpriteWidthProperty); }
set { SetValue(SpriteWidthProperty, value); }
}
public static readonly BindableProperty SpriteIndexProperty = BindableProperty.Create(
nameof(SpriteIndex),
typeof(int),
typeof(SkiaBitmapControl),
-1);
public int SpriteIndex
{
get { return (int)GetValue(SpriteIndexProperty); }
set { SetValue(SpriteIndexProperty, value); }
}
#endregion
public override bool CanDraw => Bitmap == null ? DrawWhenEmpty && base.CanDraw : base.CanDraw;
//bool _needClearBitmap;
//public void ClearBitmap()
//{
// Bitmap = null;
// _needClearBitmap = true;
//}
#region RENDERiNG
/// <summary> Reusing this </summary>
protected SKPaint ImagePaint;
protected override void Paint(SkiaDrawingContext ctx, SKRect destination, float scale, object arguments)
{
base.Paint(ctx, destination, scale, arguments);
if (Bitmap != null && !CheckIsGhost())
{
if (ImagePaint == null)
{
ImagePaint = new()
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High
};
}
DrawBitmap(ctx, Bitmap, destination, scale, Aspect, HorizontalAlignment, VerticalAlignment, ImagePaint);
}
}
object lockDraw = new();
private bool _hasError;
//private RescaledBitmap _scaledSource;
public override void Invalidate()
{
base.Invalidate();
Update();
}
protected override void Draw(SkiaDrawingContext context, SKRect destination, float scale)
{
// until we implement 2-threads rendering this is needed for ImageDoubleBuffered cache rendering
if (IsDisposing || IsDisposed)
return;
if (Bitmap != null)
{
if (NeedAutoSize)
{
Invalidate(); // resize on next frame
if (Bitmap == null)
return;
}
if (DrawingRect == SKRect.Empty || Bitmap == null)
{
NeedMeasure = true;
}
else
{
// fast insert new image into presized rect
SetAspectScale(Bitmap.Width, Bitmap.Height, DrawingRect, Aspect, scale);
}
}
Update(); // gamechanger for doublebuffering and other complicated cases
DrawUsingRenderObject(context, SizeRequest.Width, SizeRequest.Height, destination, scale);
}
protected override void OnLayoutChanged()
{
base.OnLayoutChanged();
if (DrawingRect != SKRect.Empty && Bitmap != null)
{
SetAspectScale(Bitmap.Width, Bitmap.Height, DrawingRect, this.Aspect, RenderingScale);
}
}
public override void OnDisposing()
{
//ClearBitmap();
ImagePaint?.Dispose();
ImagePaint = null;
Bitmap?.Dispose();
Bitmap = null;
//ScaledSource?.Dispose();
//ScaledSource = null;
base.OnDisposing();
}
public SKPoint AspectScale { get; protected set; }
//public RescaledBitmap ScaledSource
//{
// get => _scaledSource;
// protected set
// {
// _scaledSource = value;
// }
//}
//public class RescaledBitmap : IDisposable
//{
// public SKBitmap Bitmap { get; set; }
// public SKFilterQuality Quality { get; set; }
// public Guid Source { get; set; }
// public void Dispose()
// {
// Bitmap?.Dispose();
// }
//}
protected virtual void DrawBitmap(
SkiaDrawingContext ctx,
SKBitmap bitmap,
SKRect dest,
float scale,
TransformAspect stretch,
DrawImageAlignment horizontal = DrawImageAlignment.Center,
DrawImageAlignment vertical = DrawImageAlignment.Center,
SKPaint paint = null)
{
try
{
if (bitmap == null)
return;
if (AspectScale == SKPoint.Empty)
{
throw new ApplicationException("AspectScale is not set");
}
var aspectScaleX = AspectScale.X * (float)(ZoomX);
var aspectScaleY = AspectScale.Y * (float)(ZoomY);
SKRect display = CalculateDisplayRect(dest,
aspectScaleX * bitmap.Width, aspectScaleY * bitmap.Height,
horizontal, vertical);
display.Offset((float)Math.Round(scale * HorizontalOffset), (float)Math.Round(scale * VerticalOffset));
TextureScale = new(dest.Width / display.Width, dest.Height / display.Height);
{
if (this.RescalingQuality != SKFilterQuality.None)
{
ctx.Canvas.DrawBitmap(bitmap, display, paint);
}
}
}
catch (Exception e)
{
Debug.WriteLine(e.ToString());
}
finally
{
}
}
public SKPoint TextureScale { get; protected set; }
public ScaledRect SourceImageSize { get; protected set; }
protected void SetAspectScale(int pxWidth, int pxHeight, SKRect dest, TransformAspect stretch, float scale)
{
var scaled = RescaleAspect(pxWidth, pxHeight, dest, stretch);
SourceImageSize = ScaledRect.FromPixels(new SKRect(0, 0, pxWidth, pxHeight), scale);
SourceWidth = SourceImageSize.Units.Width;
SourceHeight = SourceImageSize.Units.Height;
AspectScale = new SKPoint(scaled.X, scaled.Y);
}
protected override ScaledSize SetMeasuredAsEmpty(float scale)
{
AspectScale = SKPoint.Empty;
return base.SetMeasuredAsEmpty(scale);
}
//public override void Arrange(SKRect destination, float widthRequest, float heightRequest, float scale)
//{
// if (IsDisposed || !CanDraw)
// return;
// if (NeedMeasure)
// {
// var adjustedDestination = CalculateLayout(destination, widthRequest, heightRequest, scale);
// Measure(adjustedDestination.Width, adjustedDestination.Height, scale);
// }
// base.Arrange(destination, MeasuredSize.Units.Width, MeasuredSize.Units.Height, scale);
//}
public override ScaledSize Measure(float widthRequest, float heightRequest, float dscale)
{
//background measuring or invisible or self measure from draw because layout will never pass -1
if (IsMeasuring || !CanDraw)
{
return MeasuredSize;
}
var request = CreateMeasureRequest(widthRequest, heightRequest, dscale);
if (request.IsSame)
{
return MeasuredSize;
}
if (request.WidthRequest == 0 || request.HeightRequest == 0)
{
InvalidateCacheWithPrevious();
return SetMeasuredAsEmpty(request.Scale);
}
var widthConstraint = request.WidthRequest;
var heightConstraint = request.HeightRequest;
if ((float.IsInfinity(widthConstraint) || float.IsInfinity(heightConstraint)) && Bitmap != null)
{
var aspect = Bitmap.Width / (double)Bitmap.Height;
if (widthConstraint > 0)
{
heightConstraint = (float)(widthConstraint / aspect);
}
else
if (heightConstraint > 0)
{
widthConstraint = (float)(heightConstraint * aspect);
}
}
if (widthConstraint < 0 || heightConstraint < 0)
{
//set auto-size from image using newly image dimensions
//this is the case of one dimension being Fill or explicit and the other being Auto (-1)
if (Bitmap != null && (widthConstraint > 0 || heightConstraint > 0))
{
var aspect = Bitmap.Width / (double)Bitmap.Height;
if (widthConstraint > 0)
{
heightConstraint = (float)(widthConstraint / aspect);
}
else
if (heightConstraint > 0)
{
widthConstraint = (float)(heightConstraint * aspect);
}
//Invalidate(); //remeasure us - disabled works superfine so far!!!
}
else
{
//not setting NeedMeasure=false; to force remeasurement on next frame
AspectScale = SKPoint.Empty;
return ScaledSize.Default;
//return ScaledSize.FromPixels(0, 0, request.Scale);
}
}
if (widthConstraint == 0 || heightConstraint == 0)
{
InvalidateCacheWithPrevious();
return SetMeasuredAsEmpty(request.Scale);
}
var constraints = GetMeasuringConstraints(new(widthConstraint, heightConstraint, request.Scale));
//we measured no children, simulated !
ContentSize = ScaledSize.FromPixels(constraints.Content.Width, constraints.Content.Height, request.Scale);
//return SetMeasuredAdaptToContentSize(constraints, request.Scale);
var width = AdaptWidthConstraintToContentRequest(constraints, ContentSize.Pixels.Width, HorizontalOptions.Expands);
var height = AdaptHeightConstraintToContentRequest(constraints, ContentSize.Pixels.Height, VerticalOptions.Expands);
if (Bitmap != null)
{
SetAspectScale(Bitmap.Width, Bitmap.Height, constraints.Content, this.Aspect, request.Scale);
if (NeedAutoHeight)
height = SourceImageSize.Pixels.Height * this.AspectScale.Y;
if (NeedAutoWidth)
width = SourceImageSize.Pixels.Width * this.AspectScale.X;
}
else
{
AspectScale = SKPoint.Empty;
}
return SetMeasured(width, height, false, false, request.Scale);
}
#endregion
/// <summary>
/// From current set Source in points
/// </summary>
public float SourceWidth { get; protected set; }
/// <summary>
/// From current set Source in points
/// </summary>
public float SourceHeight { get; protected set; }
public static SKRect CalculateDisplayRect(SKRect dest,
float destWidth, float destHeight,
DrawImageAlignment horizontal, DrawImageAlignment vertical)
{
float x = 0;
float y = 0;
switch (horizontal)
{
case DrawImageAlignment.Center:
x = (dest.Width - destWidth) / 2.0f;
break;
case DrawImageAlignment.Start:
break;
case DrawImageAlignment.End:
x = dest.Width - destWidth;
break;
}
switch (vertical)
{
case DrawImageAlignment.Center:
y = (dest.Height - destHeight) / 2.0f;
break;
case DrawImageAlignment.Start:
break;
case DrawImageAlignment.End:
y = dest.Height - destHeight;
break;
}
x += dest.Left;
y += dest.Top;
return new SKRect(x, y, x + destWidth, y + destHeight);
}
} Here is the class I made. |
Beta Was this translation helpful? Give feedback.
-
Usage of SkiaBitmapControl in my (WIP) BuyUnitButton. I see that VerticalOptions="Fill" isn't working in my button. Aqua background should extend to bottom of button. Looks like I cut out too much code of SkiaImage. I'll debug that another day. (Pic is rough cut of kid's version of a game unit; for my grandson.) |
Beta Was this translation helpful? Give feedback.
-
Looking good. Looks and works just like standard Maui controls. Without the annoying layout bug that Maui's button has, when I gave it a streaming ImageSource. Feels great to be working directly with SKBitmaps. (That was the Maui issue that led me to say to myself "Do I need Maui's platform-specific UI rendering at all? Why not do the UI with SkiaSharp, like the rest of the game.") Almost time for me to test on mobile platforms. |
Beta Was this translation helpful? Give feedback.
-
Hi! You have 2 ways:
This will use
Also play with |
Beta Was this translation helpful? Give feedback.
-
Everything should work as intended for SkiaImage, I see that you use your own code with commented out |
Beta Was this translation helpful? Give feedback.
-
i hope this answers:
Would guess this answers:
|
Beta Was this translation helpful? Give feedback.
Hi!
You have 2 ways:
<draw:SkiaImage x:Name="BtnImage" Source="baboon.jpg" ... />
where image is embedded inResources/Raw
.This will use
SkiaImageManager
SetBitmapInternal
orSetImageInternal
, exampleAlso play with
BtnImage.LoadSourceOnFirstDraw
would want it to beFalse
in your case