Как создать тиснение вокруг растрового изображения?


Популярная игра слова с друзьями рисует буквы плитки на игровом поле как единое целое -

Вы можете увидеть желтый линейный градиент , примененный ко всем буквенным плиткам на следующем снимке экрана, а также эффект тиснения на краю:

скриншот приложения

В моей игре в слова я хотел бы иметь аналогичные эффекты:

пример тиснения

Поэтому я создаю игровую доску размером mBitmap, затем рисую все плитки в нее и, наконец, нарисуйте растровое изображение в мой пользовательский вид -

Настройка:

setLayerType(View.LAYER_TYPE_SOFTWARE, null);

// create yellow linear gradient
mGradStart = new Point(3 * mWidth / 4, mHeight / 3);
mGradEnd = new Point(mWidth / 4, 2 * mHeight / 3);
LinearGradient gradient = new LinearGradient(
        mGradStart.x,
        mGradStart.y,
        mGradEnd.x,
        mGradEnd.y,
        new int[]{ 0xCCFFCC00, 0xCCFFCC99, 0xCCFFCC00 },
        null,
        TileMode.CLAMP);

// create the big bitmap holding all tiles
mBitmap = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mBitmap);

mPaintGrad = new Paint();
mPaintGrad.setShader(gradient);

mPaintEmboss = new Paint();
mPaintEmboss.setShader(gradient);

EmbossMaskFilter filter = new EmbossMaskFilter(
    new float[] { 0f, 1f, 0.5f }, 0.8f, 3f, 3f);
mPaintEmboss.setMaskFilter(filter);

Рисунок:

@Override
protected void onDraw(Canvas canvas) {
    mGameBoard.draw(canvas);

    // draw all tiles as rectangles into big bitmap 
    // (this code will move to onTouchEvent later)
    mBitmap.eraseColor(Color.TRANSPARENT);
    for (SmallTile tile: mTiles) {
        mCanvas.drawRect(
                tile.left, 
                tile.top, 
                tile.left + tile.width, 
                tile.top + tile.height, 
                mPaintGrad);
        tile.draw(mCanvas);
    }
    canvas.drawBitmap(mBitmap, 0, 0, mPaintEmboss); // emboss NOT displayed
    canvas.drawText("TEXT WORKS OK", 400, 400, mPaintEmboss); // ebmoss OK
    canvas.drawRect(300, 600, 800, 1200, mPaintEmboss); // emboss OK
}

Эффект EmbossMaskFilter хорошо работает с вызовами drawText() и drawRect(), но он не работает для drawBitmap():

скриншот приложения

Мой вопрос: можно ли использовать некоторые комбинации PorterDuff.Режим extractAlpha?) нарисовать тиснение вокруг моего большого растрового изображения?

Обновление:

Глядя на голографический Каутлинхелпер.java я смог добавить внешнюю тень:

скриншот приложения

Со следующим кодом в MyView.java -

Настройка:

public MyView(Context context, AttributeSet attrs) {
    super(context, attrs);

    mScale = getResources().getDisplayMetrics().density;
    mGradStart = new Point(3 * mWidth / 4, mHeight / 3);
    mGradEnd = new Point(mWidth / 4, 2 * mHeight / 3);
    LinearGradient gradient = new LinearGradient(
            mGradStart.x,
            mGradStart.y,
            mGradEnd.x,
            mGradEnd.y,
            new int[]{ 0xCCFFCC00, 0xCCFFCC99, 0xCCFFCC00 },
            null,
            TileMode.CLAMP);

    mBitmap = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888);
    mCanvas = new Canvas(mBitmap);

    mPaintGrad = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
    mPaintGrad.setShader(gradient);

    mPaintBlur = new Paint();
    mPaintBlur.setColor(Color.BLACK);
    BlurMaskFilter blurFilter = new BlurMaskFilter(mScale * 1, Blur.OUTER);
    mPaintBlur.setMaskFilter(blurFilter);
}

Рисунок:

private void prepareBitmaps() {
    mBitmap.eraseColor(Color.TRANSPARENT);
    for (SmallTile tile: mTiles) {
        mCanvas.drawRect(
                tile.left, 
                tile.top, 
                tile.left + tile.width, 
                tile.top + tile.height, 
                mPaintGrad);
        tile.draw(mCanvas);
    }

    mAlphaBitmap = mBitmap.extractAlpha(mPaintBlur, mOffset);
}

@Override
protected void onDraw(Canvas canvas) {
    mGameBoard.draw(canvas);
    canvas.drawBitmap(mAlphaBitmap, mOffset[0], mOffset[1], mPaintBlur);
    canvas.drawBitmap(mBitmap, 0, 0, mPaintGrad);
}
Но, к сожалению, приложение теперь работает медленно , и я до сих пор не знаю, как добавить эффект тиснения вокруг растрового изображения.
2 7

2 ответа:

Я не уверен, что получил именно то, что вам нужно, но если вы просто хотите применить EmbossMaskFilter вокруг какой-то буквы png с альфа-каналом, вы можете в значительной степени сделать этот трюк с

EmbossMaskFilter filter = new EmbossMaskFilter(new float[]{1, 1, 1}, 0.5f, 0.6f, 2f);

Paint paintEmboss = new Paint();
paintEmboss.setMaskFilter(embossMaskFilter);

Bitmap helperBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas helperCanvas = new Canvas(helperBitmap);

Bitmap alpha = src.extractAlpha();
helperCanvas.drawBitmap(alpha, 0, 0, paintEmboss);
alpha.recycle();

