【问题标题】:saving a canvas, bitmap with drawn circles保存画布,绘制圆圈的位图
【发布时间】:2017-07-10 12:32:34
【问题描述】:

我有以下类,它扩展了 View 并重写了 onDraw 方法以在已加载的位图上绘制圆。

位图加载成功并显示在屏幕上。当用户触摸屏幕时,会画一个圆圈。

我有一个将位图压缩为字节[] 的浮动操作按钮。这个 byte[] 然后被发送到另一个 Activity 来显示。不幸的是,生成的位图没有任何圆圈。

canvas 是 onDraw 中的局部对象,mCBitmap 和 tCanvas 是全局变量,因此 saveImage 方法可以访问数据。

谁能告诉我为什么没有一个圆圈被复制到生成的位图中?

import android.content.Context;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Environment;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.SparseArray;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Toast;

import org.apache.commons.io.IOUtils;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import android.graphics.PointF;

/**
 * Created by MatthewW on 14/06/2017.
 */

public class TouchView extends View {

    private static final String TAG = TouchView.class.getSimpleName();
    Bitmap bgr;
    File tempFile;
    private byte[] imageArray;
    private Paint pTouch;

    Context context;

    private SparseArray<ColouredCircle> mPointers;
    public int x;
    public int y;


    int circleCount;
    int radius;
    protected byte [] data;

    Bitmap mCBitmap;
    Canvas tCanvas;




    public TouchView(Context context) {
        super(context);

        this.context = context;

    }



    public TouchView(Context context, AttributeSet attr) {
        super(context,attr);
        Log.e(TAG, "inside touchview constructor");

        this.context = context;

        radius = 70;
        circleCount = 0;



        copyReadAssets();

        imageArray = new byte[(int)tempFile.length()];


        Log.e(TAG, "imageArray has length = " + imageArray.length);



        try{

            InputStream is = new FileInputStream(tempFile);
            BufferedInputStream bis = new BufferedInputStream(is);
            DataInputStream dis = new DataInputStream(bis);


            int i = 0;

            while (dis.available() > 0 ) {
                imageArray[i] = dis.readByte();
                i++;
            }

            dis.close();


        } catch (Exception e) {

            e.printStackTrace();
        }



        Bitmap bm = BitmapFactory.decodeByteArray(imageArray, 0, imageArray.length);

        if(bm == null){
            Log.e(TAG, "bm = null");
        }else{
            Log.e(TAG, "bm =  not null");
        }



        mPointers = new SparseArray<ColouredCircle>();


        bgr = bm.copy(bm.getConfig(), true);





        bm.recycle();

        pTouch = new Paint(Paint.ANTI_ALIAS_FLAG);
        // pTouch.setXfermode(new PorterDuffXfermode(Mode.SRC_OUT));
        pTouch.setColor(Color.RED);
        pTouch.setStyle(Paint.Style.STROKE);

        pTouch.setStrokeWidth(5);




    }// end of touchView constructor




    private void copyReadAssets() {

        AssetManager assetManager = context.getAssets();

        InputStream in = null;
        OutputStream out = null;
        tempFile = new File(context.getFilesDir(), "bodymap.jfif");
        try {
            in = assetManager.open("bodymap.jfif");
            out = context.openFileOutput(tempFile.getName(), Context.MODE_WORLD_READABLE);

            IOUtils.copy(in, out);

            in.close();
            in = null;
            out.flush();
            out.close();
            out = null;
        } catch (Exception e) {
            Log.e(TAG, e.getMessage());
        }


    }


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



        Log.e(TAG, "about to draw bgr ");
       // canvas.drawBitmap(bgr, 0, 0, null);

        DisplayMetrics metrics = context.getResources().getDisplayMetrics();
        int width = metrics.widthPixels;
        int height = metrics.heightPixels;



        Rect frameToDraw = new Rect(0, 0, width, height);
        RectF whereToDraw = new RectF(0, 0, width, height - 300);

        canvas.drawBitmap(bgr,frameToDraw,whereToDraw, null);




