Draw outline from image - android

I wanted to draw a contour around the picture by drawing and expanding a second picture in the Background, but I wasn't very successful, how can I draw a regular stroke?
The contour I drew:
The contour I want to draw:
My Code;
private Bitmap ContourBitmap() {
int strokeWidth = 8;
Bitmap originalBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.flower_icon);
Bitmap newStrokedBitmap = Bitmap.createBitmap(originalBitmap.getWidth() + 2 * strokeWidth,
originalBitmap.getHeight() + 2 * strokeWidth, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(newStrokedBitmap);
float scaleX = (originalBitmap.getWidth() + 2.0f * strokeWidth) / originalBitmap.getWidth();
float scaleY = (originalBitmap.getHeight() + 2.0f * strokeWidth) / originalBitmap.getHeight();
Matrix matrix = new Matrix();
matrix.setScale(scaleX, scaleY);
canvas.drawBitmap(originalBitmap, matrix, null);
canvas.drawColor(Color.WHITE, PorterDuff.Mode.SRC_ATOP); //Color.WHITE is stroke color
canvas.drawBitmap(originalBitmap, strokeWidth, strokeWidth, null);
}

I think you are on the right track...
Here is a strategy, instead of scaling just draw the same original picture a few times each time slightly offset, see sample below:
var canvas = document.getElementById('canvas')
var ctx = canvas.getContext('2d')
var img = new Image;
img.onload = draw;
img.src = "http://i.stack.imgur.com/UFBxY.png";
var s = 10, // thickness scale
x = 15, // final position
y = 15;
function draw() {
ctx.globalAlpha = 0.2
ctx.filter = 'brightness(0%)'
for (i = 0; i < 360; i++)
ctx.drawImage(img, x + Math.sin(i) * s, y + Math.cos(i) * s);
ctx.globalAlpha = 1
ctx.filter = 'none'
ctx.drawImage(img, x, y);
}
<canvas id=canvas width=350 height=600></canvas>
I applied a filter = 'brightness(0%)' but there are many more you can apply:
https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/filter
I'm using HTML canvas but that same idea should "translate" nicely to an android canvas.

Related

How to draw dashed border with padding around bitmap android?

I would like to draw stroke around bitmap. getting left and right side point of bitmap and drawing line using path and points.
got border around bitmap using below code and set it below imageview. but when i apply padding it want draw border with appropriate padding.
private suspend fun drawBorder(
src: Bitmap,
colorToReplace: Int,
): Bitmap? {
return kotlin.runCatching {
withContext(IO) {
val width = src.width
val height = src.height
val pixels = IntArray(width * height)
// get pixel array from source
src.getPixels(pixels, 0, width, 0, 0, width , height )
val bmOut = Bitmap.createBitmap(width, height, src.config)
val canvas = Canvas(bmOut)
val paint = Paint().apply {
isAntiAlias = true
isDither = true
color = colorCode
style = Paint.Style.STROKE
strokeWidth = radius
strokeJoin = Paint.Join.ROUND
strokeCap = Paint.Cap.ROUND
}
if (isDashed) {
paint.pathEffect = DashPathEffect(floatArrayOf(50f, radius * 2), 0f)
}
var moved = false
val path = Path()
var pixel = 0
// iteration through pixels
for (y in height - 1 downTo 0) {
for (x in width - 1 downTo 0) {
val index = y * width + x
pixel = pixels[index]
if (pixel != colorToReplace) {
if (!moved) {
moved = true
path.moveTo(x.toFloat(), y.toFloat())
}
path.lineTo(if(isPadded) x.toFloat() + 20 else x.toFloat(), y.toFloat())
break
}
} //x
} //y
for (y in 0 until height) {
for (x in 0 until width) {
val index = y * width + x
pixel = pixels[index]
if (pixel != colorToReplace) {
path.lineTo(if(isPadded) x.toFloat() - 20 else x.toFloat(), if(isPadded) y.toFloat() - 20 else y.toFloat())
break
}
} //x
} //y
canvas.drawPath(path, paint)
bmOut
}
}.getOrNull()
}
Now i would like to draw dashed with padding like below image.
If anyone have idea please let me know. Thanks.
this is a function I used for the same, it could help you
private Bitmap addWhiteBorder(Bitmap bmp, int borderSize) {
Bitmap bmpWithBorder = Bitmap.createBitmap(bmp.getWidth() + borderSize * 2, bmp.getHeight() + borderSize * 2, bmp.getConfig());
Canvas canvas = new Canvas(bmpWithBorder);
canvas.drawColor(Color.WHITE);
canvas.drawBitmap(bmp, borderSize, borderSize, null);
return bmpWithBorder;
}
Basically, it creates a new Bitmap adding 2 * border size to each dimension, and then paints the original Bitmap over it, offsetting it with border size.
You make your bitmap bigger than the one you are adding to it and then fill the canvas with the background you want. If you need to add other effects you can look into the canvas options for clipping the rect and adding rounded corners and such.

