How to draw equidistant dashes using DashPathEffect - android

At the moment I’m using DashPathEffect with hardcoded intervals to draw a circle as next:
float[] intervals = new float[]{ 3, 18 };
DashPathEffect path = new DashPathEffect(intervals, 0);
paint.setPathEffect(path);
… … … …
canvas.drawCircle(x, y, radius, paint);
But this produces a non-equidistant dash where the circle starts and ends, as shown in the image below:
I can of course adjust it manually, but this would only work for one specific device density, and produce again the same problem in a different display density.
What would the formula to calculate equidistant dashes?

You need n dashes plus n gaps to have the same total length as the circumference of the circle. The below code assumes you've correctly determined both the center point and the radius you want to use.
double circumference = 2 * Math.PI * radius;
float dashPlusGapSize = (float) (circumference / NUM_DASHES);
intervals[0] = dashPlusGapSize * DASH_PORTION;
intervals[1] = dashPlusGapSize * GAP_PORTION;
DashPathEffect effect = new DashPathEffect(intervals, 0);
paint.setPathEffect(effect);
canvas.drawCircle(center, center, radius, paint);
For instance, I've used NUM_DASHES = 20, DASH_PORTION = 0.75f, and GAP_PORTION = 0.25f, and I see:
You can use different values for these constants to change how many dashes you chop the cirlce into, or how big the dash/gap are relative to each other (as long as DASH_PORTION + GAP_PORTION adds up to 1).

In case you have a different figure you can use this method to measure your custom path length:
val measure = PathMeasure(path, false)
val length = measure.getLength()

Related

Hough circle doesn't detect eyes iris

