Refactor /img to be more modular

This commit is contained in:
UnknownShadow200 2017-02-16 15:14:03 +11:00
parent e3500790f1
commit bddec7cca6
6 changed files with 260 additions and 275 deletions

View File

@ -41,44 +41,36 @@ namespace MCGalaxy.Commands.Building {
public override void Use(Player p, string message) {
if (!Directory.Exists("extra/images/"))
Directory.CreateDirectory("extra/images/");
bool layer = false;
byte popType = 1;
string bitmapLoc = null;
if (message == "") { Help(p); return; }
if (message.IndexOf(' ') != -1) {
string[] args = message.Split(' ');
for (int i = 0; i < args.Length; i++)
{
if (args[i] == "layer" || args[i] == "l") layer = true;
else if (args[i] == "1" || args[i] == "2color") popType = 1;
else if (args[i] == "2" || args[i] == "1color") popType = 2;
else if (args[i] == "3" || args[i] == "2gray") popType = 3;
else if (args[i] == "4" || args[i] == "1gray") popType = 4;
else if (args[i] == "5" || args[i] == "bw") popType = 5;
else if (args[i] == "6" || args[i] == "gray") popType = 6;
}
message = args[args.Length - 1];
}
if (message.IndexOf('/') == -1 && message.IndexOf('.') != -1) {
if (!DownloadWebFile("http://www.imgur.com/" + message, p)) return;
bitmapLoc = "tempImage_" + p.name;
} else if (message.IndexOf('.') != -1) {
if (!DownloadWebFile(message, p)) return;
bitmapLoc = "tempImage_" + p.name;
} else {
bitmapLoc = message;
}
if (!File.Exists("extra/images/" + bitmapLoc + ".bmp")) {
Player.Message(p, "The URL entered was invalid!"); return;
}
string[] parts = message.SplitSpaces(3);
DrawArgs dArgs = default(DrawArgs);
dArgs.layer = layer;
dArgs.name = bitmapLoc;
dArgs.popType = popType;
dArgs.palette = ImagePalette.Palettes[0];
dArgs.dualLayered = true;
if (parts.Length == 3) {
string mode = parts[2];
if (mode.CaselessEq("horizontal")) dArgs.layer = true;
if (mode.CaselessEq("vertical")) dArgs.dualLayered = false;
}
if (parts.Length >= 2) {
dArgs.palette = ImagePalette.Find(parts[1]);
if (dArgs.palette == null) {
Player.Message(p, "Palette {0} not found.", parts[1]); return;
}
}
if (message.IndexOf('.') != -1) {
if (!DownloadWebFile(message, p)) return;
dArgs.name = "tempImage_" + p.name;
} else {
dArgs.name = message;
}
if (!File.Exists("extra/images/" + dArgs.name + ".bmp")) {
Player.Message(p, "The URL entered was invalid!"); return;
}
Player.Message(p, "Place two blocks to determine direction.");
p.MakeSelection(2, dArgs, DoImage);
}
@ -123,30 +115,20 @@ namespace MCGalaxy.Commands.Building {
}
op.SetLevel(p.level);
op.Player = p;
op.Source = bmp;
op.Layer = dArgs.layer;
op.Mode = dArgs.popType;
op.Filename = dArgs.name;
if (op.Layer) {
if (op.Mode == 1) op.Mode = 2;
if (op.Mode == 3) op.Mode = 4;
}
op.Player = p; op.Source = bmp;
op.LayerMode = dArgs.layer; op.DualLayer = dArgs.dualLayered;
op.Palette = dArgs.palette; op.Filename = dArgs.name;
DrawOpPerformer.Do(op, null, p, m, false);
}
public override void Help(Player p) {
Player.Message(p, "/imageprint <switch> <localfile> - Print local file in extra/images/ folder. Must be type .bmp, type filename without extension.");
Player.Message(p, "/imageprint <switch> <imgurfile.extension> - Print IMGUR stored file. Example: /i piCCm.gif will print www.imgur.com/piCCm.gif. Case-sensitive");
Player.Message(p, "/imageprint <switch> <webfile> - Print web file in format domain.com/folder/image.jpg. Does not need http:// or www.");
Player.Message(p, "Available switches: (&f1%S) 2-Layer Color image, (&f2%S) 1-Layer Color Image, " +
"(&f3%S) 2-Layer Grayscale, (&f4%S) 1-Layer Grayscale, (%f5%S) Black and White, (&f6%S) Mathematical Grayscale");
Player.Message(p, "Local filetypes: .bmp. Remote Filetypes: .gif .png .jpg .bmp. PNG and GIF may use transparency");
Player.Message(p, "Use switch (&flayer%S) or (&fl%S) to print horizontally.");
Player.Message(p, "%T/imageprint [file] [palette] <mode> %H- Prints image in extra/images/ folder. (File must have .bmp extension)");
Player.Message(p, "%T/imageprint [url] [palette] <mode> %H- Downloads then prints the given image. (transparency supported)");
Player.Message(p, "%HPalettes: &f{0}", ImagePalette.Palettes.Join(pal => pal.Name));
Player.Message(p, "%HModes: &fVertical, Vertical2Layer, Horizontal");
}
struct DrawArgs { public bool layer; public byte popType; public string name; }
struct DrawArgs { public bool layer, dualLayered; public ImagePalette palette; public string name; }
}
}