android canvas draw text in the triangle

In this Image I want text to totally be in the triangle with CYAN color.
I have created my own ImageView:
public class BookImageView extends android.support.v7.widget.AppCompatImageView {
private static final Float DISCOUNT_SIDE_SIZE = 0.33333F;
private Bitmap bitmap;
private Paint drawPaint = new Paint();
private Paint trianglePaint = new Paint();
{
trianglePaint.setColor(Constants.DISCOUNT_COLOR);
trianglePaint.setStyle(Paint.Style.FILL);
trianglePaint.setShadowLayer(10.0f, 10.0f, 10.0f, Color.parseColor("#7f000000"));
trianglePaint.setAntiAlias(true);
drawPaint.setColor(Color.BLACK);
drawPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD));
drawPaint.setShadowLayer(1f, 0f, 1f, Color.BLACK);
}
// Constractors ...
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (bitmap != null) {
Bitmap tempBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.RGB_565);
Canvas tempCanvas = new Canvas(tempBitmap);
tempCanvas.drawBitmap(bitmap, 0, 0, null);
Path path = new Path();
path.setFillType(Path.FillType.EVEN_ODD);
float size = bitmap.getWidth() * DISCOUNT_SIDE_SIZE;
path.lineTo(size, 0);
path.lineTo(0, size);
path.lineTo(0, 0);
path.close();
tempCanvas.drawPath(path, trianglePaint);
float scale = getResources().getDisplayMetrics().density;
drawPaint.setTextSize((int) (14 * scale));
Rect textBounds = new Rect();
drawPaint.getTextBounds("50%", 0, "50%".length(), textBounds);
int x = (int) (size / 2) - textBounds.width() / 2;
int y = (int) (size / 2) - textBounds.height() / 2;
tempCanvas.save();
tempCanvas.rotate(-45, x, y);
tempCanvas.drawText("50%", x, y, drawPaint);
tempCanvas.restore();
setImageDrawable(new BitmapDrawable(getContext().getResources(), tempBitmap));
}
}
#Override
public void setImageBitmap(Bitmap bitmap) {
this.bitmap = bitmap;
invalidate();
}
}
what can I do to solve this problem ?
You can try something like this
1) Measure the width of your text
Use measureText
2) From the point you are drawing calculate the width remaining to draw
3) Now depending on the use case you can curtail the length of text or scale the text as needed
int textWidthRequired = (int) drawPaint.measureText(textToDraw);
int widthRemainingToDraw = totalWidth/2 - textDrawX;
if(textWidthRequired > widthRemainingToDraw){
//handling
}
// draw text
tempCanvas.drawText(textToDraw,textDrawX, textDrawY, drawPaint);
Depending on how high up you want the text to be, you can use properties of similar triangles to first determine the maximum width of the text. In your case, size = the triangle's base, and size = the triangle's height/altitude. Let's define two variables:
Correction: The altitude won't be equal to the base. You'd need to calculate the altitude in order to use the below solution.
float triangleBase = size; // triangle base
float triangleAltitude = size; // Calculate this.
Let's say we want the text to be halfway up the center of the triangle:
float textYHeight = triangleHeight/2;
We figure out the width of the triangle at this point by using the following formula since the sides of similar triangles are proportional:
baseOfTriangleA/baseOfTriangleB = altitudeOfTriangleA/altitudeOfTriangleB;
float triangleWidthAtTextYLocation = (textYHeight * triangleBase)/triangleAltitude;
Now that we know what the width of the triangle is at this location, we can just iterate through different text scales until the text width is less than the value triangleWidthAtTextYlocation.
float scale = getResources().getDisplayMetrics().density;
int scaleFactor = 0;
drawPaint.setTextSize((int) (scaleFactor * scale));
Rect textBounds = new Rect();
drawPaint.getTextBounds("50%", 0, "50%".length(), textBounds);
while(textBounds.length < triangleWidthAtTextYLocation){
// Re-measure the text until it exceeds the width
scaleFactor++;
drawPaint.setTextSize((int) (scaleFactor * scale));
drawPaint.getTextBounds("50%", 0, "50%".length(), textBounds);
}
// Once we know the scaleFactor that puts it over the width of the triangle
// at that location, we reduce it by 1 to be just under that width:
scaleFactor = Maths.abs(scaleFactor - 1);
// final text size:
drawPaint.setTextSize((int) (scaleFactor * scale));

