Applying Android drawBitmapMesh for Image Warping Effects (Face Slimming, Curtain, Water Ripple)
This article explains how Android's drawBitmapMesh method can be used to split a bitmap into a mesh, manipulate the vertex array to achieve various image‑warping effects such as face slimming, curtain folding and water‑ripple animations, and provides complete Java code examples and implementation steps.
The drawBitmapMesh API in Android divides a bitmap into a grid defined by meshWidth and meshHeight , producing (meshWidth+1)*(meshHeight+1) vertices whose coordinates are stored in a float[] verts array (even indices for X, odd for Y). By modifying these vertex coordinates you can deform the image in real time.
Key parameters :
bitmap : the source image to be warped.
meshWidth : number of horizontal cells.
meshHeight : number of vertical cells.
verts : array of vertex coordinates (size (meshWidth+1)*(meshHeight+1)*2 ).
vertOffset : start index in the verts array.
Face‑slimming demo
First the bitmap is loaded and the original vertex positions are saved in orig[] :
// number of cells
private int WIDTH = 200;
private int HEIGHT = 200;
// total vertices
private int COUNT = (WIDTH + 1) * (HEIGHT + 1);
private float[] verts = new float[COUNT * 2];
private float[] orig = new float[COUNT * 2];
private void initView() {
int index = 0;
Bitmap mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test00);
float bmWidth = mBitmap.getWidth();
float bmHeight = mBitmap.getHeight();
for (int i = 0; i < HEIGHT + 1; i++) {
float fy = bmHeight * i / HEIGHT;
for (int j = 0; j < WIDTH + 1; j++) {
float fx = bmWidth * j / WIDTH;
verts[index * 2] = fx;
orig[index * 2] = fx;
verts[index * 2 + 1] = fy;
orig[index * 2 + 1] = fy;
index++;
}
}
}When the user touches the screen, the warp method changes the vertices inside a circular radius r :
private void warp(float startX, float startY, float endX, float endY) {
float ddPull = (endX - startX) * (endX - startX) + (endY - startY) * (endY - startY);
float dPull = (float) Math.sqrt(ddPull);
dPull = screenWidth - dPull >= 0.0001f ? screenWidth - dPull : 0.0001f;
for (int i = 0; i < COUNT * 2; i += 2) {
float dx = verts[i] - startX;
float dy = verts[i + 1] - startY;
float dd = dx * dx + dy * dy;
float d = (float) Math.sqrt(dd);
if (d < r) {
double e = (r * r - dd) * (r * r - dd) / ((r * r - dd + dPull * dPull) * (r * r - dd + dPull * dPull));
double pullX = e * (endX - startX);
double pullY = e * (endY - startY);
verts[i] = (float) (verts[i] + pullX);
verts[i + 1] = (float) (verts[i + 1] + pullY);
}
}
invalidate();
}The drawing routine simply calls canvas.drawBitmapMesh(mBitmap, WIDTH, HEIGHT, verts, 0, null, 0, null) and optionally draws the deformation circle and direction line:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawBitmapMesh(mBitmap, WIDTH, HEIGHT, verts, 0, null, 0, null);
if (showCircle) {
canvas.drawCircle(startX, startY, r, circlePaint);
}
if (showDirection) {
canvas.drawLine(startX, startY, moveX, moveY, directionPaint);
}
}A reset button restores the original vertices by copying orig[] back to verts[] :
public void resetView() {
for (int i = 0; i < verts.length; i++) {
verts[i] = orig[i];
}
invalidate();
}Other effects
The same technique can create curtain‑fold and water‑ripple animations. For the ripple effect a mask bitmap with a gradient shader is generated when hardware acceleration does not support the colors argument of drawBitmapMesh :
private void setupMask() {
if (!newApiFlag && bitmap != null) {
shadowMask = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
Canvas maskCanvas = new Canvas(shadowMask);
// compute wave parameters, build color array and offsets
// ... (omitted for brevity) ...
maskShader = new LinearGradient(offset, 0, singleWave + offset, 0, colors, offsets, Shader.TileMode.REPEAT);
paint.setShader(maskShader);
maskCanvas.drawRect(0, 0, bitmap.getWidth(), bitmap.getHeight(), paint);
paint.setShader(null);
}
}During onDraw the mesh is drawn with the calculated vertices and, when newApiFlag is false, the shadow mask is drawn on top using the prepared paint:
if (!newApiFlag) {
paint.setAlpha(alpha);
canvas.drawBitmapMesh(shadowMask, bitmapWidth, bitmapHeight, verts, 0, null, 0, paint);
paint.setAlpha(255);
}Finally, the article outlines the general workflow for using drawBitmapMesh :
Determine the mesh grid size.
Compute the original vertex array ( verts ) and keep a copy ( orig ).
Listen to touch or animation events and modify verts accordingly.
Call canvas.drawBitmapMesh inside onDraw to render the transformed bitmap.
The source of the warping algorithm is credited to http://www.gson.org/thesis/warping-thesis.pdf .
Sohu Tech Products
A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.