Как преобразовать растровое изображение Android, чтобы обернуть цилиндр и изменить перспективу

Я написал образец приложения, которое позволяет пользователю 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);
}

Этот вопрос / answer может предоставить подробную информацию о том, как изменить перспективу, но я не верю, что он отвечает на вопрос о моделировании обтекания текста вокруг цилиндра.


person Dale    schedule 14.03.2017    source источник
comment
Вы нашли на это ответ?   -  person selva_pollachi    schedule 16.03.2017
comment
@selva_pollachi, ответа пока нет. Тем не менее, я планирую поддерживать этот вопрос по мере моего прогресса. Я предполагаю, что ответ будет включать OpenGL, GLSurfaceView.Renderer и т. Д.   -  person Dale    schedule 17.03.2017
comment
хорошо, чувак. Если вы найдете ответ на родном языке, отличном от OpenGL, разместите здесь   -  person selva_pollachi    schedule 18.03.2017


Ответы (1)


Одно из решений этой проблемы - использовать 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;
}

Чтобы интегрировать этот новый метод в код исходного вопроса, мы определяем некоторые элементы для управления внешним видом результата (curveFactor, topBottomRatio). Переменную steps можно настроить для получения более плавного результата. Наконец, мы определили элементы для входа в метод drawBitmapMesh() (columns, scaledVertices). Тогда вместо использования 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);

Android Layout в форме цилиндра

person Dale    schedule 22.03.2017
comment
Спасибо чувак. Я это проверю - person selva_pollachi; 15.04.2017