Emboss edges of a image shape showing the depth in android

前端 未结 3 1518
予麋鹿
予麋鹿 2021-02-06 09:18

I want to show 3D embossed look and feel as shown in following image. I used EmbossMaskFilter but cannot get it to show the effect (see code below). Is there a different way to

3条回答
  •  野性不改
    2021-02-06 09:51

    If you only want to do bitmap processing (as opposed to 3D or vectors), your best bet probably is to:

    1. Generate a stencil mask from your puzzle piece,
    2. Use Difference of Gaussians to process it (I used kernels of size 12 and 2 pixels in this example), then normalize and invert the result,
    3. Alpha-blend the output of "2" into original image using the mask (1.) as the stencil channel.

    UPDATE: here comes the code. I tried to reuse your variable names so that it's easier to understand. The code uses Renderscript intrinsics whenever possible in order to make things faster and more interesting.

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

    Let's make a short break and see what we have so far. Observe that the entire image on the left is opaque (i.e. including the space outside the puzzle), and we now have to cut out the shape and antialias its edge properly. Unfortunately, using original shape won't work, as it is too large and cuts out too much, leading to unpleasant artifacts near the edge (figure on the right).

    We therefore draw another path, this time using a narrower stroke...

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

    ...for a much better looking result. Let's use it to mask out a background image.

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

    Hello there! Now just the Renderscript setup code.

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

    And finally onDraw():

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

    TODO: check if other stroke miters would give more pleasant corners.

提交回复
热议问题