2015-07-22 10 views
6

Voglio mostrare l'aspetto 3D in rilievo come mostrato nell'immagine seguente. Ho usato EmbossMaskFilter ma non riesco a farlo mostrare l'effetto (vedi il codice sotto). C'è un modo diverso per farlo? o come posso usare EmbossMaskFilter per questo.Rilievo bordi di una forma immagine che mostra la profondità in Android

richieste Uscita

enter image description here

La mia uscita

enter image description here

Path path2 = new Path(); 
public Paint fillPaint = null; 
// called in constructor 
public void createPath() 
{ 
    //path 2 Big one 
    araay = new Point[]{new Point(144,320),new Point(109,200), new Point(171,308),new Point(178,240),new Point(171,172),new Point(109,282),new Point(144,160)}; 
    AddBeziers(path2, araay, 320, 144); 
    AddLine(path2, 216, 144); 
    AddLine(path2, 216, 216); 
    AddLine(path2, 144, 320); 

    MaskFilter mEmboss = new EmbossMaskFilter(new float[] { 1, 1, 1 }, 0.4f, 6, 3.5f); 
    fillPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 
    fillPaint.setColor(Color.WHITE); 
    fillPaint.setFlags(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG); 
    fillPaint.setAntiAlias(true); 
    fillPaint.setDither(true); 
    fillPaint.setStrokeJoin(Paint.Join.ROUND); 
    fillPaint.setStrokeCap(Paint.Cap.ROUND); 
    fillPaint.setStyle(Paint.Style.FILL); 
    paint.setMaskFilter(mEmboss); 
} 

// add lines to the path 
protected Path AddLine(Path path, int endX, int endY) { 
    //path.moveTo(startX, startY); 

    path.lineTo(endX, endY); 
    return path; 
} 

// add curves to the path 
protected Path AddBeziers(Path path, Point[] points, int lastX, int lastY) { 

    if (points[0].X != lastX && points[0].Y != lastY) 
     path.moveTo(points[0].X, points[0].Y); 

    int index = 1; 

    path.cubicTo(points[index].X, points[index].Y, points[index + 1].X, 
     points[index + 1].Y, points[index + 2].X, points[index + 2].Y); 
    index = index + 3; 
    path.cubicTo(points[index].X, points[index].Y, points[index + 1].X, 
     points[index + 1].Y, points[index + 2].X, points[index + 2].Y); 

    return path; 
} 

//draw on canvas 
@Override 
public void onDraw(Canvas canvas) { 

    canvas.drawPath(path2, fillPaint); 
    super.onDraw(canvas); 
} 

risposta

4

Se si desidera solo fare l'elaborazione bitmap (al contrario di 3D o vettori), la cosa migliore è probabilmente quello di:

  1. generare una maschera di stencil dal vostro pezzo di puzzle,
  2. Usa Difference of Gaussians per elaborare (Ho usato kernel di dimensioni 12 e 2 pixel in questo esempio), quindi normalizzare e invertire il risultato,
  3. Alpha-blend l'output di "2" nell'immagine originale utilizzando la maschera (1.) come canale stencil.

MaskDifference of GaussiansResult

UPDATE: ecco che arriva il codice. Ho provato a riutilizzare i nomi delle variabili in modo che sia più facile da capire. Il codice utilizza gli elementi intrinseci di Renderscript ogni volta che è possibile per rendere le cose più veloci e più interessanti.

private Paint fillPaint = null; 
private Path path2; 
private Bitmap mBitmapIn; 
private Bitmap mBitmapPuzzle; 
private RenderScript mRS; 
private Allocation mInAllocation; 
private Allocation mPuzzleAllocation; 
private Allocation mCutterAllocation; 

private Allocation mOutAllocation; 
private Allocation mOutAllocation2; 
private Allocation mAllocationHist; 
private ScriptIntrinsicBlur mScriptBlur; 
private ScriptIntrinsicBlend mScriptBlend; 
private ScriptIntrinsicHistogram mScriptHistogram; 
private ScriptIntrinsicLUT mScriptLUT; 
private Context ctx; 
private int bw = 780; 
private int bh = 780; 

