I have been following this tutorial on how to make a custom marker for Google maps, and I have got this part to work. However, I had to change some code in order to resize the image I am using on the marker.
This is the method I am using to do resize:
private void UpdateMarkers(float zoom)
{
// Max zoom out => zoom = 3
// Max zoom in => zoom = 21
int dimension = (int)zoom * 10;
if (dimension == currentDimension)
return;
currentDimension = dimension;
map.Clear();
foreach (var pin in customPins)
{
var immutableBitmap = BitmapFactory.DecodeResource(Context.Resources, Resource.Drawable.icon);
var mutableBitmap = immutableBitmap.Copy(Bitmap.Config.Argb8888, true);
mutableBitmap.Height = dimension;
mutableBitmap.Width = dimension;
BitmapDescriptorFactory.FromBitmap(mutableBitmap);
var img = BitmapDescriptorFactory.FromBitmap(mutableBitmap);
var marker = new MarkerOptions();
marker.SetPosition(new LatLng(pin.Pin.Position.Latitude, pin.Pin.Position.Longitude));
marker.SetTitle(pin.Pin.Label);
marker.SetSnippet(pin.Pin.Address);
marker.SetIcon(img);
map.AddMarker(marker);
}
}
And here is a picture on how it looks:
In the original code, I would do something like this
var img = BitmapDescriptorFactory.FromResource(Resource.Drawable.icon);
And this works. But I want to resize the image, so I found a way to do it, sort of.
Any idea what's going on?
Once you have a Bitmap, you can scale it via Bitmap.CreateScaledBitmap:
var bitmap = BitmapFactory.DecodeResource(Context.Resources, Resource.Drawable.icon);
var scaledBitmap = Bitmap.CreateScaledBitmap(bitmap, dimension, dimension, false);
var img = BitmapDescriptorFactory.FromBitmap(scaledBitmap);
Note: Make sure you Recycle and Dispose your Bitmaps to avoid leaking memory.
Note BitmapFactory's DecodeResource and CreateScaledBitmap are heavy API calls, you might want to cache the results versus calling them over and over in your foreach (var pin in customPins) loop.
Related
I have a big svg image.
I would like to crop it to a rectangle, using coordinates, and convert it to png image.
I have to say that I'm not used to drawing with c#.
Surface, Canvas and other notions are new to me.
I have figured out how to load the svg, using SkiaShark and SkiaShark.Svg:
var svg = new SkiaSharp.Extended.Svg.SKSvg();
svg.Load(tmpPath);
And found a gist that saves a png. But this is "Chinese" for me.
var imageInfo = new SKImageInfo(100, 100);
using (var surface = SKSurface.Create(imageInfo))
using (var canvas = surface.Canvas)
{
// calculate the scaling need to fit to screen
var scaleX = 100 / svg.Picture.CullRect.Width;
var scaleY = 100 / svg.Picture.CullRect.Height;
var matrix = SKMatrix.CreateScale((float)scaleX, (float)scaleY);
// draw the svg
canvas.Clear(SKColors.Transparent);
canvas.DrawPicture(svg.Picture, ref matrix);
canvas.Flush();
using (var data = surface.Snapshot())
using (var pngImage = data.Encode(SKEncodedImageFormat.Png, 100))
{
tmpPath = Path.ChangeExtension(tmpPath, "PNG");
using var imageStream = new FileStream(tmpPath, FileMode.Create);
pngImage.SaveTo(imageStream);
}
}
If someone could show me the directions, it would be much appreciated.
EDIT
I've come to this implentation myself, though it is not working... The result bitmap is transparent and empty.
private string ConvertSVGToPNGRectangleWithSkiaSharpExtended(string path, double left, double top, double right, double bottom)
{
var svg = new SkiaSharp.Extended.Svg.SKSvg();
var picture = svg.Load(path);
// Get the initial map size
var source = new SKRect(picture.CullRect.Left, picture.CullRect.Top, picture.CullRect.Width, picture.CullRect.Height);
// Cropping Rect
var cropRect = new SKRect((int)left, (int)top, (int)right, (int)bottom);
var croppedBitmap = new SKBitmap((int)cropRect.Width, (int)cropRect.Height);
using var canvas = new SKCanvas(croppedBitmap);
canvas.Clear(SKColors.Transparent);
canvas.DrawBitmap(croppedBitmap, source, cropRect);
var data = croppedBitmap.Encode(SKEncodedImageFormat.Png, 100);
path = Path.ChangeExtension(path, "PNG");
using var imageStream = new FileStream(path, FileMode.Create);
data.SaveTo(imageStream);
return path;
}
EDIT 2:
I'm sorry , if it is not clear. You can refer to this question for doing so on Android.
Disclaimer: I'm not an expert with SkiaSharp, but I do know about drawing and canvases and I looked up the documentation at https://learn.microsoft.com/en-us/dotnet/api/skiasharp.skcanvas.drawpicture
I think the problem is this line:
canvas.DrawBitmap(croppedBitmap, source, cropRect);
I think you need:
canvas.DrawPicture(picture, -cropRect.Left, -cropRect.Top);
canvas.Flush();
I might have the coordinates wrong, but the problem behind your failure is that you are never drawing picture to the canvas.
To help with your confusion about what a canvas is: SKCanvas(croppedBitmap) is making it so that every time you draw to canvas, what you are actually drawing on is the bitmap. So in order to draw your picture onto the bitmap, you have to draw picture onto canvas. Using negative coordinates make it so the top and left of the svg picture are above and to the left of the top-left corner of the bitmap, which is the equivalent of having the bitmap start somewhere in the middle of the svg picture.
Hope this helps!
I coded example from https://learn.microsoft.com/en-us/windows/uwp/audio-video-camera/custom-video-effects
Part of the code:
public void ProcessFrame(ProcessVideoFrameContext context)
{
using (CanvasBitmap inputBitmap = CanvasBitmap.CreateFromDirect3D11Surface(canvasDevice, context.InputFrame.Direct3DSurface))
using (CanvasRenderTarget renderTarget = CanvasRenderTarget.CreateFromDirect3D11Surface(canvasDevice, context.OutputFrame.Direct3DSurface))
using (CanvasDrawingSession ds = renderTarget.CreateDrawingSession())
{
var gaussianBlurEffect = new GaussianBlurEffect
{
Source = inputBitmap,
BlurAmount = (float)BlurAmount,
Optimization = EffectOptimization.Speed
};
ds.DrawImage(gaussianBlurEffect);
}
}
The problem is: i want to draw points (bitmaps) on frames but i have no idea how to pass specific coord to ProcessFrame function. On input i have x and y coords for every frame where to draw point and on the output i want to have video with added points for every frame.
Thanks for help.
EDIT:
The code below is not suitable solution as the ProcessFrame(ProcessVideoFrameContext context) is part of an interface implementation.
My next solution proposal is to create a custom effect, similar to the GaussianBlusEffect and many more. An example here:
https://github.com/Microsoft/Win2D-Samples/blob/master/ExampleGallery
~~~
Below the original answer for reference.
You can pass in the X and Y parameters and access the pixels of the image.
public void ProcessFrame(ProcessVideoFrameContext context, int X, int Y)
{
using (CanvasBitmap inputBitmap = CanvasBitmap.CreateFromDirect3D11Surface(canvasDevice, context.InputFrame.Direct3DSurface))
using (CanvasRenderTarget renderTarget = CanvasRenderTarget.CreateFromDirect3D11Surface(canvasDevice, context.OutputFrame.Direct3DSurface))
using (CanvasDrawingSession ds = renderTarget.CreateDrawingSession())
{
Color[] Pixels = inputBitmap.GetPixelColors();
// Manipulate the array using X and Y with the Width parameter of the bitmap
var gaussianBlurEffect = new GaussianBlurEffect
{
Source = inputBitmap,
BlurAmount = (float)BlurAmount,
Optimization = EffectOptimization.Speed
};
ds.DrawImage(gaussianBlurEffect);
}
}
More info: https://microsoft.github.io/Win2D/html/M_Microsoft_Graphics_Canvas_CanvasBitmap_GetPixelColors.htm
I did not check if the Color[] is a pointer to the live buffer or a copy. If it is a copy, then you have to write back the buffer with SetPixelColors.
Does anybody know of any ways to use an image as a mask for another image in UWP, the only masking function I can see is CompositionMaskBrush which I don't believe can achieve what I want.
An example of what I'm looking to achieve is the following.
I have a solid black PNG in the shape of a mobile phone case, the user adds their own image which is then clipped and masked to the dimensions of the solid black PNG - Resulting in the image below.
Any help whatsoever would be greatly appreciated. I've spent quite a while browsing for a solution.
Example Image Here
Just posting for anybody else who needs and answer to this, but I finally managed to find a solution using Win2D and an Imageloader.
Here is a link to the ImageLoader. Note that I had to roll back a few versions in order make it work how the documentation states. The link below is to the version that I'm using. Anything later than this version will not work with the sample code I'm going to post.
https://www.nuget.org/packages/Robmikh.Util.CompositionImageLoader/0.4.0-alpha
private Compositor _compositor;
private IImageLoader _imageLoader;
private CompositionEffectFactory _effectFactory;
private async void InitMask()
{
// Store our Compositor and create our ImageLoader.
_compositor = ElementCompositionPreview.GetElementVisual(this).Compositor;
_imageLoader = ImageLoaderFactory.CreateImageLoader(_compositor);
// Setup our effect definition. First is the CompositeEffect that will take
// our sources and produce the intersection of the images (because we selected
// the DestinationIn mode for the effect). Next we take our CompositeEffect
// and make it the source of our next effect, the InvertEffect. This will take
// the intersection image and invert the colors. Finally we take that combined
// effect and put it through a HueRotationEffect, were we can adjust the colors
// using the Angle property (which we will animate below).
IGraphicsEffect graphicsEffect = new HueRotationEffect
{
Name = "hueEffect",
Angle = 0.0f,
Source = new InvertEffect
{
Source = new CompositeEffect
{
Mode = CanvasComposite.DestinationIn,
Sources =
{
new CompositionEffectSourceParameter("image"),
new CompositionEffectSourceParameter("mask")
}
}
}
};
// Create our effect factory using the effect definition and mark the Angle
// property as adjustable/animatable.
_effectFactory = _compositor.CreateEffectFactory(graphicsEffect, new string[] { "hueEffect.Angle" });
// Create MangedSurfaces for both our base image and the mask we'll be using.
// The mask is a transparent image with a white circle in the middle. This is
// important since the CompositeEffect will use just the circle for the
// intersectionsince the rest is transparent.
var managedImageSurface = await _imageLoader.CreateManagedSurfaceFromUriAsync(new Uri("http://sendus.pics/uploads/" + ImagePass + "/0.png", UriKind.Absolute));
//var managedImageSurface = await _imageLoader.CreateManagedSurfaceFromUriAsync(new Uri("ms-appx:///Assets/colour.jpg", UriKind.Absolute));
var managedMaskSurface = await _imageLoader.CreateManagedSurfaceFromUriAsync(new Uri("ms-appx:///" + MaskImage, UriKind.Absolute));
// Create brushes from our surfaces.
var imageBrush = _compositor.CreateSurfaceBrush(managedImageSurface.Surface);
var maskBrush = _compositor.CreateSurfaceBrush(managedMaskSurface.Surface);
// Create an setup our effect brush.Assign both the base image and mask image
// brushes as source parameters in the effect (with the same names we used in
// the effect definition). If we wanted, we could create many effect brushes
// and use different images in all of them.
var effectBrush = _effectFactory.CreateBrush();
effectBrush.SetSourceParameter("image", imageBrush);
effectBrush.SetSourceParameter("mask", maskBrush);
// All that's left is to create a visual, assign the effect brush to the Brush
// property, and attach it into the tree...
var visual = _compositor.CreateSpriteVisual();
visual.Size = new Vector2(MaskH, MaskW);
visual.Offset = new Vector3(0, 300, 0);
visual.Brush = effectBrush;
ElementCompositionPreview.SetElementChildVisual(this, visual);
}
I have this foreach loop that changes the Image's Source for four images (in a Xamarin Forms app):
int i = 0;
Device.StartTimer(TimeSpan.FromMilliseconds(50), () =>
{
i ++;
foreach (var img in imgs)
{
img.Source = $"radial{i}.png";
}
if (i == 5)
i = 0;
return true;
});
but the result is that the four images are not changing simultaneously and the animation is not smooth. Here the grid is divided to four quarters, each quarter has its own image view:
When I use FromSeconds(1) instead of FromMilliseconds(200), the animation become more smooth
These are the images I use, I rotate them when I want to draw a complete circle:
you can try using System.Collections.Concurrent and call your loop using
Parallel.ForEach(imgs, img => {
img.Source = $"radial{i}.png";
});
However judging by what you want to achieve, I'd rather create the animation programmatically with the SkiaSharp drawing plugin.
I'm writing a 2D game in the latest XNA (C#). I'm doing a bit of an overhaul of old code and have just put back in buttons to change resolution. However, after either fullscreen mode being toggled or the window size/resolution being changed the screen is just black (the color of my clear screen call) forever, although the game still runs, as I can quit it with a keypress.
This didn't happen in my previous version - it all worked fine - but there's no difference in the relevant code I can find. However I did redo my graphics loading to use my own Texture loader instead of the Content Loader - could this be the issue?
If no other option, is there a simple way to make the game restart, as the graphics are ok and in the selected format after a restart?
My code is:
public override void Click()
{
base.Click();
Settings.Default.screenWidth = resolution[0];
Settings.Default.screenHeight = resolution[1];
Settings.Default.Save();
Variables.screenWidth = Settings.Default.screenWidth;
Variables.screenHeight = Settings.Default.screenHeight;
Game1.graphics.PreferredBackBufferWidth = Variables.screenWidth;
Game1.graphics.PreferredBackBufferHeight = Variables.screenHeight;
Game1.graphics.ApplyChanges();
}
Thanks!
Edit: My texture loadign code - loads all files with a name included in an enum as Texture2Ds..
public static void LoadAll(string subFolder)
{
List<string> s = Directory.GetFiles(path + "\\" + subFolder, "*.png", SearchOption.AllDirectories).ToList<string>();
foreach (string S in s)
{
if (Enum.IsDefined(typeof(T), Path.GetFileNameWithoutExtension(S)))
{
FileStream stream = new FileStream(S, FileMode.Open);
Texture2D t = Texture2D.FromStream(Game1.graphics.GraphicsDevice, stream);
RenderTarget2D result = null;
//Setup a render target to hold our final texture which will have premulitplied alpha values
result = new RenderTarget2D(Game1.graphics.GraphicsDevice, t.Width, t.Height);
Game1.graphics.GraphicsDevice.SetRenderTarget(result);
Game1.graphics.GraphicsDevice.Clear(Microsoft.Xna.Framework.Color.Black);
//Multiply each color by the source alpha, and write in just the color values into the final texture
BlendState blendColor = new BlendState();
blendColor.ColorWriteChannels = ColorWriteChannels.Red | ColorWriteChannels.Green | ColorWriteChannels.Blue;
blendColor.AlphaDestinationBlend = Blend.Zero;
blendColor.ColorDestinationBlend = Blend.Zero;
blendColor.AlphaSourceBlend = Blend.SourceAlpha;
blendColor.ColorSourceBlend = Blend.SourceAlpha;
Game1.spriteBatch.Begin(SpriteSortMode.Immediate, blendColor);
Game1.spriteBatch.Draw(t, t.Bounds, Microsoft.Xna.Framework.Color.White);
Game1.spriteBatch.End();
//Now copy over the alpha values from the PNG source texture to the final one, without multiplying them
BlendState blendAlpha = new BlendState();
blendAlpha.ColorWriteChannels = ColorWriteChannels.Alpha;
blendAlpha.AlphaDestinationBlend = Blend.Zero;
blendAlpha.ColorDestinationBlend = Blend.Zero;
blendAlpha.AlphaSourceBlend = Blend.One;
blendAlpha.ColorSourceBlend = Blend.One;
Game1.spriteBatch.Begin(SpriteSortMode.Immediate, blendAlpha);
Game1.spriteBatch.Draw(t, t.Bounds, Microsoft.Xna.Framework.Color.White);
Game1.spriteBatch.End();
//Release the GPU back to drawing to the screen
Game1.graphics.GraphicsDevice.SetRenderTarget(null);
t = result;
textureDictionary.Add(Path.GetFileNameWithoutExtension(S), t);
}
// else
// Console.WriteLine("Did not load -- " + Path.GetFileNameWithoutExtension(S) + " -- (add to enum to enable loading)");
}
}
Edit: The working code from following below advice - probably not the most efficient but it works!
public static void LoadAll(string subFolder)
{
List<string> s = Directory.GetFiles(path + "\\" + subFolder, "*.png", SearchOption.AllDirectories).ToList<string>();
foreach (string S in s)
{
if (Enum.IsDefined(typeof(T), Path.GetFileNameWithoutExtension(S)))
{
FileStream stream = new FileStream(S, FileMode.Open);
Texture2D t = Texture2D.FromStream(Game1.graphics.GraphicsDevice, stream);
RenderTarget2D result = null;
Texture2D resultTexture;
//Setup a render target to hold our final texture which will have premulitplied alpha values
result = new RenderTarget2D(Game1.graphics.GraphicsDevice, t.Width, t.Height);
Game1.graphics.GraphicsDevice.SetRenderTarget(result);
Game1.graphics.GraphicsDevice.Clear(Microsoft.Xna.Framework.Color.Black);
//Multiply each color by the source alpha, and write in just the color values into the final texture
BlendState blendColor = new BlendState();
blendColor.ColorWriteChannels = ColorWriteChannels.Red | ColorWriteChannels.Green | ColorWriteChannels.Blue;
blendColor.AlphaDestinationBlend = Blend.Zero;
blendColor.ColorDestinationBlend = Blend.Zero;
blendColor.AlphaSourceBlend = Blend.SourceAlpha;
blendColor.ColorSourceBlend = Blend.SourceAlpha;
Game1.spriteBatch.Begin(SpriteSortMode.Immediate, blendColor);
Game1.spriteBatch.Draw(t, t.Bounds, Microsoft.Xna.Framework.Color.White);
Game1.spriteBatch.End();
//Now copy over the alpha values from the PNG source texture to the final one, without multiplying them
BlendState blendAlpha = new BlendState();
blendAlpha.ColorWriteChannels = ColorWriteChannels.Alpha;
blendAlpha.AlphaDestinationBlend = Blend.Zero;
blendAlpha.ColorDestinationBlend = Blend.Zero;
blendAlpha.AlphaSourceBlend = Blend.One;
blendAlpha.ColorSourceBlend = Blend.One;
Game1.spriteBatch.Begin(SpriteSortMode.Immediate, blendAlpha);
Game1.spriteBatch.Draw(t, t.Bounds, Microsoft.Xna.Framework.Color.White);
Game1.spriteBatch.End();
//Release the GPU back to drawing to the screen
Game1.graphics.GraphicsDevice.SetRenderTarget(null);
resultTexture = new Texture2D(Game1.graphics.GraphicsDevice, result.Width, result.Height);
Color[] data = new Color[result.Height * result.Width];
Color[] textureColor = new Color[result.Height * result.Width];
result.GetData<Color>(textureColor);
resultTexture.SetData(textureColor);
textureDictionary.Add(Path.GetFileNameWithoutExtension(S), resultTexture);
}
// else
// Console.WriteLine("Did not load -- " + Path.GetFileNameWithoutExtension(S) + " -- (add to enum to enable loading)");
}
}
As per your updated code, you're copying textures into a render target (RenderTarget2D).
Unlike regular textures, these are not backed by CPU-side memory. Regular textures will automatically re-set their data on the GPU when the graphics device is lost. A render target, however, will raise its ContentLost event and set IsContentLost - you are then expected to reset its content yourself!
There are a few solutions: You could simply respond to ContentLost and refresh the data.
I prefer to use GetData and SetData at load time to copy the contents of the render target into a regular Texture2D, because this "just works". Although for simple cases like switching a texture to use premultiplied alpha, I prefer to just keep everything on the CPU.
I've got a very detailed answer about RenderTarget2D usage for fixed textures over here. It includes code to do premultiplication directly on the CPU.
Although in your case, I'd try and use the content manager. It is possible to have it process all files in a directory and then load them dynamically.