diff --git a/DroidFishApp/src/main/assets/fonts/ChessCases.ttf b/DroidFishApp/src/main/assets/fonts/ChessCases.ttf deleted file mode 100644 index b5a7a93..0000000 Binary files a/DroidFishApp/src/main/assets/fonts/ChessCases.ttf and /dev/null differ diff --git a/DroidFishApp/src/main/assets/pieces/chesscases.zip b/DroidFishApp/src/main/assets/pieces/chesscases.zip new file mode 100644 index 0000000..ba32f4c Binary files /dev/null and b/DroidFishApp/src/main/assets/pieces/chesscases.zip differ diff --git a/DroidFishApp/src/main/java/com/larvalabs/svgandroid/SVG.java b/DroidFishApp/src/main/java/com/larvalabs/svgandroid/SVG.java index 23b149d..e5bc9c6 100644 --- a/DroidFishApp/src/main/java/com/larvalabs/svgandroid/SVG.java +++ b/DroidFishApp/src/main/java/com/larvalabs/svgandroid/SVG.java @@ -111,7 +111,9 @@ public class SVG { } /** - * Gets the bounding rectangle for the SVG that was computed upon parsing. It may not be entirely accurate for certain curves or transformations, but is often better than nothing. + * Gets the bounding rectangle for the SVG that was computed upon parsing. + * It may not be entirely accurate for certain curves or transformations, + * but is often better than nothing. * @return rectangle representing the computed bounds. */ public RectF getLimits() { diff --git a/DroidFishApp/src/main/java/com/larvalabs/svgandroid/SVGParser.java b/DroidFishApp/src/main/java/com/larvalabs/svgandroid/SVGParser.java index d74678c..db6fbfc 100644 --- a/DroidFishApp/src/main/java/com/larvalabs/svgandroid/SVGParser.java +++ b/DroidFishApp/src/main/java/com/larvalabs/svgandroid/SVGParser.java @@ -17,6 +17,7 @@ import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.HashMap; +import java.util.Map; /* @@ -62,7 +63,7 @@ public class SVGParser { * @throws SVGParseException if there is an error while parsing. */ public static SVG getSVGFromInputStream(InputStream svgData) throws SVGParseException { - return SVGParser.parse(svgData, 0, 0, false); + return SVGParser.parse(svgData, null, false); } /** @@ -73,7 +74,7 @@ public class SVGParser { * @throws SVGParseException if there is an error while parsing. */ public static SVG getSVGFromString(String svgData) throws SVGParseException { - return SVGParser.parse(new ByteArrayInputStream(svgData.getBytes()), 0, 0, false); + return SVGParser.parse(new ByteArrayInputStream(svgData.getBytes()), null, false); } /** @@ -85,7 +86,7 @@ public class SVGParser { * @throws SVGParseException if there is an error while parsing. */ public static SVG getSVGFromResource(Resources resources, int resId) throws SVGParseException { - return SVGParser.parse(resources.openRawResource(resId), 0, 0, false); + return SVGParser.parse(resources.openRawResource(resId), null, false); } /** @@ -108,26 +109,26 @@ public class SVGParser { * Parse SVG data from an input stream, replacing a single color with another color. * * @param svgData the input stream, with SVG XML data in UTF-8 character encoding. - * @param searchColor the color in the SVG to replace. - * @param replaceColor the color with which to replace the search color. + * @param colorReplace Map from colors in the SVG to colors to use instead. May be null. * @return the parsed SVG. * @throws SVGParseException if there is an error while parsing. */ - public static SVG getSVGFromInputStream(InputStream svgData, int searchColor, int replaceColor) throws SVGParseException { - return SVGParser.parse(svgData, searchColor, replaceColor, false); + public static SVG getSVGFromInputStream(InputStream svgData, + Map colorReplace) throws SVGParseException { + return SVGParser.parse(svgData, colorReplace, false); } /** * Parse SVG data from a string. * * @param svgData the string containing SVG XML data. - * @param searchColor the color in the SVG to replace. - * @param replaceColor the color with which to replace the search color. + * @param colorReplace Map from colors in the SVG to colors to use instead. May be null. * @return the parsed SVG. * @throws SVGParseException if there is an error while parsing. */ - public static SVG getSVGFromString(String svgData, int searchColor, int replaceColor) throws SVGParseException { - return SVGParser.parse(new ByteArrayInputStream(svgData.getBytes()), searchColor, replaceColor, false); + public static SVG getSVGFromString(String svgData, + HashMap colorReplace) throws SVGParseException { + return SVGParser.parse(new ByteArrayInputStream(svgData.getBytes()), colorReplace, false); } /** @@ -135,13 +136,13 @@ public class SVGParser { * * @param resources the Android context * @param resId the ID of the raw resource SVG. - * @param searchColor the color in the SVG to replace. - * @param replaceColor the color with which to replace the search color. + * @param colorReplace Map from colors in the SVG to colors to use instead. May be null. * @return the parsed SVG. * @throws SVGParseException if there is an error while parsing. */ - public static SVG getSVGFromResource(Resources resources, int resId, int searchColor, int replaceColor) throws SVGParseException { - return SVGParser.parse(resources.openRawResource(resId), searchColor, replaceColor, false); + public static SVG getSVGFromResource(Resources resources, int resId, + HashMap colorReplace) throws SVGParseException { + return SVGParser.parse(resources.openRawResource(resId), colorReplace, false); } /** @@ -149,15 +150,15 @@ public class SVGParser { * * @param assetMngr the Android asset manager. * @param svgPath the path to the SVG file in the application's assets. - * @param searchColor the color in the SVG to replace. - * @param replaceColor the color with which to replace the search color. + * @param colorReplace Map from colors in the SVG to colors to use instead. May be null. * @return the parsed SVG. * @throws SVGParseException if there is an error while parsing. * @throws IOException if there was a problem reading the file. */ - public static SVG getSVGFromAsset(AssetManager assetMngr, String svgPath, int searchColor, int replaceColor) throws SVGParseException, IOException { + public static SVG getSVGFromAsset(AssetManager assetMngr, String svgPath, + Map colorReplace) throws SVGParseException, IOException { InputStream inputStream = assetMngr.open(svgPath); - SVG svg = getSVGFromInputStream(inputStream, searchColor, replaceColor); + SVG svg = getSVGFromInputStream(inputStream, colorReplace); inputStream.close(); return svg; } @@ -172,7 +173,7 @@ public class SVGParser { return doPath(pathString); } - private static SVG parse(InputStream in, Integer searchColor, Integer replaceColor, boolean whiteMode) throws SVGParseException { + private static SVG parse(InputStream in, Map colorReplace, boolean whiteMode) throws SVGParseException { // Util.debug("Parsing SVG..."); try { // long start = System.currentTimeMillis(); @@ -181,7 +182,7 @@ public class SVGParser { XMLReader xr = sp.getXMLReader(); final Picture picture = new Picture(); SVGHandler handler = new SVGHandler(picture); - handler.setColorSwap(searchColor, replaceColor); + handler.setColorSwap(colorReplace); handler.setWhiteMode(whiteMode); xr.setContentHandler(handler); xr.parse(new InputSource(in)); @@ -405,17 +406,29 @@ public class SVGParser { case '6': case '7': case '8': - case '9': - if (prevCmd == 'm' || prevCmd == 'M') { - cmd = (char) (((int) prevCmd) - 1); - break; - } else if (prevCmd == 'c' || prevCmd == 'C') { - cmd = prevCmd; - break; - } else if (prevCmd == 'l' || prevCmd == 'L') { - cmd = prevCmd; - break; + case '9': { + boolean handled = true; + switch (prevCmd) { + case 'm': + cmd = 'l'; + break; + case 'M': + cmd = 'L'; + break; + case 'l': case 'L': + case 'c': case 'C': + case 's': case 'S': + case 'q': case 'Q': + case 't': case 'T': + cmd = prevCmd; + break; + default: + handled = false; + break; } + if (handled) + break; + } default: { ph.advance(); prevCmd = cmd; @@ -539,6 +552,44 @@ public class SVGParser { lastY = y; break; } + case 'Q': + case 'q': { + wasCurve = true; + float x1 = ph.nextFloat(); + float y1 = ph.nextFloat(); + float x = ph.nextFloat(); + float y = ph.nextFloat(); + if (cmd == 'q') { + x1 += lastX; + x += lastX; + y1 += lastY; + y += lastY; + } + p.quadTo(x1, y1, x, y); + lastX1 = x1; + lastY1 = y1; + lastX = x; + lastY = y; + break; + } + case 'T': + case 't': { + wasCurve = true; + float x = ph.nextFloat(); + float y = ph.nextFloat(); + if (cmd == 't') { + x += lastX; + y += lastY; + } + float x1 = 2 * lastX - lastX1; + float y1 = 2 * lastY - lastY1; + p.quadTo(x1, y1, x, y); + lastX1 = x1; + lastY1 = y1; + lastX = x; + lastY = y; + break; + } case 'A': case 'a': { float rx = ph.nextFloat(); @@ -778,8 +829,7 @@ public class SVGParser { RectF bounds = null; RectF limits = new RectF(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY, Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY); - Integer searchColor = null; - Integer replaceColor = null; + Map colorReplace = null; boolean whiteMode = false; @@ -793,9 +843,8 @@ public class SVGParser { paint.setAntiAlias(true); } - public void setColorSwap(Integer searchColor, Integer replaceColor) { - this.searchColor = searchColor; - this.replaceColor = replaceColor; + public void setColorSwap(Map colorReplace) { + this.colorReplace = colorReplace; } public void setWhiteMode(boolean whiteMode) { @@ -934,19 +983,27 @@ public class SVGParser { private void doColor(Properties atts, Integer color, boolean fillMode) { int c = (0xFFFFFF & color) | 0xFF000000; - if (searchColor != null && searchColor.intValue() == c) { - c = replaceColor; + int opac = -1; + if (colorReplace != null) { + Integer replaceColor = colorReplace.get(c); + if (replaceColor != null) { + c = replaceColor; + opac = replaceColor >>> 24; + } + } + if (opac == -1) { + Float opacity = atts.getFloat("opacity", false); + if (opacity == null) { + opacity = atts.getFloat(fillMode ? "fill-opacity" : "stroke-opacity", false); + } + if (opacity == null) { + opac = 255; + } else { + opac = (int) (255 * opacity); + } } paint.setColor(c); - Float opacity = atts.getFloat("opacity", false); - if (opacity == null) { - opacity = atts.getFloat(fillMode ? "fill-opacity" : "stroke-opacity", false); - } - if (opacity == null) { - paint.setAlpha(255); - } else { - paint.setAlpha((int) (255 * opacity)); - } + paint.setAlpha(opac); } private boolean hidden = false; diff --git a/DroidFishApp/src/main/java/org/petero/droidfish/DroidFish.java b/DroidFishApp/src/main/java/org/petero/droidfish/DroidFish.java index c0233ba..81ef19a 100644 --- a/DroidFishApp/src/main/java/org/petero/droidfish/DroidFish.java +++ b/DroidFishApp/src/main/java/org/petero/droidfish/DroidFish.java @@ -1370,6 +1370,7 @@ public class DroidFish extends Activity pgnOptions.exp.clockInfo = settings.getBoolean("exportTime", false); ColorTheme.instance().readColors(settings); + PieceSet.instance().readPrefs(settings); cb.setColors(); overrideViewAttribs(); @@ -2867,6 +2868,7 @@ public class DroidFish extends Activity builder.setSingleChoiceItems(themeNames, -1, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int item) { ColorTheme.instance().setTheme(settings, item); + PieceSet.instance().readPrefs(settings); cb.setColors(); gameTextListener.clear(); ctrl.prefsChanged(false); diff --git a/DroidFishApp/src/main/java/org/petero/droidfish/PieceSet.java b/DroidFishApp/src/main/java/org/petero/droidfish/PieceSet.java new file mode 100644 index 0000000..fd2451b --- /dev/null +++ b/DroidFishApp/src/main/java/org/petero/droidfish/PieceSet.java @@ -0,0 +1,136 @@ +package org.petero.droidfish; + +import android.content.SharedPreferences; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Rect; + +import com.larvalabs.svgandroid.SVG; +import com.larvalabs.svgandroid.SVGParser; + +import org.petero.droidfish.gamelogic.Piece; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +/** Handle rendering of chess pieces. */ +public class PieceSet { + private static PieceSet inst = null; + + private HashMap nameToPieceType; + private SVG[] svgTable = new SVG[Piece.nPieceTypes]; + private Bitmap[] bitmapTable = new Bitmap[Piece.nPieceTypes]; + private int cachedSquareSize = -1; + private int cachedWhiteColor = 0xffffffff; + private int cachedBlackColor = 0xff000000; + + /** Get singleton instance. */ + public static PieceSet instance() { + if (inst == null) + inst = new PieceSet(); + return inst; + } + + private PieceSet() { + nameToPieceType = new HashMap<>(); + nameToPieceType.put("wk.svg", Piece.WKING); + nameToPieceType.put("wq.svg", Piece.WQUEEN); + nameToPieceType.put("wr.svg", Piece.WROOK); + nameToPieceType.put("wb.svg", Piece.WBISHOP); + nameToPieceType.put("wn.svg", Piece.WKNIGHT); + nameToPieceType.put("wp.svg", Piece.WPAWN); + nameToPieceType.put("bk.svg", Piece.BKING); + nameToPieceType.put("bq.svg", Piece.BQUEEN); + nameToPieceType.put("br.svg", Piece.BROOK); + nameToPieceType.put("bb.svg", Piece.BBISHOP); + nameToPieceType.put("bn.svg", Piece.BKNIGHT); + nameToPieceType.put("bp.svg", Piece.BPAWN); + + parseSvgData(cachedWhiteColor, cachedBlackColor); + } + + /** Re-parse SVG data if piece properties have changed. */ + final void readPrefs(SharedPreferences settings) { + ColorTheme ct = ColorTheme.instance(); + int whiteColor = ct.getColor(ColorTheme.BRIGHT_PIECE); + int blackColor = ct.getColor(ColorTheme.DARK_PIECE); + if (whiteColor != cachedWhiteColor || blackColor != cachedBlackColor) { + recycleBitmaps(); + parseSvgData(whiteColor, blackColor); + cachedWhiteColor = whiteColor; + cachedBlackColor = blackColor; + cachedSquareSize = -1; + } + } + + /** Return a bitmap for the specified piece type and square size. */ + public Bitmap getPieceBitmap(int pType, int sqSize) { + if (sqSize != cachedSquareSize) { + recycleBitmaps(); + createBitmaps(sqSize); + cachedSquareSize = sqSize; + } + return bitmapTable[pType]; + } + + private void parseSvgData(int whiteColor, int blackColor) { + HashMap colorReplace = new HashMap<>(); + colorReplace.put(0xffffffff, whiteColor); + colorReplace.put(0xff000000, blackColor); + try { + ZipInputStream zis = getZipStream(); + ZipEntry entry; + while ((entry = zis.getNextEntry()) != null) { + if (!entry.isDirectory()) { + String name = entry.getName(); + Integer pType = nameToPieceType.get(name); + if (pType != null) { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + byte[] buf = new byte[4096]; + int len; + while ((len = zis.read(buf)) != -1) + bos.write(buf, 0, len); + buf = bos.toByteArray(); + ByteArrayInputStream bis = new ByteArrayInputStream(buf); + svgTable[pType] = SVGParser.getSVGFromInputStream(bis, colorReplace); + } + } + zis.closeEntry(); + } + zis.close(); + } catch (IOException ex) { + throw new RuntimeException("Cannot read chess pieces data", ex); + } + } + + private ZipInputStream getZipStream() throws IOException { + InputStream is = DroidFishApp.getContext().getAssets().open("pieces/chesscases.zip"); + return new ZipInputStream(is); + } + + private void recycleBitmaps() { + for (int i = 0; i < Piece.nPieceTypes; i++) { + if (bitmapTable[i] != null) { + bitmapTable[i].recycle(); + bitmapTable[i] = null; + } + } + } + + private void createBitmaps(int sqSize) { + for (int i = 0; i < Piece.nPieceTypes; i++) { + SVG svg = svgTable[i]; + if (svg != null) { + Bitmap bm = Bitmap.createBitmap(sqSize, sqSize, Bitmap.Config.ARGB_8888); + Canvas bmCanvas = new Canvas(bm); + bmCanvas.drawPicture(svg.getPicture(), new Rect(0, 0, sqSize, sqSize)); + bitmapTable[i] = bm; + } + } + } +} diff --git a/DroidFishApp/src/main/java/org/petero/droidfish/view/ChessBoard.java b/DroidFishApp/src/main/java/org/petero/droidfish/view/ChessBoard.java index 598698c..c0e3c92 100644 --- a/DroidFishApp/src/main/java/org/petero/droidfish/view/ChessBoard.java +++ b/DroidFishApp/src/main/java/org/petero/droidfish/view/ChessBoard.java @@ -23,6 +23,7 @@ import java.util.Collections; import java.util.List; import org.petero.droidfish.ColorTheme; +import org.petero.droidfish.PieceSet; import org.petero.droidfish.gamelogic.Move; import org.petero.droidfish.gamelogic.Piece; import org.petero.droidfish.gamelogic.Position; @@ -30,12 +31,12 @@ import org.petero.droidfish.gamelogic.UndoInfo; import org.petero.droidfish.tb.ProbeResult; import android.content.Context; +import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Path; import android.graphics.Rect; -import android.graphics.Typeface; import android.os.Handler; import android.util.AttributeSet; import android.view.MotionEvent; @@ -51,7 +52,6 @@ public abstract class ChessBoard extends View { public boolean cursorVisible; protected int x0, y0; public int sqSize; - int pieceXDelta, pieceYDelta; // top/left pixel draw position relative to square public boolean flipped; public boolean drawSquareLabels; public boolean toggleSelection; @@ -79,8 +79,7 @@ public abstract class ChessBoard extends View { protected Paint brightPaint; private Paint selectedSquarePaint; private Paint cursorSquarePaint; - private Paint whitePiecePaint; - private Paint blackPiecePaint; + private Paint piecePaint; private Paint labelPaint; private Paint decorationPaint; private ArrayList moveMarkPaint; @@ -93,7 +92,6 @@ public abstract class ChessBoard extends View { cursorX = cursorY = 0; cursorVisible = false; x0 = y0 = sqSize = 0; - pieceXDelta = pieceYDelta = -1; flipped = false; drawSquareLabels = false; toggleSelection = false; @@ -111,11 +109,8 @@ public abstract class ChessBoard extends View { cursorSquarePaint.setStyle(Paint.Style.STROKE); cursorSquarePaint.setAntiAlias(true); - whitePiecePaint = new Paint(); - whitePiecePaint.setAntiAlias(true); - - blackPiecePaint = new Paint(); - blackPiecePaint.setAntiAlias(true); + piecePaint = new Paint(); + piecePaint.setAntiAlias(true); labelPaint = new Paint(); labelPaint.setAntiAlias(true); @@ -134,10 +129,6 @@ public abstract class ChessBoard extends View { if (isInEditMode()) return; - Typeface chessFont = Typeface.createFromAsset(getContext().getAssets(), "fonts/ChessCases.ttf"); - whitePiecePaint.setTypeface(chessFont); - blackPiecePaint.setTypeface(chessFont); - setColors(); } @@ -148,8 +139,6 @@ public abstract class ChessBoard extends View { brightPaint.setColor(ct.getColor(ColorTheme.BRIGHT_SQUARE)); selectedSquarePaint.setColor(ct.getColor(ColorTheme.SELECTED_SQUARE)); cursorSquarePaint.setColor(ct.getColor(ColorTheme.CURSOR_SQUARE)); - whitePiecePaint.setColor(ct.getColor(ColorTheme.BRIGHT_PIECE)); - blackPiecePaint.setColor(ct.getColor(ColorTheme.DARK_PIECE)); labelPaint.setColor(ct.getColor(ColorTheme.SQUARE_LABEL)); decorationPaint.setColor(ct.getColor(ColorTheme.DECORATION)); for (int i = 0; i < ColorTheme.MAX_ARROWS; i++) @@ -369,7 +358,6 @@ public abstract class ChessBoard extends View { int sqSizeW = getSqSizeW(width); int sqSizeH = getSqSizeH(height); int sqSize = Math.min(sqSizeW, sqSizeH); - pieceXDelta = pieceYDelta = -1; labelBounds = null; if (height > width) { int p = getMaxHeightPercentage(); @@ -394,8 +382,6 @@ public abstract class ChessBoard extends View { final int width = getWidth(); final int height = getHeight(); sqSize = Math.min(getSqSizeW(width), getSqSizeH(height)); - blackPiecePaint.setTextSize(sqSize); - whitePiecePaint.setTextSize(sqSize); labelPaint.setTextSize(sqSize/4.0f); decorationPaint.setTextSize(sqSize/3.0f); computeOrigin(width, height); @@ -507,41 +493,15 @@ public abstract class ChessBoard extends View { protected final void drawPiece(Canvas canvas, int xCrd, int yCrd, int p) { if (blindMode) return; - String psb, psw; - boolean rotate = false; - switch (p) { - default: - case Piece.EMPTY: psb = null; psw = null; break; - case Piece.WKING: psb = "H"; psw = "k"; break; - case Piece.WQUEEN: psb = "I"; psw = "l"; break; - case Piece.WROOK: psb = "J"; psw = "m"; break; - case Piece.WBISHOP: psb = "K"; psw = "n"; break; - case Piece.WKNIGHT: psb = "L"; psw = "o"; break; - case Piece.WPAWN: psb = "M"; psw = "p"; break; - case Piece.BKING: psb = "N"; psw = "q"; rotate = true; break; - case Piece.BQUEEN: psb = "O"; psw = "r"; rotate = true; break; - case Piece.BROOK: psb = "P"; psw = "s"; rotate = true; break; - case Piece.BBISHOP: psb = "Q"; psw = "t"; rotate = true; break; - case Piece.BKNIGHT: psb = "R"; psw = "u"; rotate = true; break; - case Piece.BPAWN: psb = "S"; psw = "v"; rotate = true; break; - } - if (psb != null) { - if (pieceXDelta < 0) { - Rect bounds = new Rect(); - blackPiecePaint.getTextBounds("H", 0, 1, bounds); - pieceXDelta = (sqSize - (bounds.left + bounds.right)) / 2; - pieceYDelta = (sqSize - (bounds.top + bounds.bottom)) / 2; - } - rotate ^= flipped; - rotate = false; // Disabled for now + + Bitmap bm = PieceSet.instance().getPieceBitmap(p, sqSize); + if (bm != null) { + boolean rotate = flipped & false; // Disabled for now if (rotate) { canvas.save(); canvas.rotate(180, xCrd + sqSize * 0.5f, yCrd + sqSize * 0.5f); } - xCrd += pieceXDelta; - yCrd += pieceYDelta; - canvas.drawText(psw, xCrd, yCrd, whitePiecePaint); - canvas.drawText(psb, xCrd, yCrd, blackPiecePaint); + canvas.drawBitmap(bm, xCrd, yCrd, piecePaint); if (rotate) canvas.restore(); }