private void init() 
{ 
    mBitmapIn = loadBitmap(R.drawable.cat7); // background image 
    mBitmapPuzzle = Bitmap.createBitmap(bw, bh, Bitmap.Config.ARGB_8888); // this will hold the puzzle 
    Canvas c = new Canvas(mBitmapPuzzle); 

    path2 = new Path(); 
    createPath(5); // create the path with stroke width of 5 pixels 
    c.drawPath(path2, fillPaint); // draw it on canvas 

    createScript(); // get renderscripts and Allocations ready 

    // Apply gaussian blur of radius 25 to our drawing 
    mScriptBlur.setRadius(25); 
    mScriptBlur.setInput(mPuzzleAllocation); 
    mScriptBlur.forEach(mOutAllocation); 

    // Now apply the blur of radius 1 
    mScriptBlur.setRadius(1); 
    mScriptBlur.setInput(mPuzzleAllocation); 
    mScriptBlur.forEach(mOutAllocation2); 

    // Subtract one blur result from another 
    mScriptBlend.forEachSubtract(mOutAllocation, mOutAllocation2); 

    // We now want to normalize the result (e.g. make it use full 0-255 range). 
    // To do that, we will first compute the histogram of our image 
    mScriptHistogram.setOutput(mAllocationHist); 
    mScriptHistogram.forEach(mOutAllocation2); 

    // copy the histogram to Java array... 
    int []hist = new int[256 * 4]; 
    mAllocationHist.copyTo(hist); 

    // ...and walk it from the end looking for the first non empty bin 
    int i; 
    for(i = 255; i > 1; i--) 
     if((hist[i * 4] | hist[i * 4 + 1] | hist[i * 4 + 2]) != 0) 
      break; 

    // Now setup the LUTs that will map the image to the new, wider range. 
    // We also use the opportunity to inverse the image ("255 -"). 
    for(int x = 0; x <= i; x++) 
    { 
     int val = 255 - x * 255/i; 

     mScriptLUT.setAlpha(x, 255); // note we always make it fully opaque 
     mScriptLUT.setRed(x, val); 
     mScriptLUT.setGreen(x, val); 
     mScriptLUT.setBlue(x, val); 
    } 


    // the mapping itself. 
    mScriptLUT.forEach(mOutAllocation2, mOutAllocation); 

Facciamo una breve pausa e vediamo cosa abbiamo finora. Osserva che l'intera immagine a sinistra è opaca (cioè includendo lo spazio al di fuori del puzzle), e ora dobbiamo ritagliare la forma e antialias correttamente il suo bordo. Sfortunatamente, l'uso della forma originale non funzionerà, poiché è troppo grande e taglia troppo, causando spiacevoli artefatti vicino al bordo (figura a destra).

enter image description here enter image description here

Abbiamo quindi disegnare un altro percorso, questa volta usando un colpo stretto ...

Bitmap mBitmapCutter = Bitmap.createBitmap(bw, bh, Bitmap.Config.ARGB_8888); 
    c = new Canvas(mBitmapCutter); 
    path2 = new Path(); 
    createPath(1); // stroke width 1 
    c.drawPath(path2, fillPaint); 
    mCutterAllocation = Allocation.createFromBitmap(mRS, mBitmapCutter); 

    // cookie cutter now 
    mScriptBlend.forEachDstIn(mCutterAllocation, mOutAllocation); 

... per un risultato molto più bello. Usiamolo per mascherare un'immagine di sfondo. enter image description here

mScriptBlend.forEachMultiply(mOutAllocation, mInAllocation); 
    mInAllocation.copyTo(mBitmapPuzzle); 
} 

enter image description here

Ciao a tutti! Ora solo il codice di installazione di Renderscript.

private void createScript() { 
    mRS = RenderScript.create(ctx); 

    mPuzzleAllocation = Allocation.createFromBitmap(mRS, mBitmapPuzzle); 

    // three following allocations could actually use createSized(), 
    // but the code would be longer. 
    mInAllocation = Allocation.createFromBitmap(mRS, mBitmapIn); 
    mOutAllocation = Allocation.createFromBitmap(mRS, mBitmapPuzzle); 
    mOutAllocation2 = Allocation.createFromBitmap(mRS, mBitmapPuzzle); 

    mAllocationHist = Allocation.createSized(mRS, Element.I32_3(mRS), 256); 

    mScriptBlur = ScriptIntrinsicBlur.create(mRS, Element.U8_4(mRS)); 
    mScriptBlend = ScriptIntrinsicBlend.create(mRS, Element.U8_4(mRS)); 
    mScriptHistogram = ScriptIntrinsicHistogram.create(mRS, Element.U8_4(mRS)); 
    mScriptLUT = ScriptIntrinsicLUT.create(mRS, Element.U8_4(mRS)); 
} 

