Im developing an android app with the help of dlib, i have a code that with the help of the 68 face landmarks i draw a path for the outer part of the lips and another one for the inner path of the lips is there any way that with 2 different paths i can fill the gap in between them?
i've seen methods like, fill the outer one and fill the inside one with white so it looks like you're only painting the outside part but i cant do that since im painting it over the an image and i just want to color the lips, is there any way to accomplish this?
This is the code i used to paint the lips:
for (final VisionDetRet ret : results) {
// Draw landmark
int x = 0;
ArrayList<android.graphics.Point> landmarks = ret.getFaceLandmarks();
int ppX = 0, ppY = 0;
Paint mFaceLandmardkPaint = new Paint();
mFaceLandmardkPaint.setColor(Color.GREEN);
mFaceLandmardkPaint.setStrokeWidth(2);
mFaceLandmardkPaint.setStyle(Paint.Style.STROKE);
Path pth = new Path();
pth.setFillType(Path.FillType.EVEN_ODD);
Path pth2 = new Path();
pth2.setFillType(Path.FillType.EVEN_ODD);
for (android.graphics.Point point : landmarks) {
if (x >= 48 && x <= 60) {
int pointX = (int) (point.x);
int pointY = (int) (point.y);
if (x != 48) {
//canvas.drawLine(pointX, pointY, ppX, ppY, mFaceLandmardkPaint);
pth.lineTo(pointX, pointY);
} else {
pth.moveTo(pointX, pointY);
}
ppX = pointX;
ppY = pointY;
//canvas.drawCircle(pointX, pointY, 1, mFaceLandmardkPaint);
} else if (x > 60) {
pth.close();
}
if (x >= 61) {
int pointX = (int) (point.x);
int pointY = (int) (point.y);
if (x != 61) {
//canvas.drawLine(pointX, pointY, ppX, ppY, mFaceLandmardkPaint);
pth2.lineTo(pointX, pointY);
} else {
pth2.moveTo(pointX, pointY);
}
ppX = pointX;
ppY = pointY;
//canvas.drawCircle(pointX, pointY, 1, mFaceLandmardkPaint);
}
x++;
}
pth2.close();
Paint red = new Paint();
red.setColor(android.graphics.Color.RED);
red.setStyle(Paint.Style.FILL);
c.drawPath(pth2, mFaceLandmardkPaint);
c.drawPath(pth, red);
}
This code generates a full painted mouth.
Is there any way to accomplish this with native canvas, paint and paths?
Related
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.
So I created a game where u can move your character and u need to pass lines with that character, but I have 1 problem. When the first line reaches bottom of my screen only then the score starts counting. I want to make so that when my character passes the lines gap the score would +1. What should I do?
EDIT: Also the score counts when last line reaches bottom!
if (obstacles.get(obstacles.size()-1).getRectangle().top >= Constants.SCREEN_HEIGHT ){
int xStart = (int) (Math.random()*(Constants.SCREEN_WIDTH- playerGap));
obstacles.add(0, new Obstacle(obstacleHeight, color, xStart, obstacles.get(0).getRectangle().top - obstacleHeight - obstacleGap, playerGap));
obstacles.remove(obstacles.size()-1);
score++;
}
}
public void draw(Canvas canvas){
for(Obstacle ob : obstacles)
ob.draw(canvas);
Paint paint = new Paint();
paint.setTextSize(100);
paint.setColor(Color.GREEN);
canvas.drawText("" + score, 50, 100 + paint.descent()- paint.descent(), paint);
EDIT:
int currY = -5*Constants.SCREEN_HEIGHT/4, rect;
while(currY < 0){
int xStart = (int) (Math.random()*(Constants.SCREEN_WIDTH- playerGap));
obstacles.add(new Obstacle(obstacleHeight, color, xStart, currY, playerGap));
currY += obstacleHeight + obstacleGap;
}
if (obstacles.get(obstacles.size() - 1).getRectangle().top >= currY) {
score++;
}
EDIT2:
private void populateObatacles(){
int currY = -5*Constants.SCREEN_HEIGHT/4;
while(currY < 0){
int xStart = (int) (Math.random()*(Constants.SCREEN_WIDTH- playerGap));
obstacles.add(new Obstacle(obstacleHeight, color, xStart, currY, playerGap));
currY += obstacleHeight + obstacleGap;
}
}
EDIT3:
if (obstacles.get(lastLineScored-1).getRectangle().top >= currY) {
score++;
lastLineScored--;
}
if (obstacles.get(obstacles.size()-1).getRectangle().top >= Constants.SCREEN_HEIGHT ){
int xStart = (int) (Math.random()*(Constants.SCREEN_WIDTH- playerGap));
obstacles.add(0, new Obstacle(obstacleHeight, color, xStart, obstacles.get(0).getRectangle().top - obstacleHeight - obstacleGap, playerGap));
obstacles.remove(obstacles.size()-1);
lastLineScored++;
}
}
}
public void draw(Canvas canvas){
for(Obstacle ob : obstacles)
ob.draw(canvas);
Paint paint = new Paint();
paint.setTextSize(100);
paint.setColor(Color.GREEN);
canvas.drawText("" + score , 50, 100 + paint.descent()- paint.descent (), paint);
EDIT4:
Obstacle manager http://pastebin.com/6E77QtHj
Obstacle http://pastebin.com/p33mPrat
Change your test. Instead of checking whether the top of the line is greater than SCREEN_HEIGHT, check whether it is greater than the character's Y coordinate. Also, you need to check against each
if (obstacles.get(obstacles.size()-1).getRectangle().top >= /** character Y coordinate */) {
score++;
}
if (obstacles.get(obstacles.size()-1).getRectangle().top >= Constants.SCREEN_HEIGHT ){
int xStart = (int) (Math.random()*(Constants.SCREEN_WIDTH- playerGap));
obstacles.add(0, new Obstacle(obstacleHeight, color, xStart, obstacles.get(0).getRectangle().top - obstacleHeight - obstacleGap, playerGap));
obstacles.remove(obstacles.size()-1);
}
EDIT: You need to keep track of which lines are already past the character and only up the score when a new line goes past. You'll need to keep an index of which lines have already been scored. So create a field to keep that index; let's call it lastScoredLine. It should be initialized to obstacles.size(). You'll have to adjust that field whenever a line falls off the screen.
if (obstacles.get(lastLineScored-1).getRectangle().top >= /** character Y coordinate */) {
score++;
lastLineScored--;
}
if (obstacles.get(obstacles.size()-1).getRectangle().top >= Constants.SCREEN_HEIGHT ){
int xStart = (int) (Math.random()*(Constants.SCREEN_WIDTH- playerGap));
obstacles.add(0, new Obstacle(obstacleHeight, color, xStart, obstacles.get(0).getRectangle().top - obstacleHeight - obstacleGap, playerGap));
obstacles.remove(obstacles.size()-1);
lastLineScored++;
}
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);
}
}
}
I'm new to Android, and I'm trying to get the hang of multi touch input. I've begun with a simple app that allows the user to create rectangles on a Canvas by dragging and releasing with one finger, which I have working. To expand upon that, I now want a user to be able to rotate the rectangle they are drawing using a second finger, which is where my problems begin. As it stands, adding a second finger will cause multiple rectangles to rotate, instead of just the current one, but they will revert to their default orientation as soon as the second finger is released.
I've been working at it for a while, and I think my core problem is that I'm mishandling the multiple MotionEvents that come with two (or more fingers). Logging statements I left to display the coordinates on the screen for each event stay tied to the first finger touching the screen, instead of switching to the second. I've tried multiple configurations of accessing and changing the event pointer ID, and still no luck. If anyone could provide some guidance in the right direction, I would be extremely grateful.
My code is as follows:
public class BoxDrawingView extends View {
private static final String TAG = "BoxDrawingView";
private static final int INVALID_POINTER_ID = -1;
private int mActivePointerId = INVALID_POINTER_ID;
private Box mCurrentBox;
private List<Box> mBoxen = new ArrayList<>();
private Float mLastTouchX;
private Float mLastTouchY;
...
#Override
public boolean onTouchEvent(MotionEvent event) {
switch(MotionEventCompat.getActionMasked(event)) {
case MotionEvent.ACTION_DOWN:
mActivePointerId = MotionEventCompat.getPointerId(event, 0);
current = new PointF(MotionEventCompat.getX(event, mActivePointerId),
MotionEventCompat.getY(event, mActivePointerId));
action = "ACTION_DOWN";
// Reset drawing state
mCurrentBox = new Box(current);
mBoxen.add(mCurrentBox);
mLastTouchX = MotionEventCompat.getX(event, MotionEventCompat.getPointerId(event, 0));
mLastTouchY = MotionEventCompat.getY(event, MotionEventCompat.getPointerId(event, 0));
break;
case MotionEvent.ACTION_POINTER_DOWN:
action = "ACTION_POINTER_DOWN";
mActivePointerId = MotionEventCompat.getPointerId(event, 0);
mLastTouchX = MotionEventCompat.getX(event, MotionEventCompat.getPointerId(event, 0));
mLastTouchY = MotionEventCompat.getY(event, MotionEventCompat.getPointerId(event, 0));
break;
case MotionEvent.ACTION_MOVE:
action = "ACTION_MOVE";
current = new PointF(MotionEventCompat.getX(event, mActivePointerId),
MotionEventCompat.getY(event, mActivePointerId));
if (mCurrentBox != null) {
mCurrentBox.setCurrent(current);
invalidate();
}
if(MotionEventCompat.getPointerCount(event) > 1) {
int pointerIndex = MotionEventCompat.findPointerIndex(event, mActivePointerId);
float currX = MotionEventCompat.getX(event, pointerIndex);
float currY = MotionEventCompat.getY(event, pointerIndex);
if(mLastTouchX < currX) {
// simplified: only use x coordinates for rotation for now.
// +X for clockwise, -X for counter clockwise
Log.d(TAG, "Clockwise");
mRotationAngle = 30;
}
else if (mLastTouchX > getX()) {
Log.d(TAG, "Counter clockwise");
mRotationAngle = -30;
}
}
break;
case MotionEvent.ACTION_UP:
action = "ACTION_UP";
mCurrentBox = null;
mLastTouchX = null;
mLastTouchY = null;
mActivePointerId = INVALID_POINTER_ID;
break;
case MotionEvent.ACTION_POINTER_UP:
action = "ACTION_POINTER_UP";
int pointerIndex = event.getActionIndex();
int pointerId = event.getPointerId(pointerIndex);
if(pointerId == mActivePointerId){
mActivePointerId = INVALID_POINTER_ID;
}
break;
case MotionEvent.ACTION_CANCEL:
action = "ACTION_CANCEL";
mCurrentBox = null;
mActivePointerId = INVALID_POINTER_ID;
break;
}
return true;
}
#Override
protected void onDraw(Canvas canvas){
// Fill the background
canvas.drawPaint(mBackgroundPaint);
for(Box box : mBoxen) {
// Box is a custom object. Origin is the origin point,
// Current is the point of the opposite diagonal corner
float left = Math.min(box.getOrigin().x, box.getCurrent().x);
float right = Math.max(box.getOrigin().x, box.getCurrent().x);
float top = Math.min(box.getOrigin().y, box.getCurrent().y);
float bottom = Math.max(box.getOrigin().y, box.getCurrent().y);
if(mRotationAngle != 0) {
canvas.save();
canvas.rotate(mRotationAngle);
canvas.drawRect(left, top, right, bottom, mBoxPaint);
canvas.rotate(-mRotationAngle);
canvas.restore();
mRotationAngle = 0;
} else {
canvas.drawRect(left, top, right, bottom, mBoxPaint);
}
}
}
}
There are several ways to draw things, not just in android, but in Java as well. The thing is that you are trying to draw the rectangles by rotating the Canvas. That's a way, but in my personal experience I think that is only a good choice if you want to rotate the whole picture. If not, that may get a little tricky because you need to place a rotation axis, which it seems you are not using, so Android will asume that you want to rotate from the left top corner or the center of the view (I don't remember).
If you are opting for that choice, you may try to do it like this:
Matrix matrix = new Matrix();
matrix.setRotate(angle, rectangleCenterX, rectangleCenterY);
canvas.setMatrix(matrix);
But I recommend you to try a different approach. Do the rotation directly on the rectangle that you are moving, by calculating the axes of the polygon. This you can do it using Java Math operations:
public void formShape(int cx[], int cy[], double scale) {
double xGap = (width / 2) * Math.cos(angle) * scale;
double yGap = (width / 2) * Math.sin(angle) * scale;
cx[0] = (int) (x * scale + xGap);
cy[0] = (int) (y * scale + yGap);
cx[1] = (int) (x * scale - xGap);
cy[1] = (int) (y * scale - yGap);
cx[2] = (int) (x * scale - xGap - length * Math.cos(radians) * scale);
cy[2] = (int) (y * scale - yGap - length * Math.sin(radians) * scale);
cx[3] = (int) (x * scale + xGap - length * Math.cos(radians) * scale);
cy[3] = (int) (y * scale + yGap - length * Math.sin(radians) * scale);
}
So (x,y) is the center of your rectangle and with, height tell you how big is it. In the formShape(int[], int[], double) method cx and cy are going to be used to draw your shape and scale is the value to use if you want to do zoom in or zoom out later, if not just use scale = 1;
Now for drawing your rectangles, this is how you do it:
Paint paint = new Paint();
paint.setColor(Color.GRAY);
paint.setStyle(Style.FILL);
int[] cx = new int[4];
int[] cy = new int[4];
Box box = yourBoxHere;
box.formShape(cx, cy, 1);
Path path = new Path();
path.reset(); // only needed when reusing this path for a new build
path.moveTo(cx[0], cy[0]); // used for first point
path.lineTo(cx[1], cy[1]);
path.lineTo(cx[2], cy[2]);
path.lineTo(cx[3], cy[3]);
path.lineTo(cx[0], cy[0]); // repeat the first point
canvas.drawPath(wallpath, paint);
For multitouch rotation listener you should override 2 methods in your Activity or View:
#Override
public boolean onTouch(View v, MotionEvent event) {
if(event.getId() == MotionEvent.ACTION_UP)
this.points = null;
}
}
#Override
public boolean dispatchTouchEvent(MotionEvent event) {
if(event.getPointerCount() >= 2) {
float newPoints[][] = new float[][] {
{event.getX(0), event.getY(0)},
{event.getX(1), event.getY(1)}
};
double angle = angleBetweenTwoPoints(newPoints[0][0], newPoints[0][1], newPoints[1][0], newPoints[1][1]);
if(points != null) {
double difference = angle - initialAngle;
if(Math.abs(difference) > rotationSensibility) {
listener.onGestureListener(GestureListener.ROTATION, Math.toDegrees(difference));
this.initialAngle = angle;
}
} else {
this.initialAngle = angle;
}
this.points = newPoints;
}
}
public static double angleBetweenTwoPoints(double xHead, double yHead, double xTail, double yTail) {
if(xHead == xTail) {
if(yHead > yTail)
return Math.PI/2;
else
return (Math.PI*3)/2;
} else if(yHead == yTail) {
if(xHead > xTail)
return 0;
else
return Math.PI;
} else if(xHead > xTail) {
if(yHead > yTail)
return Math.atan((yHead-yTail)/(xHead-xTail));
else
return Math.PI*2 - Math.atan((yTail-yHead)/(xHead-xTail));
} else {
if(yHead > yTail)
return Math.PI - Math.atan((yHead-yTail)/(xTail-xHead));
else
return Math.PI + Math.atan((yTail-yHead)/(xTail-xHead));
}
}
Sorry, but this answer is getting long, if you have further questions about any of those operations and you want to change the approach of your solution, please ask again and tell me in the comments.
I hope this was helpful.
I have tried Romain Guy's TextureView sample code (http://pastebin.com/J4uDgrZ8), it works great. But when I change lockCanvas(null) into lockCanvas(new Rect(x, y, x+20, y+20)), the example starts to flicker.
It seems 'lockCanvas(Rect)' can not work well for TextureView or any other reasons?
I am using Motorola XOOM with android 4.0.3.
Thanks for any help!
the code i modified is as follows:
public void run() {
float x = 0.0f;
float y = 0.0f;
float speedX = 5.0f;
float speedY = 3.0f;
Paint paint = new Paint();
paint.setColor(0xff00ff00);
while (mRunning && !Thread.interrupted()) {
//final Canvas canvas = mSurface.lockCanvas(null);
**final Canvas canvas = mSurface.lockCanvas(new Rect((int)x, (int)y,
(int)(x+20.0f), (int)(y+20.0f)));**
try {
canvas.drawColor(0x00000000, PorterDuff.Mode.CLEAR);
canvas.drawRect(x, y, x + 20.0f, y + 20.0f, paint);
} finally {
mSurface.unlockCanvasAndPost(canvas);
}
if (x + 20.0f + speedX >= mSurface.getWidth() || x + speedX <= 0.0f) {
speedX = -speedX;
}
if (y + 20.0f + speedY >= mSurface.getHeight() || y + speedY <= 0.0f) {
speedY = -speedY;
}
x += speedX;
y += speedY;
try {
//Thread.sleep(15);
**Thread.sleep(1);**
} catch (InterruptedException e) {
// Interrupted
}
}
I checked it carefully, found that it is "Thread.sleep(1)" together with "lockCanvas(Rect)" leads to the flicker. When use lockCanvas(null), sleep(1) is OK. So lockCanvas(Rect) can not be refreshed as fast as lockCanvas(null)??