I want to detect eyes irises and their centers using Hough Circle algorithm.
I'm using this code:
private void houghCircle()
{
Bitmap obtainedBitmap = imagesList.getFirst();
/* convert bitmap to mat */
Mat mat = new Mat(obtainedBitmap.getWidth(),obtainedBitmap.getHeight(),
CvType.CV_8UC1);
Mat grayMat = new Mat(obtainedBitmap.getWidth(), obtainedBitmap.getHeight(),
CvType.CV_8UC1);
Utils.bitmapToMat(obtainedBitmap, mat);
/* convert to grayscale */
int colorChannels = (mat.channels() == 3) ? Imgproc.COLOR_BGR2GRAY : ((mat.channels() == 4) ? Imgproc.COLOR_BGRA2GRAY : 1);
Imgproc.cvtColor(mat, grayMat, colorChannels);
/* reduce the noise so we avoid false circle detection */
Imgproc.GaussianBlur(grayMat, grayMat, new Size(9, 9), 2, 2);
// accumulator value
double dp = 1.2d;
// minimum distance between the center coordinates of detected circles in pixels
double minDist = 100;
// min and max radii (set these values as you desire)
int minRadius = 0, maxRadius = 1000;
// param1 = gradient value used to handle edge detection
// param2 = Accumulator threshold value for the
// cv2.CV_HOUGH_GRADIENT method.
// The smaller the threshold is, the more circles will be
// detected (including false circles).
// The larger the threshold is, the more circles will
// potentially be returned.
double param1 = 70, param2 = 72;
/* create a Mat object to store the circles detected */
Mat circles = new Mat(obtainedBitmap.getWidth(), obtainedBitmap.getHeight(), CvType.CV_8UC1);
/* find the circle in the image */
Imgproc.HoughCircles(grayMat, circles, Imgproc.CV_HOUGH_GRADIENT, dp, minDist, param1, param2, minRadius, maxRadius);
/* get the number of circles detected */
int numberOfCircles = (circles.rows() == 0) ? 0 : circles.cols();
/* draw the circles found on the image */
for (int i=0; i<numberOfCircles; i++) {
/* get the circle details, circleCoordinates[0, 1, 2] = (x,y,r)
* (x,y) are the coordinates of the circle's center
*/
double[] circleCoordinates = circles.get(0, i);
int x = (int) circleCoordinates[0], y = (int) circleCoordinates[1];
Point center = new Point(x, y);
int radius = (int) circleCoordinates[2];
/* circle's outline */
Core.circle(mat, center, radius, new Scalar(0,
255, 0), 4);
/* circle's center outline */
Core.rectangle(mat, new Point(x - 5, y - 5),
new Point(x + 5, y + 5),
new Scalar(0, 128, 255), -1);
}
/* convert back to bitmap */
Utils.matToBitmap(mat, obtainedBitmap);
MediaStore.Images.Media.insertImage(getContentResolver(),obtainedBitmap, "testgray", "gray" );
}
But it doesn't detect iris in all images correctly. Specially, if the iris has a dark color like brown. How can I fix this code to detect the irises and their centers correctly?
EDIT: Here are some sample images (which I got from the web) that shows the performance of the algorithm (Please ignore the landmarks which are represented by the red squares):
In these images the algorithm doesn't detect all irises:
This image shows how the algorithm couldn't detect irises at all:
EDIT 2: Here is a code which uses Canny edge detection, but it causes the app to crash:
private void houghCircle()
{
Mat grayMat = new Mat();
Mat cannyEdges = new Mat();
Mat circles = new Mat();
Bitmap obtainedBitmap = imagesList.getFirst();
/* convert bitmap to mat */
Mat originalBitmap = new Mat(obtainedBitmap.getWidth(),obtainedBitmap.getHeight(),
CvType.CV_8UC1);
//Converting the image to grayscale
Imgproc.cvtColor(originalBitmap,grayMat,Imgproc.COLOR_BGR2GRAY);
Imgproc.Canny(grayMat, cannyEdges,10, 100);
Imgproc.HoughCircles(cannyEdges, circles,
Imgproc.CV_HOUGH_GRADIENT,1, cannyEdges.rows() / 15); //now circles is filled with detected circles.
//, grayMat.rows() / 8);
Mat houghCircles = new Mat();
houghCircles.create(cannyEdges.rows(),cannyEdges.cols()
,CvType.CV_8UC1);
//Drawing lines on the image
for(int i = 0 ; i < circles.cols() ; i++)
{
double[] parameters = circles.get(0,i);
double x, y;
int r;
x = parameters[0];
y = parameters[1];
r = (int)parameters[2];
Point center = new Point(x, y);
//Drawing circles on an image
Core.circle(houghCircles,center,r,
new Scalar(255,0,0),1);
}
//Converting Mat back to Bitmap
Utils.matToBitmap(houghCircles, obtainedBitmap);
MediaStore.Images.Media.insertImage(getContentResolver(),obtainedBitmap, "testgray", "gray" );
}
This is the error I get in the log
FATAL EXCEPTION: Thread-28685
CvException [org.opencv.core.CvException: cv::Exception: /hdd2/buildbot/slaves/slave_ardbeg1/50-SDK/opencv/modules/imgproc/src/color.cpp:3739: error: (-215) scn == 3 || scn == 4 in function void cv::cvtColor(cv::InputArray, cv::OutputArray, int, int)
]
at org.opencv.imgproc.Imgproc.cvtColor_1(Native Method)
at org.opencv.imgproc.Imgproc.cvtColor(Imgproc.java:4598)
Which is caused by this line: Imgproc.cvtColor(originalBitmap,grayMat,Imgproc.COLOR_BGR2GRAY);
Can anyone please tell me how this error can solved? Perhaps adding a canny edge detection will improve the results.
Hough circles work better on well defined circles. They are not good with things like iris.
After some thresholding, morphological operations or canny edge detection, feature detection methods like MSER work much better for iris detection.
Here is a similar question with a solution if you are looking for some code.
As you want to detect iris using hough transform (there are others), you had better studying the Canny edge detector and its parameters.
cv::HoughCircles takes the Canny-hysteresis threshold in param1. Investigating Canny alone, you get the impression of good threshold range.
Maybe instead of gaussian blur, you apply a better denoising (non local means with say h=32 and window sizes 5 and 15), and also try to harmonize the image contrast, e.g., using contrast limited adaptive histogram equalization (cv::CLAHE).
Harmonization is to make sure all (highlight and shadow) eyes map to similar intensity range.
I wanted to know if those images are the images you processed or if you like took a cell phone snapshot of your screen to upload them here. Because the irises are bigger than the maximum radius you set in your code. Therefor I don't understand how you could find any iris at all. The irises in the first image have a radius of over 20. So you shouldn't be able to detect them.
You should set the radii to the radius range you expect your irises to be.

Library for android for Google cardboard barrel distortion

