Open olegbaslak opened 1 year ago
It does not work for Java bindings too. Looks like the problem may be in the Skia code.
I think i ran into the same problem.
I am using InputFile to offer the user the option to pick a file from his local client-site filesystem.
After has been picked, the OnChange
-Event is fired and after all conditions are met, stream is read by the event handler PickFileAsync
.
Stream lengths have been checked to make sure the streams contain all bytes of the file.
SKBitmap.Decode
returns null, when passing the memorystream.
I have been using simple .png files with QR-Codes as content, like this one:
@using SkiaSharp.Views.Blazor
@using SkiaSharp;
<div>
@*InputFile is of type Microsoft.AspNetCore.Components.Forms.IBrowserFile.InputFile*@
<InputFile OnChange="PickFileAsync"></InputFile>
</div>
<div>
<SKCanvasView @ref="_canvasView" OnPaintSurface="PaintSurface" width="@(_bitmap?.Width ?? 0)" height="@(_bitmap?.Height ?? 0)"></SKCanvasView>
</div>
@code {
private SKCanvasView? _canvasView;
private SKBitmap? _bitmap;
private async Task PickFileAsync(InputFileChangeEventArgs e)
{
if (e.FileCount > 1)
{
Console.WriteLine("To many files selected!");
return;
}
var allowedContentType = "image/png";
if (e.File.ContentType != allowedContentType)
{
Console.WriteLine($"File ContentType '{e.File.ContentType}' not supported! Pick an image of ContentType {allowedContentType}");
return;
}
if (_bitmap != null)
{
_bitmap.Dispose();
_bitmap = null;
}
//File is of type Microsoft.AspNetCore.Components.Forms.IBrowserFile
using var stream = e.File.OpenReadStream();
Console.WriteLine(stream.Length); //returns correct byte size of the file.
using var memoryStream = new MemoryStream();
await stream.CopyToAsync(memoryStream);
Console.WriteLine(memoryStream.Length); //returns correct byte size of the file.
//_bitmap is null after SKBitmap.Decode returns.
this._bitmap = SKBitmap.Decode(memoryStream);
this._canvasView?.Invalidate();
}
public void PaintSurface(SKPaintSurfaceEventArgs e)
{
if (this._bitmap == null)
{
return;
}
var canvas = e.Surface.Canvas;
canvas.Clear();
canvas.DrawBitmap(this._bitmap, new SKPoint(0, 0));
}
}
The position of the pointer of the stream has already been at 0. I tried setting the position to 0 anyway, just to make sure that this is not the same problem as in Issue https://github.com/mono/SkiaSharp/issues/640#issuecomment-711481285.
//Did not help.
memoryStream.Seek(0, SeekOrigin.Begin);
memoryStream.Position = 0;
I checked, if SKData.Create
may be the point of failure, but it returned an object.
//skData is not null, but I am absolutley not sure, what the content should look like.
var skData = SKData.Create(memoryStream);
Workaround
In my case, I am able to workaround this problem using byte-arrays as shown in the following example.
using var stream = e.File.OpenReadStream();
using var memoryStream = new MemoryStream();
await stream.CopyToAsync(memoryStream);
var byteArray = memoryStream.ToArray();
this._bitmap = SKBitmap.Decode(byteArray);
this._canvasView?.Invalidate();
It is somehow weird that SKBitmap.Decode
behaves differently for byte-arrays and streams.
Error Handling
It is kind of weird to return null to indicate errors, as the API-Description hints The decoded bitmap, or null on error.
.
This makes it very hard to find the true problem.
SkiaSharpResult<SKBitmap>
or SKBitmapDecodeResult<SKBitmap>
. The result could contain a boolean like IsSuccess
or IsError
and nullable properties for error messages and error codes and a property of the result type, which can be null on error or not null on success.Basic Information
@JanKotschenreuther SKBitmap.Decode(memoryStream);
overload for streams simply wraps it if it's seekable. So you create the SKBitmap this._bitmap
but before using it you dispose the stream(it's wrapped in using statement) making SKBitmap unusable. This explains why "the workaround" with byte arrays works
As far as I know, the using statement I am using there, is scoped to the scope of the method.
Using statements without braces and termination by ;
are disposed at the end of the scope in which they are declared.
I know how 'using' is scoped. Are you sure though thatthis._canvasView?.Invalidate();
is causing synchronous access to the this._bitmap
field before the nethod ends? Because I'm pretty sure it does not.
I do not know, if the method SKCanvasView.Invalidate
is doing anything asynchronous.
The name, parameters or return-types are not telling me it is using any of the well known asynchronous patterns. I also were not able to find any docs telling me that it could be an asynchronous operation.
This is strange, either something wrong with Bitmap Data creation, OnPaint event scope or something that isn't properly documented. I was getting the same issue in a slightly different scenario.
Platform: WebAssembly Problem statement: SKBitmap.Decode on simple byte[] within an OnPaint of the blazor view, bitmap is returned non-null. Image info looks right too (Height, Width, DPI etc). It just doesn't render rightaway in the same OnPaint call. What didn't work. Switching from SKGLView to SKCanvasView. The only difference was the error shown in console. SKGLView showed a warning of failed image drawing, no info about memory access. SKCanvasView displayed a error with memory access exception. try/catch didn't work either and the OnPaint event returned rightaway. What partially worked: Cacheing the bitmap in global scope and rendering in any coming OnPaint was always successful. Calling the Bitmap Cache routine in mouse events instead of OnPaint Wrong Suspect My initial suspect was some thread sync issue in WebGL, but there something must be wrong with OnPaint Root Cause: Unknown Resolve: Double Buffering. Keep a buffer of SKBitmap, the same size as the the canvas. Render on the buffer outside OnPaint. Draw on OnPaint request only if the buffer has been prepared. Works like a charm. I only have to keep in mind one extra refresh/invalidate request due to an added layer of buffer.
Description
Cannot create Bitmap or Codec of a specific jpg image (attached).
The image is not corrupted, it can be opened in Windows Photos and other image viewers, it can be displayed by browser.
Looks similar to https://github.com/mono/SkiaSharp/issues/1621 and https://github.com/mono/SkiaSharp/issues/1551. However, this workaround does not work:
Code
Expected Behavior
Bitmap and codec are created from a stream.
Actual Behavior
Bitmap and codec creation returns
null
.Basic Information
Screenshots
Reproduction Link
image.zip