FFImageLoading, SVGCachedImage blurry when setting HeightRequest/WidthRequest from a binding - xamarin

I'm using FFImageLoading to display svg icons in my Xamarin.Forms project. My understanding is that the Height and Width requests must be set explicitly in order for the SVG to be rendered properly. I'm getting a lot of pixilation whenever I try to bind the Height/Width requests to values on the ViewModel (I have this as a need because the desired size depends on data). If I set the size explicitly everything looks fine.
Does SvgCachedImage not redraw the SVG whenever the bindings for Height/Width request change?
If not, is there a way for me to explicitly force them to invalidate and redraw when the size changes?

The blur issue was resolved by setting Horizontal and Vertical options to fill instead of Center:
<Grid>
<ffimageloadingsvg:SvgCachedImage BackgroundColor="Transparent"
Margin="{Binding HarmonicIconMargin}"
HorizontalOptions="Fill"
VerticalOptions="Fill"
WidthRequest="{Binding HarmonicIconWidth}"
HeightRequest="{Binding HarmonicIconWidth}"
Source="{Binding CurrentTestItem, Converter={StaticResource TestItemToHarmonicIconConverter}}" />
</Grid>
At that point it seemed be ignoring the height/width requests. I could've experimented around with that more (perhaps the request was for too much space) but I found that binding the margin to a computed property effectively enabled me to control the size of the SVG Image while not causing it to become blurred.

In order to solve svg blur issue when scale size of view,
Change SvgImageSource.VectorWidth or SvgImageSource.VectorHeight
Reload Image
protected override void OnSizeAllocated(double width, double height)
{
if (0 < width && 0 < height && && Source is SvgImageSource imageSource)
{
imageSource.VectorWidth = (int)Math.Ceiling(width);
imageSource.VectorHeight = (int)Math.Ceiling(height);
svgImage.ReloadImage();
base.OnSizeAllocated(width, height);
}
}
According to FFImageLoading source code, SVG image size is determined by SvgImageSource.VectorWidth or SvgImageSource.VectorHeight.
double sizeX = VectorWidth;
double sizeY = VectorHeight;
if (UseDipUnits)
{
sizeX = VectorWidth.DpToPixels();
sizeY = VectorHeight.DpToPixels();
}
if (sizeX <= 0 && sizeY <= 0)
{
if (picture.CullRect.Width > 0)
sizeX = picture.CullRect.Width;
else
sizeX = 400;
if (picture.CullRect.Height > 0)
sizeY = picture.CullRect.Height;
else
sizeY = 400;
}
else if (sizeX > 0 && sizeY <= 0)
{
sizeY = (int)(sizeX / picture.CullRect.Width * picture.CullRect.Height);
}
else if (sizeX <= 0 && sizeY > 0)
{
sizeX = (int)(sizeY / picture.CullRect.Height * picture.CullRect.Width);
}
resolvedData.ImageInformation.SetType(ImageInformation.ImageType.SVG);
using (var bitmap = new SKBitmap(new SKImageInfo((int)sizeX, (int)sizeY)))
using (var canvas = new SKCanvas(bitmap))
using (var paint = new SKPaint())
{
canvas.Clear(SKColors.Transparent);
var scaleX = (float)sizeX / picture.CullRect.Width;
var scaleY = (float)sizeY / picture.CullRect.Height;
var matrix = SKMatrix.MakeScale(scaleX, scaleY);
canvas.DrawPicture(picture, ref matrix, paint);
canvas.Flush();
token.ThrowIfCancellationRequested();
return await Decode(picture, bitmap, resolvedData).ConfigureAwait(false);
}
binding cause a pixellation because view's initial width and height are used as VectorWidth and VectorHeight, which is -1 or somthing you set as default for binding property. Therefore, your svg image resolution set too low at first and then binding process scales up view without redrawing svg image.

Related

How to use x2 canvas elements and rendering paper.js on img capture of html5 video to base64

