Introduction: This article uses code lui a “goddess” in your mind, code on makeup. The main technical content is the application of Canvas.

background

Recently, I saw some big guys change into “goddess” through Douyin. This makeup can be called counterattack. Big guys change into girls.

As a technology, most of them are male, and there are often men who are asked by women how many shades of my lipstick is, is it the same red?

In order to the majority of male compatriots can well “live in” female ticket in front of, today to tell about [makeup], with the code masturbated a good-looking female ticket.

The effect

First, let’s talk about the effect. Learning douyin’s makeup tutorial, just draw half of it to facilitate the formation of contrast. The effect is as follows:

If the person reading this article is a girl, you know what it’s about, right? In order to take care of the gentlemen, let’s talk about what I drew.

Let’s just look at the code

public enum Region {

    FOUNDATION("Foundation"),
    BLUSH("Blush"),
    LIP("Lip"),
    BROW("Eyebrows"),

    EYE_LASH("Eyelash"),
    EYE_CONTACT("Lenses"),
    EYE_DOUBLE("Double eyelid"),
    EYE_LINE("Line"),
    EYE_SHADOW("Eye shadow");

    private String name;
    Region(String name) {
        this.name = name; }}Copy the code

Female coders, do you see all this? In fact, I also quite admire myself, a boy know so much, scared many of my friends, otaku world you do not understand.

Code has been hosted to Github, if you like, please give a star, thank you github.com/DingProg/Ma…

exfoliating

We know that the general acne with foundation is not covered, so first to a skin, the “floor” clean, we use a high-pass filter (remove the low-frequency signal, The process of applying some frequency Adjustment and then fusing to achieve the purpose of peeling is something like this

PNG 080819243542_01.png

This article does not have such a library in the library, directly uses the Github open source abrasion library. Using HighPassSkinSmoothing

But I’m only taking the left side of the face for contrast

 Bitmap leftAndRightBitmap = Bitmap.createBitmap(bitmap.getWidth(),bitmap.getHeight(), Bitmap.Config.ARGB_8888);
 Canvas canvas = new Canvas(leftAndRightBitmap);
 //+3, to make up for the loss of intensive reading by dividing int values, make the left side more
Rect left = new Rect(0.0,bitmap.getWidth()/2 + 3,bitmap.getHeight());
Rect right = new Rect(bitmap.getWidth() - bitmap.getWidth()/2 ,0,bitmap.getWidth(),bitmap.getHeight());
  canvas.drawBitmap(result,left,left,null);
canvas.drawBitmap(bitmap,right,right,null);
Copy the code

Face key point detection

On person face makeup, get the whole picture of exfoliating sure there’s no way, we need accurate face, it needs to face recognition technology, open source libraries also have some, but the accuracy needs to be strengthened, so this article chooses the commercial points face detection technology, probably looked at it and there are so few families can also face recognition technology

  • Thomson
  • Face++
  • baidu
  • Rainbow soft

Their technology, face accuracy, price to use, is not commented on here. Face++ dense key point detection is used in this paper. To make it easy to see, instead of downloading its SDK, the web version of keypoint detection is used to upload local photos and then take the data down.

The JSON with key points on the right can be copied directly for later use.

{
  "time_used": 140."request_id": "1565152700,b5efc234-055c-4109-8899-e7bd0b9d1d63"."face": {
    "landmark": {
      "left_eye": {
        "left_eye_43": {
          "y": 170."x": 140
        },
        "left_eye_42": {
          "y": 170."x": 141
        },
        "left_eye_41": {
          "y": 170."x": 142
        },
        "left_eye_40": {
          "y": 170."x": 143
        },
        "left_eye_47": {
          "y": 170."x": 136
        },
        "left_eye_46": {
          "y": 170."x": 137
        }
       }
     }
   }
}
Copy the code

If commercial advise to buy its SDK. With these points, we can then “paint” the makeup.

Powdery bottom

There is skin grinding, but it is not enough white ah, the above library actually contains whitening, which is to process the whole picture, overlay white filtering, but the effect is very poor, certainly not what we want. But with the point of face detection, then we are easy to do, coated with a layer of foundation.(girls should first coated with water ah, milk ah what, the photo can not water….)

Go straight to the left face area.

 public static Path landmark(String faceJson){
        JSONObject jsonObject = null;
        try {
            jsonObject = new JSONObject(faceJson);
            JSONObject eye = jsonObject.getJSONObject("face").getJSONObject("landmark").getJSONObject("face");

            Path path = new Path();
            Point start = getPointByJson(eye.getJSONObject("face_contour_left_0"));
            path.moveTo(start.x,start.y);
            for(int i= 1; i<64; i++){ Point point = getPointByJson(eye.getJSONObject("face_contour_left_"+i));
                path.lineTo(point.x,point.y);
            }

            for(int i= 144; i>=72; i--){ Point point = getPointByJson(eye.getJSONObject("face_hairline_"+i));
                path.lineTo(point.x,point.y);
            }
            path.close();
            return  path;

        } catch (JSONException e) {
            e.printStackTrace();
        }
        return null;

    }
Copy the code

With the left area, it can be painted on with only one brush (the original picture can be the new Canvas(originBitmap)). It will not work if we paint white directly, which will scare our friends. Is white and transparent ok? Let’s give it a try

 Canvas canvas = new Canvas(originBitmap);
 Paint paint = new Paint();
 paint.setColor(Color.WHITE);
 paint.setAlpha(50);
 paint.setStyle(Paint.Style.FILL);
 canvas.drawPath(facePath,paint);
Copy the code

The effect

  private static Bitmap createMask(final Path path, int color, @Nullable PointF position, int alpha, int blur_radius) {
        if (path == null || path.isEmpty())
            return null;

        RectF bounds = new RectF();
        path.computeBounds(bounds, true);

        int width = (int) bounds.width();
        int height = (int) bounds.height();
        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);  // mutable
        Canvas canvas = new Canvas(bitmap);
        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setMaskFilter(new BlurMaskFilter(blur_radius, BlurMaskFilter.Blur.NORMAL));
        paint.setColor(color);
        paint.setAlpha(alpha);
        paint.setStyle(Paint.Style.FILL);
        path.offset(-bounds.left, -bounds.top);
        canvas.drawPath(path, paint);
        if(position ! =null) {
            position.x = bounds.left;
            position.y = bounds.top;
        }
        return bitmap;
    }
