Refactor and implement img to text block importing
This commit is contained in:
parent
6acf1b7d4e
commit
2e314595ac
8 changed files with 450 additions and 270 deletions
17
Pixi/Common/BlockInfo.cs
Normal file
17
Pixi/Common/BlockInfo.cs
Normal file
|
@ -0,0 +1,17 @@
|
|||
using System;
|
||||
|
||||
using GamecraftModdingAPI.Blocks;
|
||||
|
||||
namespace Pixi.Common
|
||||
{
|
||||
public struct BlockInfo
|
||||
{
|
||||
public BlockIDs block;
|
||||
|
||||
public BlockColors color;
|
||||
|
||||
public byte darkness;
|
||||
|
||||
public bool visible;
|
||||
}
|
||||
}
|
214
Pixi/Images/ImageCommands.cs
Normal file
214
Pixi/Images/ImageCommands.cs
Normal file
|
@ -0,0 +1,214 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
using UnityEngine;
|
||||
using Unity.Mathematics;
|
||||
using Svelto.ECS.Experimental;
|
||||
using Svelto.ECS;
|
||||
|
||||
using GamecraftModdingAPI.Blocks;
|
||||
using GamecraftModdingAPI.Commands;
|
||||
using GamecraftModdingAPI.Players;
|
||||
using GamecraftModdingAPI.Utility;
|
||||
using GamecraftModdingAPI;
|
||||
|
||||
using Pixi.Common;
|
||||
|
||||
namespace Pixi.Images
|
||||
{
|
||||
public static class ImageCommands
|
||||
{
|
||||
public const uint PIXEL_WARNING_THRESHOLD = 25_000;
|
||||
// hash length to display after Pixi in text block id field
|
||||
public const uint HASH_LENGTH = 6;
|
||||
|
||||
private static double blockSize = 0.2;
|
||||
|
||||
private static uint thiccness = 1;
|
||||
|
||||
public static void CreateThiccCommand()
|
||||
{
|
||||
CommandBuilder.Builder()
|
||||
.Name("PixiThicc")
|
||||
.Description("Set the image thickness for Pixi2D. Use this if you'd like add depth to a 2D image after importing. (Pixi)")
|
||||
.Action<int>((d) => {
|
||||
if (d > 0)
|
||||
{
|
||||
thiccness = (uint)d;
|
||||
}
|
||||
else Logging.CommandLogError("");
|
||||
})
|
||||
.Build();
|
||||
}
|
||||
|
||||
public static void CreateImportCommand()
|
||||
{
|
||||
CommandBuilder.Builder()
|
||||
.Name("Pixi2D")
|
||||
.Description("Converts an image to blocks. Larger images will freeze your game until conversion completes. (Pixi)")
|
||||
.Action<string>(Pixelate2DFile)
|
||||
.Build();
|
||||
}
|
||||
|
||||
public static void CreateTextCommand()
|
||||
{
|
||||
CommandBuilder.Builder()
|
||||
.Name("PixiText")
|
||||
.Description("Converts an image to coloured text in a new text block. Larger images may cause save issues. (Pixi)")
|
||||
.Action<string>(Pixelate2DFileToTextBlock)
|
||||
.Build();
|
||||
}
|
||||
|
||||
public static void CreateTextConsoleCommand()
|
||||
{
|
||||
CommandBuilder.Builder()
|
||||
.Name("PixiConsole")
|
||||
.Description("Converts an image to a ChangeTextBlockCommand in a new console block. The first parameter is the image filepath and the second parameter is the text block id. Larger images may cause save issues. (Pixi)")
|
||||
.Action<string, string>(Pixelate2DFileToCommand)
|
||||
.Build();
|
||||
}
|
||||
|
||||
public static void Pixelate2DFile(string filepath)
|
||||
{
|
||||
// Load image file and convert to Gamecraft blocks
|
||||
Texture2D img = new Texture2D(64, 64);
|
||||
// load file into texture
|
||||
try
|
||||
{
|
||||
byte[] imgData = File.ReadAllBytes(filepath);
|
||||
img.LoadImage(imgData);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logging.CommandLogError($"Failed to load picture data. Reason: {e.Message}");
|
||||
Logging.MetaLog(e.Message + "\n" + e.StackTrace);
|
||||
return;
|
||||
}
|
||||
Logging.CommandLog($"Image size: {img.width}x{img.height}");
|
||||
float3 position = new Player(PlayerType.Local).Position;
|
||||
uint blockCount = 0;
|
||||
position.x += 1f;
|
||||
position.y += (float)blockSize;
|
||||
float zero_y = position.y;
|
||||
// convert the image to blocks
|
||||
// this groups same-colored pixels in the same column into a single block to reduce the block count
|
||||
// any further pixel-grouping optimisations (eg 2D grouping) risk increasing conversion time higher than O(x*y)
|
||||
for (int x = 0; x < img.width; x++)
|
||||
{
|
||||
BlockInfo qVoxel = new BlockInfo
|
||||
{
|
||||
block = BlockIDs.AbsoluteMathsBlock, // impossible canvas block
|
||||
color = BlockColors.Default,
|
||||
darkness = 10,
|
||||
visible = false,
|
||||
};
|
||||
float3 scale = new float3(1, 1, thiccness);
|
||||
position.x += (float)(blockSize);
|
||||
for (int y = 0; y < img.height; y++)
|
||||
{
|
||||
//position.y += (float)blockSize;
|
||||
Color pixel = img.GetPixel(x, y);
|
||||
BlockInfo qPixel = PixelUtility.QuantizePixel(pixel);
|
||||
if (qPixel.darkness != qVoxel.darkness
|
||||
|| qPixel.color != qVoxel.color
|
||||
|| qPixel.visible != qVoxel.visible
|
||||
|| qPixel.block != qVoxel.block)
|
||||
{
|
||||
if (y != 0)
|
||||
{
|
||||
if (qVoxel.visible)
|
||||
{
|
||||
position.y = zero_y + (float)((y * blockSize + (y - scale.y) * blockSize) / 2);
|
||||
Block.PlaceNew(qVoxel.block, position, color: qVoxel.color, darkness: qVoxel.darkness, scale: scale);
|
||||
blockCount++;
|
||||
}
|
||||
scale = new float3(1, 1, thiccness);
|
||||
}
|
||||
qVoxel = qPixel;
|
||||
}
|
||||
else
|
||||
{
|
||||
scale.y += 1;
|
||||
}
|
||||
|
||||
}
|
||||
if (qVoxel.visible)
|
||||
{
|
||||
position.y = zero_y + (float)((img.height * blockSize + (img.height - scale.y) * blockSize) / 2);
|
||||
Block.PlaceNew(qVoxel.block, position, color: qVoxel.color, darkness: qVoxel.darkness, scale: scale);
|
||||
blockCount++;
|
||||
}
|
||||
//position.y = zero_y;
|
||||
}
|
||||
Logging.CommandLog($"Placed {img.width}x{img.height} image beside you ({blockCount} blocks total)");
|
||||
Logging.MetaLog($"Saved {(img.width * img.height) - blockCount} blocks ({img.width * img.height / blockCount}x) while placing {filepath}");
|
||||
}
|
||||
|
||||
public static void Pixelate2DFileToTextBlock(string filepath)
|
||||
{
|
||||
// Thanks to TheGreenGoblin for the idea (and the working Python implementation for reference)
|
||||
// Load image file and convert to Gamecraft blocks
|
||||
Texture2D img = new Texture2D(64, 64);
|
||||
// load file into texture
|
||||
try
|
||||
{
|
||||
byte[] imgData = File.ReadAllBytes(filepath);
|
||||
img.LoadImage(imgData);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logging.CommandLogError($"Failed to load picture data. Reason: {e.Message}");
|
||||
Logging.MetaLog(e.Message + "\n" + e.StackTrace);
|
||||
return;
|
||||
}
|
||||
float3 position = new Player(PlayerType.Local).Position;
|
||||
position.x += 1f;
|
||||
position.y += (float)blockSize;
|
||||
string text = PixelUtility.TextureToString(img);
|
||||
TextBlock textBlock = TextBlock.PlaceNew(position, scale: new float3(Mathf.Ceil(img.width / 16), Mathf.Ceil(img.height / 16), 1));
|
||||
textBlock.Text = text;
|
||||
byte[] textHash;
|
||||
using (HashAlgorithm hasher = SHA256.Create())
|
||||
textHash = hasher.ComputeHash(Encoding.UTF8.GetBytes(text));
|
||||
string textId = "Pixi_";
|
||||
// every byte converts to 2 hexadecimal characters so hash length needs to be halved
|
||||
for (int i = 0; i < HASH_LENGTH/2 && i < textHash.Length; i++)
|
||||
{
|
||||
textId += textHash[i].ToString("X2");
|
||||
}
|
||||
textBlock.TextBlockId = textId;
|
||||
}
|
||||
|
||||
public static void Pixelate2DFileToCommand(string filepath, string textBlockId)
|
||||
{
|
||||
// Thanks to Nullpersonan for the idea
|
||||
// Load image file and convert to Gamecraft blocks
|
||||
Texture2D img = new Texture2D(64, 64);
|
||||
// load file into texture
|
||||
try
|
||||
{
|
||||
byte[] imgData = File.ReadAllBytes(filepath);
|
||||
img.LoadImage(imgData);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logging.CommandLogError($"Failed to load picture data. Reason: {e.Message}");
|
||||
Logging.MetaLog(e.Message + "\n" + e.StackTrace);
|
||||
return;
|
||||
}
|
||||
float3 position = new Player(PlayerType.Local).Position;
|
||||
position.x += 1f;
|
||||
position.y += (float)blockSize;
|
||||
float zero_y = position.y;
|
||||
string text = PixelUtility.TextureToString(img); // conversion
|
||||
ConsoleBlock console = ConsoleBlock.PlaceNew(position);
|
||||
// set console's command
|
||||
console.Command = "ChangeTextBlockCommand";
|
||||
console.Arg1 = textBlockId;
|
||||
console.Arg2 = text;
|
||||
console.Arg3 = "";
|
||||
}
|
||||
}
|
||||
}
|
168
Pixi/Images/PixelUtility.cs
Normal file
168
Pixi/Images/PixelUtility.cs
Normal file
|
@ -0,0 +1,168 @@
|
|||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
using GamecraftModdingAPI.Blocks;
|
||||
using GamecraftModdingAPI.Utility;
|
||||
|
||||
using Pixi.Common;
|
||||
|
||||
namespace Pixi.Images
|
||||
{
|
||||
public static class PixelUtility
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static BlockInfo QuantizePixel(Color pixel)
|
||||
{
|
||||
BlockColors color = BlockColors.Default;
|
||||
int darkness = 0;
|
||||
bool force = false;
|
||||
#if DEBUG
|
||||
Logging.MetaLog($"Color (r:{pixel.r}, g:{pixel.g}, b:{pixel.b})");
|
||||
#endif
|
||||
if (Mathf.Abs(pixel.r - pixel.g) <= pixel.r * 0.1f && Mathf.Abs(pixel.r - pixel.b) <= pixel.r * 0.1f)
|
||||
{
|
||||
color = BlockColors.White;
|
||||
darkness = (int)(10 - ((pixel.r + pixel.g + pixel.b) * 3.5));
|
||||
//Logging.MetaDebugLog($"Color (r:{pixel.r}, g:{pixel.g}, b:{pixel.b})");
|
||||
}
|
||||
else if (pixel.r >= pixel.g && pixel.r >= pixel.b)
|
||||
{
|
||||
// Red is highest
|
||||
if ((pixel.r - pixel.g) > pixel.r * 0.65 && (pixel.r - pixel.b) > pixel.r * 0.55)
|
||||
{
|
||||
// Red is much higher than other pixels
|
||||
darkness = (int)(9 - (pixel.r * 8.01));
|
||||
color = BlockColors.Red;
|
||||
}
|
||||
else if ((pixel.g - pixel.b) > pixel.g * 0.25)
|
||||
{
|
||||
// Green is much higher than blue
|
||||
if ((pixel.r - pixel.g) < pixel.r * 0.8)
|
||||
{
|
||||
darkness = (int)(10 - ((pixel.r * 2.1 + pixel.g) * 2.1));
|
||||
color = BlockColors.Orange;
|
||||
}
|
||||
else
|
||||
{
|
||||
darkness = (int)(10 - ((pixel.r * 2.1 + pixel.g) * 2.2));
|
||||
color = BlockColors.Yellow;
|
||||
}
|
||||
|
||||
}
|
||||
else if ((pixel.b - pixel.g) > pixel.b * 0.3)
|
||||
{
|
||||
// Blue is much higher than green
|
||||
darkness = (int)(10 - ((pixel.r + pixel.b) * 5.0));
|
||||
color = BlockColors.Purple;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Green is close strength to blue
|
||||
darkness = (int)(10 - ((pixel.r * 2.1 + pixel.g + pixel.b) * 2.5));
|
||||
color = darkness < 6 ? BlockColors.Pink : BlockColors.Orange;
|
||||
force = true;
|
||||
}
|
||||
}
|
||||
else if (pixel.g >= pixel.r && pixel.g >= pixel.b)
|
||||
{
|
||||
// Green is highest
|
||||
if ((pixel.g - pixel.r) > pixel.g * 0.6 && (pixel.g - pixel.b) > pixel.g * 0.48)
|
||||
{
|
||||
// Green is much higher than other pixels
|
||||
darkness = (int)(10 - (pixel.g * 10.1));
|
||||
color = BlockColors.Green;
|
||||
}
|
||||
else if ((pixel.r - pixel.b) > pixel.r * 0.3)
|
||||
{
|
||||
// Red is much higher than blue
|
||||
darkness = (int)(10 - ((pixel.r + pixel.g) * 5.1));
|
||||
color = BlockColors.Yellow;
|
||||
}
|
||||
else if ((pixel.b - pixel.r) > pixel.b * 0.2)
|
||||
{
|
||||
// Blue is much higher than red
|
||||
darkness = (int)(9 - ((pixel.g + pixel.b) * 5.1));
|
||||
color = BlockColors.Aqua;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Red is close strength to blue
|
||||
darkness = (int)(10 - ((pixel.r + pixel.g * 2.2 + pixel.b) * 2.9));
|
||||
color = BlockColors.Lime;
|
||||
}
|
||||
}
|
||||
else if (pixel.b >= pixel.g && pixel.b >= pixel.r)
|
||||
{
|
||||
// Blue is highest
|
||||
if ((pixel.b - pixel.g) > pixel.b * 0.6 && (pixel.b - pixel.r) > pixel.b * 0.6)
|
||||
{
|
||||
// Blue is much higher than other pixels
|
||||
darkness = (int)(10 - (pixel.b * 10.1));
|
||||
color = BlockColors.Blue;
|
||||
}
|
||||
else if ((pixel.g - pixel.r) > pixel.g * 0.3)
|
||||
{
|
||||
// Green is much higher than red
|
||||
darkness = (int)(10 - ((pixel.g + pixel.b) * 5.1));
|
||||
if (darkness == 4 || darkness == 5) darkness = 0;
|
||||
else if (darkness < 3) darkness = 4;
|
||||
color = BlockColors.Aqua;
|
||||
}
|
||||
else if ((pixel.r - pixel.g) > pixel.r * 0.3)
|
||||
{
|
||||
// Red is much higher than green
|
||||
darkness = (int)(10 - ((pixel.r + pixel.b) * 5.0));
|
||||
color = BlockColors.Purple;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Green is close strength to red
|
||||
darkness = (int)(10 - ((pixel.r + pixel.g + pixel.b * 2.2) * 3.0));
|
||||
color = BlockColors.Aqua;
|
||||
}
|
||||
}
|
||||
// level 9 is not darker than lvl 8
|
||||
if (darkness > 8 && !force) darkness = 8;
|
||||
// darkness 0 is the most saturated (it's not just the lightest)
|
||||
if (darkness < 0) darkness = 0;
|
||||
|
||||
BlockInfo result = new BlockInfo
|
||||
{
|
||||
block = pixel.a > 0.75 ? BlockIDs.AluminiumCube : BlockIDs.GlassCube,
|
||||
color = color,
|
||||
darkness = (byte)darkness,
|
||||
visible = pixel.a > 0.5f,
|
||||
};
|
||||
#if DEBUG
|
||||
Logging.MetaLog($"Quantized {color} (b:{result.block} d:{result.darkness} v:{result.visible})");
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
public static string HexPixel(Color pixel)
|
||||
{
|
||||
return "#"+ColorUtility.ToHtmlStringRGBA(pixel);
|
||||
}
|
||||
|
||||
public static string TextureToString(Texture2D img)
|
||||
{
|
||||
StringBuilder imgString = new StringBuilder("<cspace=-0.1em><line-height=42%>");
|
||||
for (int y = img.height-1; y >= 0 ; y--) // text origin is top right, but img origin is bottom right
|
||||
{
|
||||
for (int x = 0; x < img.width; x++)
|
||||
{
|
||||
Color pixel = img.GetPixel(x, y);
|
||||
imgString.Append("<color=");
|
||||
imgString.Append(HexPixel(pixel));
|
||||
imgString.Append(">\u25a0</color>");
|
||||
}
|
||||
imgString.Append("<br>");
|
||||
}
|
||||
imgString.Append("</cspace></line-height>");
|
||||
return imgString.ToString();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,17 +3,13 @@
|
|||
<PropertyGroup>
|
||||
<TargetFramework>net472</TargetFramework>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<Version>0.2.0</Version>
|
||||
<Version>0.3.0</Version>
|
||||
<Authors>NGnius</Authors>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<PackageProjectUrl>https://git.exmods.org/NGnius/Pixi</PackageProjectUrl>
|
||||
<NeutralLanguage>en-CA</NeutralLanguage>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Lib.Harmony" Version="1.2.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<!--Start Dependencies-->
|
||||
<ItemGroup>
|
||||
<Reference Include="GamecraftModdingAPI">
|
||||
|
@ -781,7 +777,33 @@
|
|||
<HintPath>..\..\ref\Gamecraft_Data\Managed\VisualProfiler.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="GamecraftModdingAPI">
|
||||
<HintPath>..\..\ref\Plugins\GamecraftModdingAPI.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="GamecraftModdingAPI">
|
||||
<HintPath>..\..\ref\Plugins\GamecraftModdingAPI.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="GamecraftModdingAPI">
|
||||
<HintPath>..\..\ref\Plugins\GamecraftModdingAPI.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="GamecraftModdingAPI">
|
||||
<HintPath>..\..\ref\Plugins\GamecraftModdingAPI.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="GamecraftModdingAPI">
|
||||
<HintPath>..\..\ref\Plugins\GamecraftModdingAPI.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="GamecraftModdingAPI">
|
||||
<HintPath>..\..\ref\Plugins\GamecraftModdingAPI.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="GamecraftModdingAPI">
|
||||
<HintPath>..\..\ref\Plugins\GamecraftModdingAPI.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="GamecraftModdingAPI">
|
||||
<HintPath>..\..\ref\Plugins\GamecraftModdingAPI.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="GamecraftModdingAPI">
|
||||
<HintPath>..\..\ref\Plugins\GamecraftModdingAPI.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<!--End Dependencies-->
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -13,23 +13,18 @@ using GamecraftModdingAPI.Utility;
|
|||
using GamecraftModdingAPI.Blocks;
|
||||
using GamecraftModdingAPI.Players;
|
||||
|
||||
using Pixi.Images;
|
||||
|
||||
namespace Pixi
|
||||
{
|
||||
public class PixiPlugin : IPlugin // the Illusion Plugin Architecture (IPA) will ignore classes that don't implement IPlugin'
|
||||
{
|
||||
private const uint PIXEL_WARNING_THRESHOLD = 25_000;
|
||||
|
||||
public string Name { get; } = Assembly.GetExecutingAssembly().GetName().Name; // Pixi
|
||||
// To change the name, change the project's name
|
||||
|
||||
public string Version { get; } = Assembly.GetExecutingAssembly().GetName().Version.ToString(); // 0.1.0 (for now)
|
||||
// To change the version, change <Version>#.#.#</Version> in Pixi.csproj
|
||||
|
||||
private uint width = 64;
|
||||
private uint height = 64;
|
||||
|
||||
private double blockSize = 0.2;
|
||||
|
||||
// called when Gamecraft shuts down
|
||||
public void OnApplicationQuit()
|
||||
{
|
||||
|
@ -48,28 +43,11 @@ namespace Pixi
|
|||
// check out the modding API docs here: https://mod.exmods.org/
|
||||
|
||||
// Initialize Pixi mod
|
||||
// create SimpleCustomCommandEngine for 2D image importing
|
||||
SimpleCustomCommandEngine<string> pixelate2DCommand = new SimpleCustomCommandEngine<string>(
|
||||
pixelate2DFile, // command action
|
||||
"Pixi2D", // command name (used to invoke it in the console)
|
||||
"Converts an image to blocks.\nLarger images will freeze your game until conversion completes. (Pixi)" // command description (displayed when help command is executed)
|
||||
);
|
||||
|
||||
SimpleCustomCommandEngine<string> pixelate3DCommand = new SimpleCustomCommandEngine<string>(
|
||||
pixelate3DFile, // command action
|
||||
"Pixi3D", // command name (used to invoke it in the console)
|
||||
"Converts a 3D model to blocks.\nLarger models will freeze your game until conversion completes. (Pixi)" // command description (displayed when help command is executed)
|
||||
);
|
||||
|
||||
SimpleCustomCommandEngine<uint, uint> scaleCommand = new SimpleCustomCommandEngine<uint, uint>(
|
||||
setScale, // command action
|
||||
"PixiScale", // command name (used to invoke it in the console)
|
||||
"Sets the image scale factor for Pixi2D.\nBigger images take longer to convert. (Pixi)" // command description (displayed when help command is executed)
|
||||
);
|
||||
|
||||
// register commands so the modding API knows about it
|
||||
CommandManager.AddCommand(pixelate2DCommand);
|
||||
CommandManager.AddCommand(scaleCommand);
|
||||
// 2D image functionality
|
||||
ImageCommands.CreateThiccCommand();
|
||||
ImageCommands.CreateImportCommand();
|
||||
ImageCommands.CreateTextCommand();
|
||||
ImageCommands.CreateTextConsoleCommand();
|
||||
|
||||
GamecraftModdingAPI.Utility.Logging.LogDebug($"{Name} has started up");
|
||||
}
|
||||
|
@ -83,239 +61,5 @@ namespace Pixi
|
|||
public void OnLevelWasLoaded(int level) { } // called after a level is loaded
|
||||
|
||||
public void OnUpdate() { } // called once per rendered frame (frame update)
|
||||
|
||||
// pixelation methods
|
||||
|
||||
private void pixelate2DFile(string filepath)
|
||||
{
|
||||
// Load image file and convert to Gamecraft blocks
|
||||
Texture2D img = new Texture2D((int)width, (int)height);
|
||||
// load file into texture
|
||||
try
|
||||
{
|
||||
byte[] imgData = File.ReadAllBytes(filepath);
|
||||
img.LoadImage(imgData);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logging.CommandLogError($"Failed to load picture data. Reason: {e.Message}");
|
||||
Logging.MetaLog(e.Message + "\n" + e.StackTrace);
|
||||
return;
|
||||
}
|
||||
float3 position = new Player(PlayerType.Local).Position;
|
||||
uint blockCount = 0;
|
||||
position.x += 1f;
|
||||
//position.y += 1f;
|
||||
float zero_y = position.y;
|
||||
// convert the image to blocks
|
||||
// this groups same-colored pixels in the same column into a single block to reduce the block count
|
||||
// any further pixel-grouping optimisations (eg 2D grouping) risk increasing conversion time higher than O(x*y)
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
QuantizedPixel qVoxel = new QuantizedPixel
|
||||
{
|
||||
block = BlockIDs.AbsoluteMathsBlock, // impossible canvas block
|
||||
color = BlockColors.Default,
|
||||
darkness = 10,
|
||||
visible = false,
|
||||
};
|
||||
float3 scale = new float3(1, 1, 1);
|
||||
position.x += (float)(blockSize);
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
//position.y += (float)blockSize;
|
||||
Color pixel = img.GetPixel(x, y);
|
||||
QuantizedPixel qPixel = quantizeColor(pixel);
|
||||
if (qPixel.darkness != qVoxel.darkness
|
||||
|| qPixel.color != qVoxel.color
|
||||
|| qPixel.visible != qVoxel.visible
|
||||
|| qPixel.block != qVoxel.block)
|
||||
{
|
||||
if (y != 0)
|
||||
{
|
||||
if (qVoxel.visible)
|
||||
{
|
||||
position.y = zero_y + (float)((y * blockSize + (y - scale.y) * blockSize) / 2);
|
||||
Block.PlaceNew(qVoxel.block, position, color: qVoxel.color, darkness: qVoxel.darkness, scale: scale);
|
||||
blockCount++;
|
||||
}
|
||||
scale = new float3(1, 1, 1);
|
||||
}
|
||||
qVoxel = qPixel;
|
||||
}
|
||||
else
|
||||
{
|
||||
scale.y += 1;
|
||||
}
|
||||
|
||||
}
|
||||
if (qVoxel.visible)
|
||||
{
|
||||
position.y = zero_y + (float)((height * blockSize + (height - scale.y) * blockSize) / 2);
|
||||
Block.PlaceNew(qVoxel.block, position, color: qVoxel.color, darkness: qVoxel.darkness, scale: scale);
|
||||
blockCount++;
|
||||
}
|
||||
//position.y = zero_y;
|
||||
}
|
||||
Logging.CommandLog($"Placed {width}x{height} image beside you ({blockCount} blocks total)");
|
||||
Logging.MetaLog($"Saved {(width * height) - blockCount} blocks ({width * height / blockCount}x) while placing {filepath}");
|
||||
}
|
||||
|
||||
private void setScale(uint _width, uint _height)
|
||||
{
|
||||
width = _width;
|
||||
height = _height;
|
||||
if (width * height > PIXEL_WARNING_THRESHOLD)
|
||||
{
|
||||
Logging.CommandLogWarning($"That's a lot of pixels ({width * height}px)!\nImporting large images may freeze your game for a long time.");
|
||||
}
|
||||
Logging.CommandLog($"Pixi image size set to {width}x{height}");
|
||||
}
|
||||
|
||||
private void pixelate3DFile(string filepath)
|
||||
{
|
||||
// TODO?
|
||||
Logging.CommandLogError("Oh no you found this command!\nCommand functionality not implemented (yet)");
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private QuantizedPixel quantizeColor(Color pixel)
|
||||
{
|
||||
BlockColors color = BlockColors.Default;
|
||||
int darkness = 0;
|
||||
bool force = false;
|
||||
#if DEBUG
|
||||
Logging.MetaLog($"Color (r:{pixel.r}, g:{pixel.g}, b:{pixel.b})");
|
||||
#endif
|
||||
if (Mathf.Abs(pixel.r - pixel.g) <= pixel.r * 0.1f && Mathf.Abs(pixel.r - pixel.b) <= pixel.r * 0.1f)
|
||||
{
|
||||
color = BlockColors.White;
|
||||
darkness = (int)(10 - ((pixel.r + pixel.g + pixel.b) * 3.5));
|
||||
//Logging.MetaDebugLog($"Color (r:{pixel.r}, g:{pixel.g}, b:{pixel.b})");
|
||||
}
|
||||
else if (pixel.r >= pixel.g && pixel.r >= pixel.b)
|
||||
{
|
||||
// Red is highest
|
||||
if ((pixel.r - pixel.g) > pixel.r * 0.65 && (pixel.r - pixel.b) > pixel.r * 0.55)
|
||||
{
|
||||
// Red is much higher than other pixels
|
||||
darkness = (int)(9 - (pixel.r * 8.01));
|
||||
color = BlockColors.Red;
|
||||
}
|
||||
else if ((pixel.g - pixel.b) > pixel.g * 0.25)
|
||||
{
|
||||
// Green is much higher than blue
|
||||
if ((pixel.r - pixel.g) < pixel.r * 0.8)
|
||||
{
|
||||
darkness = (int)(10 - ((pixel.r * 2.1 + pixel.g) * 2.1));
|
||||
color = BlockColors.Orange;
|
||||
}
|
||||
else
|
||||
{
|
||||
darkness = (int)(10 - ((pixel.r * 2.1 + pixel.g) * 2.2));
|
||||
color = BlockColors.Yellow;
|
||||
}
|
||||
|
||||
}
|
||||
else if ((pixel.b - pixel.g) > pixel.b * 0.3)
|
||||
{
|
||||
// Blue is much higher than green
|
||||
darkness = (int)(10 - ((pixel.r + pixel.b) * 5.0));
|
||||
color = BlockColors.Purple;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Green is close strength to blue
|
||||
darkness = (int)(10 - ((pixel.r * 2.1 + pixel.g + pixel.b) * 2.5));
|
||||
color = darkness < 6 ? BlockColors.Pink : BlockColors.Orange;
|
||||
force = true;
|
||||
}
|
||||
}
|
||||
else if (pixel.g >= pixel.r && pixel.g >= pixel.b)
|
||||
{
|
||||
// Green is highest
|
||||
if ((pixel.g - pixel.r) > pixel.g * 0.6 && (pixel.g - pixel.b) > pixel.g * 0.48)
|
||||
{
|
||||
// Green is much higher than other pixels
|
||||
darkness = (int)(10 - (pixel.g * 10.1));
|
||||
color = BlockColors.Green;
|
||||
}
|
||||
else if ((pixel.r - pixel.b) > pixel.r * 0.3)
|
||||
{
|
||||
// Red is much higher than blue
|
||||
darkness = (int)(10 - ((pixel.r + pixel.g) * 5.1));
|
||||
color = BlockColors.Yellow;
|
||||
}
|
||||
else if ((pixel.b - pixel.r) > pixel.b * 0.2)
|
||||
{
|
||||
// Blue is much higher than red
|
||||
darkness = (int)(9 - ((pixel.g + pixel.b) * 5.1));
|
||||
color = BlockColors.Aqua;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Red is close strength to blue
|
||||
darkness = (int)(10 - ((pixel.r + pixel.g * 2.2 + pixel.b) * 2.9));
|
||||
color = BlockColors.Lime;
|
||||
}
|
||||
}
|
||||
else if (pixel.b >= pixel.g && pixel.b >= pixel.r)
|
||||
{
|
||||
// Blue is highest
|
||||
if ((pixel.b - pixel.g) > pixel.b * 0.6 && (pixel.b - pixel.r) > pixel.b * 0.6)
|
||||
{
|
||||
// Blue is much higher than other pixels
|
||||
darkness = (int)(10 - (pixel.b * 10.1));
|
||||
color = BlockColors.Blue;
|
||||
}
|
||||
else if ((pixel.g - pixel.r) > pixel.g * 0.3)
|
||||
{
|
||||
// Green is much higher than red
|
||||
darkness = (int)(10 - ((pixel.g + pixel.b) * 5.1));
|
||||
if (darkness == 4 || darkness == 5) darkness = 0;
|
||||
else if (darkness < 3) darkness = 4;
|
||||
color = BlockColors.Aqua;
|
||||
}
|
||||
else if ((pixel.r - pixel.g) > pixel.r * 0.3)
|
||||
{
|
||||
// Red is much higher than green
|
||||
darkness = (int)(10 - ((pixel.r + pixel.b) * 5.0));
|
||||
color = BlockColors.Purple;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Green is close strength to red
|
||||
darkness = (int)(10 - ((pixel.r + pixel.g + pixel.b * 2.2) * 3.0));
|
||||
color = BlockColors.Aqua;
|
||||
}
|
||||
}
|
||||
// level 9 is not darker than lvl 8
|
||||
if (darkness > 8 && !force) darkness = 8;
|
||||
// darkness 0 is the most saturated (it's not just the lightest)
|
||||
if (darkness < 0) darkness = 0;
|
||||
|
||||
QuantizedPixel result = new QuantizedPixel
|
||||
{
|
||||
block = pixel.a > 0.75 ? BlockIDs.AluminiumCube : BlockIDs.GlassCube,
|
||||
color = color,
|
||||
darkness = (byte)darkness,
|
||||
visible = pixel.a > 0.5f,
|
||||
};
|
||||
#if DEBUG
|
||||
Logging.MetaLog($"Quantized {color} (b:{result.block} d:{result.darkness} v:{result.visible})");
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
internal struct QuantizedPixel
|
||||
{
|
||||
public BlockIDs block;
|
||||
|
||||
public BlockColors color;
|
||||
|
||||
public byte darkness;
|
||||
|
||||
public bool visible;
|
||||
}
|
||||
}
|
7
Pixi/Robots/CubeUtility.cs
Normal file
7
Pixi/Robots/CubeUtility.cs
Normal file
|
@ -0,0 +1,7 @@
|
|||
using System;
|
||||
namespace Pixi.Robots
|
||||
{
|
||||
public static class CubeUtility
|
||||
{
|
||||
}
|
||||
}
|
7
Pixi/Robots/RobotCommands.cs
Normal file
7
Pixi/Robots/RobotCommands.cs
Normal file
|
@ -0,0 +1,7 @@
|
|||
using System;
|
||||
namespace Pixi.Robots
|
||||
{
|
||||
public static class RobotCommands
|
||||
{
|
||||
}
|
||||
}
|
|
@ -5,7 +5,8 @@ Think of it like automatic pixel art.
|
|||
|
||||
## Installation
|
||||
|
||||
To install the Pixi mod, copy the build's `Pixi.dll` into the `Plugins` folder in Gamecraft's main folder.
|
||||
To install the Pixi mod, copy `Pixi.dll` (from the latest release) into the `Plugins` folder in Gamecraft's main folder.
|
||||
You'll also need [GamecraftModdingAPI](https://git.exmods.org/modtainers/GamecraftModdingAPI) installed and Gamecraft patched with [GCIPA](https://git.exmods.org/modtainers/GCIPA/releases).
|
||||
|
||||
## Usage
|
||||
|
||||
|
|
Loading…
Reference in a new issue