如何在表面视图上绘图?

Posted

技术标签:

【中文标题】如何在表面视图上绘图?【英文标题】:How to draw on a surfaceview? 【发布时间】:2014-12-21 07:10:26 【问题描述】:

大家好,我正在尝试制作一个 QRCode 阅读器,所以我使用了 dlzaaro66 提供的 QRCodeReaderView 库,它提供了 Zxing 库的简单实现。代码正在扫描二维码,但我想制作一个参考框,以便在相机表面视图上指示代码被扫描的位置我尝试使用正常绘制技术。它没有给出任何错误,但它也没有绘制,你能帮我解决可能出现问题的地方吗?

这是我的活动课。

import android.app.Activity;
import android.content.Intent;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PointF;
import android.net.Uri;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.CompoundButton;
import android.widget.Switch;
import android.widget.Toast;

import com.dlazaro66.qrcodereaderview.QRCodeReaderView;
import com.dlazaro66.qrcodereaderview.QRCodeReaderView.OnQRCodeReadListener;


public class MyActivity extends Activity implements OnQRCodeReadListener

    QRCodeReaderView decoder;
    Switch start_stop;
    Paint paint;

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_my);
        decoder = (QRCodeReaderView) findViewById(R.id.view2);
        decoder.setOnQRCodeReadListener(this);
        start_stop=(Switch) findViewById(R.id.switch1);
        start_stop.setChecked(true);

        start_stop.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() 
            @Override
            public void onCheckedChanged(CompoundButton compoundButton, boolean b) 
                if(b)
                    decoder.getCameraManager().startPreview();
                
                else
                    decoder.getCameraManager().stopPreview();
                
            
        );
        paint= new Paint();
        paint.setColor(Color.RED);
        paint.setStrokeWidth(100);
        paint.setAntiAlias(true);
        paint.setStyle(Paint.Style.STROKE);

    


    @Override
    public boolean onCreateOptionsMenu(Menu menu) 
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.my, menu);
        return true;
    

    @Override
    public boolean onOptionsItemSelected(MenuItem item) 
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();
        if (id == R.id.action_settings) 
            return true;
        
        return super.onOptionsItemSelected(item);
    

    @Override
    public void onQRCodeRead(String text, PointF[] points) 
        start_stop.setChecked(false);
        if(text.startsWith("http"))
            Toast.makeText(getApplicationContext(),text,Toast.LENGTH_SHORT).show();
            final Intent intent = new Intent(Intent.ACTION_VIEW).setData(Uri.parse(text));
            startActivity(intent);
        
        else
            Toast.makeText(getApplicationContext(),text,Toast.LENGTH_SHORT).show();
        
        Canvas canvas=new Canvas();
        for(int i=0;i<points.length-1;i++)
            canvas.drawLine(points[i].x,points[i].y,points[i+1].x,points[i+1].y,paint);
        

    

    @Override
    public void cameraNotFound() 

    

    @Override
    public void QRCodeNotFoundOnCamImage() 

    


这是我从中获取方法和自定义表面视图的库项目类