...
canvas.drawBitmap(helperBitmap, 0, 0, anyPaint);

Вам никогда не понадобится весь этот код в 1 onDraw, потому что он создает много объектов в памяти. И src.extractAlpha(); создает новый растровый рисунок каждый раз. (Кстати, я всегда получаю из памяти ошибку от вашего проекта git . Добавлено mAlphaBitmap.recycle(); , и он может, по крайней мере, загрузиться. Но он все равно отстает, как черт)

Итак, я играл с ваш Git репозиторий и получил некоторые результаты. Вот демонстрационный образ и git repo первого коммита :

Введите описание изображения здесь

Но потом я понял, что вам не нужен фильтр Embossmask вокруг букв, они нужны вокруг прямоугольников. И это можно сделать примерно так же. Вот как я это сделал:
  1. создайте новый вспомогательный статический растровый рисунок и холст для тиснения фона, как и mAlphaBitmap
  2. на каждом prepareBitmaps () рисует rects на вспомогательном растровом изображении. Чистый цвет без Альфы.
  3. извлеките Альфа-код из созданного растрового изображения следующим образом Bitmap alpha = helperCanvas.extractAlpha();
  4. нарисуйте извлеченный Альфа-растровый рисунок на хелпере с помощью краски с фильтром тиснения helperCanvas.drawBitmap(alpha, 0, 0, paintEmboss);
  5. в onDraw выведите helperBitmap с некоторым Альфа перед основным растровым изображением.

Вот скриншот без Альфы(потому что так гораздо проще видеть фигуры) Введите описание изображения здесь

Вот git демо этой версии: https://github.com/varren/AndroidEmbossMaskFilterForPng/blob/1d692d576e78bd434252a8a6c6ad2ee9f4c6dbd8/app/src/main/java/de/afarber/mytiles2/MyView.java

И вот существенная часть кода, которую я изменил в вашем проекте:

private static final EmbossMaskFilter filter = 
                 new EmbossMaskFilter(new float[]{1, 1, 1}, 0.5f, 0.6f, 2f);
private static Canvas helperCanvas;
private static Paint paintEmboss;

public  Canvas getHelperCanvas(int width, int height){
    if (mAlphaBitmap == null) {
        mAlphaBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        helperCanvas = new Canvas(mAlphaBitmap);
        paintEmboss = new Paint();
        paintEmboss.setColor(Color.BLACK);
    }
    return helperCanvas;
}

private void prepareBitmaps() {
    mCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
    helperCanvas = getHelperCanvas(mBitmap.getWidth(),mBitmap.getHeight());
    helperCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
    paintEmboss.setMaskFilter(null);
    paintEmboss.setAlpha(255);

    for (SmallTile tile: mTiles) {
        if (!tile.visible) continue;
        helperCanvas.drawRect(tile.left,tile.top,tile.left + tile.width,
                tile.top + tile.height,paintEmboss);
        mCanvas.drawRect(tile.left, tile.top,tile.left + tile.width, 
                tile.top + tile.height, mPaintGrad);
        tile.draw(mCanvas);
    }

    paintEmboss.setMaskFilter(filter);
    Bitmap alpha = mAlphaBitmap.extractAlpha();
    helperCanvas.drawBitmap(alpha, 0, 0, paintEmboss);
}

protected void onDraw(Canvas canvas) {
     // ...
     paintEmboss.setAlpha(255); //todo change alpha here
    if(mAlphaBitmap!= null)canvas.drawBitmap(mAlphaBitmap, 0,0, paintEmboss);
    if(mBitmap!= null)canvas.drawBitmap(mBitmap, 0, 0, mPaintGrad);
    // ...
}

И последний 3-d шаг, который я сделал, - это переместить все из onDraw в prepareBitmaps(), и преформанс теперь в порядке, но у нас есть текстовое разрушение при изменении размера. Итак, вот исходный код для этого шага.

И здесь вроде как отлично работает окончательное решение . Перемещение всех красок с фильтрами решило проблемы преформанса, но я думаю, что есть еще лучшие варианты для реализации этого. Как я уже сказал erlier, я не знаю, что вам нужно, но этот код в значительной степени создает тиснение вокруг Bitmap

ЗЫ: прикольно, когда эффект разделения и добавления ячеек

PS2: new EmbossMaskFilter(new float[] { 0f, 1f, 0.5f }, 0.8f, 3f, 3f); это не будет выглядеть одинаково на разных устройствах с разным разрешением экрана

Вот предложение с использованием пользовательского макета.

Вам понадобится свой собственный макет для доски scrabble. Поскольку это сетка, это должно быть довольно легко кодировать.

Основная идея состоит в том, чтобы иметь набор теневых изображений PNG, по одному для каждого типа комбинации соседних ячеек. В вашем макете onDraw () сначала нарисуйте тени, а затем нарисуйте плитку в onLayout().

В onDraw () выполните итерацию по массиву заполнителей плиток. Если у вас есть плитка, то для каждого края осмотрите соседние клетки. В зависимости от того, что находится рядом, выберите правильное теневое изображение и нарисуйте его.

Вы можете существенно уменьшить количество теневых изображений, имея теневое изображение, которое точно соответствует ширине плитки, а затем специализируя угловую область: один для 270 градусов, один для прямого выравнивания, один для 90 градусов.

Я не знаю, Может ли использование porter-duff помочь, так как вам все еще нужно определить все эти "крайние" случаи (без каламбура).