Skip to content

Commit

Permalink
Merge pull request #721 from Goodlyay/master
Browse files Browse the repository at this point in the history
Imageprint improvements
  • Loading branch information
UnknownShadow200 committed Sep 6, 2022
2 parents cfd9880 + ef73d67 commit f88d8c1
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 18 deletions.
22 changes: 16 additions & 6 deletions MCGalaxy/Commands/building/CmdImageprint.cs
Expand Up @@ -64,8 +64,14 @@ public sealed class CmdImageprint : Command2 {

if (parts.Length > 2) {
string mode = parts[2];
if (mode.CaselessEq("horizontal")) dArgs.Layer = true;
if (mode.CaselessEq("vertical2layer")) dArgs.Dual = true;
// Dithered and 2 layer mode are mutually exclusive because dithering is not visually effective when the (dark) sides of blocks are visible all over the image.

if (mode.CaselessEq("wall")) { }
else if (mode.CaselessEq("walldither")) { dArgs.Dithered = true; }
else if (mode.CaselessEq("wall2layer")) { dArgs.TwoLayer = true; }
else if (mode.CaselessEq("floor")) { dArgs.Floor = true; }
else if (mode.CaselessEq("floordither")) { dArgs.Floor = true; dArgs.Dithered = true; }
else { p.Message("&WUnknown print mode \"{0}\".", mode); return; }
}

if (parts.Length > 4) {
Expand Down Expand Up @@ -108,8 +114,8 @@ public sealed class CmdImageprint : Command2 {
IBitmap2D bmp = HeightmapGen.DecodeImage(dArgs.Data, p);
if (bmp == null) return;

ImagePrintDrawOp op = new ImagePrintDrawOp();
op.LayerMode = dArgs.Layer; op.DualLayer = dArgs.Dual;
ImagePrintDrawOp op = dArgs.Dithered ? new ImagePrintDitheredDrawOp() : new ImagePrintDrawOp();
op.LayerMode = dArgs.Floor; op.DualLayer = dArgs.TwoLayer;
op.CalcState(marks);

int width = dArgs.Width == 0 ? bmp.Width : dArgs.Width;
Expand Down Expand Up @@ -155,11 +161,15 @@ public sealed class CmdImageprint : Command2 {
p.Message("&T/ImagePrint [file/url] [palette] <mode> <width height>");
p.Message("&HPrints image from given URL, or from a .bmp file in /extra/images/ folder");
p.Message("&HPalettes: &f{0}", ImagePalette.Palettes.Join(pal => pal.Name));
p.Message("&HModes: &fVertical, Vertical2Layer, Horizontal");
p.Message("&HModes: &fWall, WallDither, Wall2Layer, Floor, FloorDither");
p.Message("&H <width height> optionally resize the printed image");
}

class DrawArgs { public bool Layer, Dual; public ImagePalette Pal; public byte[] Data; public int Width, Height; }
class DrawArgs {
public bool Floor, TwoLayer, Dithered;
public ImagePalette Pal;
public byte[] Data;
public int Width, Height; }
}
}

21 changes: 16 additions & 5 deletions MCGalaxy/Drawing/Image/IPaletteMatcher.cs
Expand Up @@ -17,13 +17,15 @@
*/
using System;
using BlockID = System.UInt16;
using MCGalaxy.Util;

namespace MCGalaxy.Drawing
{
public interface IPaletteMatcher
{
void SetPalette(PaletteEntry[] front, PaletteEntry[] back);
BlockID BestMatch(byte R, byte G, byte B);
BlockID BestMatch(byte R, byte G, byte B, out Pixel pixel);
BlockID BestMatch(byte R, byte G, byte B, out bool backLayer);
}

Expand All @@ -39,7 +41,14 @@ public sealed class RgbPaletteMatcher : IPaletteMatcher
MinDist(R, G, B, front, out pos);
return front[pos].Block;
}

public BlockID BestMatch(byte R, byte G, byte B, out Pixel pixel) {
int pos;
MinDist(R, G, B, front, out pos);
pixel = new Pixel(); pixel.A = byte.MaxValue; pixel.R = front[pos].R; pixel.G = front[pos].G; pixel.B = front[pos].B;
return front[pos].Block;
}


public BlockID BestMatch(byte R, byte G, byte B, out bool backLayer) {
int frontPos, backPos;
int frontDist = MinDist(R, G, B, front, out frontPos);
Expand All @@ -48,8 +57,7 @@ public sealed class RgbPaletteMatcher : IPaletteMatcher
backLayer = backDist < frontDist;
return backLayer ? back[backPos].Block : front[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++) {
Expand Down Expand Up @@ -89,13 +97,16 @@ public sealed class LabPaletteMatcher : IPaletteMatcher
}
return palette[pos].Block;
}

public BlockID BestMatch(byte R, byte G, byte B, out Pixel pixel) {
throw new NotImplementedException();
}

public BlockID BestMatch(byte R, byte G, byte B, out bool backLayer) {
backLayer = false;
return BestMatch(R, G, B);
}



struct LabColor {
public double L, A, B;
public BlockID Block;
Expand Down
87 changes: 80 additions & 7 deletions MCGalaxy/Drawing/Image/ImagePrintDrawOp.cs
Expand Up @@ -36,7 +36,7 @@ public class ImagePrintDrawOp : DrawOp
public ImagePalette Palette;

internal Vec3S32 dx, dy, adj;
IPaletteMatcher selector;
protected IPaletteMatcher selector;

public override void Perform(Vec3S32[] marks, Brush brush, DrawOpOutput output) {
selector = new RgbPaletteMatcher();
Expand All @@ -52,13 +52,13 @@ public class ImagePrintDrawOp : DrawOp

// Put all the blocks in shadow
if (DualLayer) {
ushort y = (ushort)(Origin.Y + Source.Height);
ushort y = (ushort)Math.Min(Origin.Y + Source.Height, Level.Height-1);
for (int i = 0; i < Source.Width; i++) {
ushort x = (ushort)(Origin.X + dx.X * i);
ushort z = (ushort)(Origin.Z + dx.Z * i);
output(Place(x, y, z, Block.Stone));

x = (ushort)(x + adj.X); z = (ushort)(z + adj.Z);
x = (ushort)(x - adj.X); z = (ushort)(z - adj.Z);
output(Place(x, y, z, Block.Stone));
}
}
Expand Down Expand Up @@ -103,7 +103,7 @@ public class ImagePrintDrawOp : DrawOp
return entry;
}

void OutputPixels(DrawOpOutput output) {
protected virtual void OutputPixels(DrawOpOutput output) {
int width = Source.Width, height = Source.Height;
int srcY = height - 1; // need to flip coords in bitmap vertically

Expand All @@ -122,9 +122,9 @@ public class ImagePrintDrawOp : DrawOp
} else {
bool backLayer;
block = selector.BestMatch(P.R, P.G, P.B, out backLayer);
if (backLayer) {
x = (ushort)(x + adj.X);
z = (ushort)(z + adj.Z);
if (!backLayer) {
x = (ushort)(x - adj.X);
z = (ushort)(z - adj.Z);
}
}
output(Place(x, y, z, block));
Expand Down Expand Up @@ -163,4 +163,77 @@ public class ImagePrintDrawOp : DrawOp
}
}
}
public class ImagePrintDitheredDrawOp : ImagePrintDrawOp
{
Vec3F32[,] pixels;
protected override void OutputPixels(DrawOpOutput output) {
int width = Source.Width, height = Source.Height;
int srcY = height - 1; // need to flip coords in bitmap vertically

pixels = new Vec3F32[width, height];

//setup image
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
Pixel p = Source.Get(x, y);
pixels[x, y] = new Vec3F32(p.R, p.G, p.B);
}
}

//dither image
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
Vec3F32 oldPixel = pixels[x, y];
// No Clamp for float?
if (oldPixel.X > 255) { oldPixel.X = 255; } if (oldPixel.X < 0) { oldPixel.X = 0; }
if (oldPixel.Y > 255) { oldPixel.Y = 255; } if (oldPixel.Y < 0) { oldPixel.Y = 0; }
if (oldPixel.Z > 255) { oldPixel.Z = 255; } if (oldPixel.Z < 0) { oldPixel.Z = 0; }

Vec3F32 newPixel;
{
byte oldClampedR, oldClampedG, oldClampedB;
oldClampedR = (byte)Utils.Clamp((int)oldPixel.X, byte.MinValue, byte.MaxValue);
oldClampedG = (byte)Utils.Clamp((int)oldPixel.Y, byte.MinValue, byte.MaxValue);
oldClampedB = (byte)Utils.Clamp((int)oldPixel.Z, byte.MinValue, byte.MaxValue);
Pixel temp;
selector.BestMatch(oldClampedR, oldClampedG, oldClampedB, out temp);
newPixel = new Vec3F32(temp.R, temp.G, temp.B);
}


pixels[x, y] = newPixel;
Vec3F32 quantError = oldPixel - newPixel;
if (x + 1 < width ) pixels[x + 1, y ] += (7.0f / 16.0f) * quantError;
if (x - 1 > 0 && y + 1 < height) pixels[x - 1, y + 1] += (3.0f / 16.0f) * quantError;
if (y + 1 < height ) pixels[x, y + 1] += (5.0f / 16.0f) * quantError;
if (x + 1 < width && y + 1 < height) pixels[x + 1, y + 1] += (1.0f / 16.0f) * quantError;
}
}


for (int yy = 0; yy < height; yy++, srcY--)
for (int xx = 0; xx < width; xx++) {
Pixel P = GetPixel(xx, srcY);
ushort x = (ushort)(Origin.X + dx.X * xx + dy.X * yy);
ushort y = (ushort)(Origin.Y + dx.Y * xx + dy.Y * yy);
ushort z = (ushort)(Origin.Z + dx.Z * xx + dy.Z * yy);
if (P.A < 20) { output(Place(x, y, z, Block.Air)); continue; }

BlockID block;
block = selector.BestMatch(P.R, P.G, P.B);
output(Place(x, y, z, block));
}
}


Pixel GetPixel(int x, int y) {
Pixel P = Source.Get(x, y);
Vec3F32 floatPixel = pixels[x, y];
P.R = (byte)Utils.Clamp((int)floatPixel.X, byte.MinValue, byte.MaxValue);
P.G = (byte)Utils.Clamp((int)floatPixel.Y, byte.MinValue, byte.MaxValue);
P.B = (byte)Utils.Clamp((int)floatPixel.Z, byte.MinValue, byte.MaxValue);
return P;
}

}
}

0 comments on commit f88d8c1

Please sign in to comment.