        if(mPointers != null) {

            Log.e(TAG, "mPointers.size() = " + mPointers.size());

            for (int i = 0; i < mPointers.size(); i++) {

                PointF p = mPointers.get(i).getPointF();
                x = (int) p.x;
                y = (int) p.y;

                pTouch.setColor(mPointers.get(i).getColour());


                canvas.drawCircle(x, y, radius, pTouch);




                mCBitmap = Bitmap.createBitmap(bgr.getWidth(), bgr.getHeight(), bgr.getConfig());

                tCanvas = new Canvas(mCBitmap);

                tCanvas.drawBitmap(bgr, 0, 0, null);

                tCanvas.drawCircle(x, y, radius, pTouch);

                tCanvas.drawBitmap(mCBitmap, 0, 0, null);






            }

        }






    }//end of onDraw





    @Override
    public boolean onTouchEvent(MotionEvent me) {
        switch (me.getActionMasked()) {
            case MotionEvent.ACTION_DOWN:

            case MotionEvent.ACTION_POINTER_DOWN: {
                /*int ai = me.getActionIndex();
                PointF pt = new PointF(me.getX(ai), me.getY(ai));
                mPointers.put(me.getPointerId(ai), pt);

                Log.e(TAG, "mPointers.size() = " + mPointers.size() + "me.getPointerId(ai) = "
                                 + me.getPointerId(ai) + " me.getX(ai) = " + me.getX(ai) + " me.getY(ai) = " + me.getY(ai));*/

                int ai = me.getActionIndex();
                PointF pt = new PointF(me.getX(ai), me.getY(ai));

                ColouredCircle cc = new ColouredCircle(pTouch.getColor(),pt);

                mPointers.put(circleCount, cc);

                circleCount++;

                invalidate();
                return true;
            }

            case MotionEvent.ACTION_UP: {

            }

            case MotionEvent.ACTION_POINTER_UP: {
                /*int pid = me.getPointerId(me.getActionIndex());
                mPointers.remove(pid);*/
                return true;
            }

            case MotionEvent.ACTION_MOVE: {
                /*for (int i = 0; i < me.getPointerCount(); ++i) {
                    PointF pt = mPointers.get(me.getPointerId(i));
                    pt.set(me.getX(i), me.getY(i));
                    invalidate();
                }*/
                return true;
            }
        }
        return false;
    }



    public void showToastMessage(String mess){

        Toast.makeText(TouchView.this.getContext(), mess.toString(), Toast.LENGTH_SHORT).show();

    }

    public  int getRadius() {
        return radius;
    }




    public  void setRadius(int r) {
        radius = r;
        invalidate();
    }


    public void setCircleColour(String colourMode){


        if(colourMode.equalsIgnoreCase("RED")){

            pTouch.setColor(Color.RED);

        }else if(colourMode.equalsIgnoreCase("BLUE")){

            pTouch.setColor(Color.BLUE);

        }else if(colourMode.equalsIgnoreCase("GREY")){

            pTouch.setColor(Color.GRAY);

        }

    }



    public byte[] saveImage(){



        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        mCBitmap.compress(Bitmap.CompressFormat.JPEG, 100 /*ignored for PNG*/, bos);
        data = bos.toByteArray();
        try {
            bos.flush();
            bos.close();
        } catch (IOException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }

        try {
            bos.flush();
            bos.close();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        if ( data == null){
            Log.e(TAG, "data in touchview before save clicked is null");
        }else{
            Log.e(TAG, "data in touchview before saved clicked is not null and has length + " + data.length);
        }

        return data;

    }

}//end of class

[编辑1]

我已将 onDraw() 和 saveImage() 方法更改为以下内容。图像现在与圆圈一起保存。

但是,bgr 图像非常小并且位于屏幕的左上方(屏幕的其余部分为黑色)。圆圈在原图的正确位置,它只是非全屏尺寸的背景图像。

如何将 bg 图像以全尺寸复制到生成的位图?

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



        Log.e(TAG, "about to draw bgr ");
       // canvas.drawBitmap(bgr, 0, 0, null);

        DisplayMetrics metrics = context.getResources().getDisplayMetrics();
        int width = metrics.widthPixels;
        int height = metrics.heightPixels;



        Rect frameToDraw = new Rect(0, 0, width, height);
        RectF whereToDraw = new RectF(0, 0, width, height - 300);

        canvas.drawBitmap(bgr,frameToDraw,whereToDraw, null);




        if(mPointers != null) {

            Log.e(TAG, "mPointers.size() = " + mPointers.size());

            for (int i = 0; i < mPointers.size(); i++) {

                PointF p = mPointers.get(i).getPointF();
                x = (int) p.x;
                y = (int) p.y;

                pTouch.setColor(mPointers.get(i).getColour());


                canvas.drawCircle(x, y, radius, pTouch);





            }

        }






    }//end of onDraw

.

