com.richtodd.android.quiltdesign.block.PaperPiecedBlockPiece.java Source code

Java tutorial

Introduction

Here is the source code for com.richtodd.android.quiltdesign.block.PaperPiecedBlockPiece.java

Source

/* Copyright (c) 2013 Richard G. Todd.
 * Licensed under the terms of the GNU General Public License (GPL) Version 3.0.
 */

package com.richtodd.android.quiltdesign.block;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

import org.json.JSONException;
import org.json.JSONObject;

import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Paint.Cap;
import android.graphics.Paint.Style;
import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.Shader.TileMode;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.PathShape;
import android.os.Parcel;
import android.os.Parcelable;

public class PaperPiecedBlockPiece implements Parcelable {
    // private static final String TAG = "PaperPiecedBlockPiece";

    private static final PointF DEFAULT_POINT = new PointF(0, 0);

    private enum Edges {
        TOP, RIGHT, BOTTOM, LEFT
    }

    //
    // Fields
    //

    private UUID m_id;
    private PointF m_from;
    private PointF m_to;
    private int m_color;

    private int m_cachedWidth;
    private int m_cachedHeight;
    private int m_cachedShadowStrokeWidth;
    private List<PointF> m_cachedPoints;
    private Path m_cachedPath;
    private Drawable m_cachedDrawable;
    private Drawable m_cachedBlackWhiteDrawable;
    private Paint m_cachedShadowPaint;

    //
    // Constructors
    //

    public PaperPiecedBlockPiece() {
        m_id = UUID.randomUUID();
        m_from = DEFAULT_POINT;
        m_to = DEFAULT_POINT;
        m_color = 0;

        m_cachedPoints = null;
        m_cachedPath = null;
        m_cachedDrawable = null;
        m_cachedBlackWhiteDrawable = null;
        m_cachedShadowPaint = null;
    }

    public PaperPiecedBlockPiece(PointF from, PointF to, int color) {
        if (from.x < 0f || from.x > 1f || from.y < 0f || from.y > 1f) {
            throw new IllegalArgumentException("Invalid from: " + from);
        }
        if (to.x < 0f || to.x > 1f || to.y < 0f || to.y > 1f) {
            throw new IllegalArgumentException("Invalid to: " + to);
        }

        m_id = UUID.randomUUID();
        m_from = from;
        m_to = to;
        m_color = color;

        m_cachedPoints = null;
        m_cachedPath = null;
        m_cachedDrawable = null;
        m_cachedBlackWhiteDrawable = null;
        m_cachedShadowPaint = null;
    }

    private PaperPiecedBlockPiece(Parcel in) {
        m_id = (UUID) in.readSerializable();
        m_from = in.readParcelable(PointF.class.getClassLoader());
        m_to = in.readParcelable(PointF.class.getClassLoader());
        m_color = in.readInt();

        m_cachedPoints = null;
        m_cachedPath = null;
        m_cachedDrawable = null;
        m_cachedBlackWhiteDrawable = null;
        m_cachedShadowPaint = null;
    }

    //
    // Public
    //

    public UUID getId() {
        return m_id;
    }

    public PointF getFrom() {
        return m_from;
    }

    public PointF getBlockFrom(int width, int height) {
        return new PointF(m_from.x * width, m_from.y * height);
    }

    public PointF getTo() {
        return m_to;
    }

    public PointF getBlockTo(int width, int height) {
        return new PointF(m_to.x * width, m_to.y * height);
    }

    public int getColor() {
        return m_color;
    }

    //
    // Package-Private
    //

    void setFrom(PointF from) {
        if (from.x < 0f || from.x > 1f || from.y < 0f || from.y > 1f) {
            throw new IllegalArgumentException("Invalid from: " + from);
        }

        if (!m_from.equals(from)) {
            m_from = new PointF(from.x, from.y);

            clearCache();
        }
    }

    void setTo(PointF to) {
        if (to.x < 0f || to.x > 1f || to.y < 0f || to.y > 1f) {
            throw new IllegalArgumentException("Invalid to: " + to);
        }

        if (!m_to.equals(to)) {
            m_to = new PointF(to.x, to.y);

            clearCache();
        }
    }