E infine onDraw():

@Override 
protected void onDraw(Canvas canvas) { 
    canvas.drawBitmap(mBitmapPuzzle, 0, 0, fillPaint); 
    super.onDraw(canvas); 
} 

TODO: controllare se altri mitre ictus darebbe più piacevoli angoli.

+0

Questo è esattamente quello che cercavo. Potete per favore fornire maggiori informazioni su questo? Come implementarlo in modo efficiente in java/android? (Ho anche il path object.) – pats

+0

Inserirò un esempio di codice in poche ore. –

+0

Ciao grazie per la tua grande risposta. L'ho provato, ma non ha avuto alcun effetto. Si prega di vedere l'output di seguito. – pats

5

Penso che avrete bisogno di un grande raggio di sfocatura e valori più piccoli per la luce ambientale e speculare Ciao ghlight.

Ho giocato con questo creando un'interfaccia utente per modificare i parametri.

image 1

image 2

Ecco il codice che ho usato, in modo da poter provare. Ho usato un semplice rettangolo blu, ma dovresti essere in grado di collegare facilmente qualunque immagine tu voglia disegnare.

MainActivity.java:

import android.graphics.EmbossMaskFilter; 
import android.os.Bundle; 
import android.support.v7.app.ActionBarActivity; 
import android.view.Menu; 
import android.view.MenuItem; 
import android.widget.SeekBar; 
import android.widget.TextView; 

public class MainActivity extends ActionBarActivity { 

    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 
     setContentView(R.layout.activity_main); 

     final DrawView drawView = (DrawView) findViewById(R.id.drawView); 

     final TextView ambientLightTextView = (TextView) findViewById(R.id.ambientLightTextView); 

     SeekBar ambientLightSeekBar = (SeekBar) findViewById(R.id.ambientLightSeekBar); 
     ambientLightSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { 
      @Override 
      public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 
       float value = (float) progress/1000F; 
       ambientLightTextView.setText(Float.toString(value)); 
       drawView.setAmbientLight(value); 
      } 

      @Override 
      public void onStartTrackingTouch(SeekBar seekBar) {} 
      @Override 
      public void onStopTrackingTouch(SeekBar seekBar) {} 
     }); 

     final TextView specularHighlightTextView = (TextView) findViewById(R.id.specularHighlightTextView); 

     SeekBar specularHighlightSeekBar = (SeekBar) findViewById(R.id.specularHighlightSeekBar); 
     specularHighlightSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { 
      @Override 
      public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 
       float value = (float) progress/100f; 
       specularHighlightTextView.setText(Float.toString(value)); 
       drawView.setSpecularHighlight(value); 
      } 

      @Override 
      public void onStartTrackingTouch(SeekBar seekBar) {} 
      @Override 
      public void onStopTrackingTouch(SeekBar seekBar) {} 
     }); 

     final TextView blurRadiusTextView = (TextView) findViewById(R.id.blurRadiusTextView); 

     SeekBar blurRadiusSeekBar = (SeekBar) findViewById(R.id.blurRadiusSeekBar); 
     blurRadiusSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { 
      @Override 
      public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 
       float value = (float) progress/5F; 
       blurRadiusTextView.setText(Float.toString(value)); 
       drawView.setBlurRadius(value); 
      } 

      @Override 
      public void onStartTrackingTouch(SeekBar seekBar) {} 
      @Override 
      public void onStopTrackingTouch(SeekBar seekBar) {} 
     }); 
    } 

} 

DrawView.java:

import android.content.Context; 
import android.graphics.Canvas; 
import android.graphics.EmbossMaskFilter; 
import android.graphics.Paint; 
import android.graphics.Rect; 
import android.util.AttributeSet; 
import android.view.View; 

