diff --git a/BlazorWebAssembly/BlazorWebAssembly.sln b/BlazorWebAssembly/BlazorWebAssembly.sln index c4d865e..4624d6c 100644 --- a/BlazorWebAssembly/BlazorWebAssembly.sln +++ b/BlazorWebAssembly/BlazorWebAssembly.sln @@ -3,11 +3,13 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.6.33712.159 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlazorWebAssembly.Server", "Server\BlazorWebAssembly.Server.csproj", "{5D0639F4-35F5-4070-BD81-E8F9994D58B3}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorWebAssembly.Server", "Server\BlazorWebAssembly.Server.csproj", "{5D0639F4-35F5-4070-BD81-E8F9994D58B3}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlazorWebAssembly.Client", "Client\BlazorWebAssembly.Client.csproj", "{DCBFC8DF-785B-41D0-B7CA-F5D36808875B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorWebAssembly.Client", "Client\BlazorWebAssembly.Client.csproj", "{DCBFC8DF-785B-41D0-B7CA-F5D36808875B}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlazorWebAssembly.Shared", "Shared\BlazorWebAssembly.Shared.csproj", "{2ABAA9E5-A116-4201-88F1-2A7832267CF0}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorWebAssembly.Shared", "Shared\BlazorWebAssembly.Shared.csproj", "{2ABAA9E5-A116-4201-88F1-2A7832267CF0}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TexMetadataGenerator", "TexMetadataGenerator\TexMetadataGenerator.csproj", "{7AE0A7E1-CA81-4EAB-B203-2944D40111D2}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -27,6 +29,9 @@ Global {2ABAA9E5-A116-4201-88F1-2A7832267CF0}.Debug|Any CPU.Build.0 = Debug|Any CPU {2ABAA9E5-A116-4201-88F1-2A7832267CF0}.Release|Any CPU.ActiveCfg = Release|Any CPU {2ABAA9E5-A116-4201-88F1-2A7832267CF0}.Release|Any CPU.Build.0 = Release|Any CPU + {7AE0A7E1-CA81-4EAB-B203-2944D40111D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7AE0A7E1-CA81-4EAB-B203-2944D40111D2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7AE0A7E1-CA81-4EAB-B203-2944D40111D2}.Release|Any CPU.ActiveCfg = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/BlazorWebAssembly/Client/BlazorWebAssembly.Client.csproj b/BlazorWebAssembly/Client/BlazorWebAssembly.Client.csproj index 5e8f7c8..a0a9b7e 100644 --- a/BlazorWebAssembly/Client/BlazorWebAssembly.Client.csproj +++ b/BlazorWebAssembly/Client/BlazorWebAssembly.Client.csproj @@ -10,6 +10,7 @@ + diff --git a/BlazorWebAssembly/Client/Core/ImageReconstructor.cs b/BlazorWebAssembly/Client/Core/ImageReconstructor.cs new file mode 100644 index 0000000..e742031 --- /dev/null +++ b/BlazorWebAssembly/Client/Core/ImageReconstructor.cs @@ -0,0 +1,97 @@ +using Img2mc.Shared; +using Microsoft.AspNetCore.Components; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.Processing.Processors.Transforms; +using System.Collections.Concurrent; + +namespace BlazorWebAssembly.Client.Core; + +public class ImageReconstructor +{ + public MinecraftBlock[] blockData = Array.Empty(); + + [Inject] + public HttpClient? HttpClient { get; set; } + + public OutputTexture[,] OutputTextures { get; private set; } = new OutputTexture[0,0]; + public int OutputRows { get; private set; } = 0; + public int OutputColumns { get; private set; } = 0; + public int MaxOutputImageSize { get; set; } = 128; + public float ContrastBias { get; set; } = 2f; + + public event Action? OnOutputChanged; + + public readonly Dictionary blockCounts = new(); + + public async void ReadFile(Stream stream) + { + try + { + using Image img = await Image.LoadAsync(stream); + Console.WriteLine($"Read the file as image: {img}"); + blockCounts.Clear(); + var px = img[0, 0]; + // Available Resamplers: + // Bicubic NearestNeighbor Box MitchellNetravali CatmullRom Lanczos2 Lanczos3 Lanczos5 Lanczos8 Welch Robidoux RobidouxSharp Spline Hermite + if (img.Width > MaxOutputImageSize) + { + img.Mutate(x => x.Resize(MaxOutputImageSize, 0, new BicubicResampler())); + } + else if (img.Height > MaxOutputImageSize) + { + img.Mutate(x => x.Resize(0, MaxOutputImageSize, new BicubicResampler())); + } + Console.WriteLine($"Resized image to {img}"); + OutputColumns = img.Width; + OutputRows = img.Height; + var buffer = new OutputTexture[OutputColumns, OutputRows]; + for (int x = 0; x < OutputColumns; x++) + { + for (int y = 0; y < OutputRows; y++) + { + var result = FindBestMatchingTexture(img[x, y]); + buffer[x, y] = new(result.Item1.blockName, result.Item2.fileName); + if (blockCounts.ContainsKey(result.Item1)) + { + blockCounts[result.Item1]++; + } + else + { + blockCounts[result.Item1] = 1; + } + } + } + Console.WriteLine("Done processing image."); + OutputTextures = buffer; + img.Dispose(); + OnOutputChanged?.Invoke(); + } + catch (Exception ex) + { + Console.WriteLine(ex.Message); + } + } + + private (MinecraftBlock, TextureMetadata) FindBestMatchingTexture(Rgba32 pixel) + { + RGB col = new() { r = pixel.R, g = pixel.G, b = pixel.B }; + var query = from block in blockData + from texture in block.textures + orderby texture.ColorDistance(block, col, ContrastBias) ascending + select (block, texture); + return query.First(); + } + + public readonly struct OutputTexture + { + public readonly string blockName; + public readonly string fileName; + + public OutputTexture(string blockName, string fileName) + { + this.blockName = blockName; + this.fileName = fileName; + } + } + +} diff --git a/BlazorWebAssembly/Client/Pages/Index.razor b/BlazorWebAssembly/Client/Pages/Index.razor index 448e35e..336081a 100644 --- a/BlazorWebAssembly/Client/Pages/Index.razor +++ b/BlazorWebAssembly/Client/Pages/Index.razor @@ -1,138 +1,108 @@ @page "/" -@using System.Text.Json; -@using static System.Net.WebRequestMethods; +@using System.Text.Json +@using static System.Net.WebRequestMethods +@using Img2mc.Shared +@using BlazorWebAssembly.Client.Core + +@implements IHandleEvent @inject HttpClient Http +@inject ImageReconstructor imageReconstructor @code { - string json = "[]"; - string firstPixelRGBA = ""; - TextureMetadata[] textureData = new TextureMetadata[0]; - - TextureMetadata[,]? outputTextures = null; - int outputRows = 0; - int outputColumns = 0; + const long fileSizeLimit = 52428800; + int blockSize = 8; + public string BlockSizeString => blockSize.ToString() + "px"; - bool check = true; + private IBrowserFile? selectedFile; protected override async Task OnInitializedAsync() { + //blockData persists across page swaps. no need to get it again. + if (imageReconstructor.blockData.Length != 0) + return; var path = "images/tex_metadata.json"; string json = await Http.GetStringAsync(path); - textureData = JsonSerializer.Deserialize(json) ?? new TextureMetadata[0]; + imageReconstructor.blockData = JsonSerializer.Deserialize(json) ?? new MinecraftBlock[0]; + imageReconstructor.OnOutputChanged += StateHasChanged; } - private void ButtonClicked() + ~Index() { - Console.WriteLine("hello button"); + imageReconstructor.OnOutputChanged -= StateHasChanged; } private void SelectedFileChanged(InputFileChangeEventArgs args) { + selectedFile = args.File; Console.WriteLine($"File Selected: {args.File.Name} ||| is: {args.File.ContentType}"); - ReadFile(args.File.OpenReadStream()); + imageReconstructor.ReadFile(args.File.OpenReadStream(fileSizeLimit)); } - private async void ReadFile(Stream stream) + private void RetryButtonPressed() { - try + if(selectedFile != null) { - using Image img = await Image.LoadAsync(stream); - Console.WriteLine($"Read the file as image: {img.ToString()}"); - var px = img[0, 0]; - - if(img.Width > 128) - { - img.Mutate(x => x.Resize(128, 0)); - } - else if(img.Height > 128) - { - img.Mutate(x => x.Resize(0, 128)); - } - Console.WriteLine($"Resized image to {img.ToString()}"); - outputColumns = img.Width; - outputRows = img.Height; - var buffer = new TextureMetadata[outputColumns, outputRows]; - for(int x = 0; x < outputColumns; x++) - { - for(int y = 0; y < outputRows; y++) - { - buffer[x, y] = FindBestMatchingTexture(img[x, y]); - } - } - Console.WriteLine("Done processing image."); - outputTextures = buffer; - img.Dispose(); - this.StateHasChanged(); - } - catch(Exception ex) - { - Console.WriteLine(ex.Message); + imageReconstructor.ReadFile(selectedFile.OpenReadStream(fileSizeLimit)); } } - private TextureMetadata FindBestMatchingTexture(Rgba32 pixel) - { - RGB col = new() { r = pixel.R, g = pixel.G, b = pixel.B }; - return textureData.OrderBy(x => x.averageRGB.RGBDistance(col)).First(); - } + Task IHandleEvent.HandleEventAsync(EventCallbackWorkItem callback, object? arg) + => callback.InvokeAsync(arg); } - -Index - -

Hello, world!

-Welcome to your new app. +Img2Mc - + + Max Size: @imageReconstructor.MaxOutputImageSize + + + Block Size: @blockSize + + + Contrast Bias: @imageReconstructor.ContrastBias + +Retry - - +Please Select an image file: - + -@if(outputTextures != null) + @if (imageReconstructor.OutputTextures != null) {
Output texture is not null here
- @for(int row = 0; row < outputRows; row++) + @for (int row = 0; row < imageReconstructor.OutputRows; row++) { - - @for(int column = 0; column < outputColumns; column ++) - { - - } + + @for (int column = 0; column < imageReconstructor.OutputColumns; column++) + { + var tex = imageReconstructor.OutputTextures[column, row]; + + + } }
- - -
+ + + +
} +

Required blocks

+
    + @foreach (var pair in imageReconstructor.blockCounts.OrderByDescending(x => x.Value)) + { +
  1. + @pair.Key.blockName x @pair.Value +
  2. + } +
+ +