    void setColor(int color) {
        if (color != m_color) {
            m_color = color;

            clearCache();
        }
    }

    void flipHorizontal() {
        float flippedFromX = 1f - m_from.x;
        float flippedFromY = m_from.y;

        float flippedToX = 1f - m_to.x;
        float flippedToY = m_to.y;

        m_from.set(flippedToX, flippedToY);
        m_to.set(flippedFromX, flippedFromY);

        clearCache();
    }

    void flipVertical() {
        float flippedFromX = m_from.x;
        float flippedFromY = 1f - m_from.y;

        float flippedToX = m_to.x;
        float flippedToY = 1f - m_to.y;

        m_from.set(flippedToX, flippedToY);
        m_to.set(flippedFromX, flippedFromY);

        clearCache();
    }

    void rotateClockwise() {
        float rotatedFromX = -m_from.y + 1f;
        float rotatedFromY = m_from.x;
        m_from.set(rotatedFromX, rotatedFromY);

        float rotatedToX = -m_to.y + 1f;
        float rotatedToY = m_to.x;
        m_to.set(rotatedToX, rotatedToY);

        clearCache();
    }

    void rotateCounterclockwise() {
        float rotatedFromX = m_from.y;
        float rotatedFromY = -m_from.x + 1f;
        m_from.set(rotatedFromX, rotatedFromY);

        float rotatedToX = m_to.y;
        float rotatedToY = -m_to.x + 1f;
        m_to.set(rotatedToX, rotatedToY);

        clearCache();
    }

    boolean containsPoint(PointF point, int width, int height) {
        boolean oddNodes = false;

        List<PointF> points = getPoints(width, height);
        int j = points.size() - 1;

        for (int i = 0; i < points.size(); i++) {
            PointF from = points.get(i);
            PointF to = points.get(j);

            // @formatter:off
            if ((from.y < point.y && point.y <= to.y || to.y < point.y && point.y <= from.y)
                    && (from.x <= point.x || to.x <= point.x)) {

                if (from.x + (point.y - from.y) / (to.y - from.y) * (to.x - from.x) < point.x) {
                    oddNodes = !oddNodes;
                }
            }
            // @formatter:on

            j = i;
        }

        return oddNodes;
    }

    void draw(Canvas canvas, RenderOptions renderOptions) {
        if (renderOptions.getRenderShadow()) {
            renderShadow(canvas, renderOptions);
        }

        getDrawable(renderOptions).draw(canvas);
    }

    //
    // JSON
    //

    JSONObject createJSONObject() throws JSONException {

        JSONObject jsonPiece = new JSONObject();

        jsonPiece.put("from_x", m_from.x);
        jsonPiece.put("from_y", m_from.y);
        jsonPiece.put("to_x", m_to.x);
        jsonPiece.put("to_y", m_to.y);
        jsonPiece.put("color", m_color);

        return jsonPiece;
    }

    static final PaperPiecedBlockPiece createFromJSONObject(JSONObject jsonObject) throws JSONException {

        PointF from = new PointF((float) jsonObject.getDouble("from_x"), (float) jsonObject.getDouble("from_y"));
        PointF to = new PointF((float) jsonObject.getDouble("to_x"), (float) jsonObject.getDouble("to_y"));
        int color = jsonObject.getInt("color");

        PaperPiecedBlockPiece piece = new PaperPiecedBlockPiece(from, to, color);

        return piece;
    }