public class DrawView extends View { 

    private Rect rect = new Rect(); 

    private Paint paint; 

    private EmbossMaskFilter filter; 

    private float[] mLightSource = new float[] {2, 5, 5}; 

    private float mAmbientLight = 0.5F; 

    private float mSpecularHighlight = 8F; 

    private float mBlurRadius = 15f; 

    public DrawView(Context context) { 
     super(context); 
     init(); 
    } 

    public DrawView(Context context, AttributeSet attrs) { 
     super(context, attrs); 
     init(); 
    } 

    public DrawView(Context context, AttributeSet attrs, int defStyleAttr) { 
     super(context, attrs, defStyleAttr); 
     init(); 
    } 

    private void init() { 

     setLayerType(LAYER_TYPE_SOFTWARE, null); 

     filter = new EmbossMaskFilter(mLightSource, mAmbientLight, mSpecularHighlight, mBlurRadius); 

     paint = new Paint(); 
     paint.setColor(0xFF0000FF); 
     paint.setStyle(Paint.Style.FILL); 
     paint.setAntiAlias(true); 
     paint.setDither(true); 
     paint.setMaskFilter(filter); 
    } 

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

     rect.left = getWidth()/3; 
     rect.right = rect.left * 2; 
     rect.top = getHeight()/3; 
     rect.bottom = rect.top * 2; 

     canvas.drawRect(rect, paint); 
    } 

    public void setAmbientLight(float value) { 
     if (value > 1.0F) value = 1.0F; 
     if (value < 0) value = 0; 
     mAmbientLight = value; 
     filter = new EmbossMaskFilter(mLightSource, mAmbientLight, mSpecularHighlight, mBlurRadius); 
     paint.setMaskFilter(filter); 
     invalidate(); 
    } 

    public void setSpecularHighlight(float value) { 
     mSpecularHighlight = value; 
     filter = new EmbossMaskFilter(mLightSource, mAmbientLight, mSpecularHighlight, mBlurRadius); 
     paint.setMaskFilter(filter); 
     invalidate(); 
    } 

    public void setBlurRadius(float value) { 
     mBlurRadius = value; 
     filter = new EmbossMaskFilter(mLightSource, mAmbientLight, mSpecularHighlight, mBlurRadius); 
     paint.setMaskFilter(filter); 
     invalidate(); 
    } 
} 

