Skip to content

Commit

Permalink
Added PNG download sample.
Browse files Browse the repository at this point in the history
  • Loading branch information
KristofferStrube committed Feb 18, 2024
1 parent 40e2486 commit 20d5289
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 29 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ jobs:
- uses: actions/checkout@v2

# Install .NET SDK
- name: Setup .NET 7 preview
- name: Setup .NET 8
uses: actions/setup-dotnet@v1
with:
dotnet-version: '7.0.x'
dotnet-version: '8.0.x'
include-prerelease: true

# Add Nuget.Config to solution
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">

<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<RunAOTCompilation>true</RunAOTCompilation>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<None Remove="Pages\CustomElements.razor.css" />
</ItemGroup>

<ItemGroup>
<Content Include="Pages\CustomElements.razor.css" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="KristofferStrube.Blazor.FileAPI" Version="0.3.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="7.0.2" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="7.0.2" PrivateAssets="all" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.2" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.2" PrivateAssets="all" />
<PackageReference Include="SkiaSharp.Svg" Version="1.60.0" />
<PackageReference Include="SkiaSharp.Views.Blazor" Version="2.88.7" />
</ItemGroup>

<ItemGroup>
Expand Down
123 changes: 108 additions & 15 deletions samples/KristofferStrube.Blazor.SVGEditor.WasmExample/Pages/Save.razor
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,32 @@
@using System.Reactive.Subjects;
@using System.Reactive.Linq;
@using KristofferStrube.Blazor.FileAPI;
@using System.Text
@using SkiaSharp
@implements IAsyncDisposable
@inject IURLServiceInProcess URL
@inject IJSRuntime JSRuntime

<PageTitle>Save SVG with SVGEditor</PageTitle>

@if (SaveLink is null)
{
<buttton class="btn btn-warning btn-md" @onclick="GenerateSaveLink">Generate Save Link</buttton>
}
else
{
<a download="BlazorSVG.svg" href="@SaveLink"><button disabled="@(SaveLink is null)" @onclick="LinkPressed" class="btn btn-success btn-md">Save</button></a>
}
<br />
<div style="display:flex;flex-direction:row;grid-gap: 5px;margin-bottom:5px;">
@if (SvgSaveLink is null && PngSaveLink is null)
{
<buttton class="btn btn-warning btn-md" @onclick="GenerateSaveLink">Generate Save Links</buttton>
}
else
{
@if (SvgSaveLink is not null)
{
<a download="BlazorGenerated.svg" href="@SvgSaveLink"><button class="btn btn-success btn-md">Save as SVG</button></a>
}
@if (PngSaveLink is not null)
{
<a download="BlazorGenerated.png" href="@PngSaveLink"><button class="btn btn-success btn-md">Save as PNG using SkiaSharp</button></a>
}
<button class="btn btn-warning btn-md" @onclick=Reset>Reset</button>
}
</div>
<textarea @bind="Input" @bind:event="oninput" style="width:100%;height:15vh;" />
<div style="height:75vh">
<SVGEditor @ref=@SVG Input=@Input InputUpdated="(string s) => { Input = s; StateHasChanged(); }" SelectionMode="SelectionMode.WindowSelection" />
Expand All @@ -24,7 +36,9 @@ else
@code {
public required SVGEditor SVG;
protected string Input = @"<path d=""M 250 170 c -39 37 -111 35 -136 -1 s -14 -74 2 -93 s 43 -28 63 -27 s 51 -2 67 -23 c -6 18 -7 18 -12 33 c 28 -8 37 -12 52 -43 c 28 49 1 96 -20 120 s -47 33 -84 36 s -58 -23 -57 -53 s 29 -46 51 -46 s 46 24 47 45 s -8 20 -12 20 s -10 -2 -10 -22 s -2 -26 -18 -27 s -43 10 -42 30 s 12 33 29 34 s 18 -5 23 -12 c 9 15 20 10 27 8 s 18 -17 15 -40 s -36 -49 -59 -49 s -65 17 -66 63 s 51 89 138 45 z m -61 -46 v -20 h -20 c -10 0 -18 5 -17 20 s 10 15 15 16 s 22 0 22 -15 z"" fill=""purple""></path>";
protected string? SaveLink;

protected string? SvgSaveLink;
protected string? PngSaveLink;

private async Task GenerateSaveLink()
{
Expand All @@ -40,18 +54,97 @@ else
var width = maxX - minX;
var height = maxY - minY;

Blob blob = await Blob.CreateAsync(
var svgText = $@"<svg xmlns=""http://www.w3.org/2000/svg"" viewBox=""{(minX - 10).AsString()} {(minY - 10).AsString()} {(width + 20).AsString()} {(height + 20).AsString()}"" width=""{width + 20}"" height=""{height + 20}"">{Input}</svg>";

using MemoryStream svgStream = new(Encoding.ASCII.GetBytes(svgText));
using MemoryStream imageStream = new();

var svg = new SkiaSharp.Extended.Svg.SKSvg();
svg.Load(svgStream);

var imageInfo = new SKImageInfo((int)width + 20, (int)height + 20);
using (var surface = SKSurface.Create(imageInfo))
using (var canvas = surface.Canvas)
{
// draw the svg
canvas.Clear(SKColors.Transparent);
canvas.DrawPicture(svg.Picture);
canvas.Flush();

using (var data = surface.Snapshot())
using (var pngImage = data.Encode(SKEncodedImageFormat.Png, 100))
{
pngImage.SaveTo(imageStream);
}
}
imageStream.Position = 0;

Blob svgBlob = await Blob.CreateAsync(
JSRuntime,
new List<BlobPart>() {
svgText
}
);

Blob pngBlob = await Blob.CreateAsync(
JSRuntime,
new List<BlobPart>() {
$@"<svg xmlns=""http://www.w3.org/2000/svg"" viewBox=""{(minX - 10).AsString()} {(minY - 10).AsString()} {(width + 20).AsString()} {(height + 20).AsString()}"" width=""400"" height=""400"">{Input}</svg>"
ReadFully(imageStream)
}
);
SaveLink = URL.CreateObjectURL(blob);

if (SvgSaveLink is not null)
{
await URL.RevokeObjectURLAsync(SvgSaveLink);
}
SvgSaveLink = URL.CreateObjectURL(svgBlob);

if (PngSaveLink is not null)
{
await URL.RevokeObjectURLAsync(PngSaveLink);
}
PngSaveLink = URL.CreateObjectURL(pngBlob);
}
}

private void LinkPressed()
public static byte[] ReadFully(Stream input)
{
SaveLink = null;
byte[] buffer = new byte[16 * 1024];
using (MemoryStream ms = new MemoryStream())
{
int read;
while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
{
ms.Write(buffer, 0, read);
}
return ms.ToArray();
}
}

private async Task Reset()
{
if (PngSaveLink is not null)
{
await URL.RevokeObjectURLAsync(PngSaveLink);
}
PngSaveLink = null;

if (SvgSaveLink is not null)
{
await URL.RevokeObjectURLAsync(SvgSaveLink);
}
SvgSaveLink = null;
}

public async ValueTask DisposeAsync()
{
if (SvgSaveLink is not null)
{
await URL.RevokeObjectURLAsync(SvgSaveLink);
}
if (PngSaveLink is not null)
{
await URL.RevokeObjectURLAsync(PngSaveLink);
}
}
}

0 comments on commit 20d5289

Please sign in to comment.