Sketcher2 source code
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

962 lines
30 KiB

package com.jotuntech.sketcher.common;
import java.awt.AlphaComposite;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.image.ImageObserver;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import com.jotuntech.sketcher.common.filter.Filter;
/**
* @author Thor Harald Johansen
*
*/
public class BitmapLayer implements Layer, Copyable<Layer>, Serializable {
static final long serialVersionUID = 3583655360668336368L;
private transient final static int HASHMAP_CAPACITY = 768;
private transient final static float SUBPIXEL_THRESHOLD = 16f;
private transient final static float MINIMUM_SPACING = 0.04f;
private transient final static float MINIMUM_RADIUS = 1.5f;
private transient final static int DATA_TYPE_PIXELS = 1;
private transient final static int DATA_TYPE_CLEAN = 2;
private String name;
private float opacity;
private transient int blendMode;
private Map<Point, BitmapTile> tiles;
private transient Iterator<Entry<Point, BitmapTile>> tileIterator = null;
private transient ImageObserver observer;
private transient BitmapTile currentTile;
private Point currentTilePoint;
private transient static PixelPacker packer;
private transient static PixelUnpacker unpacker;
private transient static BitmapTile tempTile = new BitmapTile();
public BitmapLayer() {
currentTile = null;
currentTilePoint = new Point(Integer.MAX_VALUE, Integer.MAX_VALUE);
blendMode = AlphaComposite.SRC_OVER;
}
/**
* Creates a bitmap layer
*/
public BitmapLayer(String name) {
this.name = name;
this.opacity = 1;
this.tiles = new LinkedHashMap<Point, BitmapTile>(HASHMAP_CAPACITY);
this.currentTile = null;
this.currentTilePoint = new Point(Integer.MAX_VALUE, Integer.MAX_VALUE);
this.blendMode = AlphaComposite.SRC_OVER;
}
public BitmapLayer(BitmapLayer layer) {
/** Create new tile map */
this.tiles = new LinkedHashMap<Point, BitmapTile>();
/** Iterate through old tile map */
for(Iterator<Entry<Point, BitmapTile>> i = layer.tiles.entrySet().iterator(); i.hasNext();) {
/** Fetch next tile entry */
Entry<Point, BitmapTile> e = i.next();
/** Make copy of tile and put in new map */
this.tiles.put(e.getKey(), new BitmapTile(e.getValue()));
}
/** Copy other parameters */
this.name = layer.name;
this.opacity = layer.opacity;
this.blendMode = layer.blendMode;
this.currentTile = layer.currentTile;
this.currentTilePoint = layer.currentTilePoint;
}
public void draw(Graphics2D g) {
AlphaComposite ac = AlphaComposite.getInstance(blendMode, opacity);
g.setComposite(ac);
g.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED);
Rectangle clip = g.getClipBounds();
for(Entry<Point, BitmapTile> e : tiles.entrySet()) {
Point k = e.getKey();
int xp = k.x << BitmapTile.SIZE_2;
int yp = k.y << BitmapTile.SIZE_2;
if(xp + BitmapTile.SIZE < clip.x || xp > clip.x + clip.width || yp + BitmapTile.SIZE < clip.y || yp > clip.y + clip.height) {
/* Ignore tile */
} else {
BitmapTile v = e.getValue();
g.drawImage(v.getImage(), xp, yp, null);
}
}
}
public void draw(Graphics2D g, Set<Layer> phantomLayers) {
AlphaComposite ac = AlphaComposite.getInstance(blendMode, opacity);
g.setComposite(ac);
g.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED);
Rectangle clip = g.getClipBounds();
Set<Point> ps = new HashSet<Point>();
ps.addAll(tiles.keySet());
for(Layer phl : phantomLayers) {
BitmapLayer bphl = (BitmapLayer) phl;
ps.addAll(bphl.tiles.keySet());
}
for(Point k : ps) {
int xp = k.x << BitmapTile.SIZE_2;
int yp = k.y << BitmapTile.SIZE_2;
if(xp + BitmapTile.SIZE < clip.x || xp > clip.x + clip.width || yp + BitmapTile.SIZE < clip.y || yp > clip.y + clip.height) {
/* Ignore tile */
} else {
if(!phantomLayers.isEmpty()) {
Graphics2D tg = (Graphics2D) tempTile.getImage().getGraphics();
/** Clear temporary tile */
ac = AlphaComposite.getInstance(AlphaComposite.DST_OUT, 1);
tg.setComposite(ac);
tg.fillRect(0, 0, BitmapTile.SIZE, BitmapTile.SIZE);
/** Draw destination on tile */
BitmapTile dt = tiles.get(k);
if(dt != null) {
ac = AlphaComposite.getInstance(AlphaComposite.SRC, 1);
tg.setComposite(ac);
tg.drawImage(dt.getImage(), 0, 0, null);
}
/** Draw all phantom layers on tile */
for(Layer phl : phantomLayers) {
BitmapLayer bphl = (BitmapLayer) phl;
BitmapTile pht = bphl.getTiles().get(k);
if(pht != null) {
AlphaComposite dac = AlphaComposite.getInstance(bphl.blendMode, bphl.opacity);
tg.setComposite(dac);
tg.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED);
tg.drawImage(pht.getImage(), 0, 0, null);
}
}
g.drawImage(tempTile.getImage(), xp, yp, null);
} else {
BitmapTile dt = tiles.get(k);
if(dt != null) {
g.drawImage(dt.getImage(), xp, yp, null);
}
}
}
}
}
public float getOpacity() {
return opacity;
}
public Layer copy() {
return new BitmapLayer(this);
}
public void clear() {
tiles = new LinkedHashMap<Point, BitmapTile>(HASHMAP_CAPACITY);
currentTile = null;
currentTilePoint = new Point(Integer.MAX_VALUE, Integer.MAX_VALUE);
if(observer != null) {
observer.imageUpdate(null, ImageObserver.ALLBITS, 0, 0, 0, 0);
}
}
public Input line(Input start, Input end, int color, Brush brush, Layer originalLayer) {
/* Calculate final radius */
float radius = Math.max(MINIMUM_RADIUS, brush.getRadius() * (brush.isPressureToRadius() ? start.pressure : 0x100) / 256f);
if(end == null) {
/** Paint daub */
daub(start, brush, color, (BitmapLayer) originalLayer);
/** Return point */
return start;
} else {
/** Find line length. */
float length = (float)Math.hypot(end.x - start.x, end.y - start.y);
/** Calculate delta */
float delta = Math.max(1, radius * 2 * Math.max(MINIMUM_SPACING, brush.getSpacing()));
/** Set starting point. */
Input input = start;
/** Set paint status. */
boolean hasDrawn = false;
/** Store area for repaint */
Rectangle dirty = null;
for(float t = delta; t <= length; t += delta) {
/** Calculate new input point. */
input = new Input(
start.x + (end.x - start.x) * t / length,
start.y + (end.y - start.y) * t / length,
(int)(start.pressure + (end.pressure - start.pressure) * t / length));
/** Calculate final radius */
radius = Math.max(MINIMUM_RADIUS, brush.getRadius() * (brush.isPressureToRadius() ? input.pressure : 0x100) / 256f);
/** Calculate delta */
delta = Math.max(1, radius * 2 * Math.max(MINIMUM_SPACING, brush.getSpacing()));
/** Paint daub */
Rectangle dirtyDaub = daub(input, brush, color, (BitmapLayer) originalLayer);
if(dirty == null) {
dirty = dirtyDaub;
} else {
dirty = dirty.union(dirtyDaub);
}
hasDrawn = true;
}
if(hasDrawn && observer != null) {
observer.imageUpdate(null, ImageObserver.SOMEBITS, dirty.x, dirty.y, dirty.width, dirty.height);
}
return hasDrawn ? input : null;
}
}
public int getColor(Input input) {
int pixel = getPixel(Math.round(input.x), Math.round(input.y));
return Pixels.lerp(0xFFFFFF, pixel & 0xFFFFFF, Pixels.getChannel0(pixel));
}
public int getColor(int x, int y) {
int pixel = getPixel(x, y);
return Pixels.lerp(0xFFFFFF, pixel & 0xFFFFFF, Pixels.getChannel0(pixel));
}
public int getColor(int x, int y, int background) {
int pixel = getPixel(x, y);
return Pixels.lerp(background, pixel & 0xFFFFFF, Pixels.getChannel0(pixel));
}
int getPixel(int x, int y) {
BitmapTile tile = getBitmapTile(x, y, false);
if(tile == null) {
return 0x00000000;
} else {
int tileX = x % BitmapTile.SIZE;
int tileY = y % BitmapTile.SIZE;
return tile.getPixels()[tileY * BitmapTile.SIZE + tileX];
}
}
public void addImageObserver(ImageObserver observer) {
this.observer = observer;
}
public ImageObserver getImageObserver() {
return this.observer;
}
public int getType() {
return LayerType.BITMAP;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
private BitmapTile getBitmapTile(int x, int y, boolean writable) {
int tileX = x >> BitmapTile.SIZE_2;
int tileY = y >> BitmapTile.SIZE_2;
if(tileX != currentTilePoint.x || tileY != currentTilePoint.y) {
currentTilePoint = new Point(tileX, tileY);
currentTile = tiles.get(currentTilePoint);
}
if(writable) {
if(currentTile == null) {
currentTile = new BitmapTile();
tiles.put(currentTilePoint, currentTile);
} else {
currentTile.setEncoded(null);
}
}
return currentTile;
}
private void blendPixel(int x, int y, int sourceColor, int sourceAlpha, int maxAlpha) {
if(sourceAlpha != 0 && x >= 0 && y >= 0) {
BitmapTile tile = getBitmapTile(x, y, true);
if(tile != null) {
tile.blendPixel(x & BitmapTile.SIZE_MASK, y & BitmapTile.SIZE_MASK, sourceColor, sourceAlpha, maxAlpha);
}
}
}
public Rectangle daub(Input input, Brush brush, int color, BitmapLayer originalLayer) {
float radius = Math.max(MINIMUM_RADIUS, brush.isPressureToRadius() ? (brush.getRadius() * input.pressure) / 255f : brush.getRadius());
float x = input.x + (float)(Math.random() - 0.5) * radius * brush.getJitter();
float y = input.y + (float)(Math.random() - 0.5) * radius * brush.getJitter();
/** X & Y, integer */
int floorX = (int) Math.floor(x);
int floorY = (int) Math.floor(y);
if(brush.isLockTransparency() && brush.getOpacity() <= 0) {
return new Rectangle(floorX, floorY, 0, 0);
}
int maxAlpha = brush.isPressureToOpacity() ? input.pressure : 255;
int flow = Math.min(255, brush.isPressureToFlow() ? (brush.getFlow() * maxAlpha * input.pressure) / 65025 : (brush.getFlow() * maxAlpha) / 255);
int noise = brush.getNoise();
/** Radius, rounded up */
int ceilRadius = (int)Math.ceil(radius) + 1;
int ceilRadius255 = ceilRadius * 255;
/** Radius, squared and premultiplied */
int radiusSquared255 = (int)(radius * radius * 255f);
int radiusSquared65025 = (int)(radius * radius * 65025f);
/** X & Y, remainders */
int xMod256 = (int)(x * 255f) % 255;
int yMod256 = (int)(y * 255f) % 255;
/** Watercolor */
if(brush.getWater() > 0) {
int rim = (int) Math.ceil(radius * brush.getWaterArea());
int diagRim = (int) Math.ceil(radius * brush.getWaterArea() * 0.70710678118654752440084436210485);
int center = originalLayer.getPixel(floorX, floorY);
int top = originalLayer.getPixel(floorX, floorY - rim);
int topRight = originalLayer.getPixel(floorX + diagRim, floorY - diagRim);
int right = originalLayer.getPixel(floorX + rim, floorY);
int bottomRight = originalLayer.getPixel(floorX + diagRim, floorY + diagRim);
int bottom = originalLayer.getPixel(floorX, floorY + rim);
int bottomLeft = originalLayer.getPixel(floorX - diagRim, floorY + diagRim);
int left = originalLayer.getPixel(floorX - rim, floorY);
int topLeft = originalLayer.getPixel(floorX - diagRim, floorY - diagRim);
float sampleRed = 0, sampleGreen = 0, sampleBlue = 0, sampleAlpha = 0;
if(Pixels.getChannel0(center) > 0) {
float alpha = Pixels.gammaDecode(Pixels.getChannel0(center));
sampleAlpha += alpha;
sampleRed += Pixels.gammaDecode(Pixels.getChannel1(center)) * alpha;
sampleGreen += Pixels.gammaDecode(Pixels.getChannel2(center)) * alpha;
sampleBlue += Pixels.gammaDecode(Pixels.getChannel3(center)) * alpha;
}
if(Pixels.getChannel0(top) > 0) {
float alpha = Pixels.gammaDecode(Pixels.getChannel0(top));
sampleAlpha += alpha;
sampleRed += Pixels.gammaDecode(Pixels.getChannel1(top)) * alpha;
sampleGreen += Pixels.gammaDecode(Pixels.getChannel2(top)) * alpha;
sampleBlue += Pixels.gammaDecode(Pixels.getChannel3(top)) * alpha;
}
if(Pixels.getChannel0(topRight) > 0) {
float alpha = Pixels.gammaDecode(Pixels.getChannel0(topRight));
sampleAlpha += alpha;
sampleRed += Pixels.gammaDecode(Pixels.getChannel1(topRight)) * alpha;
sampleGreen += Pixels.gammaDecode(Pixels.getChannel2(topRight)) * alpha;
sampleBlue += Pixels.gammaDecode(Pixels.getChannel3(topRight)) * alpha;
}
if(Pixels.getChannel0(right) > 0) {
float alpha = Pixels.gammaDecode(Pixels.getChannel0(right));
sampleAlpha += alpha;
sampleRed += Pixels.gammaDecode(Pixels.getChannel1(right)) * alpha;
sampleGreen += Pixels.gammaDecode(Pixels.getChannel2(right)) * alpha;
sampleBlue += Pixels.gammaDecode(Pixels.getChannel3(right)) * alpha;
}
if(Pixels.getChannel0(bottomRight) > 0) {
float alpha = Pixels.gammaDecode(Pixels.getChannel0(bottomRight));
sampleAlpha += alpha;
sampleRed += Pixels.gammaDecode(Pixels.getChannel1(bottomRight)) * alpha;
sampleGreen += Pixels.gammaDecode(Pixels.getChannel2(bottomRight)) * alpha;
sampleBlue += Pixels.gammaDecode(Pixels.getChannel3(bottomRight)) * alpha;
}
if(Pixels.getChannel0(bottom) > 0) {
float alpha = Pixels.gammaDecode(Pixels.getChannel0(bottom));
sampleAlpha += alpha;
sampleRed += Pixels.gammaDecode(Pixels.getChannel1(bottom)) * alpha;
sampleGreen += Pixels.gammaDecode(Pixels.getChannel2(bottom)) * alpha;
sampleBlue += Pixels.gammaDecode(Pixels.getChannel3(bottom)) * alpha;
}
if(Pixels.getChannel0(bottomLeft) > 0) {
float alpha = Pixels.gammaDecode(Pixels.getChannel0(bottomLeft));
sampleAlpha += alpha;
sampleRed += Pixels.gammaDecode(Pixels.getChannel1(bottomLeft)) * alpha;
sampleGreen += Pixels.gammaDecode(Pixels.getChannel2(bottomLeft)) * alpha;
sampleBlue += Pixels.gammaDecode(Pixels.getChannel3(bottomLeft)) * alpha;
}
if(Pixels.getChannel0(left) > 0) {
float alpha = Pixels.gammaDecode(Pixels.getChannel0(left));
sampleAlpha += alpha;
sampleRed += Pixels.gammaDecode(Pixels.getChannel1(left)) * alpha;
sampleGreen += Pixels.gammaDecode(Pixels.getChannel2(left)) * alpha;
sampleBlue += Pixels.gammaDecode(Pixels.getChannel3(left)) * alpha;
}
if(Pixels.getChannel0(topLeft) > 0) {
float alpha = Pixels.gammaDecode(Pixels.getChannel0(topLeft));
sampleAlpha += alpha;
sampleRed += Pixels.gammaDecode(Pixels.getChannel1(topLeft)) * alpha;
sampleGreen += Pixels.gammaDecode(Pixels.getChannel2(topLeft)) * alpha;
sampleBlue += Pixels.gammaDecode(Pixels.getChannel3(topLeft)) * alpha;
}
float brushRed = Pixels.gammaDecode(Pixels.getChannel1(color));
float brushGreen = Pixels.gammaDecode(Pixels.getChannel2(color));
float brushBlue = Pixels.gammaDecode(Pixels.getChannel3(color));
if(sampleAlpha > 0f) {
sampleRed = sampleRed / sampleAlpha;
sampleGreen = sampleGreen / sampleAlpha;
sampleBlue = sampleBlue / sampleAlpha;
sampleAlpha /= 9f;
} else {
sampleRed = brushRed;
sampleGreen = brushGreen;
sampleBlue = brushBlue;
}
float resultRed = brushRed + ((sampleRed - brushRed) * brush.getWater()) / 255f;
float resultGreen = brushGreen + ((sampleGreen - brushGreen) * brush.getWater()) / 255f;
float resultBlue = brushBlue + ((sampleBlue - brushBlue) * brush.getWater()) / 255f;
color = Pixels.pack(Pixels.gammaEncode(resultRed), Pixels.gammaEncode(resultGreen), Pixels.gammaEncode(resultBlue));
flow = ((65025 + ((Pixels.gammaEncode(sampleAlpha) - 255) * brush.getWater())) * flow) / 65025;
maxAlpha = flow;
}
if(radius > SUBPIXEL_THRESHOLD) {
/** top of Y cathetus */
int cathetusY = 0;
/** Y loop */
for(int pixelY = 0; pixelY < ceilRadius; pixelY++) {
/** left side of X cathetus */
int cathetusX = 0;
/** Y cathetus squared */
int cathetusYSquared = cathetusY * cathetusY;
/** X loop */
for(int pixelX = 0; pixelX < ceilRadius; pixelX++) {
int alpha = cathetusX * cathetusX + cathetusYSquared;
if(alpha < radiusSquared65025) {
alpha /= radiusSquared255;
alpha = 255 - alpha;
switch(brush.getHardness()) {
case 1:
alpha = (alpha * alpha * alpha) / 255;
break;
case 2:
alpha = alpha * alpha;
break;
case 3:
alpha = alpha * 255;
break;
default:
alpha <<= 4 + brush.getHardness();
if(alpha > 65025) {
alpha = 65025;
}
break;
}
/** Apply noise */
alpha -= noise > 0 ? (Pixels.random() % noise) << 8 : 0;
if(alpha > 0) {
/** Apply flow */
alpha = (alpha * flow) / 255;
/* Blend mirrored pixels. */
if(pixelX == 0 && pixelY == 0) {
/* Blend center pixel */
blendPixel(floorX, floorY, color, alpha, maxAlpha);
} else if(pixelX == 0) {
/* Blend vertical center line */
blendPixel(floorX, floorY - pixelY, color, alpha, maxAlpha);
blendPixel(floorX, floorY + pixelY, color, alpha, maxAlpha);
} else if(pixelY == 0) {
/* Blend horizontal center line */
blendPixel(floorX - pixelX, floorY, color, alpha, maxAlpha);
blendPixel(floorX + pixelX, floorY, color, alpha, maxAlpha);
} else {
/* Blend other pixels */
blendPixel(floorX - pixelX, floorY - pixelY, color, alpha, maxAlpha);
blendPixel(floorX + pixelX, floorY - pixelY, color, alpha, maxAlpha);
blendPixel(floorX - pixelX, floorY + pixelY, color, alpha, maxAlpha);
blendPixel(floorX + pixelX, floorY + pixelY, color, alpha, maxAlpha);
}
}
}
/** Increment X cathetus */
cathetusX += 255;
}
/** increment Y cathetus */
cathetusY += 255;
}
} else {
/** top of Y cathetus */
int cathetusY = -ceilRadius255 - yMod256;
/** Y loop */
for(int pixelY = floorY - ceilRadius; pixelY < floorY + ceilRadius; pixelY++) {
/** left side of X cathetus */
int cathetusX = -ceilRadius255 - xMod256;
/** Y cathetus squared */
int cathetusYSquared = cathetusY * cathetusY;
/** X loop */
for(int pixelX = floorX - ceilRadius; pixelX < floorX + ceilRadius; pixelX++) {
int alpha = cathetusX * cathetusX + cathetusYSquared;
if(alpha < radiusSquared65025) {
alpha /= radiusSquared255;
alpha = 255 - alpha;
switch(brush.getHardness()) {
case 1:
alpha = (alpha * alpha * alpha) / 255;
break;
case 2:
alpha = alpha * alpha;
break;
case 3:
alpha = alpha * 255;
break;
default:
alpha <<= 4 + brush.getHardness();
if(alpha > 65025) {
alpha = 65025;
}
break;
}
/** Apply noise */
alpha -= noise > 0 ? (Pixels.random() % noise) << 8 : 0;
if(alpha > 0) {
/** Apply flow */
alpha = (alpha * flow) / 255;
/** Blend as pixel */
blendPixel(pixelX, pixelY, color, alpha, maxAlpha);
}
}
/** Increment X cathetus */
cathetusX += 255;
}
/** increment Y cathetus */
cathetusY += 255;
}
}
return new Rectangle(floorX - ceilRadius, floorY - ceilRadius, ceilRadius << 1, ceilRadius << 1);
}
public void clean() {
List<Point> removePoints = new ArrayList<Point>();
for(Map.Entry<Point, BitmapTile> e : tiles.entrySet()) {
Point p = e.getKey();
BitmapTile t = e.getValue();
int emptyPixels = 0;
int[] pixels = t.getPixels();
for(int j = pixels.length - 1; j >= 0; j--) {
int alpha = Pixels.getChannel0(pixels[j]);
if(alpha < 2) {
++emptyPixels;
}
}
if(emptyPixels == pixels.length) {
removePoints.add(p);
}
}
for(Iterator<Point> i = removePoints.iterator(); i.hasNext();) {
Point p = i.next();
tiles.remove(p);
}
}
public void reset() {
tileIterator = null;
}
public void decode(byte[] data) {
int offset = 0;
int dataType = data[offset++];
if(dataType == DATA_TYPE_PIXELS) {
int x = data[offset++] & 0xFF;
int y = data[offset++] & 0xFF;
Point p = new Point(x, y);
BitmapTile t = new BitmapTile();
if(unpacker == null) {
unpacker = new PixelUnpacker();
}
unpacker.unpack(data, offset, data.length - offset, t.getPixels());
tiles.put(p, t);
if(observer == null) {
Log.warn("We are supposed to have an observer when loading layers.");
} else {
observer.imageUpdate(null, ImageObserver.SOMEBITS, x << BitmapTile.SIZE_2, y << BitmapTile.SIZE_2, BitmapTile.SIZE, BitmapTile.SIZE);
}
} else if(dataType == DATA_TYPE_CLEAN) {
/** Cleanup now happens internally and separately on client and server */
} else {
throw new RuntimeException("Invalid tile data type " + dataType);
}
}
private static byte[] encodedTile;
public byte[] encode() {
if(tileIterator == null) {
tileIterator = tiles.entrySet().iterator();
}
if(tileIterator.hasNext()) {
Entry<Point, BitmapTile> e = tileIterator.next();
Point p = e.getKey();
BitmapTile t = e.getValue();
byte[] encodedBytes = t.getEncoded();
if(encodedBytes == null) {
if(encodedTile == null) {
encodedTile = new byte[BitmapTile.SIZE * BitmapTile.SIZE * 10];
}
int offset = 0;
encodedTile[offset++] = DATA_TYPE_PIXELS;
encodedTile[offset++] = (byte) p.x;
encodedTile[offset++] = (byte) p.y;
if(packer == null) {
packer = new PixelPacker();
}
int compressedLength = packer.pack(t.getPixels(), encodedTile, offset, encodedTile.length - offset);
offset += compressedLength;
encodedBytes = Arrays.copyOf(encodedTile, offset);
t.setEncoded(encodedBytes);
}
return encodedBytes;
} else {
tileIterator = null;
return null;
}
}
public void setOpacity(float opacity) {
this.opacity = opacity;
}
public boolean isEmpty() {
return tiles.isEmpty();
}
public Map<Point, BitmapTile> getTiles() {
return tiles;
}
public UndoData merge(Layer fromLayer) {
if(fromLayer instanceof BitmapLayer) {
BitmapLayer bl = (BitmapLayer) fromLayer;
BitmapUndoData bud = new BitmapUndoData();
/** Transfer tiles */
for(Map.Entry<Point, BitmapTile> e : bl.tiles.entrySet()) {
Point p = e.getKey();
BitmapTile st = e.getValue(); // Source Tile
BitmapTile dt = tiles.get(p), tt; // Destination Tile, Temporary Tile
if(dt == null) {
tt = new BitmapTile();
} else {
tt = new BitmapTile(dt);
}
Graphics2D g2 = (Graphics2D) tt.getImage().getGraphics();
AlphaComposite ac = AlphaComposite.getInstance(bl.blendMode, bl.opacity);
g2.setComposite(ac);
g2.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED);
g2.drawImage(st.getImage(), 0, 0, null);
g2.dispose();
tiles.put(p, tt);
BitmapTileDiff btd = new BitmapTileDiff();
int[] tp = tt.getPixels();
if(dt == null) {
for(int i = 0; i < BitmapTile.SIZE * BitmapTile.SIZE; i++) {
int tpi = tp[i];
btd.diffAlpha[i] = (short) Pixels.getChannel0(tpi);
btd.diffRed[i] = (short) Pixels.getChannel1(tpi);
btd.diffGreen[i] = (short) Pixels.getChannel2(tpi);
btd.diffBlue[i] = (short) Pixels.getChannel3(tpi);
}
} else {
int[] dp = dt.getPixels();
for(int i = 0; i < BitmapTile.SIZE * BitmapTile.SIZE; i++) {
int tpi = tp[i];
int dpi = dp[i];
btd.diffAlpha[i] = (short) (Pixels.getChannel0(tpi) - Pixels.getChannel0(dpi));
btd.diffRed[i] = (short) (Pixels.getChannel1(tpi) - Pixels.getChannel1(dpi));
btd.diffGreen[i] = (short) (Pixels.getChannel2(tpi) - Pixels.getChannel2(dpi));
btd.diffBlue[i] = (short) (Pixels.getChannel3(tpi) - Pixels.getChannel3(dpi));
}
}
bud.put(p, btd);
}
bl.tiles.clear();
bl.currentTile = null;
bl.currentTilePoint = new Point(Integer.MAX_VALUE, Integer.MAX_VALUE);
return bud;
} else {
throw new RuntimeException("Cannot merge layers of different types");
}
}
public int getAlphaRule() {
return blendMode;
}
public void setAlphaRule(int alphaRule) {
blendMode = alphaRule;
}
public void undo(UndoData undoData) {
if(undoData instanceof BitmapUndoData) {
BitmapUndoData bud = (BitmapUndoData) undoData;
Rectangle dirty = null;
for(Map.Entry<Point, BitmapTileDiff> e : bud.entrySet()) {
Point p = e.getKey();
BitmapTileDiff btd = e.getValue();
BitmapTile dt = tiles.get(p);
if(dt == null) {
dt = new BitmapTile();
int[] dtp = dt.getPixels();
for(int i = 0; i < BitmapTile.SIZE * BitmapTile.SIZE; i++) {
dtp[i] = Pixels.pack(btd.diffAlpha[i], btd.diffRed[i], btd.diffGreen[i], btd.diffBlue[i]);
}
} else {
int[] dtp = dt.getPixels();
for(int i = 0; i < BitmapTile.SIZE * BitmapTile.SIZE; i++) {
int dtpi = dtp[i];
int alpha = Math.max(0, Math.min(255, Pixels.getChannel0(dtpi) - btd.diffAlpha[i]));
if(alpha == 0) {
dtp[i] = 0;
} else {
int red = Math.max(0, Math.min(255, Pixels.getChannel1(dtpi) - btd.diffRed[i]));
int green = Math.max(0, Math.min(255, Pixels.getChannel2(dtpi) - btd.diffGreen[i]));
int blue = Math.max(0, Math.min(255, Pixels.getChannel3(dtpi) - btd.diffBlue[i]));
dtp[i] = Pixels.pack(alpha, red, green, blue);
}
}
}
Rectangle tileRect = new Rectangle(p.x << BitmapTile.SIZE_2, p.y << BitmapTile.SIZE_2, BitmapTile.SIZE, BitmapTile.SIZE);
dirty = dirty == null ? tileRect : dirty.union(tileRect);
}
if(dirty != null) {
observer.imageUpdate(null, ImageObserver.SOMEBITS, dirty.x, dirty.y, dirty.width, dirty.height);
}
} else {
throw new RuntimeException("Cannot undo with data from different layer type");
}
}
public UndoData copyTo(Layer destination, ImageObserver obs, boolean isMove, float sx, float sy, float dx, float dy, float width, float height) {
//if(destination instanceof BitmapLayer) {
// throw new RuntimeException("Destination of move operation must be a bitmap layer!");
//}
BitmapLayer bld = (BitmapLayer) destination;
if(width == 0 || height == 0) {
return null;
}
if(this == destination && sx == dx && sy == dy) {
return null;
}
int startX, dStartX, stopX, dirX, startY, dStartY, stopY, dirY;
if(sx > dx) {
startX = Math.round(sx);
dStartX = Math.round(dx);
stopX = Math.round(sx + width);
dirX = 1;
} else {
startX = Math.round(sx + width - 1);
dStartX = Math.round(dx + width - 1);
stopX = Math.round(sx) - 1;
dirX = -1;
}
if(sy > dy) {
startY = Math.round(sy);
dStartY = Math.round(dy);
stopY = Math.round(sy + height);
dirY = 1;
} else {
startY = Math.round(sy + height - 1);
dStartY = Math.round(dy + height - 1);
stopY = Math.round(sy) - 1;
dirY = -1;
}
for(int syy = startY, dyy = dStartY; syy != stopY; syy += dirY, dyy += dirY) {
for(int sxx = startX, dxx = dStartX; sxx != stopX; sxx += dirX, dxx += dirX) {
BitmapTile st = getBitmapTile(sxx, syy, false);
if(st == null) {
continue;
}
int[] sp = st.getPixels();
int offset = ((syy & BitmapTile.SIZE_MASK) << BitmapTile.SIZE_2) + (sxx & BitmapTile.SIZE_MASK);
int sourcePixel = sp[offset];
if(isMove) {
sp[offset] = 0;
}
BitmapTile dt = bld.getBitmapTile(dxx, dyy, true);
int[] dp = dt.getPixels();
offset = ((dyy & BitmapTile.SIZE_MASK) << BitmapTile.SIZE_2) + (dxx & BitmapTile.SIZE_MASK);
dp[offset] = sourcePixel;
}
}
if(obs != null) {
Rectangle r = new Rectangle(Math.round(sx), Math.round(sy), Math.round(width), Math.round(height)).union(new Rectangle(Math.round(dx), Math.round(dy), Math.round(width), Math.round(height)));
obs.imageUpdate(null, ImageObserver.SOMEBITS, r.x, r.y, r.width, r.height);
}
return null;
}
public void applyFilter(Filter filter, ImageObserver obs, int x, int y, int w, int h) {
filter.startPass1();
if(filter.isPass1ReadOnly()) {
for(int yy = y; yy < y + h; yy++) {
for(int xx = x; xx < x + w; xx++) {
BitmapTile t = getBitmapTile(xx, yy, false);
if(t == null) {
filter.processPass1Pixel(0);
} else {
filter.processPass1Pixel(t.getPixels()[((yy & BitmapTile.SIZE_MASK) << BitmapTile.SIZE_2) + (xx & BitmapTile.SIZE_MASK)]);
}
}
}
} else {
for(int yy = y; yy < y + h; yy++) {
for(int xx = x; xx < x + w; xx++) {
BitmapTile t = getBitmapTile(xx, yy, true);
int[] tp = t.getPixels();
int offset = ((yy & BitmapTile.SIZE_MASK) << BitmapTile.SIZE_2) + (xx & BitmapTile.SIZE_MASK);
int pixel = tp[offset];
pixel = filter.processPass1Pixel(pixel);
tp[offset] = pixel;
}
}
}
if(filter.hasPass2()) {
filter.startPass2();
if(filter.isPass2Reversed()) {
for(int yy = y + h - 1; yy >= y; yy--) {
for(int xx = x + w - 1; xx >= x; xx--) {
BitmapTile t = getBitmapTile(xx, yy, true);
int[] tp = t.getPixels();
int offset = ((yy & BitmapTile.SIZE_MASK) << BitmapTile.SIZE_2) + (xx & BitmapTile.SIZE_MASK);
int pixel = tp[offset];
pixel = filter.processPass2Pixel(pixel);
tp[offset] = pixel;
}
}
} else {
for(int yy = y; yy < y + h; yy++) {
for(int xx = x; xx < x + w; xx++) {
BitmapTile t = getBitmapTile(xx, yy, true);
int[] tp = t.getPixels();
int offset = ((yy & BitmapTile.SIZE_MASK) << BitmapTile.SIZE_2) + (xx & BitmapTile.SIZE_MASK);
int pixel = tp[offset];
pixel = filter.processPass2Pixel(pixel);
tp[offset] = pixel;
}
}
}
}
if(obs != null) {
obs.imageUpdate(null, ImageObserver.SOMEBITS, x, y, w, h);
}
}
}