    //
    // Parcelable
    //

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel out, int flags) {
        out.writeSerializable(m_id);
        out.writeParcelable(m_from, 0);
        out.writeParcelable(m_to, 0);
        out.writeInt(m_color);
    }

    public static final Parcelable.Creator<PaperPiecedBlockPiece> CREATOR = new Creator<PaperPiecedBlockPiece>() {

        @Override
        public PaperPiecedBlockPiece[] newArray(int size) {
            return new PaperPiecedBlockPiece[size];
        }

        @Override
        public PaperPiecedBlockPiece createFromParcel(Parcel source) {
            return new PaperPiecedBlockPiece(source);
        }
    };

    //
    // Private
    //

    private void validateCacheWidthHeight(int width, int height) {
        if (m_cachedWidth != width || m_cachedHeight != height) {
            clearCache();
            m_cachedWidth = width;
            m_cachedHeight = height;
        }
    }

    private void validateCacheShawdowStrokeWidth(int shawdowStrokeWidth) {
        if (shawdowStrokeWidth != m_cachedShadowStrokeWidth) {
            clearCache();

            m_cachedShadowStrokeWidth = shawdowStrokeWidth;
        }
    }

    private void clearCache() {
        m_cachedPoints = null;
        m_cachedPath = null;
        m_cachedDrawable = null;
        m_cachedBlackWhiteDrawable = null;
        m_cachedShadowPaint = null;
    }

    public List<PointF> createPoints(int width, int height) {
        ArrayList<PointF> points = new ArrayList<PointF>(5);

        int right = width;
        int bottom = height;

        PointF pieceFrom = getFrom();
        PointF pieceTo = getTo();

        Edges edgeFrom = getEdge(pieceFrom);
        Edges edgeTo = getEdge(pieceTo);

        if (edgeFrom == edgeTo) { // Block encompasses entire rectangle.
            points.add(new PointF(0, 0));
            points.add(new PointF(right, 0));
            points.add(new PointF(right, bottom));
            points.add(new PointF(0, bottom - 0));
        } else {
            PointF blockFrom = getBlockFrom(width, height);
            PointF blockTo = getBlockTo(width, height);

            points.add(blockFrom);
            points.add(blockTo);

            while (edgeTo != edgeFrom) {
                switch (edgeTo) {
                case TOP:
                    points.add(new PointF(right, 0));
                    edgeTo = Edges.RIGHT;
                    break;
                case RIGHT:
                    points.add(new PointF(right, bottom));
                    edgeTo = Edges.BOTTOM;
                    break;
                case BOTTOM:
                    points.add(new PointF(0, bottom));
                    edgeTo = Edges.LEFT;
                    break;
                case LEFT:
                    points.add(new PointF(0, 0));
                    edgeTo = Edges.TOP;
                    break;
                }
            }
        }

        return points;
    }

    private List<PointF> getPoints(int width, int height) {
        validateCacheWidthHeight(width, height);

        if (m_cachedPoints == null) {
            m_cachedPoints = createPoints(width, height);
        }

        return m_cachedPoints;
    }

    private Path createPath(int width, int height) {
        // Log.i(TAG, "pathCreate(" + width + "," + height + ")");

        Path path = null;

        for (PointF point : getPoints(width, height)) {
            // Log.i(TAG, "  point = " + point.x + ", " + point.y);

            if (path == null) {
                path = new Path();
                path.moveTo(point.x, point.y);
            } else {
                path.lineTo(point.x, point.y);
            }
        }

        if (path != null) {
            path.close();
        }

        return path;
    }

    private Path getPath(int width, int height) {
        validateCacheWidthHeight(width, height);

        if (m_cachedPath == null) {
            m_cachedPath = createPath(width, height);
        }

        return m_cachedPath;
    }

    private Drawable createDrawable(RenderOptions renderOptions) {

        PathShape pathShape = new PathShape(getPath(renderOptions.getWidth(), renderOptions.getHeight()),
                renderOptions.getWidth(), renderOptions.getHeight());

        if (renderOptions.getRenderStyle() == RenderStyles.BlackWhite) {

            ShapeDrawable strokeDrawable = new ShapeDrawable(pathShape);
            strokeDrawable.setBounds(0, 0, renderOptions.getWidth(), renderOptions.getHeight());
            strokeDrawable.getPaint().setColor(Color.BLACK);
            strokeDrawable.getPaint().setStyle(Paint.Style.STROKE);
            strokeDrawable.getPaint().setStrokeWidth(renderOptions.getStrokeWidth());

            ShapeDrawable fillDrawable = new ShapeDrawable(pathShape);
            fillDrawable.setBounds(0, 0, renderOptions.getWidth(), renderOptions.getHeight());
            fillDrawable.getPaint().setColor(Color.WHITE);
            fillDrawable.getPaint().setStyle(Paint.Style.FILL);

            Drawable[] layers = new Drawable[2];
            layers[0] = fillDrawable;
            layers[1] = strokeDrawable;
            LayerDrawable drawable = new LayerDrawable(layers);

            return drawable;

        } else {

            ShapeDrawable drawable = new ShapeDrawable(pathShape);
            drawable.getPaint().setColor(getColor());
            drawable.getPaint().setStyle(Paint.Style.FILL);

            return drawable;
        }
    }

    private Drawable getDrawable(RenderOptions renderOptions) {
        validateCacheWidthHeight(renderOptions.getWidth(), renderOptions.getHeight());

        Drawable drawable;

        if (renderOptions.getRenderStyle() == RenderStyles.BlackWhite) {
            if (m_cachedBlackWhiteDrawable == null) {
                m_cachedBlackWhiteDrawable = createDrawable(renderOptions);
            }
            drawable = m_cachedBlackWhiteDrawable;
        } else {
            if (m_cachedDrawable == null) {
                m_cachedDrawable = createDrawable(renderOptions);
            }
            drawable = m_cachedDrawable;
        }

        drawable.setBounds(renderOptions.getLeft(), renderOptions.getTop(), renderOptions.getRight(),
                renderOptions.getBottom());

        return drawable;
    }

    private void renderShadow(Canvas canvas, RenderOptions renderOptions) {
        List<PointF> points = getPoints(renderOptions.getWidth(), renderOptions.getHeight());

        PointF fromPoint = points.get(0);
        PointF toPoint = points.get(1);

        if (fromPoint.y != 0f || toPoint.y != 0f) {
            float fromX = renderOptions.getLeft() + fromPoint.x;
            float fromY = renderOptions.getTop() + fromPoint.y;
            float toX = renderOptions.getLeft() + toPoint.x;
            float toY = renderOptions.getTop() + toPoint.y;

            canvas.drawLine(fromX, fromY, toX, toY, getShadowPaint(renderOptions.getLeft(), renderOptions.getTop(),
                    renderOptions.getWidth(), renderOptions.getHeight(), 20));
        }
    }

    private Paint createShadowPaint(int left, int top, int width, int height, int shadowStrokeWidth) {
        List<PointF> points = getPoints(width, height);
        PointF fromPoint = new PointF(left + points.get(0).x, top + points.get(0).y);
        PointF toPoint = new PointF(left + points.get(1).x, top + points.get(1).y);

        PointF midpoint = new PointF((fromPoint.x + toPoint.x) * 0.5f, (fromPoint.y + toPoint.y) * 0.5f);

        PointF vector = new PointF(fromPoint.x - midpoint.x, fromPoint.y - midpoint.y);

        PointF rotatedVector = new PointF(vector.y, -vector.x);

        float vectorLength = (float) Math.sqrt((vector.x * vector.x) + (vector.y * vector.y));

        PointF shadowVector = new PointF(rotatedVector.x * (shadowStrokeWidth * 0.5f) / vectorLength,
                rotatedVector.y * (shadowStrokeWidth * 0.5f) / vectorLength);

        LinearGradient gradient = new LinearGradient(midpoint.x, midpoint.y, midpoint.x + shadowVector.x,
                midpoint.y + shadowVector.y, Color.argb(100, 0, 0, 0), Color.argb(0, 0, 0, 0), TileMode.MIRROR);

        Paint paint = new Paint();
        paint.setStyle(Style.STROKE);
        paint.setShader(gradient);
        paint.setStrokeWidth(shadowStrokeWidth);
        paint.setStrokeCap(Cap.SQUARE);

        return paint;
    }

    private Paint getShadowPaint(int left, int top, int width, int height, int shadowStrokeWidth) {
        validateCacheWidthHeight(width, height);
        validateCacheShawdowStrokeWidth(shadowStrokeWidth);

        if (m_cachedShadowPaint == null) {
            m_cachedShadowPaint = createShadowPaint(left, top, width, height, shadowStrokeWidth);
        }

        return m_cachedShadowPaint;
    }

    private Edges getEdge(PointF piecePoint) {
        if (piecePoint.y == 0f)
            return Edges.TOP;
        if (piecePoint.x == 1f)
            return Edges.RIGHT;
        if (piecePoint.y == 1f)
            return Edges.BOTTOM;
        if (piecePoint.x == 0f)
            return Edges.LEFT;

        throw new IllegalArgumentException();
    }
}