I am trying to implement a 3D app for Android that should also support cardboard like viewers. I have seen some of those images and they seem to have some kind of barrel distortion in order to be orthogonal through the cardboard lenses.
So I was looking for algorithms or libraries specifically for Java/Android that would help me achieving this.
I have found this implementation: http://www.helviojunior.com.br/fotografia/barrel-and-pincushion-distortion/
It would be great to have something like this because it has everything I'd need. Unfortunately it's for C# and it has some specific code that I just couldn't easily translate into more generic code.
Then there is a simpler Java implementation here: http://popscan.blogspot.de/2012/04/fisheye-lens-equation-simple-fisheye.html
I have changed it to:
public static Bitmap fisheye(Bitmap srcimage) {
/*
* Fish eye effect
* tejopa, 2012-04-29
* http://popscan.blogspot.com
* http://www.eemeli.de
*/
// get image pixels
double w = srcimage.getWidth();
double h = srcimage.getHeight();
int[] srcpixels = new int[(int)(w*h)];
srcimage.getPixels(srcpixels, 0, (int)w, 0, 0, (int)w, (int)h);
Bitmap resultimage = srcimage.copy(srcimage.getConfig(), true);
// create the result data
int[] dstpixels = new int[(int)(w*h)];
// for each row
for (int y=0;y<h;y++) {
// normalize y coordinate to -1 ... 1
double ny = ((2*y)/h)-1;
// pre calculate ny*ny
double ny2 = ny*ny;
// for each column
for (int x=0;x<w;x++) {
// preset to black
dstpixels[(int)(y*w+x)] = 0;
// normalize x coordinate to -1 ... 1
double nx = ((2*x)/w)-1;
// pre calculate nx*nx
double nx2 = nx*nx;
// calculate distance from center (0,0)
// this will include circle or ellipse shape portion
// of the image, depending on image dimensions
// you can experiment with images with different dimensions
double r = Math.sqrt(nx2+ny2);
// discard pixels outside from circle!
if (0.0<=r&&r<=1.0) {
double nr = Math.sqrt(1.0-r*r);
// new distance is between 0 ... 1
nr = (r + (1.0-nr)) / 2.0;
// discard radius greater than 1.0
if (nr<=1.0) {
// calculate the angle for polar coordinates
double theta = Math.atan2(ny,nx);
// calculate new x position with new distance in same angle
double nxn = nr*Math.cos(theta);
// calculate new y position with new distance in same angle
double nyn = nr*Math.sin(theta);
// map from -1 ... 1 to image coordinates
int x2 = (int)(((nxn+1)*w)/2.0);
// map from -1 ... 1 to image coordinates
int y2 = (int)(((nyn+1)*h)/2.0);
// find (x2,y2) position from source pixels
int srcpos = (int)(y2*w+x2);
// make sure that position stays within arrays
if (srcpos>=0 & srcpos < w*h) {
// get new pixel (x2,y2) and put it to target array at (x,y)
dstpixels[(int)(y*w+x)] = srcpixels[srcpos];
}
}
}
}
}
resultimage.setPixels(dstpixels, 0, (int)w, 0, 0, (int)w, (int)h);
//return result pixels
return resultimage;
}
But it doesn't have this lens factor, so the resulting image is always a full circle/ellipse.
Any chance you could point me to some working Java code or library or (maybe even better) help me to amend this code for the lens factor to be taken into account (0.0 <= factor <= 1.0)?
I managed to get it to work.
Bottom line: I created a Bitmap bigger than the original Bitmap, and then I drew the original Bitmap on the new Bitmap (and centered it there) using
Canvas canvas = new Canvas(newBitmap);
canvas.drawBitmap(originalBitmap, null, new Rect(x, y, r, b), null);
I used the Java algorithm posted in my question to create the effect on the new Bitmap. That worked great.

Drawing path gradually in Android

I have a custom view, around which I want to draw a path, like a border.
But the border should draw itself gradually, like a snake growing in size.
The aim is to use it as a timer for a player to make his move in a game.
I used the Path class and the methods lineTo and addArc to draw the border.
timerPath = new Path();
timerPath.moveTo(getTranslationX() + width / 2, getTranslationY() + 3);
timerPath.lineTo(getTranslationX() + width - 10, getTranslationY() + 3);
timerPath.addArc(new RectF(getTranslationX() + width - 20, getTranslationY() + 3,
getTranslationX() + width - 3, getTranslationY() + 20), -90f, 90f);
...
...
timerPath.lineTo(getTranslationX() + width / 2, getTranslationY() + 3);
timerPaint = new Paint();
timerPaint.setColor(Color.GREEN);
timerPaint.setStyle(Paint.Style.STROKE);
timerPaint.setStrokeWidth(6);
I use the drawPath() method in onDraw:
canvas.drawPath(timerPath, timerPaint);
It looks well.
Now, I wonder if there is a way to draw just part of the path using percentage (10%, 11%, 12% .. etc).
Then I'll be able to animate the drawing.
If it's not possible, is there another way of animating border drawing? (to use as a timer)
Appreciate your help.
You can use the PathMeasure class to do this. Create a PathMeasure object from your path, measure the length and then use getSegment() to return a partial path that you can draw to the canvas:
float percentage = 50.0f; // initialize to your desired percentage
PathMeasure measure = new PathMeasure(timerPath, false);
float length = measure.getLength();
Path partialPath = new Path();
measure.getSegment(0.0f, (length * percentage) / 100.0f, partialPath, true);
partialPath.rLineTo(0.0f, 0.0f); // workaround to display on hardware accelerated canvas as described in docs
canvas.drawPath(partialPath, timerPaint);

