@@ -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; | |||
} | |||
} |
@@ -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 = ""; | |||
} | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -0,0 +1,7 @@ | |||
using System; | |||
namespace Pixi.Robots | |||
{ | |||
public static class CubeUtility | |||
{ | |||
} | |||
} |
@@ -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 | |||