如何在Bitmap周围创建浮雕?

时间:2015-06-07 19:14:12

标签: android bitmap android-bitmap porter-duff emboss

热门游戏Words with Friends在游戏主板中将字母图块作为单个实体绘制 -

您可以在以下屏幕截图中看到应用于所有字母图块的黄色线性渐变,并且边缘还有浮雕效果:

app screenshot

my word game我希望有类似的效果:

emboss example

所以我创建了一个大小为mBitmap的游戏板,然后将所有图块绘制到其中,最后将位图绘制到my custom view -

设定:

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

app screenshot

我的问题:是否可以使用PorterDuff.Mode(和extractAlpha?)的某些组合在我的大位图周围绘制浮雕?

更新

通过查看HolographicOutlineHelper.java,我已经能够添加外部阴影:

app screenshot

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 个答案:

答案 0 :(得分:5)

我不确定我得到了你需要的东西,但是如果你只是想用一些带有alpha通道的png字母来应用EmbossMaskFilter,你几乎可以用

来做这个伎俩
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();每次都会创建新的Bitmap。 (顺便说一句,我总是从你的项目git中得到内存错误。添加了mAlphaBitmap.recycle();,它至少可以启动。但它仍然像地狱一样落后)

所以,我玩你的git存储库并获得了一些结果。这是演示图像和git repo of first commit

enter image description here

但后来我意识到,你不需要在字母周围使用EmbossMaskFilter,你需要它们围绕矩形。它可以以完全相同的方式完成。我是这样做的:

  1. 为浮雕背景创建新的辅助静态位图和画布,就像mAlphaBitmap
  2. 在每个prepareBitmaps()上绘制对辅助位图的影响。没有alpha的纯色。
  3. 从创建的位图中提取alpha,例如Bitmap alpha = helperCanvas.extractAlpha();
  4. 使用带浮雕过滤器helperCanvas.drawBitmap(alpha, 0, 0, paintEmboss);
  5. 的绘画在辅助对象上绘制提取的alpha位图
  6. 在onDraw中打印helperBitmap,在主Bitmap之前有一些alpha。
  7. 这是没有alpha的屏幕截图(因为以这种方式查看形状要容易得多) enter image description here

    以下是此版本的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);
        // ...
    }
    

    我做的最后一个步骤是将所有内容从onDraw移动到prepareBitmaps(),现在性能很好,但是我们在调整大小时会有文本失效。所以这一步是source code

    这是一个很好的工作final solution。使用过滤器移动所有涂料解决了性能问题,但我认为还有更好的选择来实现这一点。正如我所说的那样,我不知道你需要什么,但这段代码几乎创造了围绕Bitmap的Emboss

    PS:分裂和添加细胞时有点酷的效果

    PS2:new EmbossMaskFilter(new float[] { 0f, 1f, 0.5f }, 0.8f, 3f, 3f);这在具有不同屏幕分辨率的不同设备上看起来不一样

答案 1 :(得分:2)

以下是使用自定义布局的建议。

您需要自己的拼字游戏布局。由于它的网格,这应该很容易编码。

基本思想是拥有一组PNG阴影图像,每种类型的相邻细胞组合一个。在你的布局onDraw()中,首先绘制阴影,然后在onLayout()中绘制tile。

在onDraw()中,遍历您的tile占位符数组。如果您有一个图块,则为每个边缘检查相邻的单元格。根据相邻的内容,选择正确的阴影图像并绘制它。

您可以通过使阴影图像正好是图块的宽度,然后专门设置角区域来减少阴影图像的数量:一个用于270度,一个用于直线对齐,一个用于90度。

我不知道使用porter-duff是否有帮助,因为你还需要确定所有这些" edge"案件(没有双关语)。