View File

@ -20,68 +20,70 @@ using System.Drawing;
namespace MCGalaxy.Drawing {
public interface IPalette {
public interface IPaletteMatcher {
/// <summary> Sets the blocks available for this palette to pick from. </summary>
void SetAvailableBlocks(PaletteEntry[] blocks);
/// <summary> Sets the palette of blocks used to match colours from. </summary>
void SetPalette(ImagePalette palette);
/// <summary> Returns the best matching block for the given color,
/// based on this palette's colourspace. </summary>
byte BestMatch(byte R, byte G, byte B, out int position);
byte BestMatch(byte R, byte G, byte B);
/// <summary> Returns the best matching block for the given color,
/// based on this palette's colourspace. </summary>
byte BestMatch(byte R, byte G, byte B, out bool backLayer);
}
public sealed class GrayscalePalette : IPalette {
public sealed class RgbPaletteMatcher : IPaletteMatcher {
public void SetAvailableBlocks(PaletteEntry[] blocks) { }
ImagePalette palette;
public void SetPalette(ImagePalette palette) {
this.palette = palette;
}
public byte BestMatch(byte R, byte G, byte B, out int position) {
int brightness = (R + G + B) / 3; position = -1;
if (brightness < (256 / 4))
return Block.obsidian;
else if (brightness >= (256 / 4) && brightness < (256 / 4) * 2)
return Block.darkgrey;
else if (brightness >= (256 / 4) * 2 && brightness < (256 / 4) * 3)
return Block.lightgrey;
else
return Block.white;
public byte BestMatch(byte R, byte G, byte B) {
int pos;
MinDist(R, G, B, palette.FrontLayer, out pos);
return palette.FrontLayer[pos].Block;
}
public byte BestMatch(byte R, byte G, byte B, out bool backLayer) {
int frontPos, backPos;
int frontDist = MinDist(R, G, B, palette.FrontLayer, out frontPos);
int backDist = MinDist(R, G, B, palette.BackLayer, out backPos);
backLayer = backDist <= frontDist;
return backLayer ? palette.BackLayer[backPos].Block : palette.FrontLayer[frontPos].Block;
}
static int MinDist(byte R, byte G, byte B, PaletteEntry[] entries, out int pos) {
int minDist = int.MaxValue; pos = 0;
for (int i = 0; i < entries.Length; i++) {
PaletteEntry entry = entries[i];
int dist = (R - entry.R) * (R - entry.R)
+ (G - entry.G) * (G - entry.G)
+ (B - entry.B) * (B - entry.B);
if (dist < minDist) { minDist = dist; pos = i; }
}
return minDist;
}
}
public sealed class RgbPalette : IPalette {
PaletteEntry[] palette;
public void SetAvailableBlocks(PaletteEntry[] blocks) {
this.palette = blocks;
}
public byte BestMatch(byte R, byte G, byte B, out int position) {
int minimum = int.MaxValue; position = 0;
for (int i = 0; i < palette.Length; i++) {
PaletteEntry pixel = palette[i];
int dist = (R - pixel.R) * (R - pixel.R)
+ (G - pixel.G) * (G - pixel.G)
+ (B - pixel.B) * (B - pixel.B);
if (dist < minimum) {
minimum = dist; position = i;
}
}
return palette[position].Block;
}
}
public sealed class LabPalette : IPalette {
public sealed class LabPaletteMatcher : IPaletteMatcher {
LabColor[] palette;
public void SetAvailableBlocks(PaletteEntry[] blocks) {
palette = new LabColor[blocks.Length];
for (int i = 0; i < palette.Length; i++)
palette[i] = RgbToLab(blocks[i]);
public void SetPalette(ImagePalette palette) {
this.palette = new LabColor[palette.FrontLayer.Length];
for (int i = 0; i < palette.FrontLayer.Length; i++)
this.palette[i] = RgbToLab(palette.FrontLayer[i]);
}
public byte BestMatch(byte r, byte g, byte b, out int position) {
double minimum = int.MaxValue; position = 0;
LabColor col = RgbToLab(r, g, b);
public byte BestMatch(byte R, byte G, byte B) {
double minDist = int.MaxValue; int pos = 0;
LabColor col = RgbToLab(R, G, B);
for (int i = 0; i < palette.Length; i++) {
LabColor pixel = palette[i];
@ -90,13 +92,17 @@ namespace MCGalaxy.Drawing {
+ (col.A - pixel.A) * (col.A - pixel.A)
+ (col.B - pixel.B) * (col.B - pixel.B);
if (dist < minimum) {
minimum = dist; position = i;
if (dist < minDist) { minDist = dist; pos = i; }
}
return palette[pos].Block;
}
return palette[position].Block;
public byte BestMatch(byte R, byte G, byte B, out bool backLayer) {
backLayer = false;
return BestMatch(R, G, B, out backLayer);
}
struct LabColor {
public double L, A, B;
public byte Block;

View File

@ -1,4 +1,4 @@
/*
/*
Copyright 2011 MCForge
Dual-licensed under the Educational Community License, Version 2.0 and
@ -19,23 +19,43 @@ using System.Collections.Generic;
namespace MCGalaxy.Drawing {
internal static class ImagePalette {
/// <summary> Represents a mapping of block ids to RGB colours. </summary>
public sealed class ImagePalette {
internal static string[] Names = {
null, "2-layer color", "1-layer color", "2-layer grayscale",
"1-layer grayscale", "Black and White", "Mathematical grayscale"
};
public static PaletteEntry[] GetPalette(int type) {
if (type == 1) return Color_2;
if (type == 2) return Color_1;
if (type == 3) return Grayscale_2;
if (type == 4) return Grayscale_1;
if (type == 5) return BlackWhite_1;
/// <summary> The name of this palette. </summary>
public string Name;
/// <summary> Blocks in the front, used in vertical and layer mode. </summary>
public PaletteEntry[] FrontLayer;
/// <summary> Blocks in the back, used only in two-layer vertical mode. </summary>
public PaletteEntry[] BackLayer;
public ImagePalette(string name, PaletteEntry[] front, PaletteEntry[] back) {
Name = name; FrontLayer = front; BackLayer = back;
}
/// <summary> All supported palettes. </summary>
public static List<ImagePalette> Palettes = new List<ImagePalette>();
public static ImagePalette Find(string name) {
foreach (ImagePalette entry in Palettes) {
if (entry.Name.CaselessEq(name)) return entry;
}
return null;
}
internal static PaletteEntry[] Color_2 = {
// front layer
public static void Load() {
Palettes.Clear();
Palettes.Add(new ImagePalette("Color", Color_Front, Color_Back));
Palettes.Add(new ImagePalette("Grayscale", Grayscale_Front, Grayscale_Back));
Palettes.Add(new ImagePalette("BlackWhite", BlackWhite_Front, null));
Palettes.Add(new ImagePalette("SimpleGrayscale", Grayscale_Mathematical, null));
}
static PaletteEntry[] Color_Front = {
new PaletteEntry(128, 86, 57, Block.dirt),
new PaletteEntry(162, 129, 75, Block.wood),
new PaletteEntry(244, 237, 174, Block.sand),
@ -56,6 +76,8 @@ namespace MCGalaxy.Drawing {
new PaletteEntry(135, 145, 130, Block.lightgrey),
new PaletteEntry(230, 240, 225, Block.white),
new PaletteEntry(163, 163, 163, Block.staircasefull),
new PaletteEntry(0, 0, 0, Block.obsidian),
};
/*Turns out the back layer blocks are handled awfully.
new PaletteEntry(217, 131, 155, 55),
new PaletteEntry(56, 77, 24, 56),
@ -63,7 +85,7 @@ namespace MCGalaxy.Drawing {
new PaletteEntry(39, 51, 154, 58),
new PaletteEntry(39, 117, 149, 59),*/
// back layer
static PaletteEntry[] Color_Back = {
new PaletteEntry(57, 38, 25, Block.dirt),
new PaletteEntry(72, 57, 33, Block.wood),
new PaletteEntry(109, 105, 77, Block.sand),
@ -87,50 +109,28 @@ namespace MCGalaxy.Drawing {
new PaletteEntry(0, 0, 0, Block.obsidian),
};
internal static PaletteEntry[] Color_1 = {
new PaletteEntry(128, 86, 57, Block.dirt),
new PaletteEntry(162, 129, 75, Block.wood),
new PaletteEntry(244, 237, 174, Block.sand),
new PaletteEntry(93, 70, 38, Block.trunk),
new PaletteEntry(226, 31, 38, Block.red),
new PaletteEntry(223, 135, 37, Block.orange),
new PaletteEntry(230, 241, 25, Block.yellow),
new PaletteEntry(127, 234, 26, Block.lightgreen),
new PaletteEntry(25, 234, 20, Block.green),
new PaletteEntry(31, 234, 122, Block.aquagreen),
new PaletteEntry(27, 239, 225, Block.cyan),
new PaletteEntry(99, 166, 226, Block.lightblue),
new PaletteEntry(111, 124, 235, Block.blue),
new PaletteEntry(126, 34, 218, Block.purple),
new PaletteEntry(170, 71, 219, Block.lightpurple),
new PaletteEntry(227, 39, 225, Block.pink),
new PaletteEntry(234, 39, 121, Block.darkpink),
static PaletteEntry[] Grayscale_Front = {
new PaletteEntry(0, 0, 0, Block.obsidian),
new PaletteEntry(46, 68, 47, Block.darkgrey),
new PaletteEntry(135, 145, 130, Block.lightgrey),
new PaletteEntry(230, 240, 225, Block.white),
new PaletteEntry(0, 0, 0, Block.obsidian),
};
internal static PaletteEntry[] Grayscale_2 = {
// front layer
new PaletteEntry(46, 68, 47, Block.darkgrey),
new PaletteEntry(135, 145, 130, Block.lightgrey),
new PaletteEntry(230, 240, 225, Block.white),
// back layer
static PaletteEntry[] Grayscale_Back = {
new PaletteEntry(0, 0, 0, Block.obsidian),
new PaletteEntry(20, 30, 21, Block.darkgrey),
new PaletteEntry(60, 64, 58, Block.lightgrey),
new PaletteEntry(102, 107, 100, Block.white),
new PaletteEntry(0, 0, 0, Block.obsidian),
};
internal static PaletteEntry[] Grayscale_1 = {
new PaletteEntry(46, 68, 47, Block.darkgrey),
new PaletteEntry(135, 145, 130, Block.lightgrey),
new PaletteEntry(230, 240, 225, Block.white),
new PaletteEntry(0, 0, 0, Block.obsidian),
static PaletteEntry[] Grayscale_Mathematical = {
new PaletteEntry(32, 32, 32, Block.obsidian),
new PaletteEntry(96, 96, 96, Block.darkgrey),
new PaletteEntry(160, 160, 160, Block.lightgrey),
new PaletteEntry(224, 224, 224, Block.white),
};
internal static PaletteEntry[] BlackWhite_1 = {
static PaletteEntry[] BlackWhite_Front = {
new PaletteEntry(255, 255, 255, Block.white),
new PaletteEntry(0, 0, 0, Block.obsidian),
};

View File

@ -31,23 +31,20 @@ namespace MCGalaxy.Drawing.Ops {
}
internal Draw.Bitmap Source;
internal int Mode, Direction;
internal bool Layer;
internal int Direction;
internal bool DualLayer, LayerMode;
internal string Filename;
public ImagePalette Palette;
Vec3S32 dx, dy, adj;
IPalette selector;
int threshold;
IPaletteMatcher selector;
public override void Perform(Vec3S32[] marks, Brush brush, Action<DrawOpBlock> output) {
selector = null;
CalcThreshold();
CalcDirectionVectors(Direction);
CalcState(Direction);
PaletteEntry[] palette = ImagePalette.GetPalette(Mode);
if (Mode == 6) selector = new GrayscalePalette();
else selector = new RgbPalette();
selector.SetAvailableBlocks(palette);
selector = new RgbPaletteMatcher();
selector.SetPalette(Palette);
using (PixelGetter getter = new PixelGetter(Source)) {
getter.Init();
@ -58,7 +55,7 @@ namespace MCGalaxy.Drawing.Ops {
Source = null;
if (Filename == "tempImage_" + Player.name)
File.Delete("extra/images/tempImage_" + Player.name + ".bmp");
Player.Message(Player, "Finished printing image using " + ImagePalette.Names[Mode]);
Player.Message(Player, "Finished printing image using {0} palette.", Palette.Name);
}
void OutputPixel(Pixel P, Action<DrawOpBlock> output) {
@ -67,25 +64,23 @@ namespace MCGalaxy.Drawing.Ops {
ushort z = (ushort)(Origin.Z + dx.Z * P.X + dy.Z * P.Y);
if (P.A < 20) { output(Place(x, y, z, Block.air, 0)); return; }
int position;
byte block = selector.BestMatch(P.R, P.G, P.B, out position);
// Back layer block
if (position <= threshold) {
if (!DualLayer) {
byte block = selector.BestMatch(P.R, P.G, P.B);
output(Place(x, y, z, block, 0));
} else {
bool backLayer;
byte block = selector.BestMatch(P.R, P.G, P.B, out backLayer);
if (backLayer) {
x = (ushort)(x + adj.X);
z = (ushort)(z + adj.Z);
}
output(Place(x, y, z, block, 0));
}
void CalcThreshold() {
threshold = -1;
if (Mode == 1 || Mode == 3) {
threshold = Mode == 1 ? 20 : 3;
}
}
void CalcDirectionVectors(int dir) {
void CalcState(int dir) {
dx = default(Vec3S32); dy = default(Vec3S32); adj = default(Vec3S32);
DualLayer = DualLayer && !LayerMode && Palette.BackLayer != null;
// Calculate back layer offset
if (dir == 0) adj.Z = 1;
@ -93,7 +88,7 @@ namespace MCGalaxy.Drawing.Ops {
if (dir == 2) adj.X = -1;
if (dir == 3) adj.X = 1;
if (Layer) {
if (LayerMode) {
if (dir == 0) { dx.X = 1; dy.Z = -1; }
if (dir == 1) { dx.X = -1; dy.Z = 1; }
if (dir == 2) { dx.Z = 1; dy.X = 1; }

View File

@ -445,7 +445,7 @@
<Compile Include="Drawing\Flip.cs" />
<Compile Include="Drawing\Image\ImagePalette.cs" />
<Compile Include="Drawing\Image\ImagePrintDrawOp.cs" />
<Compile Include="Drawing\Image\IPalette.cs" />
<Compile Include="Drawing\Image\IPaletteMatcher.cs" />
<Compile Include="Drawing\Image\PixelGetter.cs" />
<Compile Include="Drawing\TransformFactories\RotateTransform.cs" />
<Compile Include="Drawing\TransformFactories\SimpleTransforms.cs" />

View File

@ -21,6 +21,7 @@ using System.Net;
using System.Net.Sockets;
using MCGalaxy.Blocks;
using MCGalaxy.Commands.World;
using MCGalaxy.Drawing;
using MCGalaxy.Games;
using MCGalaxy.Tasks;
using MCGalaxy.Util;
@ -171,6 +172,7 @@ namespace MCGalaxy {
Alias.Load();
Bots.BotsFile.Load();
BlockDefinition.LoadGlobal();
ImagePalette.Load();
SrvProperties.Load("properties/server.properties");
Updater.Load("properties/update.properties");