Skip to content

Commit

Permalink
Add command line options to change yaw and pitch angle of thumbnail. …
Browse files Browse the repository at this point in the history
…Also add animated GIF option.
  • Loading branch information
Arlorean committed Jun 2, 2023
1 parent e0ac711 commit 13b6d7a
Show file tree
Hide file tree
Showing 12 changed files with 213 additions and 47 deletions.
35 changes: 35 additions & 0 deletions Voxels.CommandLine/Animation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using NGif.Components;
using SkiaSharp;
using System.IO;
using Voxels.SkiaSharp;

namespace Voxels.CommandLine {
internal static class Animation {
public static byte[] RenderGif(VoxelData voxelData, RenderSettings renderSettings, int frames, float duration) {
var delay = (int)(duration / frames * 1000);

var encoder = new AnimatedGifEncoder();
var memory = new MemoryStream();
encoder.Start(memory);
encoder.SetDelay(delay);
//-1:no repeat,0:always repeat
encoder.SetRepeat(0);
encoder.SetTransparent(SKColors.Black);

var startAngle = renderSettings.rotationY;
var stepAngle = 360 / frames;
for (var i=0; i < frames; i++) {
renderSettings.rotationY = startAngle - stepAngle * i; // Rotate clockwise

var bytes = Renderer.RenderPng(voxelData, renderSettings);
var image = SKImage.FromBitmap(SKBitmap.Decode(bytes));
encoder.AddFrame(image);
}
renderSettings.rotationY = startAngle;

encoder.Finish();

return memory.ToArray();
}
}
}
6 changes: 3 additions & 3 deletions Voxels.CommandLine/App.config
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8"/>
</startup>
</configuration>
</configuration>
79 changes: 68 additions & 11 deletions Voxels.CommandLine/Program.cs
Original file line number Diff line number Diff line change
@@ -1,21 +1,78 @@
using System;
using System.IO;
using System.IO;
using Voxels.SkiaSharp;
using McMaster.Extensions.CommandLineUtils;
using System.ComponentModel.DataAnnotations;
using System;
using System.ComponentModel;

