Android Tutariel - 2D graphics : Canvas
Draw with Canvas
package app.Test; import android.app.Activity; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Path; import android.graphics.Path.Direction; import android.os.Bundle; import android.view.View; public class appTest extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(new GraphicsView(this)); } static public class GraphicsView extends View { private static final String QUOTE = "This is a test. This is a demo."; private Path circle; private Paint cPaint; private Paint tPaint; public GraphicsView(Context context) { super(context); int color = Color.BLUE; circle = new Path(); circle.addCircle(150, 150, 100, Direction.CW); cPaint = new Paint(Paint.ANTI_ALIAS_FLAG); cPaint.setStyle(Paint.Style.STROKE); cPaint.setColor(Color.LTGRAY); cPaint.setStrokeWidth(3); setBackgroundResource(R.drawable.icon); } @Override protected void onDraw(Canvas canvas) { canvas.drawPath(circle, cPaint); } } } //main.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="@drawable/icon"> <org.example.graphics.Graphics.GraphicsView android:id="@+id/graphics" android:layout_width="fill_parent" android:layout_height="fill_parent" /> </LinearLayout> //strings.xml <?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">Graphics</string> </resources> //colors.xml <?xml version="1.0" encoding="utf-8"?> <resources> <color name="mycolor">#7fff00ff</color> </resources>
Canvas Layers
package app.test; import android.app.Activity; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Picture; import android.graphics.Rect; import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; public class Test extends GraphicsActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(new SampleView(this)); } private static class SampleView extends View { private static final int LAYER_FLAGS = Canvas.MATRIX_SAVE_FLAG | Canvas.CLIP_SAVE_FLAG | Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.FULL_COLOR_LAYER_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG; private Paint mPaint; public SampleView(Context context) { super(context); setFocusable(true); mPaint = new Paint(); mPaint.setAntiAlias(true); } @Override protected void onDraw(Canvas canvas) { canvas.drawColor(Color.WHITE); canvas.translate(10, 10); canvas.saveLayerAlpha(0, 0, 200, 200, 0x88, LAYER_FLAGS); mPaint.setColor(Color.RED); canvas.drawCircle(75, 75, 75, mPaint); mPaint.setColor(Color.BLUE); canvas.drawCircle(125, 125, 75, mPaint); canvas.restore(); } } } class GraphicsActivity extends Activity { // set to true to test Picture private static final boolean TEST_PICTURE = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); } @Override public void setContentView(View view) { if (TEST_PICTURE) { ViewGroup vg = new PictureLayout(this); vg.addView(view); view = vg; } super.setContentView(view); } } class PictureLayout extends ViewGroup { private final Picture mPicture = new Picture(); public PictureLayout(Context context) { super(context); } public PictureLayout(Context context, AttributeSet attrs) { super(context, attrs); } @Override public void addView(View child) { if (getChildCount() > 1) { throw new IllegalStateException( "PictureLayout can host only one direct child"); } super.addView(child); } @Override public void addView(View child, int index) { if (getChildCount() > 1) { throw new IllegalStateException( "PictureLayout can host only one direct child"); } super.addView(child, index); } @Override public void addView(View child, LayoutParams params) { if (getChildCount() > 1) { throw new IllegalStateException( "PictureLayout can host only one direct child"); } super.addView(child, params); } @Override public void addView(View child, int index, LayoutParams params) { if (getChildCount() > 1) { throw new IllegalStateException( "PictureLayout can host only one direct child"); } super.addView(child, index, params); } @Override protected LayoutParams generateDefaultLayoutParams() { return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { final int count = getChildCount(); int maxHeight = 0; int maxWidth = 0; for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child.getVisibility() != GONE) { measureChild(child, widthMeasureSpec, heightMeasureSpec); } } maxWidth += getPaddingLeft() + getPaddingRight(); maxHeight += getPaddingTop() + getPaddingBottom(); Drawable drawable = getBackground(); if (drawable != null) { maxHeight = Math.max(maxHeight, drawable.getMinimumHeight()); maxWidth = Math.max(maxWidth, drawable.getMinimumWidth()); } setMeasuredDimension(resolveSize(maxWidth, widthMeasureSpec), resolveSize(maxHeight, heightMeasureSpec)); } private void drawPict(Canvas canvas, int x, int y, int w, int h, float sx, float sy) { canvas.save(); canvas.translate(x, y); canvas.clipRect(0, 0, w, h); canvas.scale(0.5f, 0.5f); canvas.scale(sx, sy, w, h); canvas.drawPicture(mPicture); canvas.restore(); } @Override protected void dispatchDraw(Canvas canvas) { super.dispatchDraw(mPicture.beginRecording(getWidth(), getHeight())); mPicture.endRecording(); int x = getWidth() / 2; int y = getHeight() / 2; if (false) { canvas.drawPicture(mPicture); } else { drawPict(canvas, 0, 0, x, y, 1, 1); drawPict(canvas, x, 0, x, y, -1, 1); drawPict(canvas, 0, y, x, y, 1, -1); drawPict(canvas, x, y, x, y, -1, -1); } } @Override public ViewParent invalidateChildInParent(int[] location, Rect dirty) { location[0] = getLeft(); location[1] = getTop(); dirty.set(0, 0, getWidth(), getHeight()); return getParent(); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { final int count = super.getChildCount(); for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child.getVisibility() != GONE) { final int childLeft = getPaddingLeft(); final int childTop = getPaddingTop(); child.layout(childLeft, childTop, childLeft + child.getMeasuredWidth(), childTop + child.getMeasuredHeight()); } } } }
Translate Canvas
package app.test; import android.app.Activity; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Picture; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; public class Test extends GraphicsActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(new SampleView(this)); } private static class SampleView extends View { private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); private Matrix mMatrix = new Matrix(); private Paint.FontMetrics mFontMetrics; private void doDraw(Canvas canvas, float src[], float dst[]) { canvas.save(); mMatrix.setPolyToPoly(src, 0, dst, 0, src.length >> 1); canvas.concat(mMatrix); mPaint.setColor(Color.GRAY); mPaint.setStyle(Paint.Style.STROKE); canvas.drawRect(0, 0, 64, 64, mPaint); canvas.drawLine(0, 0, 64, 64, mPaint); canvas.drawLine(0, 64, 64, 0, mPaint); mPaint.setColor(Color.RED); mPaint.setStyle(Paint.Style.FILL); // how to draw the text center on our square // centering in X is easy... use alignment (and X at midpoint) float x = 64/2; // centering in Y, we need to measure ascent/descent first float y = 64/2 - (mFontMetrics.ascent + mFontMetrics.descent)/2; canvas.drawText(src.length/2 + "", x, y, mPaint); canvas.restore(); } public SampleView(Context context) { super(context); // for when the style is STROKE mPaint.setStrokeWidth(4); // for when we draw text mPaint.setTextSize(40); mPaint.setTextAlign(Paint.Align.CENTER); mFontMetrics = mPaint.getFontMetrics(); } @Override protected void onDraw(Canvas canvas) { canvas.drawColor(Color.WHITE); canvas.save(); canvas.translate(10, 10); // translate (1 point) doDraw(canvas, new float[] { 0, 0 }, new float[] { 5, 5 }); canvas.restore(); canvas.save(); canvas.translate(160, 10); // rotate/uniform-scale (2 points) doDraw(canvas, new float[] { 32, 32, 64, 32 }, new float[] { 32, 32, 64, 48 }); canvas.restore(); canvas.save(); canvas.translate(10, 110); // rotate/skew (3 points) doDraw(canvas, new float[] { 0, 0, 64, 0, 0, 64 }, new float[] { 0, 0, 96, 0, 24, 64 }); canvas.restore(); canvas.save(); canvas.translate(160, 110); // perspective (4 points) doDraw(canvas, new float[] { 0, 0, 64, 0, 64, 64, 0, 64 }, new float[] { 0, 0, 96, 0, 64, 96, 0, 64 }); canvas.restore(); } } } class GraphicsActivity extends Activity { // set to true to test Picture private static final boolean TEST_PICTURE = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); } @Override public void setContentView(View view) { if (TEST_PICTURE) { ViewGroup vg = new PictureLayout(this); vg.addView(view); view = vg; } super.setContentView(view); } } class PictureLayout extends ViewGroup { private final Picture mPicture = new Picture(); public PictureLayout(Context context) { super(context); } public PictureLayout(Context context, AttributeSet attrs) { super(context, attrs); } @Override public void addView(View child) { if (getChildCount() > 1) { throw new IllegalStateException( "PictureLayout can host only one direct child"); } super.addView(child); } @Override public void addView(View child, int index) { if (getChildCount() > 1) { throw new IllegalStateException( "PictureLayout can host only one direct child"); } super.addView(child, index); } @Override public void addView(View child, LayoutParams params) { if (getChildCount() > 1) { throw new IllegalStateException( "PictureLayout can host only one direct child"); } super.addView(child, params); } @Override public void addView(View child, int index, LayoutParams params) { if (getChildCount() > 1) { throw new IllegalStateException( "PictureLayout can host only one direct child"); } super.addView(child, index, params); } @Override protected LayoutParams generateDefaultLayoutParams() { return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { final int count = getChildCount(); int maxHeight = 0; int maxWidth = 0; for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child.getVisibility() != GONE) { measureChild(child, widthMeasureSpec, heightMeasureSpec); } } maxWidth += getPaddingLeft() + getPaddingRight(); maxHeight += getPaddingTop() + getPaddingBottom(); Drawable drawable = getBackground(); if (drawable != null) { maxHeight = Math.max(maxHeight, drawable.getMinimumHeight()); maxWidth = Math.max(maxWidth, drawable.getMinimumWidth()); } setMeasuredDimension(resolveSize(maxWidth, widthMeasureSpec), resolveSize(maxHeight, heightMeasureSpec)); } private void drawPict(Canvas canvas, int x, int y, int w, int h, float sx, float sy) { canvas.save(); canvas.translate(x, y); canvas.clipRect(0, 0, w, h); canvas.scale(0.5f, 0.5f); canvas.scale(sx, sy, w, h); canvas.drawPicture(mPicture); canvas.restore(); } @Override protected void dispatchDraw(Canvas canvas) { super.dispatchDraw(mPicture.beginRecording(getWidth(), getHeight())); mPicture.endRecording(); int x = getWidth() / 2; int y = getHeight() / 2; if (false) { canvas.drawPicture(mPicture); } else { drawPict(canvas, 0, 0, x, y, 1, 1); drawPict(canvas, x, 0, x, y, -1, 1); drawPict(canvas, 0, y, x, y, 1, -1); drawPict(canvas, x, y, x, y, -1, -1); } } @Override public ViewParent invalidateChildInParent(int[] location, Rect dirty) { location[0] = getLeft(); location[1] = getTop(); dirty.set(0, 0, getWidth(), getHeight()); return getParent(); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { final int count = super.getChildCount(); for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child.getVisibility() != GONE) { final int childLeft = getPaddingLeft(); final int childTop = getPaddingTop(); child.layout(childLeft, childTop, childLeft + child.getMeasuredWidth(), childTop + child.getMeasuredHeight()); } } } }
Sweep
package app.test; import android.app.Activity; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Picture; import android.graphics.Rect; import android.graphics.Shader; import android.graphics.SweepGradient; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.util.AttributeSet; import android.view.KeyEvent; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; import android.widget.LinearLayout.LayoutParams; public class Test extends GraphicsActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(new SampleView(this)); } private static class SampleView extends View { private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); private float mRotate; private Matrix mMatrix = new Matrix(); private Shader mShader; private boolean mDoTiming; public SampleView(Context context) { super(context); setFocusable(true); setFocusableInTouchMode(true); float x = 160; float y = 100; mShader = new SweepGradient(x, y, new int[] { Color.GREEN, Color.RED, Color.BLUE, Color.GREEN }, null); mPaint.setShader(mShader); } @Override protected void onDraw(Canvas canvas) { Paint paint = mPaint; float x = 160; float y = 100; canvas.drawColor(Color.WHITE); mMatrix.setRotate(mRotate, x, y); mShader.setLocalMatrix(mMatrix); mRotate += 3; if (mRotate >= 360) { mRotate = 0; } invalidate(); if (mDoTiming) { long now = System.currentTimeMillis(); for (int i = 0; i < 20; i++) { canvas.drawCircle(x, y, 80, paint); } now = System.currentTimeMillis() - now; android.util.Log.d("skia", "sweep ms = " + (now / 20.)); } else { canvas.drawCircle(x, y, 80, paint); } } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { switch (keyCode) { case KeyEvent.KEYCODE_D: mPaint.setDither(!mPaint.isDither()); invalidate(); return true; case KeyEvent.KEYCODE_T: mDoTiming = !mDoTiming; invalidate(); return true; } return super.onKeyDown(keyCode, event); } } } class GraphicsActivity extends Activity { // set to true to test Picture private static final boolean TEST_PICTURE = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); } @Override public void setContentView(View view) { if (TEST_PICTURE) { ViewGroup vg = new PictureLayout(this); vg.addView(view); view = vg; } super.setContentView(view); } } class PictureLayout extends ViewGroup { private final Picture mPicture = new Picture(); public PictureLayout(Context context) { super(context); } public PictureLayout(Context context, AttributeSet attrs) { super(context, attrs); } @Override public void addView(View child) { if (getChildCount() > 1) { throw new IllegalStateException( "PictureLayout can host only one direct child"); } super.addView(child); } @Override public void addView(View child, int index) { if (getChildCount() > 1) { throw new IllegalStateException( "PictureLayout can host only one direct child"); } super.addView(child, index); } @Override public void addView(View child, LayoutParams params) { if (getChildCount() > 1) { throw new IllegalStateException( "PictureLayout can host only one direct child"); } super.addView(child, params); } @Override public void addView(View child, int index, LayoutParams params) { if (getChildCount() > 1) { throw new IllegalStateException( "PictureLayout can host only one direct child"); } super.addView(child, index, params); } @Override protected LayoutParams generateDefaultLayoutParams() { return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { final int count = getChildCount(); int maxHeight = 0; int maxWidth = 0; for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child.getVisibility() != GONE) { measureChild(child, widthMeasureSpec, heightMeasureSpec); } } maxWidth += getPaddingLeft() + getPaddingRight(); maxHeight += getPaddingTop() + getPaddingBottom(); Drawable drawable = getBackground(); if (drawable != null) { maxHeight = Math.max(maxHeight, drawable.getMinimumHeight()); maxWidth = Math.max(maxWidth, drawable.getMinimumWidth()); } setMeasuredDimension(resolveSize(maxWidth, widthMeasureSpec), resolveSize(maxHeight, heightMeasureSpec)); } private void drawPict(Canvas canvas, int x, int y, int w, int h, float sx, float sy) { canvas.save(); canvas.translate(x, y); canvas.clipRect(0, 0, w, h); canvas.scale(0.5f, 0.5f); canvas.scale(sx, sy, w, h); canvas.drawPicture(mPicture); canvas.restore(); } @Override protected void dispatchDraw(Canvas canvas) { super.dispatchDraw(mPicture.beginRecording(getWidth(), getHeight())); mPicture.endRecording(); int x = getWidth() / 2; int y = getHeight() / 2; if (false) { canvas.drawPicture(mPicture); } else { drawPict(canvas, 0, 0, x, y, 1, 1); drawPict(canvas, x, 0, x, y, -1, 1); drawPict(canvas, 0, y, x, y, 1, -1); drawPict(canvas, x, y, x, y, -1, -1); } } @Override public ViewParent invalidateChildInParent(int[] location, Rect dirty) { location[0] = getLeft(); location[1] = getTop(); dirty.set(0, 0, getWidth(), getHeight()); return getParent(); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { final int count = super.getChildCount(); for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child.getVisibility() != GONE) { final int childLeft = getPaddingLeft(); final int childTop = getPaddingTop(); child.layout(childLeft, childTop, childLeft + child.getMeasuredWidth(), childTop + child.getMeasuredHeight()); } } } }
Vertices
package app.test; import android.app.Activity; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.BitmapShader; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Picture; import android.graphics.Rect; import android.graphics.Shader; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; public class Test extends GraphicsActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(new SampleView(this)); } private static class SampleView extends View { private final Paint mPaint = new Paint(); private final float[] mVerts = new float[10]; private final float[] mTexs = new float[10]; private final short[] mIndices = { 0, 1, 2, 3, 4, 1 }; private final Matrix mMatrix = new Matrix(); private final Matrix mInverse = new Matrix(); private static void setXY(float[] array, int index, float x, float y) { array[index * 2 + 0] = x; array[index * 2 + 1] = y; } public SampleView(Context context) { super(context); setFocusable(true); Bitmap bm = BitmapFactory.decodeResource(getResources(), R.drawable.icon); Shader s = new BitmapShader(bm, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); mPaint.setShader(s); float w = bm.getWidth(); float h = bm.getHeight(); // construct our mesh setXY(mTexs, 0, w / 2, h / 2); setXY(mTexs, 1, 0, 0); setXY(mTexs, 2, w, 0); setXY(mTexs, 3, w, h); setXY(mTexs, 4, 0, h); setXY(mVerts, 0, w / 2, h / 2); setXY(mVerts, 1, 0, 0); setXY(mVerts, 2, w, 0); setXY(mVerts, 3, w, h); setXY(mVerts, 4, 0, h); mMatrix.setScale(0.8f, 0.8f); mMatrix.preTranslate(20, 20); mMatrix.invert(mInverse); } @Override protected void onDraw(Canvas canvas) { canvas.drawColor(0xFFCCCCCC); canvas.save(); canvas.concat(mMatrix); canvas.drawVertices(Canvas.VertexMode.TRIANGLE_FAN, 10, mVerts, 0, mTexs, 0, null, 0, null, 0, 0, mPaint); canvas.translate(0, 240); canvas.drawVertices(Canvas.VertexMode.TRIANGLE_FAN, 10, mVerts, 0, mTexs, 0, null, 0, mIndices, 0, 6, mPaint); canvas.restore(); } @Override public boolean onTouchEvent(MotionEvent event) { float[] pt = { event.getX(), event.getY() }; mInverse.mapPoints(pt); setXY(mVerts, 0, pt[0], pt[1]); invalidate(); return true; } } } class GraphicsActivity extends Activity { // set to true to test Picture private static final boolean TEST_PICTURE = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); } @Override public void setContentView(View view) { if (TEST_PICTURE) { ViewGroup vg = new PictureLayout(this); vg.addView(view); view = vg; } super.setContentView(view); } } class PictureLayout extends ViewGroup { private final Picture mPicture = new Picture(); public PictureLayout(Context context) { super(context); } public PictureLayout(Context context, AttributeSet attrs) { super(context, attrs); } @Override public void addView(View child) { if (getChildCount() > 1) { throw new IllegalStateException( "PictureLayout can host only one direct child"); } super.addView(child); } @Override public void addView(View child, int index) { if (getChildCount() > 1) { throw new IllegalStateException( "PictureLayout can host only one direct child"); } super.addView(child, index); } @Override public void addView(View child, LayoutParams params) { if (getChildCount() > 1) { throw new IllegalStateException( "PictureLayout can host only one direct child"); } super.addView(child, params); } @Override public void addView(View child, int index, LayoutParams params) { if (getChildCount() > 1) { throw new IllegalStateException( "PictureLayout can host only one direct child"); } super.addView(child, index, params); } @Override protected LayoutParams generateDefaultLayoutParams() { return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { final int count = getChildCount(); int maxHeight = 0; int maxWidth = 0; for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child.getVisibility() != GONE) { measureChild(child, widthMeasureSpec, heightMeasureSpec); } } maxWidth += getPaddingLeft() + getPaddingRight(); maxHeight += getPaddingTop() + getPaddingBottom(); Drawable drawable = getBackground(); if (drawable != null) { maxHeight = Math.max(maxHeight, drawable.getMinimumHeight()); maxWidth = Math.max(maxWidth, drawable.getMinimumWidth()); } setMeasuredDimension(resolveSize(maxWidth, widthMeasureSpec), resolveSize(maxHeight, heightMeasureSpec)); } private void drawPict(Canvas canvas, int x, int y, int w, int h, float sx, float sy) { canvas.save(); canvas.translate(x, y); canvas.clipRect(0, 0, w, h); canvas.scale(0.5f, 0.5f); canvas.scale(sx, sy, w, h); canvas.drawPicture(mPicture); canvas.restore(); } @Override protected void dispatchDraw(Canvas canvas) { super.dispatchDraw(mPicture.beginRecording(getWidth(), getHeight())); mPicture.endRecording(); int x = getWidth() / 2; int y = getHeight() / 2; if (false) { canvas.drawPicture(mPicture); } else { drawPict(canvas, 0, 0, x, y, 1, 1); drawPict(canvas, x, 0, x, y, -1, 1); drawPict(canvas, 0, y, x, y, 1, -1); drawPict(canvas, x, y, x, y, -1, -1); } } @Override public ViewParent invalidateChildInParent(int[] location, Rect dirty) { location[0] = getLeft(); location[1] = getTop(); dirty.set(0, 0, getWidth(), getHeight()); return getParent(); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { final int count = super.getChildCount(); for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child.getVisibility() != GONE) { final int childLeft = getPaddingLeft(); final int childTop = getPaddingTop(); child.layout(childLeft, childTop, childLeft + child.getMeasuredWidth(), childTop + child.getMeasuredHeight()); } } } }
Scale To Fit
package app.test; import android.app.Activity; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Picture; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; public class Test extends GraphicsActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(new SampleView(this)); } private static class SampleView extends View { private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); private final Paint mHairPaint = new Paint(Paint.ANTI_ALIAS_FLAG); private final Paint mLabelPaint = new Paint(Paint.ANTI_ALIAS_FLAG); private final Matrix mMatrix = new Matrix(); private final RectF mSrcR = new RectF(); private static final Matrix.ScaleToFit[] sFits = new Matrix.ScaleToFit[] { Matrix.ScaleToFit.FILL, Matrix.ScaleToFit.START, Matrix.ScaleToFit.CENTER, Matrix.ScaleToFit.END }; private static final String[] sFitLabels = new String[] { "FILL", "START", "CENTER", "END" }; private static final int[] sSrcData = new int[] { 80, 40, Color.RED, 40, 80, Color.GREEN, 30, 30, Color.BLUE, 80, 80, Color.BLACK }; private static final int N = 4; private static final int WIDTH = 52; private static final int HEIGHT = 52; private final RectF mDstR = new RectF(0, 0, WIDTH, HEIGHT); public SampleView(Context context) { super(context); mHairPaint.setStyle(Paint.Style.STROKE); mLabelPaint.setTextSize(16); } private void setSrcR(int index) { int w = sSrcData[index * 3 + 0]; int h = sSrcData[index * 3 + 1]; mSrcR.set(0, 0, w, h); } private void drawSrcR(Canvas canvas, int index) { mPaint.setColor(sSrcData[index * 3 + 2]); canvas.drawOval(mSrcR, mPaint); } private void drawFit(Canvas canvas, int index, Matrix.ScaleToFit stf) { canvas.save(); setSrcR(index); mMatrix.setRectToRect(mSrcR, mDstR, stf); canvas.concat(mMatrix); drawSrcR(canvas, index); canvas.restore(); canvas.drawRect(mDstR, mHairPaint); } @Override protected void onDraw(Canvas canvas) { canvas.drawColor(Color.WHITE); canvas.translate(10, 10); canvas.save(); for (int i = 0; i < N; i++) { setSrcR(i); drawSrcR(canvas, i); canvas.translate(mSrcR.width() + 15, 0); } canvas.restore(); canvas.translate(0, 100); for (int j = 0; j < sFits.length; j++) { canvas.save(); for (int i = 0; i < N; i++) { drawFit(canvas, i, sFits[j]); canvas.translate(mDstR.width() + 8, 0); } canvas.drawText(sFitLabels[j], 0, HEIGHT * 2 / 3, mLabelPaint); canvas.restore(); canvas.translate(0, 80); } } } } class GraphicsActivity extends Activity { // set to true to test Picture private static final boolean TEST_PICTURE = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); } @Override public void setContentView(View view) { if (TEST_PICTURE) { ViewGroup vg = new PictureLayout(this); vg.addView(view); view = vg; } super.setContentView(view); } } class PictureLayout extends ViewGroup { private final Picture mPicture = new Picture(); public PictureLayout(Context context) { super(context); } public PictureLayout(Context context, AttributeSet attrs) { super(context, attrs); } @Override public void addView(View child) { if (getChildCount() > 1) { throw new IllegalStateException( "PictureLayout can host only one direct child"); } super.addView(child); } @Override public void addView(View child, int index) { if (getChildCount() > 1) { throw new IllegalStateException( "PictureLayout can host only one direct child"); } super.addView(child, index); } @Override public void addView(View child, LayoutParams params) { if (getChildCount() > 1) { throw new IllegalStateException( "PictureLayout can host only one direct child"); } super.addView(child, params); } @Override public void addView(View child, int index, LayoutParams params) { if (getChildCount() > 1) { throw new IllegalStateException( "PictureLayout can host only one direct child"); } super.addView(child, index, params); } @Override protected LayoutParams generateDefaultLayoutParams() { return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { final int count = getChildCount(); int maxHeight = 0; int maxWidth = 0; for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child.getVisibility() != GONE) { measureChild(child, widthMeasureSpec, heightMeasureSpec); } } maxWidth += getPaddingLeft() + getPaddingRight(); maxHeight += getPaddingTop() + getPaddingBottom(); Drawable drawable = getBackground(); if (drawable != null) { maxHeight = Math.max(maxHeight, drawable.getMinimumHeight()); maxWidth = Math.max(maxWidth, drawable.getMinimumWidth()); } setMeasuredDimension(resolveSize(maxWidth, widthMeasureSpec), resolveSize(maxHeight, heightMeasureSpec)); } private void drawPict(Canvas canvas, int x, int y, int w, int h, float sx, float sy) { canvas.save(); canvas.translate(x, y); canvas.clipRect(0, 0, w, h); canvas.scale(0.5f, 0.5f); canvas.scale(sx, sy, w, h); canvas.drawPicture(mPicture); canvas.restore(); } @Override protected void dispatchDraw(Canvas canvas) { super.dispatchDraw(mPicture.beginRecording(getWidth(), getHeight())); mPicture.endRecording(); int x = getWidth() / 2; int y = getHeight() / 2; if (false) { canvas.drawPicture(mPicture); } else { drawPict(canvas, 0, 0, x, y, 1, 1); drawPict(canvas, x, 0, x, y, -1, 1); drawPict(canvas, 0, y, x, y, 1, -1); drawPict(canvas, x, y, x, y, -1, -1); } } @Override public ViewParent invalidateChildInParent(int[] location, Rect dirty) { location[0] = getLeft(); location[1] = getTop(); dirty.set(0, 0, getWidth(), getHeight()); return getParent(); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { final int count = super.getChildCount(); for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child.getVisibility() != GONE) { final int childLeft = getPaddingLeft(); final int childTop = getPaddingTop(); child.layout(childLeft, childTop, childLeft + child.getMeasuredWidth(), childTop + child.getMeasuredHeight()); } } } }
Draw Pint
import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Path; import android.graphics.drawable.Drawable; public class Pint{ private Drawable bgFull, bgEmpty, bgFront; private Bitmap foam; private static final float WIDTH = 320; private static final float HEIGHT = 480; private static final float TOTAL = WIDTH*HEIGHT - 50*320; private float currentVolume = 0; float leftY=480, rightY=480; float foamHeight; public Pint(Drawable bgFull, Drawable bgEmpty, Drawable bgFront, Bitmap foam){ this.bgEmpty = bgEmpty; this.bgEmpty.setBounds(0, 0, bgEmpty.getIntrinsicWidth(), bgEmpty.getIntrinsicHeight()); this.bgFull = bgFull; this.bgFull.setBounds(0, 0, bgFull.getIntrinsicWidth(), bgFull.getIntrinsicHeight()); this.bgFront = bgFront; this.bgFront.setBounds(0, 0, bgFront.getIntrinsicWidth(), bgFront.getIntrinsicHeight()); this.foam = foam; foamHeight = foam.getHeight()/2; } private float angle = 0f; private boolean full = false; public void setAngle(float angle) { this.angle = angle; } public float getCurrentVolume() { return currentVolume; } public void setCurrentVolume(float currentVolume) { this.currentVolume = currentVolume; } public void draw(Canvas canvas){ bgEmpty.draw(canvas); canvas.save(); float emptyHeight = 0; if(!full){ currentVolume += (10 * 320); if(currentVolume > TOTAL){ full = true; } } emptyHeight = (float) (Math.tan(Math.toRadians(angle)) * WIDTH); float adjustedHeight = currentVolume / WIDTH; //normal height adjustedHeight = adjustedHeight <= 0? 2f : adjustedHeight; leftY = Math.max(-1000, HEIGHT - ( adjustedHeight + emptyHeight)); rightY = Math.max(-1000, HEIGHT - (adjustedHeight - emptyHeight)); if(currentVolume > WIDTH){ Path p = new Path(); p.moveTo(0, HEIGHT); //lower left p.lineTo(WIDTH, HEIGHT); p.lineTo(WIDTH, rightY); p.lineTo(0, leftY); canvas.clipPath(p); bgFull.draw(canvas); canvas.restore(); canvas.drawBitmapMesh( foam, 1, 1, new float[]{ 0, leftY-foamHeight, 320, rightY-foamHeight, 0, leftY+foamHeight, 320, rightY+foamHeight,}, 0, null, 0, null); } if(leftY < 5 || rightY < 5){ currentVolume *= 0.98;// -= (2000f); currentVolume -= 200f; } bgFront.draw(canvas); } }
Class for the scribble/memo pad
//package com.neugent.aethervoice.ui; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.view.MotionEvent; import android.view.View; /** * Class for the scribble/memo pad * * @author Amando Jose Quinto II * */ public class Scribble extends View { private static final int INVALID_POINTER_ID = -1; /** The paint used in drawing the path. **/ private final Paint mPaint; /** The canvas for the path to be drawn. **/ private static Canvas mCanvas; /** The path 1 to be drawn. **/ private final Path mPath1; /** The path 2 to be drawn. **/ private final Path mPath2; /** The paint used by the bitmap **/ private final Paint mBitmapPaint; /** Bitmap for the screen **/ private final Bitmap mBitmap = Bitmap.createBitmap(480, 390, Bitmap.Config.ARGB_8888); /** The flag for erasing the canvas. **/ private boolean mErase = false; /** The starting point for x-coordinate. **/ private float mX1; /** The starting point for y-coordinate. **/ private float mY1; /** The starting point for x-coordinate. **/ private float mX2; /** The starting point for y-coordinate. **/ private float mY2; /** The tolerance of the finger movement. **/ private static final float TOUCH_TOLERANCE = 4; private int mActivePointerId = INVALID_POINTER_ID; /** * Instantiate the Scribble. * * @param context * The application context */ public Scribble(final Context context) { super(context); Scribble.mCanvas = new Canvas(mBitmap); mBitmapPaint = new Paint(Paint.DITHER_FLAG); mPath1 = new Path(); mPath2 = new Path(); mPaint = new Paint(); mPaint.setAntiAlias(true); mPaint.setDither(true); mPaint.setColor(Color.RED); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeJoin(Paint.Join.ROUND); mPaint.setStrokeCap(Paint.Cap.ROUND); mPaint.setStrokeWidth(6); } @Override protected void onSizeChanged(final int w, final int h, final int oldw, final int oldh) { super.onSizeChanged(w, h, oldw, oldh); } /** * Draw/Erases the path determined by the user * * @see android.view.View#onDraw(android.graphics.Canvas) */ @Override protected void onDraw(final Canvas canvas) { if (mErase) { final Paint p = new Paint(); p.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); Scribble.mCanvas.drawRect(0, 0, getWidth(), getHeight(), p); mErase = false; } else { canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint); canvas.drawPath(mPath1, mPaint); canvas.drawPath(mPath2, mPaint); } } /** * */ public void eraseAll() { mErase = true; invalidate(); } /** * @param x * the x-coordinate * @param y * the y-coordinate * * @see android.view.View#onTouchEvent(MotionEvent) * @see android.view.MotionEvent#ACTION_DOWN */ private void touch_start(final float x, final float y, final int index) { switch(index){ case 0: mPath1.reset(); mPath1.moveTo(x, y); mX1 = x; mY1 = y; break; case 1: mPath2.reset(); mPath2.moveTo(x, y); mX2 = x; mY2 = y; break; } } /** * Draws the path. * * @param x * the x-coordinate * @param y * the y-coordinate * * @see android.view.View#onTouchEvent(MotionEvent) * @see android.view.MotionEvent#ACTION_MOVE */ private void touch_move(final float x, final float y, int pointerIndex) { switch(pointerIndex){ case 0: if (Math.abs(x - mX1) >= Scribble.TOUCH_TOLERANCE || Math.abs(y - mY1) >= Scribble.TOUCH_TOLERANCE) { mPath1.quadTo(mX1, mY1, (x + mX1) / 2, (y + mY1) / 2); mX1 = x; mY1 = y; } break; case 1: if (Math.abs(x - mX2) >= Scribble.TOUCH_TOLERANCE || Math.abs(y - mY2) >= Scribble.TOUCH_TOLERANCE) { mPath2.quadTo(mX2, mY2, (x + mX2) / 2, (y + mY2) / 2); mX2 = x; mY2 = y; } break; } } /** * Finishes the path. * * @see android.view.View#onTouchEvent(MotionEvent) * @see android.view.MotionEvent#ACTION_UP */ private void touch_up(int index) { switch(index){ case 0: mPath1.lineTo(mX1, mY1); // commit the path to our offscreen Scribble.mCanvas.drawPath(mPath1, mPaint); // kill this so we don't double draw mPath1.reset(); break; case 1: mPath2.lineTo(mX2, mY2); // commit the path to our offscreen Scribble.mCanvas.drawPath(mPath2, mPaint); // kill this so we don't double draw mPath2.reset(); break; } } @Override public boolean onTouchEvent(final MotionEvent event) { final int action = event.getAction(); switch (action & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_DOWN:{ final float x = event.getX(); final float y = event.getY(); // Save the ID of this pointer mActivePointerId = event.getPointerId(0); touch_start(x, y, 0); invalidate(); break; } case MotionEvent.ACTION_POINTER_DOWN:{ final int pointerIndex2 = event.findPointerIndex(event.getPointerCount() - 1); final float x = event.getX(pointerIndex2); final float y = event.getY(pointerIndex2); touch_start(x, y, 1); invalidate(); break; } case MotionEvent.ACTION_MOVE:{ final int count = event.getPointerCount(); // Find the index of the active pointer and fetch its position final int pointerIndex = event.findPointerIndex(mActivePointerId); final int pointerIndex2 = event.findPointerIndex(count - 1); final float x = event.getX(pointerIndex); final float y = event.getY(pointerIndex); touch_move(x, y, pointerIndex); if(count > 1 && pointerIndex2 != pointerIndex){ final float x2 = event.getX(pointerIndex2); final float y2= event.getY(pointerIndex2); touch_move(x2, y2, pointerIndex2); } invalidate(); break; } case MotionEvent.ACTION_UP: mActivePointerId = INVALID_POINTER_ID; // System.out.println("AetherVoice ++++++++++++++++++++++++++ ACTION_UP"); if(event.getPointerCount() < 2) touch_up(0); break; case MotionEvent.ACTION_CANCEL: mActivePointerId = INVALID_POINTER_ID; // System.out.println("AetherVoice +++++++++++++++++ ACTION_CANCEL"); break; case MotionEvent.ACTION_POINTER_UP: // System.out.println("AetherVoice ++++++++++++++++++ ACTION_POINTER_UP"); final int pointerIndex = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; final int pointerId = event.getPointerId(pointerIndex); if (pointerId == mActivePointerId) { // This was our active pointer going up. Choose a new // active pointer and adjust accordingly. final int newPointerIndex = pointerIndex == 0 ? 1 : 0; mActivePointerId = event.getPointerId(newPointerIndex); touch_up(0); mPath1.moveTo(mX2, mY2); mX1 = mX2; mY1 = mY2; } // System.out.println("AetherVoice ++++++++++++++++++++++ mActivePointerId "+mActivePointerId); touch_up(1); break; } //invalidate(); return true; } }
Bezier interpolate
//package org.ametro.util; import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; class Algorithms { private final static int LEFT = 1; private final static int RIGHT = 2; private final static int TOP = 4; private final static int BOTTOM = 8; public static class Solve2x2 { float __determinant = 0; public PointF solve(float _a11, float _a12, float _a21, float _a22, float _b1, float _b2, float zeroTolerance, boolean _resolve) { if (!_resolve) { __determinant = _a11 * _a22 - _a12 * _a21; } // exercise - dispatch an event if the determinant is near zero if (__determinant > zeroTolerance) { float x = (_a22 * _b1 - _a12 * _b2) / __determinant; float y = (_a11 * _b2 - _a21 * _b1) / __determinant; return new PointF(x, y); } return null; } } public static class QBezierControls { public final float x0; public final float y0; public final float x1; public final float y1; public QBezierControls(float newX0, float newY0, float newX1, float newY1) { super(); x0 = newX0; y0 = newY0; x1 = newX1; y1 = newY1; } } public static float calculateDistance(Point p0, Point p1) { int dx = p0.x - p1.x; int dy = p0.y - p1.y; return (float) Math.sqrt(dx * dx + dy * dy); } public static float calculateAngle(float x0, float y0, float x, float y) { float angle = (float) (Math.atan((y - y0) / (x - x0)) / Math.PI * 180); float dx = x - x0; float dy = y - y0; if (angle > 0) { if (dx < 0 && dy < 0) { angle += 180; } } else if (angle < 0) { if (dx < 0 && dy > 0) { angle += 180; } else { angle += 360; } } else { if (dx < 0) { angle = 180; } } return angle; } public static PointF interpolateQuadBezier(Point p0, Point p1, Point p2) { // compute t-value using chord-length parameterization float dx = p1.x - p0.x; float dy = p1.y - p0.y; float d1 = (float) Math.sqrt(dx * dx + dy * dy); float d = d1; dx = p2.x - p1.x; dy = p2.y - p1.y; d += (float) Math.sqrt(dx * dx + dy * dy); float t = d1 / d; float t1 = 1.0f - t; float tSq = t * t; float denom = 2.0f * t * t1; PointF p = new PointF(); p.x = (p1.x - t1 * t1 * p0.x - tSq * p2.x) / denom; p.y = (p1.y - t1 * t1 * p0.y - tSq * p2.y) / denom; return p; } public static QBezierControls interpolateCubeBezierSmooth(Point p0, Point p1, Point p2, Point p3, float smoothFactor) { // Assume we need to calculate the control // points between (x1,y1) and (x2,y2). // Then x0,y0 - the previous vertex, // x3,y3 - the next one. float x0 = p0.x; float y0 = p0.y; float x1 = p1.x; float y1 = p1.y; float x2 = p2.x; float y2 = p2.y; float x3 = p3.x; float y3 = p3.y; float xc1 = (x0 + x1) / 2.0f; float yc1 = (y0 + y1) / 2.0f; float xc2 = (x1 + x2) / 2.0f; float yc2 = (y1 + y2) / 2.0f; float xc3 = (x2 + x3) / 2.0f; float yc3 = (y2 + y3) / 2.0f; float len1 = (float) Math.sqrt((x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0)); float len2 = (float) Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)); float len3 = (float) Math.sqrt((x3 - x2) * (x3 - x2) + (y3 - y2) * (y3 - y2)); float k1 = len1 / (len1 + len2); float k2 = len2 / (len2 + len3); float xm1 = xc1 + (xc2 - xc1) * k1; float ym1 = yc1 + (yc2 - yc1) * k1; float xm2 = xc2 + (xc3 - xc2) * k2; float ym2 = yc2 + (yc3 - yc2) * k2; float ctrl1_x = xm1 + (xc2 - xm1) * smoothFactor + x1 - xm1; float ctrl1_y = ym1 + (yc2 - ym1) * smoothFactor + y1 - ym1; float ctrl2_x = xm2 + (xc2 - xm2) * smoothFactor + x2 - xm2; float ctrl2_y = ym2 + (yc2 - ym2) * smoothFactor + y2 - ym2; return new QBezierControls(ctrl1_x, ctrl1_y, ctrl2_x, ctrl2_y); } public static int vcode(Rect r, Point p) { return (((p.x < r.left) ? LEFT : 0) + ((p.x > r.right) ? RIGHT : 0) + ((p.y < r.top) ? TOP : 0) + ((p.y > r.bottom) ? BOTTOM : 0)); } public static boolean clipCohenSutherland(Rect r, Point a, Point b) { a = new Point(a); b = new Point(b); int code_a, code_b, code; Point c; code_a = vcode(r, a); code_b = vcode(r, b); while (code_a != 0 || code_b != 0) { if ((code_a & code_b) != 0) return false; if (code_a != 0) { code = code_a; c = a; } else { code = code_b; c = b; } if ((code & LEFT) != 0) { c.y += (a.y - b.y) * (r.left - c.x) / (a.x - b.x); c.x = r.left; } else if ((code & RIGHT) != 0) { c.y += (a.y - b.y) * (r.right - c.x) / (a.x - b.x); c.x = r.right; } if ((code & TOP) != 0) { c.x += (a.x - b.x) * (r.top - c.y) / (a.y - b.y); c.y = r.top; } else if ((code & BOTTOM) != 0) { c.x += (a.x - b.x) * (r.bottom - c.y) / (a.y - b.y); c.y = r.bottom; } if (code == code_a) code_a = vcode(r, a); else code_b = vcode(r, b); } return true; } public static QBezierControls interpolateCubicBezierControl(Point p0, Point p1, Point p2, Point p3) { return interpolateCubeBezierSmooth(p0, p1, p2, p3, 1.0f); // int __p0X = p0.x; // int __p0Y = p0.y; // int __p3X = p3.x; // int __p3Y = p3.y; // // // currently, this method auto-parameterizes the curve using chord-length parameterization. // // A future version might allow inputting the two t-values, but this is more // // user-friendly (what an over-used term :) As an exercise, try uniform parameterization - t1 = 13/ and 52 = 2/3. // int deltaX = p1.x - p0.x; // int deltaY = p1.y - p0.y; // float d1 = (float)Math.sqrt(deltaX*deltaX + deltaY*deltaY); // // deltaX = p2.x - p1.x; // deltaY = p2.y - p1.y; // float d2 = (float) Math.sqrt(deltaX*deltaX + deltaY*deltaY); // // deltaX = p3.x - p2.x; // deltaY = p3.y - p2.y; // float d3 = (float)Math.sqrt(deltaX*deltaX + deltaY*deltaY); // // float d = d1 + d2 + d3; // float __t1 = d1/d; // float __t2 = (d1+d2)/d; // // // there are four unknowns (x- and y-coords for P1 and P2), which are solved as two separate sets of two equations in two unknowns // float t12 = __t1*__t1; // float t13 = __t1*t12; // // float t22 = __t2*__t2; // float t23 = __t2*t22; // // // x-coordinates of P1 and P2 (t = t1 and t2) - exercise: eliminate redudant // // computations in these equations // float a11 = 3*t13 - 6*t12 + 3*__t1; // float a12 = -3*t13 + 3*t12; // float a21 = 3*t23 - 6*t22 + 3*__t2; // float a22 = -3*t23 + 3*t22; // // float b1 = -t13*__p3X + __p0X*(t13 - 3*t12 + 3*__t1 -1) + p1.x; // float b2 = -t23*__p3X + __p0X*(t23 - 3*t22 + 3*__t2 -1) + p2.x; // // Solve2x2 s = new Solve2x2(); // PointF p = s.solve(a11, a12, a21, a22, b1, b2, 0, false); // // float __p1X = p.x; // float __p2X = p.y; // // // y-coordinates of P1 and P2 (t = t1 and t2) // b1 = -t13*__p3Y + __p0Y*(t13 - 3*t12 + 3*__t1 -1) + p1.y; // b2 = -t23*__p3Y + __p0Y*(t23 - 3*t22 + 3*__t2 -1) + p2.y; // // // resolving with same coefficients, but new RHS // p = s.solve(a11, a12, a21, a22, b1, b2, ZERO_TOLERANCE, true); // float __p1Y = p.x; // float __p2Y = p.y; // // return new QBezierControls(__p1X, __p1Y, __p2X, __p2Y); } }