Overlay Shapes in XML Drawable

Is there a way to overlay a bitmap onto a shape in an XML drawable? I want to put an icon on top of an oval shape.
public static Bitmap overlayBitmapToCenter(Bitmap bitmap1, Bitmap bitmap2) {
int bitmap1Width = bitmap1.getWidth();
int bitmap1Height = bitmap1.getHeight();
int bitmap2Width = bitmap2.getWidth();
int bitmap2Height = bitmap2.getHeight();
float marginLeft = (float) (bitmap1Width * 0.5 - bitmap2Width * 0.5);
float marginTop = (float) (bitmap1Height * 0.5 - bitmap2Height * 0.5);
Bitmap overlayBitmap = Bitmap.createBitmap(bitmap1Width, bitmap1Height, bitmap1.getConfig());
Canvas canvas = new Canvas(overlayBitmap);
canvas.drawBitmap(bitmap1, new Matrix(), null);
canvas.drawBitmap(bitmap2, marginLeft, marginTop, null);
return overlayBitmap;
}

Draw rectangles on circle formula

I'm trying to draw the spectrum of an audio file on a circle. Like this:
So on the circle I just want rectangles drawn like you see on the image.
I've got this code:
public void onRender(Canvas canvas, FFTData data, Rect rect) {
canvas.drawCircle(rect.width()/2, rect.height()/2, 200, mPaint);
for (int i = 0; i < data.bytes.length / mDivisions; i++) {
byte rfk = data.bytes[mDivisions * i];
byte ifk = data.bytes[mDivisions * i + 1];
float magnitude = (rfk * rfk + ifk * ifk);
int dbValue = (int) (10 * Math.log10(magnitude));
}
}
Where FFTData is the Fast Fourier Transformation data that Android gives me. Now in my dbValue I got the strength of the signal. mDivisions is how much bars I want. Currently set on 16 because I don't know how much I can set on the circle.
I'm stuck on how I can draw the rectangle with his center on the circle line... So I want a rectangle whose height is based on the dbValue so that I get high and low rectangles. And the center must be placed on my circle line.
Can someone help me on this math formula?
Run a loop over all 360 degrees of the circle (at wanted step), and, for each point, convert Polar (this angle and the radius of the circle) coordinates into Cartesian, as described here, for instance. This way you get the location of the centre of your rectangle.
Translate the system of the coordinates, making origin to be at the wanted point on the circle line and then rotate by the circle angle at that point.
Alternatively, you can build a trapezoid by getting corners at angle +- some offset and radius +- some offset (proportional to your value to plot). It will have shorter inner edge and longer outer edge. Such trapezoids may look better if painted side by side.
i think all you have needed is a pencil and a paper and a little math and also some free time to play :-)
public class MainActivity extends Activity {
ImageView drawingImageView;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
drawingImageView = (ImageView) this.findViewById(R.id.DrawingImageView);
Paint paint;
paint = new Paint();
paint.setColor(Color.GREEN);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(16);
final Bitmap bitmap = Bitmap.createBitmap((int) getWindowManager()
.getDefaultDisplay().getWidth(), (int) getWindowManager()
.getDefaultDisplay().getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
int centerX =400;
int centerY =400;
int R = 200;
canvas.drawCircle(centerX, centerY, R, paint);
int h = 100;
paint.setColor(Color.RED);
Path p = new Path();
p.moveTo(centerX + R - h/2, centerY);
p.lineTo(centerX + R + h/2, centerY);
canvas.drawPath(p, paint);
p = mySpectrumDrawer(centerX,centerY,R,h,15);
canvas.drawPath(p, paint);
h = 50;
p = mySpectrumDrawer(centerX,centerY,R,h,30);
canvas.drawPath(p, paint);
h = 60;
p = mySpectrumDrawer(centerX,centerY,R,h,60);
canvas.drawPath(p, paint);
h = 80;
p = mySpectrumDrawer(centerX,centerY,R,h,90);
canvas.drawPath(p, paint);
drawingImageView.setImageBitmap(bitmap);
}
private Path mySpectrumDrawer(int centerX, int centerY,int R,int height, int angel){
Path p = new Path();
int dX = (int) (R*(Math.cos(Math.toRadians(angel))));
int dY = (int) (R*(Math.sin(Math.toRadians(angel))));
int dhx = (int) (height/2*(Math.cos(Math.toRadians(angel))));
int dhy = (int) (height/2*(Math.sin(Math.toRadians(angel))));
p.moveTo(centerX + dX - dhx , centerY - dY + dhy);
p.lineTo(centerX + dX + dhx , centerY - dY - dhy);
return p;
}
}