namespace Voxels.CommandLine {
class Program {
static void Main(string[] args) {
if (args.Length == 0) {
Console.WriteLine("usage: Voxels.CommandLine.exe voxfiles...\n Converts .vox files to .png and .svg files.");

[Option(ShortName = "w", Description = "Size in pixels.")]
public int Size { get; set; } = 512;

[Option(ShortName = "y", Description = "The yaw in degrees.")]
public float Yaw { get; set; } = 135f;

[Option(ShortName = "x", Description = "The pitch in degrees.")]
public float Pitch { get; set; } = 26f;

[Option(Description = "Output a PNG file.")]
public bool PNG { get; set; }

[Option(Description = "Output an SVG file.")]
public bool SVG { get; set; }

[Option(Description = "Output an animated GIF file.")]
public bool GIF { get; set; }

[Option(ShortName = "n", Description = "The number of frames for the animated GIF.")]
public int RotationFrames { get; set; } = 36;

[Option(ShortName = "d", Description = "The duration for the animated GIF in seconds.")]
public float RotationDuration { get; set; } = 2f;

[Argument(0, Description = "Filenames or directories to convert."), Required]
public string[] Filenames { get; set; }

public static int Main(string[] args)
=> CommandLineApplication.Execute<Program>(args);

void OnExecute() {
// If none of PNG, SVG or GIF is specified, output PNG and SVG
if (!PNG && !SVG && !GIF) {
PNG = SVG = true;
}
else {
NativeLibrary.Initialize();

foreach (var filename in args) {
var voxelData = VoxelImport.Import(filename);
// Initialize SkiaSharp
NativeLibrary.Initialize();

File.WriteAllBytes(Path.ChangeExtension(filename, ".png"), Renderer.RenderPng(512, voxelData));
File.WriteAllBytes(Path.ChangeExtension(filename, ".svg"), Renderer.RenderSvg(512, voxelData));
var renderSettings = new RenderSettings() {
size = Size,
rotationX = Math.Abs(Pitch), // Don't allow negative Pitch
rotationY = Yaw,
};
ConvertFiles(Filenames, renderSettings);
}

void ConvertFiles(string[] filenames, RenderSettings renderSettings) {
foreach (var filename in filenames) {
// Convert all files in directories
if (Directory.Exists(filename)) {
var directoryFilenames = Directory.GetFiles(filename);
ConvertFiles(directoryFilenames, renderSettings);
}
else {
var voxelData = VoxelImport.Import(filename);
if (PNG) {
File.WriteAllBytes(Path.ChangeExtension(filename, ".png"), Renderer.RenderPng(voxelData, renderSettings));
}
if (SVG) {
File.WriteAllBytes(Path.ChangeExtension(filename, ".svg"), Renderer.RenderSvg(voxelData, renderSettings));
}
if (GIF) {
File.WriteAllBytes(Path.ChangeExtension(filename, ".gif"), Animation.RenderGif(voxelData, renderSettings, RotationFrames, RotationDuration));
}
}
}
}
Expand Down
34 changes: 33 additions & 1 deletion Voxels.CommandLine/Voxels.CommandLine.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@
<OutputType>Exe</OutputType>
<RootNamespace>Voxels.CommandLine</RootNamespace>
<AssemblyName>Voxels.CommandLine</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<TargetFrameworkProfile />
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
Expand All @@ -34,8 +37,23 @@
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<ItemGroup>
<Reference Include="McMaster.Extensions.CommandLineUtils, Version=4.0.2.0, Culture=neutral, PublicKeyToken=6f71cb76b82f055d, processorArchitecture=MSIL">
<HintPath>..\packages\McMaster.Extensions.CommandLineUtils.4.0.2\lib\net45\McMaster.Extensions.CommandLineUtils.dll</HintPath>
</Reference>
<Reference Include="SkiaSharp, Version=1.59.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL">
<HintPath>..\packages\SkiaSharp.1.59.1\lib\net45\SkiaSharp.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.ComponentModel.DataAnnotations" />
<Reference Include="System.Core" />
<Reference Include="System.Runtime.InteropServices.RuntimeInformation, Version=4.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll</HintPath>
<Private>True</Private>
<Private>True</Private>
</Reference>
<Reference Include="System.ValueTuple, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.ValueTuple.4.5.0\lib\net47\System.ValueTuple.dll</HintPath>
</Reference>
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
Expand All @@ -44,6 +62,7 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Animation.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
Expand All @@ -70,8 +89,13 @@
<Content Include="Axes.vox">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\NGif.SkiaSharp\NGif.SkiaSharp\NGif.SkiaSharp.csproj">
<Project>{F83EE85A-F1AA-49CA-9123-C009CDA79D21}</Project>
<Name>NGif.SkiaSharp</Name>
</ProjectReference>
<ProjectReference Include="..\Voxels.Core\Voxels.Core.csproj">
<Project>{5f012250-ca19-4c6a-a3a0-7144dae5e8ae}</Project>
<Name>Voxels</Name>
Expand All @@ -81,8 +105,16 @@
<Name>Voxels.SkiaSharp</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PostBuildEvent>del libSkiaSharp.dll</PostBuildEvent>
</PropertyGroup>
<Import Project="..\packages\SkiaSharp.1.59.1\build\net45\SkiaSharp.targets" Condition="Exists('..\packages\SkiaSharp.1.59.1\build\net45\SkiaSharp.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\SkiaSharp.1.59.1\build\net45\SkiaSharp.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\SkiaSharp.1.59.1\build\net45\SkiaSharp.targets'))" />
</Target>
</Project>
7 changes: 7 additions & 0 deletions Voxels.CommandLine/packages.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="McMaster.Extensions.CommandLineUtils" version="4.0.2" targetFramework="net48" />
<package id="SkiaSharp" version="1.59.1" targetFramework="net48" />
<package id="System.Runtime.InteropServices.RuntimeInformation" version="4.3.0" targetFramework="net48" />
<package id="System.ValueTuple" version="4.5.0" targetFramework="net48" />
</packages>
4 changes: 3 additions & 1 deletion Voxels.ShellExtensions/ThumbnailHandlerQb.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ protected override Bitmap GetThumbnailImage(uint width) {
if (voxelData == null) {
return null;
}
var bitmapBytes = Renderer.RenderBitmap((int)size, voxelData);

var renderSettings = new RenderSettings() { size = size };
var bitmapBytes = Renderer.RenderBitmap(voxelData, renderSettings);

// Convert Skia bytes to GDI Bitmap
var format = PixelFormat.Format32bppArgb;
Expand Down
4 changes: 3 additions & 1 deletion Voxels.ShellExtensions/ThumbnailHandlerVox.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ protected override Bitmap GetThumbnailImage(uint width) {
if (voxelData == null) {
return null;
}
var bitmapBytes = Renderer.RenderBitmap((int)size, voxelData);

var renderSettings = new RenderSettings() { size = size };
var bitmapBytes = Renderer.RenderBitmap(voxelData, renderSettings);

// Convert Skia bytes to GDI Bitmap
var format = PixelFormat.Format32bppArgb;
Expand Down
3 changes: 2 additions & 1 deletion Voxels.ShellExtensions/Voxels.ShellExtensions.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Voxels.ShellExtensions</RootNamespace>
<AssemblyName>Voxels.ShellExtensions</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
Expand Down
66 changes: 41 additions & 25 deletions Voxels.SkiaSharp/Renderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,38 @@
using System.Linq;

namespace Voxels.SkiaSharp {
public class Renderer {
static void RenderIntoBitmap(int size, VoxelData voxelData, SKBitmap bitmap) {
public class RenderSettings {
public int size = 512;
public float rotationX = 26f;
public float rotationY = 45f;
public float rotationZ = 0f;
}

public static class Renderer {
static void RenderIntoBitmap(int size, VoxelData voxelData, SKBitmap bitmap, RenderSettings renderSettings) {
using (var canvas = new SKCanvas(bitmap)) {
bitmap.Erase(SKColors.Transparent);
RenderTriangles(voxelData, size, canvas, new MeshSettings {
FrontFaces = true,
RenderTriangles(voxelData, canvas, new MeshSettings {
Rotation = renderSettings.rotationY,
FakeLighting = true,
FloorShadow = true,
MeshType = MeshType.Triangles,
});
}, renderSettings);
}
}

public static byte[] RenderBitmap(int size, VoxelData voxelData) {
public static byte[] RenderBitmap(VoxelData voxelData, RenderSettings renderSettings) {
var size = renderSettings.size;
using (var bitmap = new SKBitmap(size, size, false)) {
RenderIntoBitmap(size, voxelData, bitmap);
RenderIntoBitmap(size, voxelData, bitmap, renderSettings);
return bitmap.Bytes;
}
}

public static byte[] RenderPng(int size, VoxelData voxelData) {
public static byte[] RenderPng(VoxelData voxelData, RenderSettings renderSettings) {
var size = renderSettings.size;
using (var bitmap = new SKBitmap(size, size, false)) {
RenderIntoBitmap(size, voxelData, bitmap);
RenderIntoBitmap(size, voxelData, bitmap, renderSettings);

using (var image = SKImage.FromBitmap(bitmap)) {
using (var data = image.Encode()) {
Expand All @@ -38,28 +47,31 @@ public static byte[] RenderPng(int size, VoxelData voxelData) {
}
}

public static byte[] RenderSvg(int size, VoxelData voxelData) {
public static byte[] RenderSvg(VoxelData voxelData, RenderSettings renderSettings) {
var size = renderSettings.size;
var ms = new MemoryStream();
using (var skStream = new SKManagedWStream(ms)) {
using (var writer = new SKXmlStreamWriter(skStream)) {
using (var canvas = SKSvgCanvas.Create(SKRect.Create(0, 0, size, size), writer)) {
RenderQuads(voxelData, size, canvas, new MeshSettings {
FrontFaces = true,
Rotation = renderSettings.rotationY,
FakeLighting = true,
MeshType = MeshType.Quads,
});
}, renderSettings);
}
}
}
return ms.ToArray();
}

static SKMatrix44 GetMatrix(VoxelData voxelData, int size) {
static SKMatrix44 GetMatrix(VoxelData voxelData, RenderSettings renderSettings) {
var s = renderSettings.size;
var r = 1.61803398875f;
var d = size / (r*voxelData.size.MaxDimension);
var tran = SKMatrix44.CreateTranslate(size*0.5f, size*0.5f, 0);
var rotx = SKMatrix44.CreateRotationDegrees(1, 0, 0, -26f);
var roty = SKMatrix44.CreateRotationDegrees(0, 1, 0, 45);
var d = s / (r*voxelData.size.MaxDimension);
var tran = SKMatrix44.CreateTranslate(s*0.5f, s*0.5f, 0);
var rotx = SKMatrix44.CreateRotationDegrees(1, 0, 0, renderSettings.rotationX);
var roty = SKMatrix44.CreateRotationDegrees(0, 1, 0, renderSettings.rotationY);
var rotz = SKMatrix44.CreateRotationDegrees(0, 0, 1, renderSettings.rotationZ);
var matrix = SKMatrix44.CreateIdentity();
matrix.PreConcat(tran);
matrix.PreConcat(rotx);
Expand All @@ -69,12 +81,12 @@ static SKMatrix44 GetMatrix(VoxelData voxelData, int size) {
return matrix;
}

static void RenderTriangles(VoxelData voxelData, int size, SKCanvas canvas, MeshSettings settings) {
var matrix = GetMatrix(voxelData, size);
static void RenderTriangles(VoxelData voxelData, SKCanvas canvas, MeshSettings settings, RenderSettings renderSettings) {
var matrix = GetMatrix(voxelData, renderSettings);
settings.MeshType = MeshType.Triangles;
var triangles = new MeshBuilder(voxelData, settings);

using (var fill = new SKPaint()) {
using (var fill = new SKPaint() { IsAntialias = true, FilterQuality = SKFilterQuality.High }) {
var vertices = triangles.Vertices
.Select(v => matrix.MapScalars(v.X, v.Z, -v.Y, 1f))
.Select(v => new SKPoint(v[0], v[1]))
Expand All @@ -95,10 +107,10 @@ static void RenderTriangles(VoxelData voxelData, int size, SKCanvas canvas, Mesh
}
}

static void RenderQuads(VoxelData voxelData, int size, SKCanvas canvas, MeshSettings settings) {
var matrix = GetMatrix(voxelData, size);
settings.MeshType = MeshType.Quads;
var quads = new MeshBuilder(voxelData, settings);
static void RenderQuads(VoxelData voxelData, int size, SKCanvas canvas, MeshSettings meshSettings, RenderSettings renderSettings) {
var matrix = GetMatrix(voxelData, renderSettings);
meshSettings.MeshType = MeshType.Quads;
var quads = new MeshBuilder(voxelData, meshSettings);

var vertices = quads.Vertices
.Select(v => matrix.MapScalars(v.X, v.Z, -v.Y, 1f))
Expand All @@ -113,7 +125,11 @@ static void RenderQuads(VoxelData voxelData, int size, SKCanvas canvas, MeshSett
path.AddPoly(quad, close: true);

var color = quads.Colors[quads.Faces[i]]; // Take 1st vertex color for face
using (var fill = new SKPaint() { Color = ToSKColor(color) }) {
using (var fill = new SKPaint() {
Color = ToSKColor(color),
Style=SKPaintStyle.StrokeAndFill,
StrokeJoin=SKStrokeJoin.Round,
}) {
canvas.DrawPath(path, fill);
}
}
Expand Down
Loading

0 comments on commit 13b6d7a

Please sign in to comment.