Delayed rendering of the image - Skia ASP - c#

I'm trying to draw a set of images with SkiaSharp, convert them to byte array, and send to HttpContext stream. For some reason, every image is being displayed in the browser only when the next image has been rendered and sent to the HTTP stream.
After rendering the 1st image, browser shows empty white area, no image
After rendering the 2nd image, browser shows 1st image
After rendering the 3rd image, browser shows 2nd image, and so on
Here is the Web API controller. Uncommenting second drawShapes call will make it work, but I would like to understand why image rendering is delayed. Maybe I just don't call some important method, like Flush or something similar?
Also, this is not an ASP issue, because I tried rendering images with System.Drawing.Common and images were rendered in real time without delay.
Does anybody know what is missing in this method?
[Route("/source")]
public async Task Get()
{
// Initialize Skia
var res = new byte[1000];
var boundary = Guid.NewGuid().ToString();
var generator = new Random();
var map = new SKBitmap(250, 250);
var canvas = new SKCanvas(map);
var inPaint = new SKPaint
{
Color = SKColors.Black,
Style = SKPaintStyle.Fill,
FilterQuality = SKFilterQuality.High
};
var exPaint = new SKPaint
{
Color = SKColors.White,
Style = SKPaintStyle.Fill,
FilterQuality = SKFilterQuality.Low
};
// Create HTTP stream
Response.ContentType = "multipart/x-mixed-replace;boundary=" + boundary;
var outputStream = Response.Body;
var cancellationToken = Request.HttpContext.RequestAborted;
// Create method posting images to HTTP stream
async Task drawShapes()
{
using (var image = SKImage.FromBitmap(map))
{
var pos = generator.Next(50, 150);
canvas.DrawRect(0, 0, 250, 250, exPaint);
canvas.DrawCircle(pos, pos, 20, inPaint);
res = image.Encode(SKEncodedImageFormat.Webp, 100).ToArray();
}
var header = $"--{ boundary }\r\nContent-Type: image/webp\r\nContent-Length: { res.Length }\r\n\r\n";
var headerData = Encoding.ASCII.GetBytes(header);
await outputStream.WriteAsync(headerData);
await outputStream.WriteAsync(res);
await outputStream.WriteAsync(Encoding.ASCII.GetBytes("\r\n"));
}
// Start HTTP stream
await Task.Run(async () =>
{
//while (true)
{
await drawShapes();
//await drawShapes(); // After uncommenting this shows the image (the previous one)
}
});
}
Reproducible sample

This was answered in SkiaSharp discussions.
https://github.com/mono/SkiaSharp/discussions/1975
Possible solutions are to make sure to Encode image only after all drawings are done or to call Encode on bitmap instead of image.
The simplest one is to replace this line.
//res = image.Encode(SKEncodedImageFormat.Webp, 100).ToArray(); // wrong
res = map.Encode(SKEncodedImageFormat.Webp, 100).ToArray(); // correct

Related

How to get image itselfs?

I'm making xamarin apps application and I want to edit images with it. How can I get image to make some work with it? (I want to edit some pixels)
var result = await MediaPicker.PickPhotoAsync(new MediaPickerOptions()
{
Title = "Выберите изображение" //Eng: Pick image
});
var stream = await result.OpenReadAsync();
image.Source = ImageSource.FromStream(() => stream);

How to save media element's current frame?