Copy the code

It turns out that this works, but the effect is still not very good, so let’s use the original image to make a gradient, just to achieve the effect

  private static Bitmap getGradientBitmapByXferomd(Bitmap originBitmap, float radius){
        if(radius < 10) radius = 10;
        Bitmap canvasBitmap = Bitmap.createBitmap(originBitmap.getWidth(),originBitmap.getHeight(), Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(canvasBitmap);
        Paint paint = new Paint();

        BitmapShader bitmapShader = new BitmapShader(originBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
        RadialGradient radialGradient = new RadialGradient(originBitmap.getWidth() / 2, originBitmap.getHeight() / 2,
                radius, Color.BLACK, Color.TRANSPARENT, Shader.TileMode.CLAMP);
        paint.setShader(new ComposeShader(bitmapShader,radialGradient,new PorterDuffXfermode(PorterDuff.Mode.DST_IN)));
        canvas.drawRect(new Rect(0.0,canvasBitmap.getWidth(),canvasBitmap.getHeight()), paint);
        return canvasBitmap;
    }
Copy the code

lipstick

Lipstick is just a layer of color, with a brush, can be achieved in the same way as foundation.

Let’s take a look at how to connect the regions. For convenience, I directly connect the outer regions and then do a diff again. The code is as follows

 public static Path getMouthPath(String faceJson){
        try {
            JSONObject jsonObject = new JSONObject(faceJson);
            JSONObject mouthJson = jsonObject.getJSONObject("face").getJSONObject("landmark").getJSONObject("mouth");

             Path outPath = new Path();
             Path inPath = new Path();

            Point start = getPointByJson(mouthJson.getJSONObject("upper_lip_0"));
            outPath.moveTo(start.x,start.y);
             for(int i = 1; i <18; i++){ Point pointByJson = getPointByJson(mouthJson.getJSONObject("upper_lip_" + i));
                 outPath.lineTo(pointByJson.x,pointByJson.y);
             }

            for(int i = 16; i >0; i--){ Point pointByJson = getPointByJson(mouthJson.getJSONObject("lower_lip_" + i));
                outPath.lineTo(pointByJson.x,pointByJson.y);
            }
            outPath.close();


            Point inStart = getPointByJson(mouthJson.getJSONObject("upper_lip_32"));
            inPath.moveTo(inStart.x,inStart.y);

            for(int i = 46; i <64; i++){ Point pointByJson = getPointByJson(mouthJson.getJSONObject("upper_lip_" + i));
                inPath.lineTo(pointByJson.x,pointByJson.y);
            }

            for(int i = 63; i >=46; i--){ Point pointByJson = getPointByJson(mouthJson.getJSONObject("lower_lip_" + i));
                inPath.lineTo(pointByJson.x,pointByJson.y);
            }

            // take different places
            outPath.op(inPath, Path.Op.DIFFERENCE);
            return  outPath;
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return null;
    }
Copy the code

The path.op () method needs to be in API 19 and above to be used. If you are using a lower VERSION of the API, you can use Canvas.clippath () directly.

Cheek is red

Only foundation, that looks a little fake, do you need to use a brush to paint blush? But what shape, not easy to fix, so I chose to directly use blush material, directly affixed.

Implementation is also relatively easy.

  public static void drawBlush(Canvas canvas, Bitmap faceBlush, Path path, int alpha) {
        Paint paint = new Paint();
        paint.setAlpha(alpha);
        RectF rectF = new RectF();
        path.computeBounds(rectF,true);
        canvas.drawBitmap(faceBlush,null,rectF,paint);

    }
Copy the code

His eyebrows

Eyebrows actually bothered me for a long time, because the bottom eyebrows should be buckled, and new eyebrows should be installed on the top, otherwise it may not be completely covered. The change of eyebrow shape and recognition accuracy will lead to direct changes in the effect. There is a famous inpaint method in OpenCV to restore the image. It is also ok to look at the book and print the demo written by others, but the effect is very poor when I put it here to remove the eyebrows, because I am using it incorrectly. There is a god to help me to remove the original eyebrows by extracting the surrounding skin color.

Eventually, they gave up on removing the original eyebrows and covered them directly.

  public static Path getLeftEyeBrow(String faceJson){
        try {
            JSONObject jsonObject = new JSONObject(faceJson);
            JSONObject eye = jsonObject.getJSONObject("face").getJSONObject("landmark").getJSONObject("left_eyebrow");

            Path path = new Path();
            Point start = getPointByJson(eye.getJSONObject("left_eyebrow_0"));
            path.moveTo(start.x,start.y);
            for(int i= 1; i<64; i++){ Point point = getPointByJson(eye.getJSONObject("left_eyebrow_"+i));
                path.lineTo(point.x,point.y);
            }
            path.close();
            return  path;
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

     public static void draw(Canvas canvas, Bitmap eyeBrowRes, Path path, int alpha){
        Paint paint = new Paint();
        paint.setAlpha(alpha);

        RectF rectF = new RectF();
        path.computeBounds(rectF,true);

        canvas.drawBitmap(eyeBrowRes,new Rect(0.0,eyeBrowRes.getWidth(),eyeBrowRes.getHeight() - 30),rectF,paint);
    }

Copy the code

The final result

Eyes (eyelashes, eye shadow, double LIDS, eyeliner, contact lenses)

The eye part is the most complicated part, because there are so many things to draw.

This will be the implementation of the two places, other concrete implementation can refer to the actual code, first look at these are not the main material

强生公司注册商标,是该公司具有美容效果的隐形眼镜系列的品牌

To draw beauty pupil to the eye, then we must first have this area, the key point of the area of the face has been given, so, we know that people’s eyes are generally elliptical, can not be directly round, so when drawing, need to do an intersection with the area of the eye to get the result.

    public static void drawContact(Canvas canvas, Bitmap contactBitmap, Path eyePath, Point centerPoint, int eyeRadius, int alpha) {
        Path contactPath = new Path();
        contactPath.addCircle(centerPoint.x,centerPoint.y,eyeRadius, Path.Direction.CCW);
        // Key points, do intersection to get results
        contactPath.op(eyePath, Path.Op.INTERSECT);

        RectF bounds = new RectF();
        contactPath.computeBounds(bounds,true);
        bounds.offset(1.0);
        Paint paint = new Paint();
        paint.setAlpha(alpha);
        canvas.drawBitmap(contactBitmap,new Rect(0.30,contactBitmap.getWidth(),contactBitmap.getHeight() - 60),bounds,paint);
    }
Copy the code

eyelash

We know that eyelashes have upper eyelashes and lower eyelashes, so how to draw this eyebrow on? In fact, we know that drawing an image to the target area usually involves translation, rotation, and scaling.

We selected three points on the material and three points on the eye for the above three operations.

So with these three points, we can calculate the aspect ratio, the Angle, which we can easily calculate using trigonometry.

Rotation Angle

Use the three dots corresponding to the person’s eyes to calculate the Angle of rotation. (If the person’s head is positive, you don’t need to calculate the Angle of rotation, but the person may tilt his head, what, you need to calculate the Angle of rotation, to warp)

 / * * *@paramVertex of p1 triangle *@paramP2 triangle vertex *@paramP3 Triangle vertices *@returnTriangle vertices P3 to P1, vertical height p3 */
    public double getTriangleHeight(Point p1, Point p2, Point p3) {
        int a = p1.x;
        int b = p1.y;
        int c = p2.x;
        int d = p2.y;
        int e = p3.x;
        int f = p3.y;
        // Calculate the triangle area
        double S = (a * d + b * e + c * f - a * f - b * c - d * e) / 2;
        int lengthSquare = (p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y);
        return Math.abs(2 * S / Math.sqrt(lengthSquare));
    }

     // Get the distance between two points in the coordinate axis
    public double getLength(Point p1, Point p2) {
        double diff_x = Math.abs(p1.x - p2.x);
        double diff_y = Math.abs(p1.y - p2.y);
        // The difference between two points on the horizontal and vertical coordinates and the line between them form a right triangle. Length_pow is equal to the square of this distance
        double length_pow = Math.pow(diff_x, 2) + Math.pow(diff_y, 2);
        double sqrt = Math.sqrt(length_pow);
        return sqrt == 0?0.001 f: (float) sqrt;
    }

    static double pi180 = 180 / Math.PI;
    public double getAngle(Point p1, Point p2, Point p3) {
        double _cos1 = getCos(p1, p2, p3);// The first point is the cosine of the Angle of the vertex
        return 90 - Math.acos(_cos1) * pi180;
    }
Copy the code

Aspect ratio rotation Angle

So we have the Angle, so we’re calculating the aspect ratio.

 / * * *@paramTargetP1 Scales the target line segment point p1 *@paramTargetP2 Scale the target segment point P2 *@paramP1 Point P1 * of the line segment to be scaled@paramP2 Point P2 * of the segment to be scaled@returnHorizontal height ratio */
    public double computeScaleX(Point targetP1, Point targetP2, Point P1, Point P2) {
        int targetLengthSquare = (targetP1.x - targetP2.x) * (targetP1.x - targetP2.x) + (targetP1.y - targetP2.y) * (targetP1.y - targetP2.y);
        int sourceLengthSquare = (P1.x - P2.x) * (P1.x - P2.x) + (P1.y - P2.y) * (P1.y - P2.y);
        double scale = targetLengthSquare * 1.0 / sourceLengthSquare;
        return Math.sqrt(scale);
    }

    / * * *@paramTargetP1 Scales the target triangle vertex *@paramTargetP2 Scales the target triangle vertex *@paramTargetP3 Scales the target triangle vertex *@paramP1 Vertex * of the triangle to be scaled@paramP2 Vertex of the triangle to be scaled *@paramP3 Vertices of triangles to be scaled *@returnVertical height ratio */
    public double computeScaleY(Point targetP1, Point targetP2, Point targetP3, Point P1, Point P2, Point P3) {
        double targetHeight = getTriangleHeight(targetP1, targetP2, targetP3);
        double sourceHeight = getTriangleHeight(P1, P2, P3);
        return targetHeight / sourceHeight;
    }
Copy the code

translation

Because our graph is huge, it is impossible to draw up from the starting position, so we need to move the position of the drawing to achieve the position of the first point and the corresponding point, corresponding up.

eyeAngleAndScaleCalc.topP1.x - (int) (bean.topP1.x * eyeAngleAndScaleCalc.topScaleX),
                eyeAngleAndScaleCalc.topP1.y - (int) (bean.topP1.y * eyeAngleAndScaleCalc.topScaleY)
Copy the code

With these steps, you can now compose and draw directly, as follows

 public static void drawLash(Context context, Canvas canvas, EyeAngleAndScaleCalc.Bean bean, List<Point> pointList, int alpha, boolean needMirror) {
        EyeAngleAndScaleCalc eyeAngleAndScaleCalc = new EyeAngleAndScaleCalc(pointList,bean);

        Paint paint = new Paint();
        paint.setAlpha(alpha);

        Bitmap resTopBitmap = BitmapUtils.getBitmapByAssetsName(context,bean.resTop);
        Bitmap scaledBitmapTop = Bitmap.createScaledBitmap(resTopBitmap, (int) (resTopBitmap.getWidth() * eyeAngleAndScaleCalc.topScaleX + 0.5),
                (int) (resTopBitmap.getHeight() * eyeAngleAndScaleCalc.topScaleY + 0.5), true);
        resTopBitmap.recycle();


        Bitmap resBottomBitmap = null;
        Bitmap scaledBitmapBottom = null;
        if(! TextUtils.isEmpty(bean.resBottom)) { resBottomBitmap = BitmapUtils.getBitmapByAssetsName(context,bean.resBottom); scaledBitmapBottom = Bitmap.createScaledBitmap(resBottomBitmap, (int) (resBottomBitmap.getWidth() * eyeAngleAndScaleCalc.bottomScaleX + 0.5),
                    (int) (resBottomBitmap.getHeight() * eyeAngleAndScaleCalc.bottomScaleY + 0.5), true);
            resBottomBitmap.recycle();
        }

        if (needMirror) {
            Matrix matrix = new Matrix();
            matrix.postScale(-1.1);   // Mirror is flipped horizontally
            scaledBitmapTop = Bitmap.createBitmap(scaledBitmapTop, 0.0, scaledBitmapTop.getWidth(), scaledBitmapTop.getHeight(), matrix, true);
            if(resBottomBitmap ! =null) {
                scaledBitmapBottom = Bitmap.createBitmap(scaledBitmapBottom, 0.0, scaledBitmapBottom.getWidth(), scaledBitmapBottom.getHeight(), matrix, true);
            }
        }

        canvas.save();
        //canvas.rotate(eyeAngleAndScaleCalc.getTopEyeAngle(), eyeAngleAndScaleCalc.topP1.x, eyeAngleAndScaleCalc.topP1.y);
        canvas.drawBitmap(scaledBitmapTop,
                eyeAngleAndScaleCalc.topP1.x - (int) (bean.topP1.x * eyeAngleAndScaleCalc.topScaleX),
                eyeAngleAndScaleCalc.topP1.y - (int) (bean.topP1.y * eyeAngleAndScaleCalc.topScaleY), paint);
        canvas.restore();

        if(scaledBitmapBottom ! =null) {
            canvas.save();
            canvas.rotate(eyeAngleAndScaleCalc.getBottomEyeAngle(), eyeAngleAndScaleCalc.bottomP1.x, eyeAngleAndScaleCalc.bottomP1.y);
            canvas.drawBitmap(scaledBitmapBottom, eyeAngleAndScaleCalc.bottomP1.x,
                    eyeAngleAndScaleCalc.bottomP1.y - (int) (bean.bottomP1.y * eyeAngleAndScaleCalc.bottomScaleY), paint);
            canvas.restore();
            scaledBitmapBottom.recycle();
        }
        scaledBitmapTop.recycle();
    }
Copy the code

For the eye part, it is slightly more complicated. For the specific code, you can check Github Makeup. If you think it is ok, can you give a star? thank you

other

We know that the above content is just a little makeup on the face, that to become a real “beauty”, there may be a question mark? Then what kind of makeup is the real beauty, generally is a good person. In addition to the makeup is more beautiful, that a photo, to change the bottom good, generally have those ways? Here are some ideas (including beauty)

public enum BeautyType {

    INPAINT(1."The spot"),
    SMALLFACE(2."Thin face"),
    LONGLEG(3."Longer legs, taller legs."),
    EYE(4."Eye enlargement"),
    BREST(5."Breast enhancement"),
    WHITE(7."White"),
    MAKEUP(8."Beauty"),
    SMALLBODY(9."Thin face, thin body.");

    private int type;
    private String name;

    BeautyType(int type, String name) {
        this.type = type;
        this.name = name;
    }

    public int getType(a) {
        return type;
    }

    public String getName(a) {
        returnname; }}Copy the code

If only for the face, then only need, skin, whitening, freckle removal, big eyes, thin face and other functions.

At the end of the article

This is the end of today’s article sharing, these algorithms, the current KNOWLEDGE network paper database, can be easily implemented after viewing. If you still want to make the next blog, to create a perfect body.

The above content resources, after use, please delete within 24 hours, if there is infringement, please contact the author immediately delete.

There is also a fun blog post about the implementation of the Flutter version of the Flutter PIP effect

Recommended reading

Analysis of Flutter high performance Principle of Android drawing Principle