Android change canvas background color without los

2019-03-19 22:00发布

问题:

I'm trying to find a way to set background of canvas with a color picked up from custom color picker without removing any drawings on it. I'm trying to create an application which can draw on canvas and than save it as png. But when I set a new background to the current canvas, all drawings are gone. I'm using something like this :

mCanvas.drawColor(picker.getColor());

Any ideas how I can get things to work?

回答1:

The answers already given to your question are all pointing in the right direction: you do need to separate your background color block and your foreground drawing in separate layers, then merge them before saving the whole of it in a .png file. This is how Adobe Photoshop workflow is designed as well... It does make sense, if we think about it: take for example a software like MsPaint: because it doesn't use layers, it has to rely on stuff like floodfill algorithms to accomplish (albeit in an incomplete way) something remotely similar to a background change...

One way to implement such a thing would be to instantiate 2 Canvas objects backed by 2 different bitmaps. The first Canvas-Bitmap pair would be used for your drawing at the foreground, and the second Canvas-Bitmap pair would be used for your merged-layers drawing (i.e. foreground drawing + background color block). Then the 2nd Bitmap is what will be saved to a .png file when you need it to be saved. This way, our first Canvas-Bitmap pair stores your foreground info, which is not destroyed if a background color change needs to be made. Everytime an operation is made, the layers can be merged into the 2nd Canvas-Bitmap pair so that there is always a Bitmap with the correct content that is ready to be saved at your whim.

Here is a custom View I made so as to clear this methodology up. It implements a simple view used to paint a blue line on the touch-screen using a finger, with a background color changing depending on the X-Y position of said finger so as to demonstrate a background color change without unnecessary code complexity inherent of a complete implementation with a color wheel/menus/inter alia:

package com.epichorns.basicpaint;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Paint.Style;
import android.view.View;

public class PaintView extends View{

    Bitmap mMergedLayersBitmap=null; //Note: this bitmap here contains the whole of the drawing (background+foreground) to be saved.
    Canvas mMergedLayersCanvas=null;

    Bitmap mBitmap = null; //bitmap onto which we draw our stuff
    Canvas mCanvas = null; //Main canvas. Will be linked to a .bmp file
    int mBackgroundColor = 0xFF000000; //default background color
    Paint mDefaultPaint = new Paint();

    Paint mDrawPaint = new Paint(); //used for painting example foreground stuff... We draw line segments.
    Point mDrawCoor = new Point(); //used to store last location on our PaintView that was finger-touched

    //Constructor: we instantiate 2 Canvas-Bitmap pairs
    public PaintView(Context context, int width, int height) {
        super(context);
        mMergedLayersBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 
        mMergedLayersCanvas = new Canvas(mMergedLayersBitmap);

        mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        mCanvas = new Canvas(mBitmap);
    }

    //Change background color
    public void changeColor(int newColor){
        mBackgroundColor = newColor;
        invalidate(); //refresh view: this will indirectly invoke onDraw soon afterwards
    }

    //Called by user of PaintView in order to start a painting "stroke" (finger touching touch-screen): stores the 
    //location of the finger when it first touched the screen
    public void startDraw(int x, int y, int radius, int color){
        mDrawPaint.setColor(color);
        mDrawPaint.setStyle(Style.STROKE);
        mDrawPaint.setStrokeWidth(radius);
        mDrawCoor.x = x;
        mDrawCoor.y = y;        
    }

    //Called by user of PaintView when finger touching touch-screen is moving (must be called after a startDraw, 
    //as the latter initializes a couple of necessary things)
    public void continueDraw(int x, int y){
        mCanvas.drawLine(mDrawCoor.x, mDrawCoor.y, x, y, mDrawPaint);
        mDrawCoor.x = x;
        mDrawCoor.y = y;
        invalidate(); //refresh view: this will indirectly invoke onDraw soon afterwards
    }

    //Merge the foreground Canvas-Bitmap with a solid background color, then stores this in the 2nd Canvas-Bitmap pair.
    private void mergeLayers(){
        mMergedLayersCanvas.drawColor(mBackgroundColor);
        mMergedLayersCanvas.drawBitmap(mBitmap, 0, 0, mDefaultPaint);
    }

    @Override
    public void onDraw(Canvas canvas){
        mergeLayers();
        canvas.drawBitmap(mMergedLayersBitmap, 0, 0, mDefaultPaint);
    }

}

In order to test this view, here is a test Activity that uses the PaintView class. Both of those files are self-sufficient in an Android project, so that you can test it on your real device without hassle:

package com.epichorns.basicpaint;

import android.app.Activity;
import android.graphics.Color;
import android.os.Bundle;
import android.util.Log;
import android.view.Display;
import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout;


import com.epichorns.basicpaint.PaintView;
public class BasicPaintActivity extends Activity {
    PaintView mPaintView=null;
    LinearLayout mL = null;
    boolean mIsDrawing=false;
    int mBackgroundColor = 0xFF000000;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Display display = getWindowManager().getDefaultDisplay();       
        final float dispWidth = (float)display.getWidth();
        final float dispHeight = (float)display.getHeight();