Still very much a newbie to coding, so please be gentle :)
I'm hoping someone might be able to help how to use Paper.js on a second canvas after the first one has been executed?
I'm trying to use x2 canvas elements:
Canvas 1 - to capture a html5 video image still and convert to base64 (tick :-) = done)
Canvas 2 - Use the base64 image and perform the 'Working with Rasters to find the colors of pixels' and convert to circle paths (boo = fail :-( )
Something like this:
The code:
<script src="https://cdn.jsdelivr.net/npm/hls.js#latest"></script>
<video id="video" preload="auto" muted="" playsinline="" width="580" src="blob:https://www.georgefisher.co.uk/78e3a45c-ae07-4ea5-af56-45a5ed9cf1b0"></video>
<script>
var video = document.getElementById('video');
var videoSrc = 'https://camsecure.co/HLS/georgefisher.m3u8';
if (Hls.isSupported()) {
var hls = new Hls();
hls.loadSource(videoSrc);
hls.attachMedia(video);
}
else if (video.canPlayType('application/vnd.apple.mpegurl')) {
video.src = videoSrc;
}
video.play()
</script>
<br>
<button onclick="capture()">Capture</button>
<br>
<canvas id="canvas" style="overflow:auto">
</canvas>
<canvas id="canvas2" resize>
<img src="" id="myImg"/></canvas>
var resultb64="";
function capture() {
var canvas = document.getElementById('canvas');
var video = document.getElementById('video');
canvas.width = video.videoWidth/4;
canvas.height = video.videoHeight/4;
canvas.getContext('2d').drawImage(video, 0, 0, video.videoWidth/4, video.videoHeight/4);
resultb64=canvas.toDataURL();
document.querySelector("#myImg").src = canvas.toDataURL();
}
/*Paper JS Setup for working in CodePen */
/* ====================== *
* 0. Initiate Canvas *
* ====================== */
// expose paperjs classes into global scope
paper.install(window);
// Only executed our code once the DOM is ready.
window.onload = function() {
// bind paper to the canvas
paper.setup('canvas2');
// paper.activate();
// Get a reference to the canvas object
var canvas = document.getElementById('canvas2');
var ctx = canvas.getContext('2d');
// console.log(ctx, image);
// ctx.drawImage(image, 0, 0);
// return;
// }
// Create a raster item using the image id='' tag
var image = document.querySelector('img');
var raster = new Raster(image);
// Hide the raster:
raster.visible = false;
// The size of our grid cells:
var gridSize = 15;
// Space the cells by 120%:
var spacing = 1
;
// As the web is asynchronous, we need to wait for the raster to load before we can perform any operation on its pixels.
raster.onLoad = function() {
// Since the example image we're using is much too large, and therefore has way too many pixels, lets downsize it to 40 pixels wide and 30 pixels high:
raster.size = new Size(40, 30);
for (var y = 0; y < raster.height; y++) {
for(var x = 0; x < raster.width; x++) {
// Get the color of the pixel:
var color = raster.getPixel(x, y);
// Create a circle shaped path:
var path = new Path.Circle({
center: new Point(x, y).multiply(gridSize),
radius: gridSize / 2 / spacing,
});
// Set the fill color of the path to the color
// of the pixel:
path.fillColor = color;
}
}
// Move the active layer to the center of the view, so all the created paths in it appear centered.
project.activeLayer.position = view.center;
}
}
I've tried giving the second canvas a different Id="canvas2" and referencing that, which I can see in the console. However, nothing appears in the second canvas and the paper.js script doesn't seem to execute, can someone help me understand why?
Please see also see link to the fiddle below:
https://jsfiddle.net/jmnes/o4Lpkfs6/1/
Alternatives method.
You don't need to capture the video, you don't need to capture the pixels using paper.js and raster. You don't need to find the color of each circle and draw it.
All these methods are slow, complex, and power hungry.
You can create a mask and mask out the circles, with the colors drawn from a smaller canvas with a res that matches the number off circles.
How to
Add one (main canvas) canvas to the DOM. This will display the result
Create 2 offscreen canvas.
One (color canvas) has the same resolution as the circles you want to display. Eg if you have 30 by 40 circle the canvas res should be 30 by 40
One (mask canvas) is the circle mask. It is the same resolution as the main canvas. Draw the circles all in one color on this canvas.
Then rendering once a frame
Draw the video on the color canvas to fit.
Turn off smoothing on the main canvas eg ctxMain.imageSmoothingEnabled = false
Draw the color canvas onto the main canvas to fit.
This will draw a color square at each circle position. ctx.drawImage(colorCanvas, 0, 0, mainCanvas.width, mainCanvas.height)
Set composite operation "destination-in" eg ctxMain.globalCompositeOperation = "destination-in"
Draw the mask canvas (canvas with circles on it) onto the main canvas. This will remove pixels outside each circle.
Restore default composite operation for the main canvas ctxMain.globalCompositeOperation = "source-over"
All done for a real-time FX on almost any device.
The above methods is the fastest way to render the effect you are after using the 2D API

xamarin forms zoomable image (Cross Platform)

Is there a way to use pinch to zoom in shared Xamarin Forms, I only found an implementation for each platform.
You can use the Pan Gesture to implement it.
There is a good sample of wrapping an image in PanContainer available here - Adding a Pan Gesture Recognizer.
The pan gesture is used for detecting dragging and is implemented with
the PanGestureRecognizer class. A common scenario for the pan gesture
is to horizontally and vertically drag an image, so that all of the
image content can be viewed when it's being displayed in a viewport
smaller than the image dimensions. This is accomplished by moving the
image within the viewport.
Simple Example :
<Image Source="MonoMonkey.jpg">
<Image.GestureRecognizers>
<PanGestureRecognizer PanUpdated="OnPanUpdated" />
</Image.GestureRecognizers>
</Image>
Pan Container example XAML:
<AbsoluteLayout>
<local:PanContainer>
<Image Source="MonoMonkey.jpg" WidthRequest="1024" HeightRequest="768" />
</local:PanContainer>
</AbsoluteLayout>
Code behind :
public class PanContainer : ContentView
{
double x, y;
public PanContainer ()
{
// Set PanGestureRecognizer.TouchPoints to control the
// number of touch points needed to pan
var panGesture = new PanGestureRecognizer ();
panGesture.PanUpdated += OnPanUpdated;
GestureRecognizers.Add (panGesture);
}
void OnPanUpdated (object sender, PanUpdatedEventArgs e)
{
switch (e.StatusType) {
case GestureStatus.Running:
// Translate and ensure we don't pan beyond the wrapped user interface element bounds.
Content.TranslationX =
Math.Max (Math.Min (0, x + e.TotalX), -Math.Abs (Content.Width - App.ScreenWidth));
Content.TranslationY =
Math.Max (Math.Min (0, y + e.TotalY), -Math.Abs (Content.Height - App.ScreenHeight));
break;
case GestureStatus.Completed:
// Store the translation applied during the pan
x = Content.TranslationX;
y = Content.TranslationY;
break;
}
}
}
You can try this modification I made and verified on .NET Maui to TBertuzzi/Xamarin.Forms.PinchZoomImage to:
Adjust image boundaries for not offsetting it while pan gesture.
Sleep for .2 seconds after zoom for pan prevent.
Do not zoom over 4 times.
And it can be used as container like this after importing from appropriate namespace:
<? 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: collects = "clr-namespace:Mab.Collects"
x: Class = "N3ema.Secondpageimageshow" >
< ContentPage.Content >
< collects:Zoom >
< collects:Zoom.Content >
< StackLayout >
< Image Source = "shahid.jpeg"
HorizontalOptions = "Fill"
Aspect = "Fill"
VerticalOptions = "FillAndExpand" />
</ StackLayout >
</ collects:Zoom.Content >
</ collects:Zoom >
</ ContentPage.Content >
</ ContentPage >

drag-drop, resize images and then drawing features in 1 canvas

I am working on an application where the user comes and sees a blank area(div or canvas or whatever, lets call it mycanvas hereafter). Now he drags some images from outside(a div) and drops them on mycanvas. He can also resize them. And, he can also draw something in mycanvas with pencils and colors with erasing feature. Now, as per my research till now, I've figured out that the drawing part is a pure HTML 5 canvas stuff. So, no problem with that. But I'm not sure whether he can drop images from an outside div/canvas to mycanvas. Please tell me how to achieve all the three features(drag-drop from outside, draw with pencil, resize images) in a single area.
I have create a online dnd editor by Html5Canvas.
I will create a loop first
var loop = function(){
// Operation Here
}
self.setInterval(loop, 1000/60);
Create the data model, for example a image
var DndImage = function(x, y, width, height, image){
this.type = "image";
this.image = image;
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
Then we draw the image in the looping
var ObjectArray = new Array();
var WIDTH = 800;
var HEIGHT = 600;
var loop = function(){
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
context.clearRect(0, 0, WIDTH, HEIGHT);
for(var x = 0; x < ObjectArray.length; x++){
if(ObjectArray[x].type == "image")
context.drawImage(ObjectArray[x].image,ObjectArray[x].x,ObjectArray[x].y, ObjectArray[x].width, ObjectArray[x].height);
}
}
Function to add New image object
function addImage(src, x, y, width, height){
var img = new Image();
img.src = src;
img.onload = function(){
ObjectArray.push(new DndImage(x, y, width, height, img));
}
}
And now if you want to do a dnd, You need to do is set up a Listener to listen the mouse move event. And set the DndImage Object x and y to follow the mouse position in the image canavs. You can scale the image or changing the size too.
docuemnt.addEventListener("mousedown", function(){ });
docuemnt.addEventListener("mouseup", function(){ });
docuemnt.addEventListener("mousemove", function(){ });
docuemnt.addEventListener("click", function(){ });
Hope I can help you :D
You can achieve all the required features using kinetic js.
To drag, drop and resize
http://www.html5canvastutorials.com/labs/html5-canvas-drag-and-drop-resize-and-invert-images/
To paint using different shapes, say a line:
http://www.html5canvastutorials.com/kineticjs/html5-canvas-kineticjs-line-tutorial/
and dropping from outside canvas is the simplest thing, probably:
http://www.w3schools.com/html/html5_draganddrop.asp
Just check these and let me know if there is any problem in integration.

WebGL single frame "screenshot" of webGL

tried searching for something like this, but I've had no luck. I'm trying to open a new tab with a screenshot of the current state of my webgl image. Basically, it's a 3d model, with the ability to change which objects are displayed, the color of those objects, and the background color. Currently, I am using the following:
var screenShot = window.open(renderer.domElement.toDataURL("image/png"), 'DNA_Screen');
This line succeeds in opening a new tab with a current image of my model, but does not display the current background color. It also does not properly display the tab name. Instead, the tab name is always "PNG 1024x768".
Is there a way to change my window.open such that the background color is shown? The proper tab name would be great as well, but the background color is my biggest concern.
If you open the window with no URL you can access it's entire DOM directly from the JavaScript that opened the window.
var w = window.open('', '');
You can then set or add anything you want
w.document.title = "DNA_screen";
w.document.body.style.backgroundColor = "red";
And add the screenshot
var img = new Image();
img.src = someCanvas.toDataURL();
w.document.body.appendChild(img);
Well it is much longer than your one liner but you can change the background color of the rectangle of the context.
printCanvas (renderer.domElement.toDataURL ("image/png"), width, height,
function (url) { window.open (url, '_blank'); });
// from THREEx.screenshot.js
function printCanvas (srcUrl, dstW, dstH, callback)
{
// to compute the width/height while keeping aspect
var cpuScaleAspect = function (maxW, maxH, curW, curH)
{
var ratio = curH / curW;
if (curW >= maxW && ratio <= 1)
{
curW = maxW;
curH = maxW * ratio;
}
else if (curH >= maxH)
{
curH = maxH;
curW = maxH / ratio;
}
return { width: curW, height: curH };
}
// callback once the image is loaded
var onLoad = function ()
{
// init the canvas
var canvas = document.createElement ('canvas');
canvas.width = dstW;
canvas.height = dstH;
var context = canvas.getContext ('2d');
context.fillStyle = "black";
context.fillRect (0, 0, canvas.width, canvas.height);
// scale the image while preserving the aspect
var scaled = cpuScaleAspect (canvas.width, canvas.height, image.width, image.height);
// actually draw the image on canvas
var offsetX = (canvas.width - scaled.width ) / 2;
var offsetY = (canvas.height - scaled.height) / 2;
context.drawImage (image, offsetX, offsetY, scaled.width, scaled.height);
// notify the url to the caller
callback && callback (canvas.toDataURL ("image/png")); // dump the canvas to an URL
}
// Create new Image object
var image = new Image();
image.onload = onLoad;
image.src = srcUrl;
}

Pushpin size at WP7 map control

while trying to implement logic that show current user location i encountered an issue.
<Maps:Pushpin Location="{Binding MyLocation}" Canvas.ZIndex="1000" PositionOrigin="Center" >
<Maps:Pushpin.Template>
<ControlTemplate>
<Grid>
<Ellipse Width="{Binding MyAccuracyViewSize}" Height="{Binding MyAccuracyViewSize}"
Fill="#60008000" Stroke="Green" StrokeThickness="3"/>
<Ellipse Width="18" Height="18" Fill="#A0FF4500" VerticalAlignment="Center" HorizontalAlignment="Center" />
</Grid>
</ControlTemplate>
</Maps:Pushpin.Template>
</Maps:Pushpin>
Bigger green circle shows accuracy area. Its size in pixels varies depending on zoom. If zoom level is big - it becomes rather big (> 480 pixels). At that point it gets cropped by screen resolution. AFAIK WP7 restriction is 2000x2000 px for control size.
Seems that this is a kind of a map control restriction.
Any ideas how to remove this restriction to show ellipse of size up to 2000x2000 px?
Thanks!
How about MapPolygon?
void OnPositionChanged(object sender, GeoPositionChangedEventArgs<GeoCoordinate> e)
{
SecondsCounter = 0; //reset counter
double accuracy = e.Position.Location.HorizontalAccuracy;
if (accuracy < e.Position.Location.VerticalAccuracy)
{
accuracy = e.Position.Location.VerticalAccuracy;
}
if (PolyCircle == null)
{
PolyCircle = new MapPolygon();
PolyCircle.Opacity = 0.7;
//Set the polygon properties
PolyCircle.Fill = new SolidColorBrush(Colors.Orange);
PolyCircle.Stroke = new SolidColorBrush(Colors.Purple);
PolyCircle.StrokeThickness = 4;
map1.Children.Add(PolyCircle);
}
PolyCircle.Locations = CreateCircle(e.Position.Location, accuracy);
map1.Center = e.Position.Location;
}
public static double ToRadian(double degrees)
{
return degrees * (Math.PI / 180);
}
public static double ToDegrees(double radians)
{
return radians * (180 / Math.PI);
}
public static LocationCollection CreateCircle(GeoCoordinate center, double radius)
{
var earthRadius = 6367000; // radius in meters
var lat = ToRadian(center.Latitude); //radians
var lng = ToRadian(center.Longitude); //radians
var d = radius / earthRadius; // d = angular distance covered on earth's surface
var locations = new LocationCollection();
for (var x = 0; x <= 360; x++)
{
var brng = ToRadian(x);
var latRadians = Math.Asin(Math.Sin(lat) * Math.Cos(d) + Math.Cos(lat) * Math.Sin(d) * Math.Cos(brng));
var lngRadians = lng + Math.Atan2(Math.Sin(brng) * Math.Sin(d) * Math.Cos(lat), Math.Cos(d) - Math.Sin(lat) * Math.Sin(latRadians));
locations.Add(new GeoCoordinate(ToDegrees(latRadians), ToDegrees(lngRadians)));
}
return locations;
}
More here, in My Map position example. Good luck!

Resources