Add image brush

This commit is contained in:
Jesse Boyd 2018-05-11 17:59:32 +10:00
parent 8218e831ac
commit fe17434d00
No known key found for this signature in database
GPG Key ID: 59F1DE6293AF6E1F
8 changed files with 456 additions and 144 deletions

View File

@ -1,96 +1,129 @@
package com.boydti.fawe.object.brush;
import com.boydti.fawe.object.PseudoRandom;
import com.boydti.fawe.object.brush.heightmap.HeightMap;
import com.boydti.fawe.object.mask.AdjacentAnyMask;
import com.boydti.fawe.object.collection.SummedColorTable;
import com.boydti.fawe.util.TextureUtil;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.LocalSession;
import com.sk89q.worldedit.MaxChangedBlocksException;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.entity.Player;
import com.sk89q.worldedit.extent.clipboard.Clipboard;
import com.sk89q.worldedit.function.RegionFunction;
import com.sk89q.worldedit.blocks.BaseBlock;
import com.sk89q.worldedit.command.tool.brush.Brush;
import com.sk89q.worldedit.extent.Extent;
import com.sk89q.worldedit.function.mask.Mask;
import com.sk89q.worldedit.function.mask.Masks;
import com.sk89q.worldedit.function.mask.RegionMask;
import com.sk89q.worldedit.function.mask.SolidBlockMask;
import com.sk89q.worldedit.function.operation.Operations;
import com.sk89q.worldedit.function.pattern.Pattern;
import com.sk89q.worldedit.function.visitor.RecursiveVisitor;
import com.sk89q.worldedit.regions.CuboidRegion;
import java.io.InputStream;
import com.sk89q.worldedit.math.transform.AffineTransform;
import com.sk89q.worldedit.util.Location;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Arrays;
public class ImageBrush extends HeightBrush {
private final boolean onlyWhite;
private final Player player;
public class ImageBrush implements Brush {
private final LocalSession session;
private final SummedColorTable table;
private final int width, height;
private final double centerX, centerZ;
public ImageBrush(Player player, InputStream stream, int rotation, double yscale, boolean onlyWhite, Clipboard clipboard) {
super(stream, rotation, yscale, false, true, clipboard);
this.onlyWhite = onlyWhite;
this.player = player;
private final ColorFunction colorFunction;
public ImageBrush(BufferedImage image, LocalSession session, boolean alpha /*, boolean glass */) throws IOException {
this.session = session;
this.table = new SummedColorTable(image, alpha);
this.width = image.getWidth();
this.height = image.getHeight();
this.centerX = width / 2d;
this.centerZ = height / 2d;
if (alpha) {
colorFunction = (x1, z1, x2, z2, extent, pos) -> {
int color = table.averageRGBA(x1, z1, x2, z2);
int alpha1 = (color >> 24) & 0xFF;
switch (alpha1) {
case 0:
return 0;
case 255:
return color;
default:
BaseBlock block = extent.getBlock(pos);
TextureUtil tu = session.getTextureUtil();
int existingColor = tu.getColor(block);
return tu.combineTransparency(color, existingColor);
}
};
} else {
colorFunction = (x1, z1, x2, z2, extent, pos) -> table.averageRGB(x1, z1, x2, z2);
}
}
private interface ColorFunction {
int call(int x1, int z1, int x2, int z2, Extent extent, Vector pos);
}
private interface BlockFunction {
void apply(int color, Extent extent, Vector pos);
}
@Override
public void build(EditSession editSession, Vector position, Pattern pattern, double sizeDouble) throws MaxChangedBlocksException {
TextureUtil texture = session.getTextureUtil();
final int cx = position.getBlockX();
final int cy = position.getBlockY();
final int cz = position.getBlockZ();
int size = (int) sizeDouble;
int maxY = editSession.getMaxY();
int add;
if (yscale < 0) {
add = maxY;
} else {
add = 0;
}
double scale = (yscale / sizeDouble) * (maxY + 1);
final HeightMap map = getHeightMap();
map.setSize(size);
int cutoff = onlyWhite ? maxY : 0;
final SolidBlockMask solid = new SolidBlockMask(editSession);
final AdjacentAnyMask adjacent = new AdjacentAnyMask(Masks.negate(solid));
RegionMask region = new RegionMask(new CuboidRegion(editSession.getWorld(), position.subtract(size, size, size), position.add(size, size, size)));
double scale = Math.max(width, height) / sizeDouble;
Location loc = editSession.getPlayer().getPlayer().getLocation();
float yaw = loc.getYaw();
float pitch = loc.getPitch();
AffineTransform transform = new AffineTransform().rotateY((-yaw) % 360).rotateX(pitch - 90).inverse();
RecursiveVisitor visitor = new RecursiveVisitor(new Mask() {
private final Vector mutable = new Vector();
@Override
public boolean test(Vector vector) {
if (solid.test(vector) && region.test(vector)) {
if (solid.test(vector)) {
int dx = vector.getBlockX() - cx;
int dy = vector.getBlockY() - cy;
int dz = vector.getBlockZ() - cz;
Vector pos1 = transform.apply(mutable.setComponents(dx - 0.5, dy - 0.5, dz - 0.5));
int x1 = (int) (pos1.getX() * scale + centerX);
int z1 = (int) (pos1.getZ() * scale + centerZ);
if (dir != null) {
if (dy != 0) {
if (dir.getBlockX() != 0) {
dx += dir.getBlockX() * dy;
} else if (dir.getBlockZ() != 0) {
dz += dir.getBlockZ() * dy;
}
}
double raise = map.getHeight(dx, dz);
int val = (int) Math.ceil(raise * scale) + add;
if (val < cutoff) {
return true;
}
if (val >= 255 || PseudoRandom.random.random(maxY) < val) {
editSession.setBlock(vector.getBlockX(), vector.getBlockY(), vector.getBlockZ(), pattern);
}
return true;
Vector pos2 = transform.apply(mutable.setComponents(dx + 0.5, dy + 0.5, dz + 0.5));
int x2 = (int) (pos2.getX() * scale + centerX);
int z2 = (int) (pos2.getZ() * scale + centerZ);
if (x2 < x1) {
int tmp = x1;
x1 = x2;
x2 = tmp;
}
if (z2 < z1) {
int tmp = z1;
z1 = z2;
z2 = tmp;
}
if (x1 >= width || x2 < 0 || z1 >= height || z2 < 0) return false;
int color = colorFunction.call(x1, z1, x2, z2, editSession, vector);
if (color != 0) {
BaseBlock block = texture.getNearestBlock(color);
if (block != null) {
editSession.setBlock(vector.getBlockX(), vector.getBlockY(), vector.getBlockZ(), block);
}
}
return true;
}
return false;
}
}, new RegionFunction() {
@Override
public boolean apply(Vector vector) throws WorldEditException {
return true;
}
}, Integer.MAX_VALUE, editSession);
}, vector -> true, Integer.MAX_VALUE, editSession);
visitor.setDirections(Arrays.asList(visitor.DIAGONAL_DIRECTIONS));
visitor.visit(position);
Operations.completeBlindly(visitor);

View File

@ -8,10 +8,8 @@ import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.MaxChangedBlocksException;
import com.sk89q.worldedit.MutableBlockVector;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.blocks.BaseBlock;
import com.sk89q.worldedit.command.tool.brush.Brush;
import com.sk89q.worldedit.function.RegionFunction;
import com.sk89q.worldedit.function.mask.BlockMask;
import com.sk89q.worldedit.function.mask.Mask;
import com.sk89q.worldedit.function.mask.SolidBlockMask;
@ -43,39 +41,33 @@ public class LayerBrush implements Brush {
Operations.completeBlindly(visitor);
BlockVectorSet visited = visitor.getVisited();
BaseBlock firstPattern = layers[0];
visitor = new RecursiveVisitor(new Mask() {
@Override
public boolean test(Vector pos) {
int depth = visitor.getDepth() + 1;
if (depth > 1) {
boolean found = false;
int previous = layers[depth - 1].getCombined();
int previous2 = layers[depth - 2].getCombined();
for (Vector dir : BreadthFirstSearch.DEFAULT_DIRECTIONS) {
mutable.setComponents(pos.getBlockX() + dir.getBlockX(), pos.getBlockY() + dir.getBlockY(), pos.getBlockZ() + dir.getBlockZ());
if (visitor.isVisited(mutable) && queue.getCachedCombinedId4Data(mutable.getBlockX(), mutable.getBlockY(), mutable.getBlockZ()) == previous) {
mutable.setComponents(pos.getBlockX() + dir.getBlockX() * 2, pos.getBlockY() + dir.getBlockY() * 2, pos.getBlockZ() + dir.getBlockZ() * 2);
if (visitor.isVisited(mutable) && queue.getCachedCombinedId4Data(mutable.getBlockX(), mutable.getBlockY(), mutable.getBlockZ()) == previous2) {
found = true;
break;
} else {
return false;
}
visitor = new RecursiveVisitor((Mask) pos -> {
int depth = visitor.getDepth() + 1;
if (depth > 1) {
boolean found = false;
int previous = layers[depth - 1].getCombined();
int previous2 = layers[depth - 2].getCombined();
for (Vector dir : BreadthFirstSearch.DEFAULT_DIRECTIONS) {
mutable.setComponents(pos.getBlockX() + dir.getBlockX(), pos.getBlockY() + dir.getBlockY(), pos.getBlockZ() + dir.getBlockZ());
if (visitor.isVisited(mutable) && queue.getCachedCombinedId4Data(mutable.getBlockX(), mutable.getBlockY(), mutable.getBlockZ()) == previous) {
mutable.setComponents(pos.getBlockX() + dir.getBlockX() * 2, pos.getBlockY() + dir.getBlockY() * 2, pos.getBlockZ() + dir.getBlockZ() * 2);
if (visitor.isVisited(mutable) && queue.getCachedCombinedId4Data(mutable.getBlockX(), mutable.getBlockY(), mutable.getBlockZ()) == previous2) {
found = true;
break;
} else {
return false;
}
}
if (!found) {
return false;
}
}
return !adjacent.test(pos);
}
}, new RegionFunction() {
@Override
public boolean apply(Vector pos) throws WorldEditException {
int depth = visitor.getDepth();
BaseBlock currentPattern = layers[depth];
return editSession.setBlockFast(pos, currentPattern);
if (!found) {
return false;
}
}
return !adjacent.test(pos);
}, pos -> {
int depth = visitor.getDepth();
BaseBlock currentPattern = layers[depth];
return editSession.setBlockFast(pos, currentPattern);
}, layers.length - 1, editSession);
for (Vector pos : visited) {
visitor.visit(pos);

View File

@ -5,18 +5,18 @@ import com.boydti.fawe.object.brush.heightmap.HeightMap;
import com.boydti.fawe.object.mask.AdjacentAnyMask;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.MaxChangedBlocksException;
import com.sk89q.worldedit.MutableBlockVector;
import com.sk89q.worldedit.Vector;
import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.entity.Player;
import com.sk89q.worldedit.extent.clipboard.Clipboard;
import com.sk89q.worldedit.function.RegionFunction;
import com.sk89q.worldedit.function.mask.Mask;
import com.sk89q.worldedit.function.mask.Masks;
import com.sk89q.worldedit.function.mask.RegionMask;
import com.sk89q.worldedit.function.mask.SolidBlockMask;
import com.sk89q.worldedit.function.operation.Operations;
import com.sk89q.worldedit.function.pattern.Pattern;
import com.sk89q.worldedit.function.visitor.RecursiveVisitor;
import com.sk89q.worldedit.regions.CuboidRegion;
import com.sk89q.worldedit.math.transform.AffineTransform;
import com.sk89q.worldedit.util.Location;
import java.io.InputStream;
import java.util.Arrays;
@ -34,6 +34,7 @@ public class StencilBrush extends HeightBrush {
final int cy = position.getBlockY();
final int cz = position.getBlockZ();
int size = (int) sizeDouble;
int size2 = (int) (sizeDouble * sizeDouble);
int maxY = editSession.getMaxY();
int add;
if (yscale < 0) {
@ -47,42 +48,48 @@ public class StencilBrush extends HeightBrush {
int cutoff = onlyWhite ? maxY : 0;
final SolidBlockMask solid = new SolidBlockMask(editSession);
final AdjacentAnyMask adjacent = new AdjacentAnyMask(Masks.negate(solid));
RegionMask region = new RegionMask(new CuboidRegion(editSession.getWorld(), position.subtract(size, size, size), position.add(size, size, size)));
Player player = editSession.getPlayer().getPlayer();
Vector pos = player.getPosition();
Location loc = editSession.getPlayer().getPlayer().getLocation();
float yaw = loc.getYaw();
float pitch = loc.getPitch();
AffineTransform transform = new AffineTransform().rotateY((-yaw) % 360).rotateX(pitch - 90).inverse();
RecursiveVisitor visitor = new RecursiveVisitor(new Mask() {
private final MutableBlockVector mutable = new MutableBlockVector();
@Override
public boolean test(Vector vector) {
if (solid.test(vector) && region.test(vector)) {
if (solid.test(vector)) {
int dx = vector.getBlockX() - cx;
int dy = vector.getBlockY() - cy;
int dz = vector.getBlockZ() - cz;
Vector dir = adjacent.direction(vector);
if (dir != null) {
if (dy != 0) {
if (dir.getBlockX() != 0) {
dx += dir.getBlockX() * dy;
} else if (dir.getBlockZ() != 0) {
dz += dir.getBlockZ() * dy;
}
}
double raise = map.getHeight(dx, dz);
int val = (int) Math.ceil(raise * scale) + add;
if (val < cutoff) {
return true;
}
if (val >= 255 || PseudoRandom.random.random(maxY) < val) {
editSession.setBlock(vector.getBlockX(), vector.getBlockY(), vector.getBlockZ(), pattern);
}
Vector srcPos = transform.apply(mutable.setComponents(dx, dy, dz));
dx = srcPos.getBlockX();
dz = srcPos.getBlockZ();
int distance = dx * dx + dz * dz;
if (distance > size2 || Math.abs(dx) > 256 || Math.abs(dz) > 256) return false;
double raise = map.getHeight(dx, dz);
int val = (int) Math.ceil(raise * scale) + add;
if (val < cutoff) {
return true;
}
if (val >= 255 || PseudoRandom.random.random(maxY) < val) {
editSession.setBlock(vector.getBlockX(), vector.getBlockY(), vector.getBlockZ(), pattern);
}
return true;
}
return false;
}
}, new RegionFunction() {
@Override
public boolean apply(Vector vector) throws WorldEditException {
return true;
}
}, Integer.MAX_VALUE, editSession);
}, vector -> true, Integer.MAX_VALUE, editSession);
visitor.setDirections(Arrays.asList(visitor.DIAGONAL_DIRECTIONS));
visitor.visit(position);
Operations.completeBlindly(visitor);

View File

@ -44,7 +44,6 @@ public class SummedAreaTable {
}
public int average(int x, int z, int index) {
long centerHeight = source[index];
int minX = Math.max(0, x - radius) - x;
int minZ = Math.max(0, z - radius) - z;
int maxX = Math.min(width - 1, x + radius) - x;

View File

@ -0,0 +1,227 @@
package com.boydti.fawe.object.collection;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
public class SummedColorTable {
private static float inv256 = 1/256f;
private final long[] reds, greens, blues, alpha;
private final int[] hasAlpha;
private final int length;
private final int width;
private final float[] areaInverses;
private final float[] alphaInverse;
public SummedColorTable(BufferedImage image, final boolean calculateAlpha) {
int[] raw = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
this.width = image.getWidth();
this.length = image.getHeight();
this.reds = new long[raw.length];
this.greens = new long[raw.length];
this.blues = new long[raw.length];
this.hasAlpha = new int[raw.length];
this.alpha = calculateAlpha ? new long[raw.length] : null;
this.alphaInverse = calculateAlpha ? new float[256] : null;
this.areaInverses = new float[Character.MAX_VALUE];
for (int i = 0; i < areaInverses.length; i++) {
areaInverses[i] = 1f / (i + 1);
}
int index = 0;
if (calculateAlpha) {
for (int i = 0; i < length; i++) {
for (int j = 0; j < width; j++, index++) {
int color = raw[index];
int alpha = (color >> 24) & 0xFF;
int red, green, blue;
switch (alpha) {
case 0:
red = green = blue = 0;
break;
case 255:
red = (color >> 16) & 0xFF;
green = (color >> 8) & 0xFF;
blue = (color >> 0) & 0xFF;
break;
default:
red = (((color >> 16) & 0xFF) * alpha) >> 8;
green = (((color >> 8) & 0xFF) * alpha) >> 8;
blue = (((color >> 0) & 0xFF) * alpha) >> 8;
break;
}
this.reds[index] = getVal(i, j, index, red, this.reds);
this.greens[index] = getVal(i, j, index, green, this.greens);
this.blues[index] = getVal(i, j, index, blue, this.blues);
this.alpha[index] = getVal(i, j, index, alpha, this.alpha);
this.hasAlpha[index] = getVal(i, j, index, alpha > 0 ? 1 : 0, this.hasAlpha);
}
}
for (int i = 1; i < alphaInverse.length; i++) {
alphaInverse[i] = 256f / i;
}
} else {
for (int i = 0; i < length; i++) {
for (int j = 0; j < width; j++, index++) {
int color = raw[index];
int red, green, blue, alpha;
if (((color >> 24) != 0)) {
alpha = 1;
red = (color >> 16) & 0xFF;
green = (color >> 8) & 0xFF;
blue = (color >> 0) & 0xFF;
} else {
alpha = red = green = blue = 0;
}
this.reds[index] = getVal(i, j, index, red, this.reds);
this.greens[index] = getVal(i, j, index, green, this.greens);
this.blues[index] = getVal(i, j, index, blue, this.blues);
this.hasAlpha[index] = getVal(i, j, index, alpha, this.hasAlpha);
}
}
}
}
private long getSum(int index, long[] summed) {
if (index < 0) return 0;
return summed[index];
}
public int averageRGB(int x1, int z1, int x2, int z2) {
int minX = Math.max(0, x1);
int minZ = Math.max(0, z1);
int maxX = Math.min(width - 1, x2);
int maxZ = Math.min(length - 1, z2);
int XZ = maxZ * width + maxX;
long totRed = reds[XZ];
long totGreen = greens[XZ];
long totBlue = blues[XZ];
int area = hasAlpha[XZ];
if (minX > 0) {
int pos = maxZ * width + minX - 1;
totRed -= reds[pos];
totGreen -= greens[pos];
totBlue -= blues[pos];
area -= hasAlpha[pos];
}
if (minZ > 0) {
int pos = minZ * width - width + maxX;
totRed -= reds[pos];
totGreen -= greens[pos];
totBlue -= blues[pos];
area -= hasAlpha[pos];
}
if (minZ > 0 && minX > 0) {
int pos = minZ * width - width + minX - 1;
totRed += reds[pos];
totGreen += greens[pos];
totBlue += blues[pos];
area += hasAlpha[pos];
}
if (area == 0) return 0;
float factor = this.areaInverses[area - 1];
return (255 << 24) + (((int) (totRed * factor)) << 16) + (((int) (totGreen * factor)) << 8) + (((int) (totBlue * factor)) << 0);
}
public int averageRGBA(int x1, int z1, int x2, int z2) {
int minX = Math.max(0, x1);
int minZ = Math.max(0, z1);
int maxX = Math.min(width - 1, x2);
int maxZ = Math.min(length - 1, z2);
int XZ = maxZ * width + maxX;
long totRed = reds[XZ];
long totGreen = greens[XZ];
long totBlue = blues[XZ];
long totAlpha = alpha[XZ];
int area = hasAlpha[XZ];
if (minX > 0) {
int pos = maxZ * width + minX - 1;
totRed -= reds[pos];
totGreen -= greens[pos];
totBlue -= blues[pos];
totAlpha -= alpha[pos];
area -= hasAlpha[pos];
}
if (minZ > 0) {
int pos = minZ * width - width + maxX;
totRed -= reds[pos];
totGreen -= greens[pos];
totBlue -= blues[pos];
totAlpha -= alpha[pos];
area -= hasAlpha[pos];
}
if (minZ > 0 && minX > 0) {
int pos = minZ * width - width + minX - 1;
totRed += reds[pos];
totGreen += greens[pos];
totBlue += blues[pos];
totAlpha += alpha[pos];
area += hasAlpha[pos];
}
if (totAlpha == 0) return 0;
float factor = this.areaInverses[area - 1];
float alpha = (totAlpha * factor);
factor = (factor * 256) / alpha;
return ((int) alpha << 24) + (((int) (totRed * factor)) << 16) + (((int) (totGreen * factor)) << 8) + (((int) (totBlue * factor)) << 0);
}
private long getVal(int row, int col, int index, long curr, long[] summed) {
long leftSum; // sub matrix sum of left matrix
long topSum; // sub matrix sum of top matrix
long topLeftSum; // sub matrix sum of top left matrix
/* top left value is itself */
if (index == 0) {
return curr;
}
/* top row */
else if (row == 0 && col != 0) {
leftSum = summed[index - 1];
return curr + leftSum;
}
/* left-most column */
else if (row != 0 && col == 0) {
topSum = summed[index - width];
return curr + topSum;
} else {
leftSum = summed[index - 1];
topSum = summed[index - width];
topLeftSum = summed[index - width - 1]; // overlap between leftSum and topSum
return curr + leftSum + topSum - topLeftSum;
}
}
private int getVal(int row, int col, int index, int curr, int[] summed) {
int leftSum; // sub matrix sum of left matrix
int topSum; // sub matrix sum of top matrix
int topLeftSum; // sub matrix sum of top left matrix
/* top left value is itself */
if (index == 0) {
return curr;
}
/* top row */
else if (row == 0 && col != 0) {
leftSum = summed[index - 1];
return curr + leftSum;
}
/* left-most column */
else if (row != 0 && col == 0) {
topSum = summed[index - width];
return curr + topSum;
} else {
leftSum = summed[index - 1];
topSum = summed[index - width];
topLeftSum = summed[index - width - 1]; // overlap between leftSum and topSum
return curr + leftSum + topSum - topLeftSum;
}
}
}

View File

@ -838,7 +838,7 @@ public class TextureUtil implements TextureHolder {
/**
* Assumes the top layer is a transparent color and the bottom is opaque
*/
protected int combineTransparency(int top, int bottom) {
public int combineTransparency(int top, int bottom) {
int alpha1 = (top >> 24) & 0xFF;
int alpha2 = 255 - alpha1;
int red1 = (top >> 16) & 0xFF;

View File

@ -2,11 +2,13 @@ package com.boydti.fawe.util.image;
import com.boydti.fawe.Fawe;
import com.boydti.fawe.util.MainUtil;
import com.boydti.fawe.util.MathMan;
import com.sk89q.worldedit.util.command.parametric.ParameterException;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.io.File;
import java.io.IOException;
import java.net.URL;
@ -68,6 +70,66 @@ public class ImageUtil {
return ret;
}
public static void fadeAlpha(BufferedImage image) {
int[] raw = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
int width = image.getWidth();
int height = image.getHeight();
int centerX = width / 2;
int centerZ = height / 2;
float invRadiusX = 1f / centerX;
float invRadiusZ = 1f / centerZ;
float[] sqrX = new float[width];
float[] sqrZ = new float[height];
for (int x = 0; x < width; x++) {
float distance = Math.abs(x - centerX) * invRadiusX;
sqrX[x] = distance * distance;
}
for (int z = 0; z < height; z++) {
float distance = Math.abs(z - centerZ) * invRadiusZ;
sqrZ[z] = distance * distance;
}
for (int z = 0, index = 0; z < height; z++) {
float dz2 = sqrZ[z];
for (int x = 0; x < width; x++, index++) {
int color = raw[index];
int alpha = (color >> 24) & 0xFF;
if (alpha != 0) {
float dx2 = sqrX[x];
float distSqr = dz2 + dx2;
if (distSqr > 1) raw[index] = 0;
else {
alpha = (int) (alpha * (1 - distSqr));
raw[index] = (color & 0x00FFFFFF) + (alpha << 24);
}
}
}
}
}
public static void scaleAlpha(BufferedImage image, double alphaScale) {
int[] raw = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
int defined = (MathMan.clamp((int) (255 * alphaScale), 0, 255)) << 24;
for (int i = 0; i < raw.length; i++) {
int color = raw[i];
int alpha = ((color >> 24) & 0xFF);
switch (alpha) {
case 0:
continue;
case 255:
raw[i] = (color & 0x00FFFFFF) + defined;
continue;
default:
alpha = MathMan.clamp((int) (alpha * alphaScale), 0, 255);
raw[i] = (color & 0x00FFFFFF) + (alpha << 24);
continue;
}
}
}
public static int getColor(BufferedImage image) {
int width = image.getWidth();
int height = image.getHeight();

View File

@ -32,6 +32,7 @@ import com.boydti.fawe.object.clipboard.MultiClipboardHolder;
import com.boydti.fawe.object.mask.IdMask;
import com.boydti.fawe.util.ColorUtil;
import com.boydti.fawe.util.MathMan;
import com.boydti.fawe.util.image.ImageUtil;
import com.sk89q.minecraft.util.commands.*;
import com.sk89q.worldedit.*;
import com.sk89q.worldedit.blocks.BaseBlock;
@ -55,6 +56,7 @@ import com.sk89q.worldedit.util.command.binding.Range;
import com.sk89q.worldedit.util.command.binding.Switch;
import com.sk89q.worldedit.util.command.parametric.Optional;
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.*;
import java.net.URL;
import java.nio.channels.Channels;
@ -362,7 +364,7 @@ public class BrushCommands extends MethodCommands {
}
@Command(
aliases = {"stencil", "color"},
aliases = {"stencil"},
usage = "<pattern> [radius=5] [file|#clipboard|imgur=null] [rotation=360] [yscale=1.0]",
desc = "Use a height map to paint a surface",
help =
@ -392,44 +394,34 @@ public class BrushCommands extends MethodCommands {
}
@Command(
aliases = {"stencil", "color"},
usage = "<pattern> [radius=5] [file|#clipboard|imgur=null] [rotation=360] [yscale=1.0]",
aliases = {"image", "color"},
usage = "<radius> <image> [yscale=1]",
desc = "Use a height map to paint a surface",
flags = "a",
help =
"Use a height map to paint any surface.\n" +
"The -w flag will only apply at maximum saturation\n" +
"The -r flag will apply random rotation",
"The -a flag will use image alpha\n" +
"The -f blends the image with the existing terrain",
min = 1,
max = -1
)
@CommandPermissions("worldedit.brush.stencil")
public BrushSettings stencilBrush(Player player, EditSession editSession, LocalSession session, Pattern fill, @Optional("5") double radius, @Optional("") final String image, @Optional("0") @Step(90) @Range(min=0, max=360) final int rotation, @Optional("1") final double yscale, @Switch('w') boolean onlyWhite, @Switch('r') boolean randomRotate, CommandContext context) throws WorldEditException {
public BrushSettings imageBrush(Player player, EditSession editSession, LocalSession session, @Optional("5") double radius, BufferedImage image, @Optional("1") @Range(min=Double.MIN_NORMAL) final double yscale, @Switch('a') boolean alpha, @Switch('f') boolean fadeOut, CommandContext context) throws WorldEditException, IOException {
worldEdit.checkMaxBrushRadius(radius);
InputStream stream = getHeightmapStream(image);
HeightBrush brush;
try {
brush = new StencilBrush(stream, rotation, yscale, onlyWhite, image.equalsIgnoreCase("#clipboard") ? session.getClipboard().getClipboard() : null);
} catch (EmptyClipboardException ignore) {
brush = new StencilBrush(stream, rotation, yscale, onlyWhite, null);
if (yscale != 1) {
ImageUtil.scaleAlpha(image, yscale);
alpha = true;
}
if (randomRotate) {
brush.setRandomRotate(true);
if (fadeOut) {
ImageUtil.fadeAlpha(image);
alpha = true;
}
ImageBrush brush = new ImageBrush(image, session, alpha);
return set(session, context,
brush)
.setSize(radius)
.setFill(fill);
.setSize(radius);
}
// @Command(
// aliases = {"image", "img"}
// // TODO directional image coloring
// )
// @CommandPermissions("worldedit.brush.stencil")
// public BrushSettings imageBrush(Player player, EditSession editSession, LocalSession session, Pattern fill, @Optional("5") double radius, @Optional("") final String image, @Optional("0") @Step(90) @Range(min=0, max=360) final int rotation, @Optional("1") final double yscale, @Switch('w') boolean onlyWhite, @Switch('r') boolean randomRotate, CommandContext context) throws WorldEditException {
//
// }
@Command(
aliases = {"surface", "surf"},
usage = "<pattern> [radius=5]",