I am developing a media player like vlc. One of it's features is saving snapshot during playback. I am able to successfully save the snapshot of any element using rendertargetbitmap class, using the following c# code.
RenderTargetBitmap renderTargetBitmap = new RenderTargetBitmap();
await renderTargetBitmap.RenderAsync(RenderedGrid, width, height);
RenderedImage.Source = renderTargetBitmap;
But it does not allow to capture media element. Please help me with a workaround. I guess, I am not sure, may it be possible by extracting a thumbnail from a specific position using position of StorageItemThumbnail Class. If even so, it will be a thumbnail not an image of current frame. Someone help please. It is essential feature of my app!
in a digital signage uwp app that I developed I had also a similar requirement. Searching the web on how I can do I found that in UWP you can't directly take a screenshot of a media element due to a protection policy (for example you could create a small app that captures all the frames of a video file without DRM management).
I came up with a workaround: I have the video file locally so I can extract a single frame at a certain point and save it to disk with the same dimensions of the video rendered on screen (in this way I had a full image of the frame to use later). To doing this I wrote this function:
private async Task<string> Capture(StorageFile file, TimeSpan timeOfFrame, Size imageDimension)
{
if (file == null)
{
return null;
}
//Get FrameWidth & FrameHeight
List<string> encodingPropertiesToRetrieve = new List<string>();
encodingPropertiesToRetrieve.Add("System.Video.FrameHeight");
encodingPropertiesToRetrieve.Add("System.Video.FrameWidth");
IDictionary<string, object> encodingProperties = await file.Properties.RetrievePropertiesAsync(encodingPropertiesToRetrieve);
uint frameHeight = (uint)encodingProperties["System.Video.FrameHeight"];
uint frameWidth = (uint)encodingProperties["System.Video.FrameWidth"];
//Get image stream
var clip = await MediaClip.CreateFromFileAsync(file);
var composition = new MediaComposition();
composition.Clips.Add(clip);
var imageStream = await composition.GetThumbnailAsync(timeOfFrame, (int)frameWidth, (int)frameHeight, VideoFramePrecision.NearestFrame);
//Create BMP
var writableBitmap = new WriteableBitmap((int)frameWidth, (int)frameHeight);
writableBitmap.SetSource(imageStream);
//Get stream from BMP
string mediaCaptureFileName = "IMG" + Guid.NewGuid().ToString().Substring(0, 4) + ".jpg";
var saveAsTarget = await CreateMediaFile(mediaCaptureFileName);
Stream stream = writableBitmap.PixelBuffer.AsStream();
byte[] pixels = new byte[(uint)stream.Length];
await stream.ReadAsync(pixels, 0, pixels.Length);
using (var writeStream = await saveAsTarget.OpenAsync(FileAccessMode.ReadWrite))
{
var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.JpegEncoderId, writeStream);
encoder.SetPixelData(
BitmapPixelFormat.Bgra8,
BitmapAlphaMode.Premultiplied,
(uint)writableBitmap.PixelWidth,
(uint)writableBitmap.PixelHeight,
96,
96,
pixels);
await encoder.FlushAsync();
using (var outputStream = writeStream.GetOutputStreamAt(0))
{
await outputStream.FlushAsync();
}
}
return saveAsTarget.Name;
}
This function save the frame to disk as image and return his filename.
In my application I had more "widget" (some of those with images, some with video) so I used this function to get the video frames and put a new image on top of the media element with the same dimensions and a higher Canvas Z-Index in order to use RenderTargetBitmap for take the final screenshot.
It's not a clean solution but it was a workaround for RenderTargetBitmap not considering MediaElement.
Edit:
I forgot a reference to an internal function in my application so I canghed the previous code and the line var file = await _mediaEngine.CreateMediaFile($"{Guid.NewGuid().ToString()}.jpg"); is now var file = await CreateMediaFile($"{Guid.NewGuid().ToString()}.jpg");
The async function CreateMediaFile(string fileName) simply create a new file on the disk:
public async Task<StorageFile> CreateMediaFile(string filename)
{
StorageFolder _mediaFolder = KnownFolders.PicturesLibrary;
return await _mediaFolder.CreateFileAsync(filename);
}

Convert JPG to PNG with background transparency using ImageMagick.Net

I need to convert a JPG image to PNG and change its white background to transparent instead. I am using ImageMagick.NET and I have found the following ImageMagick command that is supposed to do what I am trying to achieve:
convert image.jpg -fuzz XX% -transparent white result.png
I have tried converting this to c# but all I am getting is a png image with a white background. My code snippet:
using (var img = new MagickImage("image.jpg"))
{
img.Format = MagickFormat.Png;
img.BackgroundColor = MagickColors.White;
img.ColorFuzz = new Percentage(10);
img.BackgroundColor = MagickColors.None;
img.Write("image.png");
}
Any kind of help will be greatly appreciated. Thank you!!
This is a late response as it took me a while to find an answer myself, but this seems to work for me quite well. Look at where the Background property is assigned the Transparent value.
using (var magicImage = new MagickImage())
{
var magicReadSettings = new MagickReadSettings
{
Format = MagickFormat.Svg,
ColorSpace = ColorSpace.Transparent,
BackgroundColor = MagickColors.Transparent,
// increasing the Density here makes a larger and sharper output to PNG
Density = new Density(950, DensityUnit.PixelsPerInch)
};
magicImage.Read("someimage.svg", magicReadSettings);
magicImage.Format = MagickFormat.Png;
magicImage.Write("someimage.png");
}
In my case, I wanted to send this to UWP Image element, so instead of Write(), I did the following after the steps above:
// Create byte array that contains a png file
byte[] imageData = magicImage.ToByteArray();
using (InMemoryRandomAccessStream stream = new InMemoryRandomAccessStream())
{
using (DataWriter writer = new DataWriter(stream.GetOutputStreamAt(0)))
{
writer.WriteBytes(imageData);
await writer.StoreAsync();
}
await bitmapImage.SetSourceAsync(stream);
}
return bitMapImage; // new BitMapImage() was scoped before all of this
Then on the UWP Image element, simply use:
imageElement.Source = bitMapImage;
Most of the arguments on the command line are either properties or method on the MagickImage class. Your command would translate to this:
using (var img = new MagickImage("image.jpg"))
{
// -fuzz XX%
img.ColorFuzz = new Percentage(10);
// -transparent white
img.Transparent(MagickColors.White);
img.Write("image.png");
}

