How to draw only a part of arc in canvas - android

I'm developing an analog clock widget and the dial of clock is an image.
I want to draw an arc segment like in the image(as shown in orange).
paint.setColor(Color.GREEN);
paint.setStrokeWidth(20);
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.STROKE);
canvas.drawCircle(100, 100, 200, paint);
I tried with drawcircle and also with drawArc but could not proceed since i want only a part of arc and not a complete arc. Any ideas ?

I made this class hope it help you, all variable are in spanish but its quite simple,
the constructor SemiCirculo use as parameters the rgb for the semicircle and the resolution (number of triangles for your semicircle)
the CalcularPuntosPorcentaje method use as parameters the center of the circle, the starting angle, the widht of the angle, and the radio.
the method ImprimeCirculo use the canvas as parameter, it is used to draw the semicircle once it has allready been calcultaed the points of the semicircle with the previus mentioned method.
the CalcularPuntosPorcentaje method is similar to CalcularPuntosPorcentaje, but insted of the starting and the widht angle parameters it use a % from 0 to 100
finaly the SetOffset and SetColor are used to change the default starting poing o reference for the angle and the color of the semicircle
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
public class SemiCirculo {
private Path circulo;
private Paint color;
private float px, py, radio, anguloI, anchoa,offset;
private int r, g, b;
private int resolucion;
private float puntox[],puntoy[];
public SemiCirculo(int r1, int g1, int b1, int resolucion1) {
this.offset = 0;
this.color = new Paint();
this.r = r1;
this.g = g1;
this.b = b1;
this.color.setColor(Color.rgb(r, g, b));
color.setAntiAlias(true);
circulo = new Path();
this.resolucion = resolucion1;
this.puntox = new float[this.resolucion];
this.puntoy = new float[this.resolucion];
this.anguloI = 0;
this.anchoa = 1;
}
public void SetOffset(float off) {
this.offset = off;
}
public void SetColor(int r1,int g1, int b1){
this.r=r1;
this.g=g1;
this.b=b1;
this.color.setColor(Color.rgb(r, g, b));
}
public void CalcularPuntosPorcentaje(float px1, float py1,
float porcentaje, float radio1) {
this.anguloI = 0 + this.offset;
this.px = px1;
this.py = py1;
this.radio = radio1;
this.anguloI = 0;
this.anchoa = porcentaje / 100 * 360;
this.CalcularPuntos(px, py, anguloI, anchoa, radio);
}
public void CalcularPuntos(float px1, float py1, float anguloI1,
float anchoangulo, float radio1) {
this.anguloI = anguloI1 + this.offset;
this.anchoa = anchoangulo;
this.px = px1;
this.py = py1;
this.radio = radio1 ;
float angulo = 360 - this.anguloI - this.anchoa;
for (int i = 0; i < resolucion; i++) {
this.puntox[i] = this.px - (float) Math.sin(Math.toRadians(angulo))
* this.radio;
this.puntoy[i] = this.py - (float) Math.cos(Math.toRadians(angulo))
* this.radio;
angulo = (360 - this.anguloI - this.anchoa)
+ ((this.anchoa / (float) (this.resolucion)) * (i + 2));
}
this.circulo.reset();
this.circulo.moveTo(this.px, this.py);
for (int i = 0; i < resolucion; i++) {
this.circulo.lineTo(this.puntox[i], this.puntoy[i]);
}
}
public void ImprimeCirculo(Canvas canvas) {
canvas.drawPath(this.circulo, this.color);
}
}

You need to use this method:
canvas.drawArc(innerRect, -11.0f, 11.0f + 6.0f, true, paintx);
for docs see here:
http://developer.android.com/reference/android/graphics/Canvas.html#drawArc%28android.graphics.RectF,%20float,%20float,%20boolean,%20android.graphics.Paint%29
Be sure set the angle parameters correctly! And use float!
The first angle is the start of your arc and the second angle param is the sweep angle, ie how many degrees long should the angle be - measured clockwise.
Try it, it will work for sure. Just needs a little playing around :-)

Related

How to calculate color from RadialGradient

