2015-07-22 35 views
6

Chcę pokazać trójwymiarowy tłoczony wygląd, jak pokazano na poniższym obrazku. Użyłem EmbossMaskFilter, ale nie mogę uzyskać tego efektu (zobacz poniższy kod). Czy jest inny sposób na zrobienie tego? lub jak mogę użyć EmbossMaskFilter do tego.Wyrzeźb krawędzie kształtu obrazu pokazujące głębokość w Androidzie

Wymagane wyjście

enter image description here

moje wyjście

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

Odpowiedz

4

Jeśli chcesz tylko zrobić przetwarzania bitmapy (w przeciwieństwie do 3D lub wektory), najlepiej chyba jest:

  1. Generowanie maskę wzornik ze swojego kawałka układanki,
  2. Korzystając Difference of Gaussians przetwarzać (użyłem w tym przykładzie jądra o rozmiarach 12 i 2 pikseli), następnie normalizowałem i odwracałem wynik,
  3. Alfa-mix wyjście "2" do oryginalnego obrazu przy użyciu maski (1.) jako kanału szablonowego.

MaskDifference of GaussiansResult

UPDATE: tu pojawia się kod. Próbowałem ponownie użyć nazw zmiennych, aby łatwiej było je zrozumieć. Kod wykorzystuje wewnętrzne elementy Renderscript, gdy tylko jest to możliwe, aby uczynić rzeczy szybszymi i bardziej interesującymi.

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

Zróbmy krótką przerwę i zobaczmy, co mamy do tej pory. Zauważ, że cały obraz po lewej stronie jest nieprzejrzysty (to znaczy zawiera przestrzeń poza puzzlem), a my musimy teraz odpowiednio wyciąć kształt i antyalias jego brzeg. Niestety, użycie oryginalnego kształtu nie będzie działać, ponieważ jest zbyt duży i zbyt mocno wycina, co prowadzi do nieprzyjemnych artefaktów w pobliżu krawędzi (rysunek po prawej).

enter image description here enter image description here

Dlatego wyciągnąć inną drogę, tym razem przy użyciu węższego udar ...

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

... o lepszym graczem wyniku. Użyjmy tego, aby zamaskować obraz tła. enter image description here

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

enter image description here

Witam! Teraz tylko kod instalacyjny 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)); 
} 

I wreszcie onDraw():

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

TODO: sprawdź czy inne miters stroke dałoby bardziej przyjemne narożniki.

+0

To jest dokładnie to, czego szukałem. Czy mógłbyś podać więcej informacji na ten temat? Jak skutecznie wdrożyć to w java/android? (Mam również obiekt ścieżki). – pats

+0

Zamierzę próbkę kodu w ciągu kilku godzin. –

+0

Cześć, dziękuję za wspaniałą odpowiedź. Wypróbowałem to, ale nie miałem żadnego efektu. Zobacz dane wyjściowe poniżej. – pats

5

myślę, trzeba będzie większy promień rozmycia i mniejsze wartości dla światła otoczenia i specular cześć ghlight.

Grałem z tym, tworząc interfejs do zmiany parametrów.

image 1

image 2

Oto kod użyłem, więc można go wypróbować. Użyłem prostego niebieskiego prostokąta, ale powinieneś być w stanie łatwo podłączyć obraz, który chcesz narysować.

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

Dziękuję Kris za poświęcony czas, nie sądzę, że wymagana wydajność w ogóle używa wytłoczenia. Zrobiłem kilka eksperymentów w ciągu ostatnich dwóch tygodni. Filtr wytłoczenia tworzy ciemne i jasne krawędzie, jest to nieuczciwa zaleta odtwarzacza gier, ponieważ może zlokalizować lokalizacje w oparciu o oświetlenie. Zobacz ten link: https://code.msdn.microsoft.com/windowsdesktop/C-Jigsaw-Puzzle-Game-5f0b7a75. Stworzyli coś, co nazywa się fazą mieszania alfa, ale algorytm używa API niskiego poziomu. Błąkałem się, jeśli istnieje sposób na uzyskanie tego efektu. Dowolny pomysł? – pats

+0

Co jeszcze mam to BItmap narysowany do kształtu przy użyciu trybu PorterDuff. Filtr wytłoczeń nie ma zastosowania w bitmapie, niezależnie od linii. – pats

2

Jeśli nie boisz się dabbling w pewnym niskim poziomie przetwarzania obrazu, można użyć convolution matrices, aby uzyskać efekt emboss. Możesz użyć swojej sylwetki obrazu (to znaczy kanału alfa) jako wejścia do filtra. Na przykład, jeśli używasz tej macierzy 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 | 

i zastosować go do takiego obrazu (reprezentująca kanał alfa):

unprocessed alpha channel

dostaniesz ten efekt:

embossed alpha channel

Wszystkie obliczone wartości zostały zmniejszone o 127, aby zapewnić, że będą w zakresie 0-255. Użyłem n = 10 w tym konkretnym przykładzie. Możesz manipulować promieniem za pomocą matrycy o różnej wielkości (nie jest to trudne do ekstrapolacji) i głębokości, dostosowując wartość n (im większa wartość, tym efekt jest subtelniejszy).

Biorąc pod uwagę pierwotny kanał alfa i obliczoną maskę, można określić przesunięcia, które zostaną zastosowane do odpowiednich pikseli oryginalnego obrazu, tworząc w ten sposób efekt wytłoczenia.

Nadzieję, że pomaga.