Asynchronously Load Image from URL into ImageView in Xamarin Android

I have a ListView of multiple items and each item in the list should have an image associated with it. I have created an Array Adapter to hold each list item and have the url of the image I wish to load.
I am trying to asynchronously load the image using a web request and have it set the image and update it in the view once it has been loaded but the view should render immediatly with a default image.
Here is the method I am using to try to load the image
private async Task<Bitmap> GetImageBitmapFromUrl(string url, ImageView imageView)
{
Bitmap imageBitmap = null;
try
{
var uri = new Uri(url);
var response = httpClient.GetAsync(uri).Result;
if (response.IsSuccessStatusCode)
{
var imageBytes = await response.Content.ReadAsByteArrayAsync();
if (imageBytes != null && imageBytes.Length > 0)
{
imageBitmap = BitmapFactory.DecodeByteArray(imageBytes, 0, imageBytes.Length);
imageView.SetImageBitmap(imageBitmap);
}
}
}
catch (Exception e)
{
Console.WriteLine(e);
}
return imageBitmap;
}
I am initializing my HttpClient like so:
httpClient = new HttpClient();
httpClient.Timeout = new TimeSpan(0, 0, 10);
httpClient.MaxResponseContentBufferSize = 256000;
And here is how I am calling the GetImageFromUrl Method
ImageView myImage = convertView.FindViewById<ImageView>(Resource.Id.AsyncImageView)
myImage.SetImageBitmap(null);
string url = GetItem(position);
GetImageBitmapFromUrl(url, myImage);
Using this code the request eventually comes back with a bad request but when I view the url I am using and paste it into a browser window it brings up the image I am expecting.
You don't need to care about downloading images.
There exist two good libraries to load an image into a ImageView async in Xamarin.Android.
Picasso component (source) usage:
Picasso.With(context)
.Load("http://i.imgur.com/DvpvklR.png")
.Into(imageView);
FFImageLoading (source) usage:
ImageService.Instance.LoadUrl(urlToImage).Into(_imageView);
Note (from documentation):
Unlike Picasso you cannot use FFImageLoading with standard ImageViews.
Instead simply load your images into ImageViewAsync instances.
Updating your code is very easy since ImageViewAsync inherits from
ImageView.

Magick.Net resize image

I have some code to convert an image, which is working now, but the width of the image that is generated is really small. I would like to force it to use a width of 600px.
My code looks like this:
public async Task<string> ConvertImage(byte[] data)
{
// Create our settings
var settings = new MagickReadSettings
{
Width = 600
};
// Create our image
using (var image = new MagickImage(data, settings))
{
// Create a new memory stream
using (var memoryStream = new MemoryStream())
{
// Set to a png
image.Format = MagickFormat.Png;
image.Write(memoryStream);
memoryStream.Position = 0;
// Create a new blob block to hold our image
var blockBlob = container.GetBlockBlobReference(Guid.NewGuid().ToString() + ".png");
// Upload to azure
await blockBlob.UploadFromStreamAsync(memoryStream);
// Return the blobs url
return blockBlob.StorageUri.PrimaryUri.ToString();
}
}
}
The image I have uploaded is an AI file, but when it gets converted it is only 64px wide.
Does anyone know why and how I can fix it?
With the current version of Magick.NET this is the expected behavior. But after seeing your post we made some changes to ImageMagick/Magick.NET. With Magick.NET 7.0.0.0103 and higher you will get an image that fits inside the bounds that you specify with Width and Height. So when you specify a Width of 600 you will get an image that is 600 pixels wide.

Categories