Android如何更改画布背景颜色而不丢失其中的任何绘图?

9

我正在尝试找到一种方法,可以从自定义颜色选择器中选择颜色并将其设置为画布的背景,而不会删除任何现有的绘制内容。我正在创建一个应用程序,可以在画布上进行绘制,然后将其保存为PNG格式。但是,当我将新的背景设置为当前画布时,所有的绘图都消失了。我使用的代码类似于:

mCanvas.drawColor(picker.getColor());

有什么想法可以让事情正常工作吗?


你需要展示给我们onDraw方法。 - Ron
嗨,我在下面附上了一个解决方案的实现。它基本上按照你在问题中描述的要求完成了你想要做的事情。 - epichorns
7个回答

8
你的问题已经得到了正确的回答:你需要将背景色块和前景绘图分别放在不同的图层中,然后在保存为.png文件之前合并它们。这也是Adobe Photoshop工作流程的设计方式…如果我们想一下,这是有道理的:以MsPaint这样的软件为例:因为它不使用图层,必须依靠像洪水填充算法这样的东西来完成(虽然是不完整的)类似于背景更改的操作...
实现这种方法的一种方式是实例化2个Canvas对象,它们由2个不同的位图支持。第一个Canvas-Bitmap对用于在前景中绘制,第二个Canvas-Bitmap对则用于合并的图层绘制(即前景图形+背景色块)。然后,在需要保存时,第二个位图将被保存为.png文件。这样,我们的第一个Canvas-Bitmap对存储前景信息,如果需要更改背景颜色,则其不会被破坏。每次进行操作时,都可以将图层合并到第二个Canvas-Bitmap对中,以便始终有一个具有正确内容的位图可供你随意保存。
这是我制作的一个自定义视图,以清楚地解释这种方法。它实现了一个简单的视图,用于使用手指在触摸屏上绘制蓝线,并根据手指的X-Y位置更改背景颜色,以演示没有完整实现的颜色轮/菜单等代码复杂性的背景颜色更改。
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);
    }

}

为了测试这个视图,这里有一个使用PaintView类的测试Activity。这两个文件在Android项目中都是自包含的,因此您可以在真实设备上轻松测试它,无需麻烦。
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);
    }




}

他需要做的是能够更改已保存在内存中的图像的背景。例如,检索图像并分离层(前景和背景),然后更改背景层的颜色,并将层合并回原来的状态。 - Ron
嗯,我从他的问题中没有得到这个。如果你是正确的,那么除了保存为另一种格式(如.psd)之外,就没有干净的解决方案了...因为任何基于泛洪填充或颜色替换的解决方案都会失败,因为没有软件能够了解半透明前景图形的 alpha 通道,等等。 - epichorns
除非他被允许将不同的图层保存在不同的图像中,例如仅将前景透明(颜色模式8888)图层保存为.psd文件,如果需要背景,则将其保存在另一个图像中,或者以他选择的某种格式序列化颜色信息。在这种情况下,PSD或等效格式可能是最好的选择。 - epichorns

5
当你绘制颜色时,它会覆盖在你的图画上。你需要先绘制颜色,然后再重新绘制其他所有东西。

是的,但如果我保存旧位图,然后再次绘制它...它的背景将是旧的,新颜色不会在前景上...那么我该怎么解决呢? - Android-Droid
他的意思是每次背景颜色改变时应该先绘制背景,然后再绘制图像。这样很快,因此在视觉上只有背景发生了变化。 - DeeV

1

如果您想在画布中进行更改,则必须调用invalidate以应用这些更改到屏幕上。如果您调用invalidate,那么您的onDraw()方法将被调用。

如果您只想从颜色选择器更改画布的背景颜色,则保存颜色值到变量中,并在保存变量后立即调用invalidate。现在您的onDraw()将被调用。现在通过在onDraw()中调用setBackgroundColor(color variable)来更改画布的背景,并绘制其他所有内容。


1
使用canvas.drawARGB(a,r,g,b)即可确保其正常工作。

最简单且正确的答案是:使用canvas.drawARGB(200, 0, 0, 0);使视图变暗。 - oxied

0

@ Android-Droid

这两行代码对我非常有效。每当用户点击任何颜色(例如:红色),将该颜色设置为mPaint,如下:

      mPaint.setColor(Color.RED);

每当您想更改画布颜色时

    dv.setBackgroundColor(mPaint.getColor());

其中dv是扩展视图(自定义视图)的类的对象。 试一下,如果遇到任何问题,请告诉我。


0
只要您的背景色不同且将来也不同,您可以执行以下操作:
for (x...)
  for (y...)
    if (bitmap.getPixel(x,y) == oldBackgroundColor)
      bitmap.setPixel(x,y,newBackgroundColor)

或者,您可以在离屏位图上绘制内容,先绘制背景,然后再将离屏绘制到实际位图上。这样,您就可以更改下一次两步绘制时使用的背景颜色。


事实上,不幸的是,OP可能希望前景绘图保持其完整性,尽管它与背景颜色相同或不同... 颜色替换滤镜 是一种破坏性操作,而OP可能想要类似于Photoshop分层的东西... - epichorns
同意,那只是特殊情况下的解决方法,不是通用方案! - Bondax

0
也许这是一个老问题,但我想用这个解决方案做出贡献。如果您正在从源中获取位图,然后使用画布创建可绘制对象,那么也许这个方法适合您:
@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;
}

希望能有所帮助...


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接