activity_main.xml:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 
    xmlns:tools="http://schemas.android.com/tools" 
    android:layout_width="match_parent" 
    android:layout_height="match_parent" 
    android:paddingBottom="@dimen/activity_vertical_margin" 
    android:paddingLeft="@dimen/activity_horizontal_margin" 
    android:paddingRight="@dimen/activity_horizontal_margin" 
    android:paddingTop="@dimen/activity_vertical_margin" 
    tools:context=".MainActivity"> 

    <!-- make sure you match this up with the actual package name in your code --> 
    <com.example.embossmaskfilter.DrawView 
     android:id="@+id/drawView" 
     android:layout_width="200dp" 
     android:layout_height="200dp" 
     android:layout_alignParentTop="true" 
     android:layout_centerHorizontal="true" 
     android:layerType="software" /> 

    <TextView 
     android:id="@+id/textView2" 
     android:layout_width="wrap_content" 
     android:layout_height="wrap_content" 
     android:layout_alignParentStart="true" 
     android:layout_below="@id/drawView" 
     android:layout_centerVertical="true" 
     android:text="Ambient Light" 
     android:textAppearance="?android:attr/textAppearanceSmall" /> 

    <TextView 
     android:id="@+id/ambientLightTextView" 
     android:layout_width="wrap_content" 
     android:layout_height="wrap_content" 
     android:layout_alignParentEnd="true" 
     android:layout_alignTop="@id/textView2" 
     android:text="0.5" 
     android:textAppearance="?android:attr/textAppearanceSmall" /> 

    <SeekBar 
     android:id="@+id/ambientLightSeekBar" 
     android:layout_width="wrap_content" 
     android:layout_height="wrap_content" 
     android:layout_alignParentEnd="true" 
     android:layout_alignParentStart="true" 
     android:layout_below="@+id/textView2" 
     android:max="1000" /> 

    <TextView 
     android:id="@+id/textView3" 
     android:layout_width="wrap_content" 
     android:layout_height="wrap_content" 
     android:layout_alignParentStart="true" 
     android:layout_below="@id/ambientLightSeekBar" 
     android:layout_centerVertical="true" 
     android:text="Specular Highlight" 
     android:textAppearance="?android:attr/textAppearanceSmall" /> 

    <TextView 
     android:id="@+id/specularHighlightTextView" 
     android:layout_width="wrap_content" 
     android:layout_height="wrap_content" 
     android:layout_alignParentEnd="true" 
     android:layout_alignTop="@id/textView3" 
     android:text="0.5" 
     android:textAppearance="?android:attr/textAppearanceSmall" /> 

    <SeekBar 
     android:id="@+id/specularHighlightSeekBar" 
     android:layout_width="wrap_content" 
     android:layout_height="wrap_content" 
     android:layout_alignParentEnd="true" 
     android:layout_alignParentStart="true" 
     android:layout_below="@+id/textView3" 
     android:max="1000" /> 

    <TextView 
     android:id="@+id/textView4" 
     android:layout_width="wrap_content" 
     android:layout_height="wrap_content" 
     android:layout_alignParentStart="true" 
     android:layout_below="@id/specularHighlightSeekBar" 
     android:layout_centerVertical="true" 
     android:text="Blur Radius" 
     android:textAppearance="?android:attr/textAppearanceSmall" /> 

    <TextView 
     android:id="@+id/blurRadiusTextView" 
     android:layout_width="wrap_content" 
     android:layout_height="wrap_content" 
     android:layout_alignParentEnd="true" 
     android:layout_alignTop="@id/textView4" 
     android:text="0.5" 
     android:textAppearance="?android:attr/textAppearanceSmall" /> 

    <SeekBar 
     android:id="@+id/blurRadiusSeekBar" 
     android:layout_width="wrap_content" 
     android:layout_height="wrap_content" 
     android:layout_alignParentEnd="true" 
     android:layout_alignParentStart="true" 
     android:layout_below="@+id/textView4" 
     android:max="1000" /> 

</RelativeLayout> 
+0

Grazie Kris per il tuo tempo, non penso che l'output richiesto stia utilizzando il rilievo. Ho fatto alcuni esperimenti per due settimane. Il filtro Emboss crea bordi scuri e chiari, questo è un vantaggio ingiusto del giocatore di gioco perché è in grado di capire le posizioni in base all'illuminazione. Vedi questo link https://code.msdn.microsoft.com/windowsdesktop/C-Jigsaw-Puzzle-Game-5f0b7a75. Hanno creato qualcosa chiamato alfa blend bevel, ma l'algoritmo utilizza API di basso livello. Stavo vagando se c'è un modo per ottenere questo effetto. Qualche idea? – pats

+0

Inoltre quello che ho è una BItmap disegnata su una forma usando la modalità PorterDuff. Il filtro Emboss non si applica affatto su una bitmap oltre alle linee. – pats

2

Se non hai paura di dilettarsi in qualche basso livello elaborazione delle immagini, è possibile utilizzare convolution matrices per ottenere un effetto rilievo. È possibile utilizzare la silhouette dell'immagine (ad esempio il canale alfa) come input per il filtro. Ad esempio, se si utilizza questa matrice 5x5:

| -2 0 0 0 0 | 
| 0 -1 0 0 0 | 
| 0 0 n 0 0 | * (1/n) 
| 0 0 0 1 0 | 
| 0 0 0 0 2 | 

e applicarlo a tale immagine (che rappresenta un canale alfa):

unprocessed alpha channel

si otterrà questo effetto:

embossed alpha channel

Tutti i valori calcolati sono stati ridotti di 127 per garantire che siano compresi nell'intervallo 0-255. Ho usato n = 10 in questo particolare esempio. Puoi manipolare il raggio usando una matrice di dimensioni diverse (non è difficile estrapolare) e la profondità regolando il valore di n (più grande è il valore, più sottile è l'effetto).

Dato il canale alfa originale e la maschera calcolata è possibile determinare gli offset da applicare ai rispettivi pixel dell'immagine originale, creando così un effetto rilievo.

Spero che questo aiuti.

Problemi correlati