diff --git a/src/com/gpl/rpg/atcontentstudio/model/maps/TMXMap.java b/src/com/gpl/rpg/atcontentstudio/model/maps/TMXMap.java index 7831da8..b278bd7 100644 --- a/src/com/gpl/rpg/atcontentstudio/model/maps/TMXMap.java +++ b/src/com/gpl/rpg/atcontentstudio/model/maps/TMXMap.java @@ -44,7 +44,10 @@ public class TMXMap extends GameDataElement { black60, black80, invert, - bw + bw, + redtint, + greentint, + bluetint } public File tmxFile = null; diff --git a/src/com/gpl/rpg/atcontentstudio/ui/map/TMXMapEditor.java b/src/com/gpl/rpg/atcontentstudio/ui/map/TMXMapEditor.java index e48295d..b6f1931 100644 --- a/src/com/gpl/rpg/atcontentstudio/ui/map/TMXMapEditor.java +++ b/src/com/gpl/rpg/atcontentstudio/ui/map/TMXMapEditor.java @@ -3,6 +3,7 @@ package com.gpl.rpg.atcontentstudio.ui.map; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; +import java.awt.Composite; import java.awt.Cursor; import java.awt.Dimension; import java.awt.Graphics; @@ -10,7 +11,6 @@ import java.awt.Graphics2D; import java.awt.Image; import java.awt.Point; import java.awt.Rectangle; -import java.awt.color.ColorSpace; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyAdapter; @@ -19,13 +19,7 @@ import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseMotionAdapter; import java.awt.event.MouseMotionListener; -import java.awt.image.BandCombineOp; import java.awt.image.BufferedImageOp; -import java.awt.image.ByteLookupTable; -import java.awt.image.ColorConvertOp; -import java.awt.image.LookupOp; -import java.awt.image.LookupTable; -import java.awt.image.ShortLookupTable; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; @@ -108,6 +102,7 @@ import com.gpl.rpg.atcontentstudio.ui.Editor; import com.gpl.rpg.atcontentstudio.ui.FieldUpdateListener; import com.gpl.rpg.atcontentstudio.ui.IntegerBasedCheckBox; import com.gpl.rpg.atcontentstudio.ui.ScrollablePanel; +import com.gpl.rpg.atcontentstudio.ui.tools.MatrixComposite; import com.jidesoft.swing.JideBoxLayout; import com.jidesoft.swing.JideTabbedPane; @@ -1473,37 +1468,101 @@ public class TMXMapEditor extends Editor { } } - if (map.colorFilter != null) { + Composite oldComp = g2d.getComposite(); + MatrixComposite newComp = null; + float f=0.0f; switch(map.colorFilter) { case black20: - g2d.setPaint(new Color(0f, 0f, 0f, 0.2f)); - g2d.fill(clip); + f=0.8f; + newComp = new MatrixComposite(new float[]{ + f, 0.00f, 0.00f, 0.0f, 0.0f, + 0.00f, f, 0.00f, 0.0f, 0.0f, + 0.00f, 0.00f, f, 0.0f, 0.0f, + 0.00f, 0.00f, 0.00f, 1.0f, 0.0f + }); break; case black40: - g2d.setPaint(new Color(0f, 0f, 0f, 0.4f)); - g2d.fill(clip); + f=0.6f; + newComp = new MatrixComposite(new float[]{ + f, 0.00f, 0.00f, 0.0f, 0.0f, + 0.00f, f, 0.00f, 0.0f, 0.0f, + 0.00f, 0.00f, f, 0.0f, 0.0f, + 0.00f, 0.00f, 0.00f, 1.0f, 0.0f + }); break; case black60: - g2d.setPaint(new Color(0f, 0f, 0f, 0.6f)); - g2d.fill(clip); + f=0.4f; + newComp = new MatrixComposite(new float[]{ + f, 0.00f, 0.00f, 0.0f, 0.0f, + 0.00f, f, 0.00f, 0.0f, 0.0f, + 0.00f, 0.00f, f, 0.0f, 0.0f, + 0.00f, 0.00f, 0.00f, 1.0f, 0.0f + }); break; case black80: - g2d.setPaint(new Color(0f, 0f, 0f, 0.8f)); - g2d.fill(clip); + f=0.2f; + newComp = new MatrixComposite(new float[]{ + f, 0.00f, 0.00f, 0.0f, 0.0f, + 0.00f, f, 0.00f, 0.0f, 0.0f, + 0.00f, 0.00f, f, 0.0f, 0.0f, + 0.00f, 0.00f, 0.00f, 1.0f, 0.0f + }); break; case bw: + newComp = new MatrixComposite(new float[]{ + 0.33f, 0.59f, 0.11f, 0.0f, 0.0f, + 0.33f, 0.59f, 0.11f, 0.0f, 0.0f, + 0.33f, 0.59f, 0.11f, 0.0f, 0.0f, + 0.00f, 0.00f, 0.00f, 1.0f, 0.0f + }); break; case invert: + newComp = new MatrixComposite(new float[]{ + -1.00f, 0.00f, 0.00f, 0.0f, 255.0f, + 0.00f, -1.00f, 0.00f, 0.0f, 255.0f, + 0.00f, 0.00f, -1.00f, 0.0f, 255.0f, + 0.00f, 0.00f, 0.00f, 1.0f, 0.0f + }); + break; + case redtint: + newComp = new MatrixComposite(new float[]{ + 1.20f, 0.20f, 0.20f, 0.0f, 25.0f, + 0.00f, 0.80f, 0.00f, 0.0f, 0.0f, + 0.00f, 0.00f, 0.80f, 0.0f, 0.0f, + 0.00f, 0.00f, 0.00f, 1.0f, 0.0f + }); + break; + case greentint: + newComp = new MatrixComposite(new float[]{ + 0.85f, 0.00f, 0.00f, 0.0f, 0.0f, + 0.15f, 1.15f, 0.15f, 0.0f, 15.0f, + 0.00f, 0.00f, 0.85f, 0.0f, 0.0f, + 0.00f, 0.00f, 0.00f, 1.0f, 0.0f + }); + break; + case bluetint: + newComp = new MatrixComposite(new float[]{ + 0.70f, 0.00f, 0.00f, 0.0f, 0.0f, + 0.00f, 0.70f, 0.00f, 0.0f, 0.0f, + 0.30f, 0.30f, 1.30f, 0.0f, 40.0f, + 0.00f, 0.00f, 0.00f, 1.0f, 0.0f + }); break; default: break; } + if (newComp != null) { + g2d.setComposite(newComp); + g2d.setPaint(new Color(1.0f, 1.0f, 1.0f, 1.0f)); + g2d.fill(clip); + g2d.setComposite(oldComp); + } } - // Draw each object map layer + // Draw each map object layer boolean paintSelected = false; for (tiled.core.MapLayer layer : ((TMXMap)target).tmxMap) { if (layer instanceof tiled.core.ObjectGroup && layer.isVisible()) { diff --git a/src/com/gpl/rpg/atcontentstudio/ui/tools/MatrixComposite.java b/src/com/gpl/rpg/atcontentstudio/ui/tools/MatrixComposite.java new file mode 100644 index 0000000..0b5b9d2 --- /dev/null +++ b/src/com/gpl/rpg/atcontentstudio/ui/tools/MatrixComposite.java @@ -0,0 +1,124 @@ +package com.gpl.rpg.atcontentstudio.ui.tools; + +import java.awt.Composite; +import java.awt.CompositeContext; +import java.awt.RenderingHints; +import java.awt.image.ColorModel; +import java.awt.image.DataBuffer; +import java.awt.image.Raster; +import java.awt.image.WritableRaster; + +/** + * This Composite emulates the behaviour of Android's ColorMatrixColorFilter, + * except that you have to use this one a posteriori instead of a filtering paint. + * + * It applies a ColorMatrix to the destination pixels, regardless of potential "source" pixels. + * Once created and activated through Graphics2D.setComposite(), just paint anything over the pixels you want "filtered". + * + * Works on a per-pixel basis, no sampling of surrounding pixels, or anything. + * + * @author pochat + * + */ +public class MatrixComposite implements Composite { + + + final float[] matrix = new float[20]; + + + /** + * Dismisses the source pixels. Just paint it black, or white, it only affects the dest RGB with the following formulae. + * + * R' = a*R + b*G + c*B + d*A + e; + * G' = f*R + g*G + h*B + i*A + j; + * B' = k*R + l*G + m*B + n*A + o; + * A' = p*R + q*G + r*B + s*A + t; + * + * @param matrix a flat float[20] array, giving the a..t values; + */ + public MatrixComposite(float[] matrix) { + if (matrix.length != this.matrix.length) { + throw new Error("MatrixComposite matrix must be of length "+this.matrix.length); + } + System.arraycopy(matrix, 0, this.matrix, 0, this.matrix.length); + } + + @Override + public CompositeContext createContext(ColorModel srcColorModel, + ColorModel dstColorModel, RenderingHints hints) { + return new MatrixCompositeContext(this); + } + + class MatrixCompositeContext implements CompositeContext { + + MatrixComposite composite; + + public MatrixCompositeContext(MatrixComposite composite) { + this.composite = composite; + } + + @Override + public void dispose() { + } + + @Override + public void compose(Raster src, Raster dstIn, WritableRaster dstOut) { + if (src.getSampleModel().getDataType() != DataBuffer.TYPE_INT || + dstIn.getSampleModel().getDataType() != DataBuffer.TYPE_INT || + dstOut.getSampleModel().getDataType() != DataBuffer.TYPE_INT) { + throw new IllegalStateException( + "Source and destination must store pixels as INT."); + } + + int width = Math.min(src.getWidth(), dstIn.getWidth()); + int height = Math.min(src.getHeight(), dstIn.getHeight()); + + float alpha = 1.0f; + + int[] srcPixel = new int[4]; + int[] dstPixel = new int[4]; + int[] srcPixels = new int[width]; + int[] dstPixels = new int[width]; + + for (int y = 0; y < height; y++) { + src.getDataElements(0, y, width, 1, srcPixels); + dstIn.getDataElements(0, y, width, 1, dstPixels); + for (int x = 0; x < width; x++) { + // pixels are stored as INT_ARGB + // our arrays are [R, G, B, A] + int pixel = srcPixels[x]; + srcPixel[0] = (pixel >> 16) & 0xFF; + srcPixel[1] = (pixel >> 8) & 0xFF; + srcPixel[2] = (pixel ) & 0xFF; + srcPixel[3] = (pixel >> 24) & 0xFF; + + pixel = dstPixels[x]; + dstPixel[0] = (pixel >> 16) & 0xFF; + dstPixel[1] = (pixel >> 8) & 0xFF; + dstPixel[2] = (pixel ) & 0xFF; + dstPixel[3] = (pixel >> 24) & 0xFF; + + int[] result = applyMatrix(matrix, dstPixel); + + // mixes the result with the opacity + dstPixels[x] = ((int) (dstPixel[3] + (result[3] - dstPixel[3]) * alpha) & 0xFF) << 24 | + ((int) (dstPixel[0] + (result[0] - dstPixel[0]) * alpha) & 0xFF) << 16 | + ((int) (dstPixel[1] + (result[1] - dstPixel[1]) * alpha) & 0xFF) << 8 | + (int) (dstPixel[2] + (result[2] - dstPixel[2]) * alpha) & 0xFF; + } + dstOut.setDataElements(0, y, width, 1, dstPixels); + } + } + + private int[] applyMatrix(float[] matrix, int[] dstPixel) { + int[] result = new int[4]; + result[0] = Math.max(0, Math.min(255, (int) (matrix[ 0] * dstPixel[0] + matrix[ 1] * dstPixel[1] + matrix[ 2] * dstPixel[2] + matrix[ 3] * dstPixel[3] + matrix[ 4]) )); + result[1] = Math.max(0, Math.min(255, (int) (matrix[ 5] * dstPixel[0] + matrix[ 6] * dstPixel[1] + matrix[ 7] * dstPixel[2] + matrix[ 8] * dstPixel[3] + matrix[ 9]) )); + result[2] = Math.max(0, Math.min(255, (int) (matrix[10] * dstPixel[0] + matrix[11] * dstPixel[1] + matrix[12] * dstPixel[2] + matrix[13] * dstPixel[3] + matrix[14]) )); + result[3] = Math.max(0, Math.min(255, (int) (matrix[15] * dstPixel[0] + matrix[16] * dstPixel[1] + matrix[17] * dstPixel[2] + matrix[18] * dstPixel[3] + matrix[19]) )); + return result; + } + + } + +}