How to make a cylinder in renderscript

I have been trying to make a cylinder in renderscript. This is the code I've tried:
public Mesh cylinder(){
float radius=1.25f, halfLength=5;
int slices=16;
Mesh.TriangleMeshBuilder mbo= new TriangleMeshBuilder(mRSGL,3, Mesh.TriangleMeshBuilder.TEXTURE_0);
for(int i=0; i<slices; i++) {
float theta = (float) (((float)i)*2.0*Math.PI);
float nextTheta = (float) (((float)i+1)*2.0*Math.PI);
/*vertex at middle of end*/
mbo.addVertex(0.0f, halfLength, 0.0f);
/*vertices at edges of circle*/
mbo.addVertex((float)(radius*Math.cos(theta)), halfLength, (float)(radius*Math.sin(theta)));
mbo.addVertex((float)(radius*Math.cos(nextTheta)), halfLength, (float)(radius*Math.sin(nextTheta)));
/* the same vertices at the bottom of the cylinder*/
mbo.addVertex((float)(radius*Math.cos(nextTheta)), -halfLength, (float)(radius*Math.sin(nextTheta)));
mbo.addVertex((float)(radius*Math.cos(theta)), halfLength, (float)(radius*Math.sin(theta)));
mbo.addVertex(0.0f, -halfLength, 0.0f);
mbo.addTriangle(0, 1, 2);
mbo.addTriangle(3, 4, 5);
}
return mbo.create(true);
}
But this code gives me a rectangle of length 5. Any ideas where I'm going wrong?
You actually have a few problems here. First, your angles are always equal to multiples of 2pi. You need to divide by the number of sectors when you calculate your angles. Additionally in this step you have an unnecessary explicit type conversion, java will handle the conversion of integer to double for you.
Second, you are constantly adding the same two triangles to the mesh and not adding any triangles for the side of the cylinder, just the two end faces. In your loop when calling addTriangle() you should use indices, for example addTriangle(n, n+1, n+2).
Finally, you were missing a negative sign when you created your 4th vertex, so it was actually at halfLength, not -halfLength.
Try this:
public Mesh cylinder(){
float radius=1.25f, halfLength=5;
int slices=16;
Mesh.TriangleMeshBuilder mbo= new TriangleMeshBuilder(mRSGL,3, Mesh.TriangleMeshBuilder.TEXTURE_0);
/*vertex at middle of end*/
mbo.addVertex(0.0f, halfLength, 0.0f);
mbo.addVertex(0.0f, -halfLength, 0.0f);
for(int i=0; i<slices; i++) {
float theta = (float) (i*2.0*Math.PI / slices);
float nextTheta = (float) ((i+1)*2.0*Math.PI / slices);
/*vertices at edges of circle*/
mbo.addVertex((float)(radius*Math.cos(theta)), halfLength, (float)(radius*Math.sin(theta)));
mbo.addVertex((float)(radius*Math.cos(nextTheta)), halfLength, (float)(radius*Math.sin(nextTheta)));
/* the same vertices at the bottom of the cylinder*/
mbo.addVertex((float)(radius*Math.cos(nextTheta)), -halfLength, (float)(radius*Math.sin(nextTheta)));
mbo.addVertex((float)(radius*Math.cos(theta)), -halfLength, (float)(radius*Math.sin(theta)));
/*Add the faces for the ends, ordered for back face culling*/
mbo.addTriangle(4*i+3, 4*i+2, 0);
//The offsets here are to adjust for the first two indices being the center points. The sector number (i) is multiplied by 4 because the way you are building this mesh, there are 4 vertices added with each sector
mbo.addTriangle(4*i+5, 4*i+4, 1);
/*Add the faces for the side*/
mbo.addTriangle(4*i+2, 4*i+4, 4*i+5);
mbo.addTriangle(4*i+4, 4*i+2, 4*i+3);
}
return mbo.create(true);
}
I have also added a slight optimization where the vertices for the centers of the circles are created only once, thus saving memory. The order of indices here is for back face culling. Reverse it if you want front face. Should your needs require a more efficient method eventually, allocation builders allow for using trifans and tristrips, but for a mesh of this complexity the ease of triangle meshes is merited. I have run this code on my own system to verify that it works.