public byte[] saveImage(){

        DisplayMetrics metrics = context.getResources().getDisplayMetrics();
        int width = metrics.widthPixels;
        int height = metrics.heightPixels - 300;

        Bitmap  bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        //Bitmap  bitmap = Bitmap.createBitmap(bgr.getWidth(), bgr.getHeight(), bgr.getConfig());

        Canvas canvas = new Canvas(bitmap);
        this.draw(canvas);

        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        bitmap.compress(Bitmap.CompressFormat.JPEG, 100 /*ignored for PNG*/, bos);
        data = bos.toByteArray();
        try {
            bos.flush();
            bos.close();
        } catch (IOException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }

        try {
            bos.flush();
            bos.close();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        if ( data == null){
            Log.e(TAG, "data in touchview before save clicked is null");
        }else{
            Log.e(TAG, "data in touchview before saved clicked is not null and has length + " + data.length);
        }

        return data;

    }

.

这是允许用户在图像上放置圆圈的 Activity 的图片。 bg 图片是全屏的。

下面是下一个 Activity 中的输出图片,其中带有圆圈的图像被转换为​​ byte[] 并显示。如您所见,背景图像不是全屏,但圆圈的位置正确。

【问题讨论】:

    标签: android bitmap android-canvas


    【解决方案1】:

    onDraw(...),更改:

    Rect frameToDraw = new Rect(0, 0, width, height);
    

    收件人:

    Rect frameToDraw = new Rect(0, 0, bgr.getWidth(), bgr.getHeight());
    

    当您在onDraw(...) 中显示图像时,当框架调用onDraw(...) 时没有出现此问题的一个原因可能是框架提供的Canvas 对象默认是硬件加速的,如果你有一个目标 sdk >= 14(source,第二段,第一句)

    here 所见,当画布被硬件加速时,它的密度似乎设置为 0。但是,如果您在字段 TouchView#bgr 上调用 Bitmap.getDensity(),您会发现它不会t 为 0。这是因为位图在创建时具有当前设备屏幕的密度。

    接下来,您在 0 ppi 画布上绘制了高密度位图(在我的设备上密度为 320 ppi) --> 它将被绘制得看起来更大。

    但是,在 saveImage 中,您从头开始创建 Bitmap,然后从该位图创建 Canvas,然后提供此画布以调用 draw(...)。您刚刚创建的这个新画布不会进行硬件加速,并且还将具有您新创建的位图的密度(在我的情况下为 320 ppi)。

    当您在onDraw(...) 中绘制身体图图像时,您将在此画布上以 320dp 绘制它,与您的图像相同。

    这仍然可以正常工作,但是您指定要从身体图绘制一个 screenWidth x screenHeight 的区域,假设为 1920x1080,它很可能更小,假设为 1000x600,使用frameToDraw

    在这种情况下,Android 会做什么?好吧,Canvas#drawBitmap(...) 的 Javadoc 告诉我们以下内容:

    注意:如果绘画包含一个生成掩码的掩码过滤器, 超出位图的原始宽度/高度(例如 BlurMaskFilter),然后将绘制位图,就好像它在 具有 CLAMP 模式的着色器。因此原色之外的颜色 宽度/高度将是复制的边缘颜色。

    这基本上意味着:我将使用你的 1000x600 并绘制你应该绘制的像素,其余的像素直到 1920x1080 我将填充我选择的某种颜色(可能是黑色或白色,或透明) ?)

    现在,您说,通过whereToDraw,您希望将 1920x1080 区域映射到 1620x1080 区域。为此,android 将进一步缩小您的位图,将bodymap 渲染得更小。

    对此最简单的解决方案是在创建frameToDraw 区域时在onDraw(...) 中指定位图的实际尺寸。

    此外,如果这些代码片段是测试代码,那很好,但请务必遵循here 中提到的准则,在使用onDraw(...) 时,您将清理您的代码。

    最重要的一点是:

    • 你真的不应该在你的onDraw(...) 方法中分配对象,因为它们可能会经常被调用,这会杀死你的垃圾收集器。 (即 frameToDraw 和 whereToDraw - 你可以在构造函数中预先分配这些)

    • 您可以利用视图的绘图缓存更轻松地获取屏幕的位图。

    只需将您的 saveImage 更改为:

    public byte[] saveImage() {
        //reder the view once, in software.
        buildDrawingCache();
        //get the rendered bitmap
        Bitmap bitmap = getDrawingCache();
    
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        bitmap.compress(Bitmap.CompressFormat.JPEG, 100 /*ignored for PNG*/, bos);
        byte[] data = bos.toByteArray();
        //destroy the drawing cache, to clear up the memory
        destroyDrawingCache();
    
        try {
            bos.flush();
            bos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    
        if (data == null) {
            Log.e(TAG, "data in touchview before save clicked is null");
        } else {
            Log.e(TAG, "data in touchview before saved clicked is not null and has length + " + data.length);
        }
    
        return data;
    }
    

    见:

    希望对你有帮助

    【讨论】:

    • 您好,感谢您提供如此详细的答复。我想我需要做更多关于 android 图形的阅读。它现在工作正常!我还将进行这些更改以将对象放置在构造函数中。再次感谢!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-25
    • 1970-01-01
    • 2020-04-17
    • 1970-01-01
    相关资源
    最近更新 更多