2024年Android:让你的“女神”逆袭,代码撸彩妆,31道Android面试题

最后

对于程序员来说,要学习的知识内容、技术有太多太多,要想不被环境淘汰就只有不断提升自己,从来都是我们去适应环境,而不是环境来适应我们!

最后,我再重复一次,如果你想成为一个优秀的 Android 开发人员,请集中精力,对基础和重要的事情做深度研究

对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。整理的这些架构技术希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。

为了大家能够顺利进阶中高级、架构师,我特地为大家准备了一套高手学习的源码和框架视频等精品Android架构师教程,保证你学了以后保证薪资上升一个台阶。

以下是今天给大家分享的一些独家干货:

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

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;
}

事实证明这样是可以的,但是效果还是不咋行,那我们在用原图来做一次渐变,刚好可以达到效果

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;
}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

口红

关于口红也只是仅仅画上一层颜色,有了画笔,就可以和粉底一样的实现方式.

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

先看一下怎么连接的区域吧,为了方便,我直接采用了把外面的区域连接起来,然后在去做一次diff就可以了,代码如下

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);
}

//取不同的地方
outPath.op(inPath, Path.Op.DIFFERENCE);
return outPath;
} catch (JSONException e) {
e.printStackTrace();
}
return null;
}

Path.op()方法需要在API 19及以上才可以使用,如果使用了低版本的api,可以直接使用canvas.clipPath().

腮红

只有粉底,那看上去,还是有点假,那是不是需要用画笔画上一个腮红呢?但是形状什么,不好搞定,所以选择了直接使用腮红素材,直接贴上去.

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

实现也相对容易一些.

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);

}

眉毛

眉毛这个其实困扰了我很长时间,因为要把底部的眉毛给扣了,在装新的眉毛在上面,不然可能完全盖不住,眉形变化,识别准确率,会导致效果的直接变化.尝试了很多方法其中OpenCV里有一个著名的inpaint方法的图片修复方法,看别人写的去书印demo,也都还行,但是放到这里去眉毛,效果很差,是因为我使用不对,还是什么问题,有大神可以指点,提取周边的皮肤颜色去掉原来的眉毛.

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

最终还是放弃了去掉原来的眉毛,直接覆盖眉毛.

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

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);
}

最终效果

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

但是文中的开始给的效果那张照片,因为识别偏差,导致效果不太好.

眼睛(睫毛,眼影,双眼皮,眼线,美瞳)

眼睛部分是最复杂的部分了,因为可以画的实在是太多了.

这就将两个地方的实现,其他具体实现可以参考实际代码,先看一下这些不是主要的素材吧

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

美瞳

要向眼睛里画美瞳,那么我们首先要有这个区域,区域人脸关键点已经给了,那么,我们知道,人的眼睛一般是椭圆性的,不可能直接是圆形的,所以画的时候,需要和眼睛的区域做一个交集来得到结果.

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

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);
//重点地方,做交集得到结果
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);
}

睫毛

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们知道,睫毛有上睫毛和下睫毛,那么怎么把这个眉毛画上去呢? 其实我们知道,一般把图片绘制到目标区域需要经过,平移,旋转,缩放来进行.

睫毛我们选取了素材上的三个点,和眼睛上的三个点来做上述的三个操作.

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

有了这三个点,我们就可以计算宽高比,角度,使用三角函数可以很容易计算得到.

旋转角度

使用人眼睛上对应的三个点来计算旋转角度,(如果人的头像是正的,可以不用计算,但是人可能偏头,什么,需要计算旋转角度,来warp)

/**

  • @param p1 三角形顶点
  • @param p2 三角形顶点
  • @param p3 三角形顶点
  • @return 三角形顶点p3 到 p1,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;
    //计算三角形面积
    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));
    }

//获取坐标轴内两个点间的距离
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);
//两个点在 横纵坐标的差值与两点间的直线 构成直角三角形。length_pow等于该距离的平方
double length_pow = Math.pow(diff_x, 2) + Math.pow(diff_y, 2);
double sqrt = Math.sqrt(length_pow);
return sqrt == 0?0.001f:(float) sqrt;
}

static double pi180 = 180 / Math.PI;
public double getAngle(Point p1, Point p2, Point p3) {
double _cos1 = getCos(p1, p2, p3);//第一个点为顶点的角的角度的余弦值
return 90 - Math.acos(_cos1) * pi180;
}

宽高比旋转角度

有了角度,那么我们在计算宽高比.

/**

  • @param targetP1 缩放目标线段点p1
  • @param targetP2 缩放目标线段点p2
  • @param P1 待缩放线段点p1
  • @param P2 待缩放线段点p2
  • @return 水平高度比值
    */
    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);
    }

/**

  • @param targetP1 缩放目标三角形顶点
  • @param targetP2 缩放目标三角形顶点
  • @param targetP3 缩放目标三角形顶点
  • @param P1 待缩放三角形顶点
  • @param P2 待缩放三角形顶点
  • @param P3 待缩放三角形顶点
  • @return 垂直高度比值
    */
    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;
    }

平移

因为我们的图形是巨型,不可能从开始位置往上画,那就需要把画的位置通过平移,来达到第一个点的位置和对应位置的点,对应上.

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

有了这些步骤,那既可以直接合成绘制了,代码如下

public static void drawLash(Context context, Canvas canvas, EyeAngleAndScaleCalc.Bean bean, List 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); //镜像水平翻转
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();
}

眼睛部分,略微复杂一些,具体代码可以查看 Github Makeup ,如果你觉得还可以,可以给一个star吗?谢谢

学习分享,共勉

Android高级架构师进阶之路

题外话,我在阿里工作多年,深知技术改革和创新的方向,Android开发以其美观、快速、高效、开放等优势迅速俘获人心,但很多Android兴趣爱好者所需的进阶学习资料确实不太系统,完整。今天我把我搜集和整理的这份学习资料分享给有需要的人

  • Android进阶知识体系学习脑图

  • Android进阶高级工程师学习全套手册

  • 对标Android阿里P7,年薪50w+学习视频

  • 大厂内部Android高频面试题,以及面试经历

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

1715715363143)]

  • 对标Android阿里P7,年薪50w+学习视频

[外链图片转存中…(img-bg46sgDC-1715715363144)]

  • 大厂内部Android高频面试题,以及面试经历

[外链图片转存中…(img-mOrugUOs-1715715363144)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值