A while back I found this great color picker from Piotr Adams which I can not find on Git anymore but it's still on this page: https://www.programcreek.com/java-api-examples/index.php?source_dir=Random-Penis-master/app/src/main/java/com/osacky/penis/picker/ColorPicker.java
The main reason I use this color picker in my app is because I want to be able to place a pointer on the RadialGradient based on a color. This library calculates the position for a certain color, this means placing a picker on the correct location is very fast and easy.
The problem is I don't quite understand how it works. I now want to generate a RadialGradient with different colors. But the logic it uses does not work when I generate a RadialGradient with different colors.
Here is the code that generates the RadialGradient:
private Bitmap createColorWheelBitmap(int width, int height) {
Bitmap bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);
int colorCount = 12;
int colorAngleStep = 360 / 12;
int colors[] = new int[colorCount + 1];
float hsv[] = new float[]{0f, 1f, 1f};
for (int i = 0; i < colors.length; i++) {
hsv[0] = (i * colorAngleStep + 180) % 360;
colors[i] = Color.HSVToColor(hsv);
}
colors[colorCount] = colors[0];
SweepGradient sweepGradient = new SweepGradient(width / 2, height / 2, colors, null);
RadialGradient radialGradient = new RadialGradient(width / 2, height / 2, colorWheelRadius, 0xFFFFFFFF, 0x00FFFFFF, TileMode.CLAMP);
ComposeShader composeShader = new ComposeShader(sweepGradient, radialGradient, PorterDuff.Mode.SRC_OVER);
colorWheelPaint.setShader(composeShader);
Canvas canvas = new Canvas(bitmap);
canvas.drawCircle(width / 2, height / 2, colorWheelRadius, colorWheelPaint);
return bitmap;
}
The code for listening to changes of the picker, so this calculates the color based on a position:
#Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
int x = (int) event.getX();
int y = (int) event.getY();
int cx = x - getWidth() / 2;
int cy = y - getHeight() / 2;
double d = Math.sqrt(cx * cx + cy * cy);
if (d <= colorWheelRadius) {
colorHSV[0] = (float) (Math.toDegrees(Math.atan2(cy, cx)) + 180f);
colorHSV[1] = Math.max(0f, Math.min(1f, (float) (d / colorWheelRadius)));
selectedPointer.setColor(Color.HSVToColor(colorHSV));
notifyListeners();
invalidate();
}
return true;
case MotionEvent.ACTION_BUTTON_PRESS:
}
return super.onTouchEvent(event);
}
Finally the code that calculates the position based on a color:
// drawing color wheel pointer
float hueAngle = (float) Math.toRadians(colorHSV[0]);
int colorPointX = (int) (-Math.cos(hueAngle) * colorHSV[1] * colorWheelRadius) + centerX;
int colorPointY = (int) (-Math.sin(hueAngle) * colorHSV[1] * colorWheelRadius) + centerY;
float pointerRadius = 0.075f * colorWheelRadius;
int pointerX = (int) (colorPointX - pointerRadius / 2);
int pointerY = (int) (colorPointY - pointerRadius / 2);
colorPointerCoords.set(pointerX, pointerY, pointerX + pointerRadius, pointerY + pointerRadius);
canvas.drawOval(colorPointerCoords, colorPointerPaint);
So my question is how can I for example change the RadialGradient to only include 2 colors, without breaking the calculations of getting the color? Even an explanation on how this works would be great!
There is great tutorial here: http://tekeye.uk/android/examples/ui/android-color-picker-tutorial (not mine). I don't know much about the theory behind it either but you can use this code to calculate color based on position.
// Calculate channel based on 2 surrounding colors and p angle.
private int ave(int s, int d, float p) {
return s + java.lang.Math.round(p * (d - s));
}
// Calculate color based on drawn colors and angle based on x and y position.
private int interpColor(int colors[], float unit) {
if (unit <= 0) {
return colors[0];
}
if (unit >= 1) {
return colors[colors.length - 1];
}
// Adjust the angle (unit) based on how many colors there are in the list.
float p = unit * (colors.length - 1);
// Get starting color position in the array.
int i = (int)p;
p -= i;
// Now p is just the fractional part [0...1) and i is the index.
// Get two composite colors for calculations.
int c0 = colors[i];
int c1 = colors[i+1];
// Calculate color channels.
int a = ave(Color.alpha(c0), Color.alpha(c1), p);
int r = ave(Color.red(c0), Color.red(c1), p);
int g = ave(Color.green(c0), Color.green(c1), p);
int b = ave(Color.blue(c0), Color.blue(c1), p);
// And finally create the color from the channels.
return Color.argb(a, r, g, b);
}
You can call the interpreting function like this for example.
#Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX() - CENTER_X;
float y = event.getY() - CENTER_Y;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
// Calculate the angle based on x and y positions clicked.
float angle = (float)java.lang.Math.atan2(y, x);
// need to turn angle [-PI ... PI] into unit [0....1]
float unit = angle/(2*PI);
if (unit < 0) {
unit += 1;
}
// mColors is your list with colors so int[].
int color = interpColor(mColors, unit);
break;
}
}
I already tried it in my project and it works like a charm. So hope it helps you too. :)
EDIT:
Oh so my colors are set up like this.
mColors = intArrayOf(-0x10000, -0xff01, -0xffff01, -0xff0001, -0xff0100, -0x100, -0x10000)
So you can add/remove as many colors as you want and since the interpret functions calculates based on size of this array it should work.