How to draw a triangle, a star, a square or a heart on the canvas?

I am able to draw a circle and a rectangle on canvas by using
path.addCircle()
and
path.addRect().
And now I am wondering how to draw a triangle or a star or a square or a heart?
For future direct answer seekers, I have drawn an almost symmetric star using canvas, as shown in the image:
The main tool is using Paths.
Assuming you have setup:
Paint paint = new Paint();
paint.setColor(Color.WHITE);
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.STROKE);
Path path = new Path();
Then in you onDraw you can use the path like I do below. It will scale properly to any sizes canvas
#Override
protected void onDraw(Canvas canvas) {
float mid = getWidth() / 2;
float min = Math.min(getWidth(), getHeight());
float fat = min / 17;
float half = min / 2;
float rad = half - fat;
mid = mid - half;
paint.setStrokeWidth(fat);
paint.setStyle(Paint.Style.STROKE);
canvas.drawCircle(mid + half, half, rad, paint);
path.reset();
paint.setStyle(Paint.Style.FILL);
// top left
path.moveTo(mid + half * 0.5f, half * 0.84f);
// top right
path.lineTo(mid + half * 1.5f, half * 0.84f);
// bottom left
path.lineTo(mid + half * 0.68f, half * 1.45f);
// top tip
path.lineTo(mid + half * 1.0f, half * 0.5f);
// bottom right
path.lineTo(mid + half * 1.32f, half * 1.45f);
// top left
path.lineTo(mid + half * 0.5f, half * 0.84f);
path.close();
canvas.drawPath(path, paint);
super.onDraw(canvas);
}
For everybody that needs a heart shape:
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Path;
import android.view.View;
public class Heart extends View {
private Path path;
private Paint paint;
public Heart(Context context) {
super(context);
path = new Path();
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// Fill the canvas with background color
canvas.drawColor(Color.WHITE);
paint.setShader(null);
float width = getContext().getResources().getDimension(R.dimen.heart_width);
float height = getContext().getResources().getDimension(R.dimen.heart_height);
// Starting point
path.moveTo(width / 2, height / 5);
// Upper left path
path.cubicTo(5 * width / 14, 0,
0, height / 15,
width / 28, 2 * height / 5);
// Lower left path
path.cubicTo(width / 14, 2 * height / 3,
3 * width / 7, 5 * height / 6,
width / 2, height);
// Lower right path
path.cubicTo(4 * width / 7, 5 * height / 6,
13 * width / 14, 2 * height / 3,
27 * width / 28, 2 * height / 5);
// Upper right path
path.cubicTo(width, height / 15,
9 * width / 14, 0,
width / 2, height / 5);
paint.setColor(Color.RED);
paint.setStyle(Style.FILL);
canvas.drawPath(path, paint);
}
}
Sorry for all the numbers but these worked best for me :) The result looks like this:
You have to find out the math behind that figures. The triangle and the star are quite easy to draw. Here is how you can draw a heart: http://www.mathematische-basteleien.de/heart.htm
To draw special paths you should create them by adding points, ellipses etc. The canvas supports a clipping mask of a specified path, so you can set the clipping mask of a heart, push the paths to the matrix, draw the content of the heart, and then pop it again.
That's what I'm doing to achieve a simulated 2D page curl effect on andriod: http://code.google.com/p/android-page-curl/
Hope this helps!
this method will return a path with the number of corners given inside a square of the given width. Add more parameters to handle small radius and such things.
private Path createStarBySize(float width, int steps) {
float halfWidth = width / 2.0F;
float bigRadius = halfWidth;
float radius = halfWidth / 2.0F;
float degreesPerStep = (float) Math.toRadians(360.0F / (float) steps);
float halfDegreesPerStep = degreesPerStep / 2.0F;
Path ret = new Path();
ret.setFillType(FillType.EVEN_ODD);
float max = (float) (2.0F* Math.PI);
ret.moveTo(width, halfWidth);
for (double step = 0; step < max; step += degreesPerStep) {
ret.lineTo((float)(halfWidth + bigRadius * Math.cos(step)), (float)(halfWidth + bigRadius * Math.sin(step)));
ret.lineTo((float)(halfWidth + radius * Math.cos(step + halfDegreesPerStep)), (float)(halfWidth + radius * Math.sin(step + halfDegreesPerStep)));
}
ret.close();
return ret;
}
If you need to draw a star inside a square, you can use the code below.
posX and posY are the coordinates for the upper left corner of the square that encloses the tips of the star (the square is not actually drawn).
size is the width and height of the square.
a is the top tip of the star. The path is created clockwise.
This is by no means a perfect solution, but it gets the job done very quickly.
public void drawStar(float posX, float posY, int size, Canvas canvas){
int hMargin = size/9;
int vMargin = size/4;
Point a = new Point((int) (posX + size/2), (int) posY);
Point b = new Point((int) (posX + size/2 + hMargin), (int) (posY + vMargin));
Point c = new Point((int) (posX + size), (int) (posY + vMargin));
Point d = new Point((int) (posX + size/2 + 2*hMargin), (int) (posY + size/2 + vMargin/2));
Point e = new Point((int) (posX + size/2 + 3*hMargin), (int) (posY + size));
Point f = new Point((int) (posX + size/2), (int) (posY + size - vMargin));
Point g = new Point((int) (posX + size/2 - 3*hMargin), (int) (posY + size));
Point h = new Point((int) (posX + size/2 - 2*hMargin), (int) (posY + size/2 + vMargin/2));
Point i = new Point((int) posX, (int) (posY + vMargin));
Point j = new Point((int) (posX + size/2 - hMargin), (int) (posY + vMargin));
Path path = new Path();
path.moveTo(a.x, a.y);
path.lineTo(b.x, b.y);
path.lineTo(c.x, c.y);
path.lineTo(d.x, d.y);
path.lineTo(e.x, e.y);
path.lineTo(f.x, f.y);
path.lineTo(f.x, f.y);
path.lineTo(g.x, g.y);
path.lineTo(h.x, h.y);
path.lineTo(i.x, i.y);
path.lineTo(j.x, j.y);
path.lineTo(a.x, a.y);
path.close();
canvas.drawPath(path, paint);
}
In addition to ellipse and rectangular you will need those two (as minimum):
drawLine(float startX, float startY, float stopX, float stopY, Paint paint)
drawLines(float[] pts, int offset, int count, Paint paint)
example:
How to draw a line in android
Documentation on Canvas: http://developer.android.com/reference/android/graphics/Canvas.html
Its very good to use instance of Shape class ))
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
HeartShape shape = new HeartShape();
ShapeDrawable shapeDrawable = new ShapeDrawable(shape);
shapeDrawable.setColorFilter(new LightingColorFilter(0, Color.BLUE));
LinearLayout linearLayout = new LinearLayout(this);
linearLayout.setLayoutParams(new LinearLayout.LayoutParams(350 * 3, 350 * 3));
linearLayout.setBackground(shapeDrawable);
setContentView(linearLayout);
}
Create a shape class which was render nice Heart
public class HeartShape extends Shape {
private final int INVALID_SIZE = -1;
private Path mPath = new Path();
private RectF mRectF = new RectF();
private float mOldWidth = INVALID_SIZE;
private float mOldHeight = INVALID_SIZE;
private float mScaleX, mScaleY;
public HeartShape() {
}
#Override
public void draw(Canvas canvas, Paint paint) {
canvas.save();
canvas.scale(mScaleX, mScaleY);
float width = mRectF.width();
float height = mRectF.height();
float halfWidth = width/2;
float halfHeight = height/2;
float stdDestX = 5 * width / 14;
float stdDestY = 2 * height / 3;
PointF point1 = new PointF(stdDestX, 0);
PointF point2 = new PointF(0, height / 15);
PointF point3 = new PointF(stdDestX / 5, stdDestY);
PointF point4 = new PointF(stdDestX, stdDestY);
// Starting point
mPath.moveTo(halfWidth, height / 5);
mPath.cubicTo(point1.x, point1.y, point2.x, point2.y, width / 28, 2 * height / 5);
mPath.cubicTo(point3.x, point3.y, point4.x, point4.y, halfWidth, height);
canvas.drawPath(mPath, paint);
canvas.scale(-mScaleX, mScaleY, halfWidth, halfHeight);
canvas.drawPath(mPath, paint);
canvas.restore();
}
#Override
protected void onResize(float width, float height) {
mOldWidth = mOldWidth == INVALID_SIZE ? width : Math.max(1, mOldWidth);
mOldHeight = mOldHeight == INVALID_SIZE ? height : Math.max(1, mOldHeight);
width = Math.max(1, width);
height = Math.max(1, height);
mScaleX = width / mOldWidth;
mScaleY = height / mOldHeight;
mOldWidth = width;
mOldHeight = height;
mRectF.set(0, 0, width, height);
}
#Override
public void getOutline(#NonNull Outline outline) {
// HeartShape not supported outlines
}
#Override
public HeartShape clone() throws CloneNotSupportedException {
HeartShape shape = (HeartShape) super.clone();
shape.mPath = new Path(mPath);
return shape;
}
}
Well if you want to draw only the shapes you specify I recommend first creat a class for each shape and make each shape implement draw() method where you can pass canvas and paint object.
For example to create multiple stars, first create a class "Star" and implement the logic there.
class Star(
var cx: Float = 0f,
var cy: Float = 0f,
var radius: Float = 0f,
var angle: Float = 0f,
var color: Int = Color.WHITE,
var numberOfSpikes: Int = 5,
var depth: Float = 0.4f
) {
val path: Path = Path()
val point: PointF = PointF()
fun init() {
path.rewind()
var totalAngle = 0f
for (i in 0 until numberOfSpikes * 2) {
val x = cx
var y = cy
y -= if (i % 2 != 0) (radius * depth)
else (radius * 1f)
StaticMethods.rotate(cx, cy, x, y, totalAngle, false, point)
totalAngle += 360f / (numberOfSpikes * 2)
if (i == 0) {
path.moveTo(point.x, point.y)
} else {
path.lineTo(point.x, point.y)
}
}
}
fun draw(canvas: Canvas, paint: Paint) {
paint.apply {
style = Paint.Style.FILL
color = this#Star.color
}
canvas.drawPath(path, paint)
}
}
object StaticMethods {
/**
* Rotate a point around a center with given angle
* #param cx rotary center point x coordinate
* #param cy rotary center point y coordinate
* #param x x coordinate of the point that will be rotated
* #param y y coordinate of the point that will be rotated
* #param angle angle of rotation in degrees
* #param anticlockWise rotate clockwise or anti-clockwise
* #param resultPoint object where the result rotational point will be stored
*/
fun rotate(cx: Float, cy: Float, x: Float, y: Float, angle: Float, anticlockWise: Boolean = false, resultPoint: PointF = PointF()): PointF {
if (angle == 0f) {
resultPoint.x = x
resultPoint.y = y
return resultPoint
}
val radians = if (anticlockWise) {
(Math.PI / 180) * angle
} else {
(Math.PI / -180) * angle
}
val cos = Math.cos(radians)
val sin = Math.sin(radians)
val nx = (cos * (x - cx)) + (sin * (y - cy)) + cx
val ny = (cos * (y - cy)) - (sin * (x - cx)) + cy
resultPoint.x = nx.toFloat()
resultPoint.y = ny.toFloat()
return resultPoint
}
/**
* Inline function that is called, when the final measurement is made and
* the view is about to be draw.
*/
inline fun View.afterMeasured(crossinline function: View.() -> Unit) {
viewTreeObserver.addOnGlobalLayoutListener(object :
ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
if (measuredWidth > 0 && measuredHeight > 0) {
viewTreeObserver.removeOnGlobalLayoutListener(this)
function()
}
}
})
}
}
And then create a custom view where you can draw your shapes, below is the code that creates 100 random stars and draws them on canvas.
class StarView : View {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
val stars: ArrayList<Helper.Star> = arrayListOf()
val paint: Paint = Paint().apply {
isAntiAlias = true
}
init {
// after the view is measured and we have the width and height
afterMeasured {
// create 100 stars
for (i in 0 until 100) {
val star = Helper.Star(
cx = width * Math.random().toFloat(),
cy = height * Math.random().toFloat(),
radius = width * 0.1f * Math.random().toFloat(),
)
star.init()
stars.add(star)
}
}
}
override fun onDraw(canvas: Canvas) {
stars.forEach {
it.draw(canvas, paint)
}
}
}
Here is the result:
If you want to create a lot of different complex shapes you can use this library. Where you can pass svg shapes using the Path Data as a string. By first creating complex shapes using any SVG vector editor such as
Microsoft Expression Design
Adobe Illustrator
Inkscape

Categories

Resources