如何转换 Android 位图以包裹圆柱体并改变视角

How to transform an Android bitmap to wrap a cylinder and change perspective

我编写了一个示例应用程序,允许 Android 用户拍照并将视图中的文本内容叠加在图像上并保存到图库相册:

我想做的是在加入两个图像之前转换文本位图。具体来说,我想让文本在两侧向上弯曲(模拟环绕圆柱体),并使其顶部比底部大(模拟自上而下的视角),如下所示:

无需解释相机图像即可确定曲率或透视变化量。问题是如何操作位图,以便可以进行两个转换。

这是我用来将未转换的文本放入相机图像和图库中的代码:

private void combinePictureWithText(String fileName) {
    Log.v(TAG, "combinePictureWithText");

    int targetW = getWindowManager().getDefaultDisplay().getWidth();
    int targetH = getWindowManager().getDefaultDisplay().getHeight();

    /* Get the size of the image */
    BitmapFactory.Options bmOptions = new BitmapFactory.Options();
    bmOptions.inJustDecodeBounds = true;
    BitmapFactory.decodeFile(fileName, bmOptions);
    int photoW = bmOptions.outWidth;
    int photoH = bmOptions.outHeight;

    /* Figure out which way needs to be reduced less */
    int scaleFactor = 1;
    if ((targetW > 0) || (targetH > 0)) {
        scaleFactor = Math.min(photoW/targetW, photoH/targetH);
    }
    Log.v(TAG, "Scale Factor: " + scaleFactor);

    /* Set bitmap options to scale the image decode target */
    bmOptions.inJustDecodeBounds = false;
    bmOptions.inSampleSize = scaleFactor;
    bmOptions.inPurgeable = true;

    mBeerLayout.setDrawingCacheEnabled(true);
    Bitmap mDrawingCache = mBeerLayout.getDrawingCache();

    Bitmap cameraBitmap = BitmapFactory.decodeFile(fileName, bmOptions);
    Bitmap textBitmap = Bitmap.createBitmap(mDrawingCache);
    Bitmap combinedBitmap = Bitmap.createBitmap(targetW, targetH, Bitmap.Config.ARGB_8888);
    Canvas comboImage = new Canvas(combinedBitmap);
    cameraBitmap = Bitmap.createScaledBitmap(cameraBitmap, targetW, targetH, true);
    comboImage.drawBitmap(cameraBitmap, 0, 0, null);
    comboImage.drawBitmap(textBitmap, 0, 0, null); // WAS: matrix (instead of 0, 0)

    /* Save to the file system */
    Log.v(TAG, "save combined picture to the file system");
    try {
        File aFile = new File(fileName);
        if (aFile.exists()) {
            Log.v(TAG, "File " + fileName + "  existed. Deleting it.");
            //aFile.delete();
        } else {
            Log.v(TAG, "File " + fileName + " did not exist.");
        }
        FileOutputStream out = new FileOutputStream(fileName);
        combinedBitmap.compress(Bitmap.CompressFormat.JPEG, 90, out);
        out.flush();
        out.close();
        Log.v(TAG, "Saved " + fileName);
    } catch (Exception e) {
        Log.v(TAG, "Failed in file output stream " + e.getMessage());
    }

    /* Associate the Bitmap to the ImageView */
    //mImageView.setImageBitmap(combinedBitmap); // DRS was "bitmap"
    //mImageView.setVisibility(View.VISIBLE);

    /* Add as a gallery picture */
    Log.v(TAG, "galleryAddPic");
    Intent mediaScanIntent = new Intent("android.intent.action.MEDIA_SCANNER_SCAN_FILE");
    File f = new File(fileName);
    Uri contentUri = Uri.fromFile(f);

    mediaScanIntent.setData(contentUri);
    this.sendBroadcast(mediaScanIntent);
}

可能会提供有关如何进行透视更改的详细信息,但我认为它无法回答模拟围绕圆柱体环绕文本的问题。

此问题的一个解决方案是使用 Canvas.drawBitmapMesh()。此解决方案的优点是 不需要 OpenGL.

的额外复杂性

使用 mesh 背后的想法是,将任何位图馈送到函数中都会被扭曲以适合定义的点。该图显示了每个椭圆只有 5 个点时发生的情况: 因此,位于左侧矩形区域的位图部分将被操纵以适应由我们计算的顶点定义的区域的左侧多边形的形状。

两个椭圆的建模部分提供了网格的值。

计算顶点的方法有很多种,通常可以满足要求,并且这种实现不一定是最好的、最有效的、最易理解的,但它的好处是可以满足要求关于原始问题。

private static float[] computeVerticesFromEllipse(int width, int height, int steps, float curveFactor, float topBottomRatio) {
    double p = width / 2d;
    double q = 0d;
    double a = width / 2d;
    double b = curveFactor * a;

    float[] verticies = new float[(steps-1) * 4];
    int j = 0;

    double increment = width / (double)steps;

    for (int i = 1; i < steps; i++, j=j+2) {
        verticies[j] = (float)(increment * (double)i);
        verticies[j+1] =-(float) (-Math.sqrt((1-Math.pow(((increment * (double)i)-p), 2)/Math.pow(a,2)) * Math.pow(b,2)) + q);
        Log.v(TAG, "Point, " + verticies[j] + ", " + verticies[j+1] + ", " + j + ", " +(j+1));
    }

    double width2 = topBottomRatio * width;
    p = width2 / 2d;
    q = (width - width2) / 2d;
    a = width2 / 2d;
    b = curveFactor * a;
    increment = width2 / (double)steps;

    double shift = width * (1d - topBottomRatio) / 2d;

    for (int i = 1; i < steps; i++, j=j+2) {
        verticies[j] = (float)(increment * (double)i) + (float)shift;
        verticies[j+1] =(float) -(-Math.sqrt((1-Math.pow(((increment * (double)i)-p), 2)/Math.pow(a,2)) * Math.pow(b,2)) + q)+ height;
        Log.v(TAG, "Point, " + verticies[j] + ", " + verticies[j+1] + ", " + j + ", " +(j+1));
    }

    return verticies;
}

为了将这个新方法集成到原始问题的代码中,我们定义了一些项目来控制结果的外观curveFactortopBottomRatio).可以调整 steps 变量以获得更平滑的外观结果。最后,我们定义了项目进入drawBitmapMesh()方法(columnsscaledVertices)。然后,我们不使用 drawBitmap(),而是使用 drawBitmapMesh()

    // 2017 03 22 - Added 5
    float curveFactor = 0.4f;
    float topBottomRatio = 0.7f;
    int steps = 48;
    float[] scaledVertices = computeVerticesFromEllipse(textBitmap.getWidth(), textBitmap.getHeight(), steps, curveFactor, topBottomRatio);
    int columns = (scaledVertices.length / 4) - 1; // divide by 2 since for two points, divide by 2 again for two rows

    Bitmap combinedBitmap = Bitmap.createBitmap(targetW, targetH, Bitmap.Config.ARGB_8888);
    Canvas comboImage = new Canvas(combinedBitmap);
    cameraBitmap = Bitmap.createScaledBitmap(cameraBitmap, targetW, targetH, true);
    comboImage.drawBitmap(cameraBitmap, 0, 0, null);
    // 2017 03 22 - Commented 1, Added 1
    // comboImage.drawBitmap(textBitmap, 0, 0, null); 
    comboImage.drawBitmapMesh(textBitmap, columns, 1, scaledVertices, 0, null, 0, null);