Simple Music Visualisation with a Circle

Im trying to build a simple Music Visualisation App which just should resize a Circle. So if the Music Part which is currently playing is loud it should get bigger and if not it should get smaller.
To Visualize the Circle I just created a custom View Class which draws the circle in the onDraw Method.
To get the informations out of the current Audio, I found the Visualizer Class of Android and also used the setDataCaptureListener.
mVisualizer = new Visualizer(mMediaPlayer.getAudioSessionId());
mVisualizer.setCaptureSize(Visualizer.getCaptureSizeRange()[0]);
mVisualizer.setDataCaptureListener(
new Visualizer.OnDataCaptureListener() {
public void onWaveFormDataCapture(Visualizer visualizer,byte[] bytes, int samplingRate) {
mVisualizerView.updateVisualizer(bytes);
}
public void onFftDataCapture(Visualizer visualizer,byte[] bytes, int samplingRate) {
}
}, (int)(Visualizer.getMaxCaptureRate() / 1.5), true, false);
But my Problem is that I don't really know how I use the byte Array which is given back, to find out the music change in general (got louder or not ?).
I just tried to get the average of the array but this gives me completely bad results. The circle changed his size like it is on drugs. So I thought maybe the array has too many outlined/extreme values (which was true) so I calculated the median of the array. This gaved me better results but still isn't what I want. It's not very smooth and it's to complex. I always have to sort the array which is not really efficient. What am I thinking wrong ?
Im really a beginner in this AudioFX section and Im completely sorry If this is a dumb question and attempt of me.
Thank you for your help !
EDIT:
private float schwelle = 5000;
private float last = 0;
...
float summe = 0;
for (Byte currentByte: mBytes)
summe += currentByte;
if (summe > schwelle && summe > last)
{
last = summe; //make it bigger
}
else {
last -= 100; //make circle smaller
}
canvas.drawCircle(getWidth()/2,getHeight()/2,last / 100,mForePaint);
A really good git project is https://github.com/felixpalmer/android-visualizer.
I myself came up with this:(it's a lot simple than the git solution)
You can use the values of the array to draw the the waveform on the outline of a circle using trigonometry, and make the start radius of the circle bigger if the sum of the array is bigger than certain treshhold:
class StarWaveformRenderer implements Renderer {
private Paint p = new Paint();
private static final int BOOST_TRASH_HOLD = 10000;
private float stretchFade = 1; //circle fades after a prominent beat
#Override
public void render(Canvas canvas, byte[] data) {
if (data == null || data.length == 0)
return;
int centerX = canvas.getWidth() / 2;
int centerY = canvas.getHeight() / 2;
float stretch = stretchFade;
int sum = RenderUtils.sum(data);
p.setColor((p.getColor() + sum / 2)); //change color of circle
if (sum > BOOST_TRASH_HOLD) {//prominent beat
stretch = (float) Math.min(canvas.getWidth(), canvas.getHeight()) / Byte.MAX_VALUE / 3; //maximum
stretchFade = stretch;
}
double radDif = 2 * Math.PI / data.length; //the angle between each element of the array
double radPos = 0;
float lX = (float) Math.cos(radPos) * data[0] + centerX;
float lY = (float) Math.sin(radPos) * data[0] + centerY;
float cX;
float cY;
for (byte b : data) {
cX = (float) Math.cos(radPos) * b * stretch + centerX;
cY = (float) Math.sin(radPos) * b * stretch + centerY;//calculate position of outline, stretch indicates promince of the beat
canvas.drawLine(lX, lY, cX, cY, p);
lX = cX;
lY = cY;
radPos += radDif;
}
stretchFade = Math.max(1, stretchFade / 1.2f);//beat fades out
}
}
You can programm your own renderes and let the user select which one he wants to use. Just pass the array from onWaveformDataCapture to the onRender method.
Utils for analysing the waveform (the amplitude is stored kind of weird):
class RenderUtils {
private static final byte SHIFT = Byte.MAX_VALUE;
static int sum(byte[] data) {
int sum = 0;
for (byte b : data)
sum += b;
return sum;
}
static int toAmplitude(byte b) {
return b > 0 ? b + SHIFT : -b;//+127=high positive;+1=low positive;-127=low negative;-1=high negative
}
static float toAmplitude(float f) {
return f > 0 ? f + SHIFT : -f;//+127=high positive;+1=low positive;-127=low negative;-1=high negative
}
}

How tro draw fading path

How can I draw Path with fading (opacity or thicknes) line? Something like this.
I know there is LinearGradient shader for Paint, but it won't bend along the Path.
One possible solution might be to get points along the Path and just draw it by myself through the segments`. But I coouldn't find any method for that either.
I came up with the following code. The mos important thing is PathMeasure's getPosTan() method.
if (getGesturePath() != null) {
final short steps = 150;
final byte stepDistance = 5;
final byte maxTrailRadius = 15;
pathMeasure.setPath(getGesturePath(), false);
final float pathLength = pathMeasure.getLength();
for (short i = 1; i <= steps; i++) {
final float distance = pathLength - i * stepDistance;
if (distance >= 0) {
final float trailRadius = maxTrailRadius * (1 - (float) i / steps);
pathMeasure.getPosTan(distance, pathPos, null);
final float x = pathPos[0] + RandomUtils.nextFloat(0, 2 * trailRadius) - trailRadius;
final float y = pathPos[1] + RandomUtils.nextFloat(0, 2 * trailRadius) - trailRadius;
paint.setShader(new RadialGradient(
x,
y,
trailRadius > 0 ? trailRadius : Float.MIN_VALUE,
ColorUtils.setAlphaComponent(Color.GREEN, random.nextInt(0xff)),
Color.TRANSPARENT,
Shader.TileMode.CLAMP
));
canvas.drawCircle(x, y, trailRadius, paint);
}
}
}

Alignment issues in view

This question is in continuation to my previous doubt
Now I am trying to make a guage view. I am drawing a scale but the alignment is not proper and I am not able to figure out the problem. Here is my code:
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
w= canvas.getWidth();
h=canvas.getHeight();
DrawRange(canvas,innerRadius,outerRadius);
}
here innerRadius =250;
and outer radius = 300;
private void DrawRange(Canvas canvas,int r,int R) {
RectF rect = new RectF(canvas.getWidth()/2- r, canvas.getHeight()/2-r, canvas.getWidth()/2+r, canvas.getHeight()/2+r);
RectF rect1 = new RectF(canvas.getWidth()/2- R, canvas.getHeight()/2-R, canvas.getWidth()/2+R, canvas.getHeight()/2+R);
Paint scalePaint = new Paint();
scalePaint.setStyle(Paint.Style.STROKE);
scalePaint.setColor(0x9f004d0f);
scalePaint.setStrokeWidth(2);
scalePaint.setAntiAlias(true);
scalePaint.setTextSize(35.0f);
scalePaint.setTypeface(Typeface.SANS_SERIF);
scalePaint.setTextScaleX(0.4f);
scalePaint.setTextAlign(Paint.Align.CENTER);
canvas.drawOval(rect1, scalePaint);
canvas.drawOval(rect, scalePaint);
canvas.save(Canvas.CLIP_SAVE_FLAG);
int xc = 0;
for (int i = 0; i < totalNicks; i++) {
float y1 = 330;
float y2 = y1 + 5;
if (i % 5 == 0) {
canvas.drawText(""+xc, r-15, y2 , scalePaint);
xc+=5;
}else{
canvas.drawLine(r, y1, r, y2, scalePaint);
}
canvas.rotate(degreesPerNick, w/2, h/2);
}
canvas.restore();
}
I wonder if you are drawing the text and the dashes in the wrong place. The key reference point is the centre of the circles:
int cX = canvas.getWidth()/2;
int cY = canvas.getHeight()/2;
The other key reference is the difference between the two radii:
int deltaR = R - r;
The dashes and text are always drawn at 12 o'clock, say 20% above the inner circle to 1/3 of the way from the outer circle:
int dashInnerY = cY - r - deltaR/5; // 20% of the way between inner and outer radii
int dashOuterY = cY - R + deltaR/3; // 1/3 of the way between outer and inner radii
Then to render a dash:
canvas.drawLine(cX, dashInnerY, cX, dashOuterY, scalePaint);
And the number:
canvas.drawText(""+xc, cX, dashInnerY, scalePaint);

Android - Why doesn't my axis draw?

The following is supposed to draw an axis in the middle of the screen. However, nothing appears. I am positive that is has to do with my Paths.
#Override
protected void onDraw(Canvas canvas) {
//Variables declared here temporarily for testing purposes
int canterX = getWidth() /2;
int centerY = getHeight() /2;
int radius = 150;
Path verticalAxis = new Path();
Path horizontalAxis = new Path();
drawAxis();
}
private void drawAxis(Canvas canvas) {
int axisLineThickness = 1;
int verticalEndX;
int verticalEndY;
int horizontalEndX;
int horizontalEndY;
Paint axisPaint = new Paint();
axisPaint.setColor(Color.WHITE);
axisPaint.setStrokeWidth(axisLineThickness);
double theta;
for(int i = 90; i < 360; i += 180) {
theta = toRadians(i);
verticalEndX = centerX + (int) ((cos(theta)) * radius);
verticalEndY = centerY + (int) ((sin(theta)) * radius);
verticalAxis.moveTo(centerX, centerY);
verticalAxis.lineTo(verticalEndX, verticalEndY);
}
canvas.drawPath(verticalAxis, axisColor);
for(int i = 90; i < 360; i += 180) {
theta = toRadians(i);
horizontalEndX = centerX + (int) ((cos(theta)) * radius);
horizontalEndY = centerY + (int) ((sin(theta)) * radius);
horizontalAxis.moveTo(centerX, centerY);
horizontalAxis.lineTo(verticalEndX, verticalEndY);
}
canvas.drawPath(horizontalAxis, axisColor);
}
I know I can make the axis draw if I add the following to the vertical and horizontal for loops respectively:
Vertical For Loop:
canvas.drawLine(centerX, centerY, verticalEndX, verticalEndY, paint);
Horizontal For Loop:
canvas.drawLine(centerX, centerY, horizontalEndX, horizontalEndY, paint);
But I don't want to solve the issue this way, I want to correct what is wrong with my paths. Can anyone tell me why the points aren't adding to my path correctly? The loop should only go through twice which creates a line for each side of the axis. Ie. One loop creates the top of the vertical axis and the second loop creates the bottom part.
How do I get my paths create that full line and then draw it outside of the loop?
Paint's default style appears to be FILL, so maybe just having a line in your path is confusing things. Try setting it to STROKE:
axisPaint.setStyle(Paint.Style.STROKE);
See Paint.Style

Categories

Resources