When resizing image files in Blazor Web Assembly, we can use an extension method on the IBrowserFile
type
which represents a file presented to a file upload control in a Blazor
application,
RequestImageFileAsync
. This method was introduced in .NET 5, so it won't work in applications built using .NET 3.2 (if, indeed, any still exist).
The method takes the file format (content type), a maximum width, and maximum height as arguments. Internally, the method utilises the JavaScript layer to perform image resizing. Specifically, it uses the HTML5 canvas API. It compares the actual dimensions of the image to the maximum width and height arguments, and applies the lower ratio to both dimensions when resizing, thereby retaining the original proportions of the image.
The following code features a Razor component that acts as a navigable page. It includes an
InputFile
component that has an
accept
attribute applied to limit the file types available to the
native file picker. This is a convenience to the user and should not be relied
upon to validate file types being uploaded. When the contents of the file upload control change, the
HandleChange
callback is invoked. This validates that the submitted file
is actually an image file by checking its content type with an array of
permitted values and then the RequestImageFileAsync
method is invoked on it. In this example, I
have decided that the maximum width of any image shall be 300px. They can be any
height, hence int.MaxValue
is passed to the maxHeight
parameter.
@page "/resize-image-demo" @inject HttpClient http <h3>Resize Image</h3> <InputFile OnChange="HandleChange" accept=".jpg,.png" /> @code { string[] imageTypes = new[] { "image.jpeg", "image/png" }; async Task HandleChange(InputFileChangeEventArgs e) { if (imageTypes.Contains(e.File.ContentType)) { var resized = await e.File.RequestImageFileAsync(e.File.ContentType, 300, int.MaxValue); var formContent = new MultipartFormDataContent { { new StreamContent(e.File.OpenReadStream(e.File.Size)), "upload", e.File.Name }, { new StringContent(e.File.ContentType), "content-type" } }; await http.PostAsync("/upload", formContent); } } }
When using the
RequestImageFileAsync
method, all files will be processed regardless of their original size and type. If a
file type that is not supported by your browser's implementation of canvas is passed to the method, the application crashes quietly
(in my testing).
If an uploaded image's original width is 300px or less, it is still processed
and a new image of the same size is returned. Rather than submit every file to
the resizing process regardless of size, you might want to check the image
dimensions to see if they exceed your maximum, so how do you obtain the image
file's width and height?
Obtaining image dimensions
You might consider using a cross-platform C# image library in your WASM
project such as ImageSharp,
and although I found that simply retrieving the image dimensions works fine
using this library,
ImageSharp is
not recommended for use in a Blazor WASM environment. Instead, you can use
JavaScript interop to make use of the browser APIs instead. The following
JavaScript code is written as a module so that we can take advantage of
JavaScript isolation support in Blazor. The function takes a stream as a
parameter and uses it to create a Blob, from which an object URL is contructed.
The object URL is assigned as the src
of an Image
. We
hook into the image's onload
event and obtain the dimensions of the
loaded image. We use a Promise
to resolve the return value of
the image's onload
event handler and serialise that to JSON. We save this module as fileSize.js in the wwwroot/js folder.
export async function getImageDimensions(content) { const url = URL.createObjectURL(new Blob([await content.arrayBuffer()])) const dimensions = await new Promise(resolve => { const img = new Image(); img.onload = function () { const data = { Width: img.naturalWidth, Height: img.naturalHeight }; resolve(data); }; img.src = url; }); return JSON.stringify(dimensions); }
Next, we need to alter our component to work with an injected
IJSRuntime
. We also need to add a using
directive for
System.Text.Json
. We add an IJSObjectReference
field representing a reference to the JavaScript module and instantiate that the first time that the component is rendered.
We also add a type representing the image and width of an image. This is
declared as a record
type named ImageDimensions
.
In the HandleChange
event, we pass the uploaded file content as a
DotNetStreamReference
to the JS module. We deserialise the returned value as an instance of
ImageDimensions
and check to see if the width is over 300. Only if it is do we resize the image.
@page "/image-file-tests" @using System.Text.Json @inject IJSRuntime JS @inject HttpClient http <h3>Image File Tests</h3> <InputFile OnChange="HandleChange" /> @code { IJSObjectReference module; protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { module = await JS.InvokeAsync<IJSObjectReference>("import","./js/fileSize.js"); } } async Task HandleChange(InputFileChangeEventArgs e) { var file = e.File; var streamRef = new DotNetStreamReference(file.OpenReadStream(file.Size)); var json = await module.InvokeAsync<string>("getImageDimensions", streamRef); var dimensions = JsonSerializer.Deserialize<ImageDimensions>(json); if(dimensions.Width > 300) { file = await file.RequestImageFileAsync(file.ContentType, 300, int.MaxValue); } var formContent = new MultipartFormDataContent { { new StreamContent(file.OpenReadStream(file.Size)), "upload", file.Name }, { new StringContent(file.ContentType), "content-type" } }; await http.PostAsync("/upload", formContent); } record ImageDimensions(int Width, int Height); }
Summary
In this post, I have looked at the RequestImageFileAsync
method
that is used for resizing images in a Blazor Web Assembly app. I have also
looked at getting the dimensions of an image. I considered using ImageSharp, a
cross-platform C# image library but heeded the project team's warning against
using this library on the browser, eventually settling on using the native
browser APIs via JavaScript interop.