Browse Source

Refactor and implement img to text block importing

tags/v0.3.0
NGnius (Graham) 4 years ago
parent
commit
2e314595ac
8 changed files with 450 additions and 270 deletions
  1. +17
    -0
      Pixi/Common/BlockInfo.cs
  2. +214
    -0
      Pixi/Images/ImageCommands.cs
  3. +168
    -0
      Pixi/Images/PixelUtility.cs
  4. +28
    -6
      Pixi/Pixi.csproj
  5. +7
    -263
      Pixi/PixiPlugin.cs
  6. +7
    -0
      Pixi/Robots/CubeUtility.cs
  7. +7
    -0
      Pixi/Robots/RobotCommands.cs
  8. +2
    -1
      README.md

+ 17
- 0
Pixi/Common/BlockInfo.cs View 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
- 0
Pixi/Images/ImageCommands.cs View 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
- 0
Pixi/Images/PixelUtility.cs View 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();
}
}
}

+ 28
- 6
Pixi/Pixi.csproj View File

@@ -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>

+ 7
- 263
Pixi/PixiPlugin.cs View File

@@ -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
- 0
Pixi/Robots/CubeUtility.cs View File

@@ -0,0 +1,7 @@
using System;
namespace Pixi.Robots
{
public static class CubeUtility
{
}
}

+ 7
- 0
Pixi/Robots/RobotCommands.cs View File

@@ -0,0 +1,7 @@
using System;
namespace Pixi.Robots
{
public static class RobotCommands
{
}
}

+ 2
- 1
README.md View File

@@ -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