Android problem with Image Rotate and Matrix

Hopefully this is an easy one because I've been trying all sorts of different ways to get rid of this.
I am making an android app which incorporates a clock animation. I've got everything working really well except one very annoying thing.
I have a second hand on the clock and I'm using the following code to rotate it around a the second hand center point. As you'll probably notice I'm trying to make this look like an analogue second hand so it sweeps instead of just ticking.
public float secdegrees, secondwidth, secondheight;
secondMatrix = new Matrix();
secondwidth = secondHand.getWidth();
secondheight = secondHand.getHeight();
secdegrees = anglepersec * secnow;
secdegrees += anglepluspermilli * millis;
secondMatrix.setRotate(secdegrees, secondwidth/2, secondheight / 2);
newSecond = Bitmap.createBitmap(secondHand, 0, 0,
(int) secondwidth, (int) secondheight, secondMatrix, true);
c.drawBitmap(newSecond, (centrex - newSecond.getWidth()/2),
((face.getHeight()/2) - newSecond.getHeight()/2), null);
It actually does just the job I want... almost.
The problem is the hand shakes/jiggles around the center point ever so slightly, but it's really noticeable and really spoils the aesthetics.
I pretty much suspect that it's the way that it's rounding the float value, but I was hoping that someone had experienced this before and had any ideas on how to get rid of it.
For reference the second hand image was originally 74 px x 28 px and is (currently) 74 x 74 pixels .png with the middle of the second hand exactly crossing the crossing point. I've also tried making it 75 x 75 so that there is actually a central pixel too but no luck.
Any help at all would be appreciated.
** UPDATE
I've tried to change the code in case the decimals were getting dropped but still no luck I'm afraid. Here is option 2 I've tried and failed with;
secondMatrix = new Matrix();
secondwidth = secondHand.getWidth();
secondheight = secondHand.getHeight();
secdegrees = anglepersec * secnow;
secdegrees += anglepluspermilli * millis;
secondMatrix.setRotate(secdegrees, secondwidth/2, secondheight / 2);
newSecond = Bitmap.createBitmap(secondHand, 0, 0, (int) secondwidth,
(int) secondheight, secondMatrix, true);
float secW = newSecond.getWidth()/2;
float secH = newSecond.getHeight()/2;
// NEW CODE HERE
float calcdeg = secdegrees % 90;
calcdeg = (float) Math.toRadians(calcdeg);
float NegY = (float) ((secondwidth*Math.cos(calcdeg)) +
(secondwidth * Math.sin(calcdeg)));
c.drawBitmap(newSecond, centrex - NegY/2,
((face.getHeight()/2) - NegY/2), null);
I understand your problem, I have never encountered it mysleft, but it sounds pretty obvious to me. Since the rotations changes the width and height of the image, your imprecision comes from centrex - NegX/2
I have not tested, but I suggest you try:
Matrix matrix=new Matrix()
//first translate to place the center of the hand at Point(0,0)
matrix.setTranslate(-secondWidth/2,-secondHeight/2);
matrix.setRotate(secdegrees);
//now place the pivot Point(0,0) at its expected location
matrix.setTranslate(centreX,centreY);
newSecond = Bitmap.createBitmap(secondHand, 0, 0, secondWidth, secondHeight, matrix, false);
c.drawBitmap(newSecond,0,0,null);
Of course, this is suboptimal, since the newSecond bitmap is much larger than it actually needs to be. So if your centrex and centrey are big, you might want to translate less than that, and then draw with a translation of the difference.
//now place the pivot to a position where the hand can be fully drawn without imprecion on the future location of Point(0,0)
matrix.setTranslate(secondWith,secondHeight);
newSecond = Bitmap.createBitmap(secondHand, 0, 0, secondWidth, secondHeight, matrix, false);
c.drawBitmap(newSecond,centrex-secondWidth,centrey-secondHeight,null);
Hope this helps.

Categories

Resources