        mPaintView = new PaintView(this, display.getWidth(), display.getHeight());    
        mPaintView.changeColor(mBackgroundColor);
        mPaintView.setOnTouchListener(new View.OnTouchListener(){

            public boolean onTouch(View v, MotionEvent event) {

            if(event.getAction()==MotionEvent.ACTION_DOWN){
                    mPaintView.startDraw((int)event.getX(), (int)event.getY(), 6, 0x806060FF);              
                    mIsDrawing=true;
                    return true;
                }
                else if(event.getAction()==MotionEvent.ACTION_UP){
                    mIsDrawing=false;
                    return true;
                }
                else if(event.getAction()==MotionEvent.ACTION_MOVE){
                    if(mIsDrawing){

                        //To demonstrate background change, change background color depending on X-Y position
                        int r = (int)(255f*event.getX()/dispWidth);
                        int g = (int)(255f*event.getY()/dispHeight);
                        mBackgroundColor = Color.argb(0xFF, r,g, 0x00);
                        Log.d("DEBUG1", "Color channels: (r, g) = ("+String.valueOf(r)+", "+String.valueOf(g)+")");
                        mPaintView.changeColor(mBackgroundColor);

                        //now, draw stuff where finger was dragging...
                        mPaintView.continueDraw((int)event.getX(), (int)event.getY());
                        return true;
                    }
                    else{
                        return false;
                    }

                }

                return false;
            }

        });

        setContentView(mPaintView);
    }




}


回答2:

When you draw the color, it's drawn over your drawings. You need to draw the color, and then draw every thing else again.



回答3:

Look if you want to change in canvas then you have to call invalidate to apply these changes your screen.And if you call invalidate then your onDraw() method will call.

If you want to change just background color of canvas from color picker then save color value in variable and call invalidate just after saving variable.Now your onDraw() will call.Now change background of canvas by calling setBackgroundColor(color variable) in onDraw() and draw everything else you want



回答4:

use canvas.drawARGB(a,r,g,b) and it will work for definite



回答5:

As long as your background is and will be in another color, you can do:

for (x...)
  for (y...)
    if (bitmap.getPixel(x,y) == oldBackgroundColor)
      bitmap.setPixel(x,y,newBackgroundColor)

Or, you can draw your content on an offscreen bitmap, and draw the background and then the offscreen to the actual bitmap. That way you can change the backgroundcolor that will be used when the next two-step-drawing will happen.



回答6:

@ Android-Droid

These two lines of code worked like charm for me. When ever user clicks on any color (Eg: Red ) set that color to mPaint like

      mPaint.setColor(Color.RED);

and when ever you wish to change canvas color

    dv.setBackgroundColor(mPaint.getColor());

where dv is the object of the class which extends view (Custom View). Try it and let me know if you face any issues.



回答7:

Maybe it's an old question but i want to contribute with this solution, In case you are taking the bitmap from a source, and then doing a drawable with canvas, maybe this can fit u:

@Override
public Bitmap transform(final Bitmap source) {
    //Background for transparent images
    Bitmap backg = Bitmap.createBitmap(source.getWidth(), source.getHeight(), Bitmap.Config.ARGB_8888);
    backg.eraseColor(Color.WHITE); // Any color you want...
    Paint back = new Paint();
    BitmapShader backshader = new BitmapShader(backg,
    BitmapShader.TileMode.CLAMP, BitmapShader.TileMode.CLAMP);
    back.setShader(backshader);
    back.setAntiAlias(true);

    // Image for the draw
    final Paint paint = new Paint();
    paint.setAntiAlias(true);
    paint.setShader(new BitmapShader(source, Shader.TileMode.CLAMP,
            Shader.TileMode.CLAMP));
    Bitmap output = Bitmap.createBitmap(source.getWidth(), source.getHeight(), source.getConfig());
    Canvas canvas = new Canvas(output);

    // IMPORTANT THING
    canvas.drawRoundRect(new RectF(margin, margin, source.getWidth()
            - margin, source.getHeight() - margin), radius, radius, back); // Draw the background first...
    canvas.drawRoundRect(new RectF(margin, margin, source.getWidth()
            - margin, source.getHeight() - margin), radius, radius, paint); // And then Draw the image, so it draws on top of the background

    if (source != output) {
        source.recycle();
    }

    // This is for if i want to put a border in the drawable, its optional
    Paint paint1 = new Paint();      
    paint1.setColor(Color.parseColor("#CC6C7B8B"));
    paint1.setStyle(Style.STROKE);
    paint1.setAntiAlias(true);
    paint1.setStrokeWidth(2);
    canvas.drawRoundRect(new RectF(margin, margin, source.getWidth()
            - margin, source.getHeight() - margin), radius, radius, paint1);

    // and then, return the final drawable...
    return output;
}

Hope it helps...