2015-06-07 16 views
7

Il popolare gioco Words with Friends disegna tessere al tabellone di gioco come una singola entità -Come creare rilievo su una bitmap?

Potete vedere una linea gialla gradiente applicato a tutte le tessere nel seguente screenshot e anche un effetto rilievo sul bordo :

app screenshot

In my word game Vorrei avere effetti simili:

emboss example

Così ho creare un gioco da tavolo di dimensioni mBitmap, quindi disegnare tutte le tessere in esso e infine disegnare la bitmap in my custom view -

Setup:

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

Disegno:

@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 effetto funziona bene con drawText() e drawRect() chiamate, ma non funziona per il drawBitmap():

app screenshot

La mia domanda: è possibile utilizzare alcune combinazioni di PorterDuff.Mode per disegnare un (e extractAlpha?) rilievo attorno alla mia grande bitmap?

UPDATE:

Osservando HolographicOutlineHelper.java sono stato in grado di aggiungere un'ombra esterna:

app screenshot

con il seguente codice nel MyView.java -

Setup:

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); 
} 

Disegno:

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); 
} 

ma purtroppo l'applicazione agisce lento ora - e io ancora non so come aggiungere un effetto rilievo in tutto il bitmap.

risposta

5

non sono sicuro ho ottenuto exacly ciò che è necessario, ma se si desidera solo applicare EmbossMaskFilter intorno a qualche lettera PNG con canale alfa, si può tranquillamente fare questo trucco con

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

Lei non potrà mai vuoi tutto questo codice in 1 suDraw, perché crea molti oggetti in memoria. E src.extractAlpha(); crea ogni volta una nuova Bitmap. (A proposito, ho sempre eliminato l'errore di memoria dal tuo git del progetto.Aggiunto mAlphaBitmap.recycle(); e potrebbe almeno avviarsi, ma è ancora lento come l'inferno)

Così, ho giocato con il tuo repository git e ottenuto alcuni risultati. Ecco un'immagine demo e git repo of first commit:

enter image description here

Ma poi ho capito che non è necessario EmbossMaskFilter intorno le lettere, ne avete bisogno in giro rettangoli. E può essere fatto più o meno allo stesso modo. Ecco come ho fatto questo:

  1. Crea nuova bitmap statica aiuto e della tela di canapa per lo sfondo imprima, proprio come mAlphaBitmap
  2. Su ogni prepareBitmaps() rettangoli di vernice su bitmap aiutante. Colore solido senza alfa.
  3. estratto alpha da bitmap creato in questo modo Bitmap alpha = helperCanvas.extractAlpha();
  4. Draw estratto alpha bitmap helper con vernice con filtro rilievo helperCanvas.drawBitmap(alpha, 0, 0, paintEmboss);
  5. In OnDraw helperBitmap stampa con alcuni alfa prima Bitmap principale.

Ecco screenshot senza alfa (perché è molto più facile vedere le forme in questo modo) enter image description here

Ecco git demo di questa versione: https://github.com/varren/AndroidEmbossMaskFilterForPng/blob/1d692d576e78bd434252a8a6c6ad2ee9f4c6dbd8/app/src/main/java/de/afarber/mytiles2/MyView.java

E qui è parte essenziale di codice che ho modificato nel progetto:

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); 
    // ... 
} 

E l'ultimo passaggio 3-d i fatto è quello di spostare tutto da onDraw a prepareBitmaps() e la preformance va bene ora, ma abbiamo ridimensionamento del testo su ridimensionamento. quindi ecco source code per questo passaggio.

E qui funziona un po 'come final solution. Lo spostamento di tutte le vernici con i filtri ha risolto i problemi di preformance, ma penso che ci siano ancora opzioni migliori per implementarlo.Come ho detto erlier non so è che cosa avete bisogno, ma questo codice crea praticamente rilievo intorno Bitmap

PS: effetto un pò freddo, quando la scissione e cellule sommando

PS2: new EmbossMaskFilter(new float[] { 0f, 1f, 0.5f }, 0.8f, 3f, 3f); Questo non guardare lo stesso su dispositivi diversi con diversa risoluzione dello schermo

+0

Grazie ... Puoi rendere il rilievo in rilievo stesso moltiplicando con 'mScale = getResources(). GetDisplayMetrics(). Density;' –

+1

Sì, volevo solo sottolineare che potrebbe essere un problema nel codice dimostrativo e l'effetto rilievo potrebbe essere difficile vedere a causa della risoluzione dello schermo. – varren

2

Ecco un suggerimento utilizzando un layout personalizzato.

Avrai bisogno del tuo layout per la scheda scrabble. Dal momento che è una griglia, questo dovrebbe essere abbastanza facile da codificare.

L'idea di base è avere un set di immagini shadow PNG, una per ogni tipo di combinazione di celle adiacenti. Nel tuo layout suDraw(), disegna prima le ombre, poi disegna la tessera in onLayout().

In onDraw(), scorrere l'array di segnaposto per tessere. Se hai una tessera, quindi per ciascun lato, ispeziona le celle adiacenti. A seconda di ciò che è adiacente, scegli l'immagine ombra corretta e disegnala.

È possibile ridurre in modo sostanziale il numero di immagini shadow avendo un'immagine ombreggiata che corrisponde esattamente alla larghezza di una piastrella e quindi specializzando l'area d'angolo: una per 270 gradi, una per l'allineamento retto, una per 90 gradi.

Non so se l'utilizzo di Porter-Duff può essere d'aiuto poiché è ancora necessario determinare tutti questi casi "marginali" (nessun gioco di parole previsto).

+0

Sì, è così che Word with Friends lo fa (guardando le sue numerose risorse grafiche) ... ma mi piacerebbe trovare un modo programmatico per disegnare un rilievo attorno a tutte le tessere . –