Add image brush
This commit is contained in:
parent
8218e831ac
commit
fe17434d00
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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]",
|
||||
|
Loading…
Reference in New Issue
Block a user