public class QRCodeReaderView extends SurfaceView implements SurfaceHolder.Callback,Camera.PreviewCallback 

    public interface OnQRCodeReadListener 

        public void onQRCodeRead(String text, PointF[] points);
        public void cameraNotFound();
        public void QRCodeNotFoundOnCamImage();
    

    private OnQRCodeReadListener mOnQRCodeReadListener;

    private static final String TAG = QRCodeReaderView.class.getName();

    private QRCodeReader mQRCodeReader;
    private int mPreviewWidth; 
    private int mPreviewHeight; 
    private SurfaceHolder mHolder;
    private CameraManager mCameraManager;

    public QRCodeReaderView(Context context) 
        super(context);
        init();
    

    public QRCodeReaderView(Context context, AttributeSet attrs) 
        super(context, attrs);
        init();
    

    public void setOnQRCodeReadListener(OnQRCodeReadListener onQRCodeReadListener) 
        mOnQRCodeReadListener = onQRCodeReadListener;
    

    public CameraManager getCameraManager() 
        return mCameraManager;
    

    @SuppressWarnings("deprecation")
    private void init() 
        if (checkCameraHardware(getContext()))
            mCameraManager = new CameraManager(getContext());

            mHolder = this.getHolder();
            mHolder.addCallback(this);
            mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);  // Need to set this flag despite it's deprecated
         else 
            Log.e(TAG, "Error: Camera not found");
            mOnQRCodeReadListener.cameraNotFound();
        
    



    /****************************************************
     * SurfaceHolder.Callback,Camera.PreviewCallback
     ****************************************************/

    @Override
    public void surfaceCreated(SurfaceHolder holder) 
        try 
            // Indicate camera, our View dimensions
            mCameraManager.openDriver(holder,this.getWidth(),this.getHeight());
         catch (IOException e) 
            Log.w(TAG, "Can not openDriver: "+e.getMessage());
            mCameraManager.closeDriver();
        

        try 
            mQRCodeReader = new QRCodeReader();
            mCameraManager.startPreview();
         catch (Exception e) 
            Log.e(TAG, "Exception: " + e.getMessage());
            mCameraManager.closeDriver();
        
    

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) 
        Log.d(TAG, "surfaceDestroyed");
        mCameraManager.getCamera().setPreviewCallback(null);
        mCameraManager.getCamera().stopPreview();
        mCameraManager.getCamera().release();
        mCameraManager.closeDriver();
    

    // Called when camera take a frame 
    @Override
    public void onPreviewFrame(byte[] data, Camera camera) 

        PlanarYUVLuminanceSource source = mCameraManager.buildLuminanceSource(data, mPreviewWidth, mPreviewHeight);

        HybridBinarizer hybBin = new HybridBinarizer(source);
        BinaryBitmap bitmap = new BinaryBitmap(hybBin);

        try 
            Result result = mQRCodeReader.decode(bitmap);  

            // Notify We're found a QRCode
            if (mOnQRCodeReadListener != null) 
                    // Transform resultPoints to View coordinates
                    PointF[] transformedPoints = transformToViewCoordinates(result.getResultPoints());
                    mOnQRCodeReadListener.onQRCodeRead(result.getText(), transformedPoints);
            

         catch (ChecksumException e) 
            Log.d(TAG, "ChecksumException");
            e.printStackTrace();
         catch (NotFoundException e) 
            // Notify QR not found
            if (mOnQRCodeReadListener != null) 
                mOnQRCodeReadListener.QRCodeNotFoundOnCamImage();
            
         catch (FormatException e) 
            Log.d(TAG, "FormatException");
            e.printStackTrace();
         finally 
            mQRCodeReader.reset();
        
    



    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) 
        Log.d(TAG, "surfaceChanged");

        if (mHolder.getSurface() == null)
            Log.e(TAG, "Error: preview surface does not exist");
            return;
        

        //preview_width = width;
        //preview_height = height;

        mPreviewWidth = mCameraManager.getPreviewSize().x;
        mPreviewHeight = mCameraManager.getPreviewSize().y;


        mCameraManager.stopPreview();
        mCameraManager.getCamera().setPreviewCallback(this);
        mCameraManager.getCamera().setDisplayOrientation(90); // Portrait mode

        mCameraManager.startPreview();
    

    /**
     * Transform result to surfaceView coordinates
     * 
     * This method is needed because coordinates are given in landscape camera coordinates.
     * Now is working but transform operations aren't very explained
     * 
     * TODO re-write this method explaining each single value    
     * 
     * @return a new PointF array with transformed points
     */
    private PointF[] transformToViewCoordinates(ResultPoint[] resultPoints) 

        PointF[] transformedPoints = new PointF[resultPoints.length];
        int index = 0;
        if (resultPoints != null)
            float previewX = mCameraManager.getPreviewSize().x;
            float previewY = mCameraManager.getPreviewSize().y;
            float scaleX = this.getWidth()/previewY;
            float scaleY = this.getHeight()/previewX;

            for (ResultPoint point :resultPoints)
                PointF tmppoint = new PointF((previewY- point.getY())*scaleX, point.getX()*scaleY);
                transformedPoints[index] = tmppoint;
                index++;
            
        
        return transformedPoints;

    


    /** Check if this device has a camera */
    private boolean checkCameraHardware(Context context) 
        if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA))
            // this device has a camera
            return true;
         
        else if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT))
            // this device has a front camera
            return true;
        
        else if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY))
            // this device has any camera
            return true;
        
        else 
            // no camera on this device
            return false;
        
    


【问题讨论】:

【参考方案1】:

Surface 是生产者-消费者缓冲区队列安排的一部分。您的应用程序位于生产者端,而对于 SurfaceView,系统合成器 (SurfaceFlinger) 位于消费者端。

一个表面一次只能有一个生产者。您已将相机预览设置为制作者,因此无法同时连接Canvas 进行绘图。您没有看到失败,因为您使用new Canvas 在真空中创建Canvas - 它没有连接到任何东西。 (通常您会使用 Surface#lockCanvas() 来获取与 Surface 关联的 Canvas。)

表面是一个完全独立的图层,默认情况下合成在其他所有图层之后,这意味着您可以使用自定义视图在其上进行绘制。不过,我认为您不需要额外的视图对象——我相信您可以使用SurfaceView 本身的“视图”部分来完成它,它应该有一个透明的背景。请参阅“custom drawing”文档。

如果您想变得花哨,可以将相机预览提供给 OpenGL ES,但这可能超出您的需要。 (一些例子here。)另外,如果你想了解更多关于Android图形架构的信息,请参阅this document。

【讨论】:

以上是关于如何在表面视图上绘图?的主要内容,如果未能解决你的问题,请参考以下文章

如何使完全透明视图上的绘图可见

如何在画布内使用动画框架?

如何在视图控制器之间传输 Xcode 8 上的绘图?

根据 julia 中的数据绘制 3d 表面(使用绘图)

添加了子视图但丢失了部分绘图?

PyQt5:用户可以在其中绘制的绘图表面[重复]