AndroidStudio实现在图片上涂鸦并记录涂鸦轨迹

这篇具有很好参考价值的文章主要介绍了AndroidStudio实现在图片上涂鸦并记录涂鸦轨迹。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

AndroidStudio实现在图片上涂鸦,并保存涂鸦轨迹

开个坑,终于有时间整理一下这个项目里用到的比较重要的技术
虽然最后甲方没有采用(笑)
因为博主学艺不精,有很多小bug

AndroidStudio版本:2020.3.1.25
实现效果:AndroidStudio实现在图片上涂鸦并记录涂鸦轨迹
本文通过重写view类,实现在选择的图片上涂鸦的功能,因为项目需要残留了一些多余代码
项目结构:
MainActivity为主程序类
HandWrite类为手写类,用于处理各种手势
res/layout/activity_main.xml 为主界面
res/xml/file_path.xml 为图片缓存路径
AndroidStudio实现在图片上涂鸦并记录涂鸦轨迹

0.Handwrite类

划重点,此类中重写了view类,画出了手指按下的轨迹,并记录在pointlist中

package com.buildmaterialapplication;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

import java.util.ArrayList;

public class HandWrite extends View {
    Paint paint = null;  //定义画笔
    Bitmap origBit = null;  //存放原始图像
    Bitmap new_1Bit = null;   //存放从原始图像复制的位图图像
    Bitmap new_2Bit = null;      //存放处理后的图像
    float startX = 0,startY = 0;   //画线的起点坐标
    float clickX = 0, clickY = 0;   //画线的终点坐标
    boolean isMove = false;   //设置是否画线的标记
    boolean isDown =false;
    boolean isClear = false;    //设置是否清除涂鸦的标记
    int color = Color.BLUE;    //设置画笔的颜色
    float strokeWidth = 4.0f;    //设置画笔的宽度
    private int pen_type=0;
    ArrayList<Point> pointList =new ArrayList();
    Point point=new Point();

    public HandWrite(Context context, AttributeSet arrs){
        super(context,arrs);
    }
    //设置画笔类型
    public void setPen_type(int x) {
        pen_type = x;
        Log.e("penType",Integer.toString(pen_type));
    }
    //构造
    public HandWrite(Context context, Bitmap bm, int type) {
        super(context);
        // 从资源中获取原始图像
        //origBit = BitmapFactory.decodeFile("/storage/emulated/0/Android/data/com.example.test/cache/19771639479845774.jpg").copy(Bitmap.Config.ARGB_8888,true);
        origBit=Bitmap.createBitmap(bm).copy(Bitmap.Config.ARGB_8888,true);
        //origBit = BitmapFactory.decodeResource(getResources(), R.drawable.p1).copy(Bitmap.Config.ARGB_8888,true);
        // 建立原始图像的位图
        new_1Bit = Bitmap.createBitmap(origBit);
        pen_type=type;
    }

    public Point getPoint() {
        return point;
    }


    public ArrayList<Point> getPointList() {
        return pointList;
    }
    // 清除涂鸦
    public void clear() {
        isClear = true;
        new_2Bit = Bitmap.createBitmap(origBit);
        invalidate();
    }
    //设置画笔样式
    public void setSytle(float strokeWidth) {
        this.strokeWidth = strokeWidth;
        this.color=Color.BLUE;
    }
    //重写ondraw方法
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawBitmap(HandWriting(new_1Bit),0,0,null);
    }

    private Bitmap HandWriting(Bitmap origBit) {  //记录绘制图形
        Canvas canvas = null;  // 定义画布
        if (isClear) {  // 创建绘制新图形的画布
            canvas = new Canvas(new_2Bit);
        }
        else {
            canvas = new Canvas(origBit);  //创建绘制原图形的画布
        }

        paint = new Paint();
        paint.setStyle(Paint.Style.STROKE);
        paint.setAntiAlias(true);
        paint.setColor(color);
        paint.setStrokeWidth(strokeWidth);
        //lasso
        if (isMove&&(pen_type==3)){

            canvas.drawLine(startX,startY,clickX,clickY,paint);  // 在画布上画线条
        }
        //eraser
        if (isMove&&pen_type==2){
            paint.setAlpha(70);
            paint.setStrokeWidth(40.0f);
            paint.setStyle(Paint.Style.FILL);
            canvas.drawLine(startX,startY,clickX,clickY,paint);  // 在画布上画线条
        }
        startX = clickX;
        startY = clickY;
        //pen
        if (pen_type==1){
            if(isDown){
                paint.setColor(Color.GREEN);
                paint.setStyle(Paint.Style.FILL);
                canvas.drawCircle(clickX,clickY,20.0f,paint);


            }
        }
        //点击的eraser
        if (isDown&&pen_type==2){
                paint.setColor(Color.BLUE);
                canvas.drawCircle(clickX,clickY,20.0f,paint);

        }
        if (isClear){
            return new_2Bit;  // 返回新绘制的图像
        }
        return origBit;  // 若清屏,则返回原图像
    }


    @Override
    public boolean onTouchEvent(MotionEvent event) {

        final int historySize = event.getHistorySize();
        final int pointerCount = event.getPointerCount();
        for (int h = 0; h < historySize; h++) {
//            Log.e("At time %d:", (event.getHistoricalEventTime(h)));
            for (int p = 0; p < pointerCount; p++) {

                /*System.out.printf("  pointer %d: (%f,%f)",
                        event.getPointerId(p), event.getHistoricalX(p, h), event.getHistoricalY(p, h));*/
                int po=(event.getPointerId(p));
                Float ghx=event.getHistoricalX(p, h);
                Float ghy=event.getHistoricalY(p, h);
                String pointer =Integer.toString(po);
                String x=Float.toString(ghx);
                String y=Float.toString(ghy);
                String tmp1=pointer+x+y;
//                Log.e("pointer1",tmp1);
            }
        }

        int time= (int) event.getEventTime();
//        String tmp2=Integer.toString(time);
//        Log.e("time",tmp2);

        //System.out.printf("At time %d:", event.getEventTime());
        //获取点集
        for (int p = 0; p < pointerCount; p++) {
            int po=(event.getPointerId(p));
            int ghx=(int) event.getX(p);
            int ghy=(int) event.getY(p);
            Point point=new Point(ghx,ghy);
            if(!pointList.contains(point)){
                if(pen_type!=2){
                    pointList.add(p,point);
                    int x=pointList.get(p).x;
                    int y=pointList.get(p).y;
                    Log.e("po",Integer.toString(x)+" "+Integer.toString(y));
                }
            }
//            String pointer =Integer.toString(po);
            /*
            System.out.printf("  pointer %d: (%f,%f)",
                    event.getPointerId(p), event.getX(p), event.getY(p));*/
        }
        clickX = event.getX();  // 获取触摸坐标位置
        clickY = event.getY();
        if(event.getAction() == MotionEvent.ACTION_DOWN){
            isMove=false;
            isDown=true;
            invalidate();
            return true;
        }
         else if (event.getAction() == MotionEvent.ACTION_MOVE) {  // 记录在屏幕上划动的轨迹
            isMove = true;
            isDown=false;
            invalidate();
            return true;
        }
        if(event.getAction()==MotionEvent.ACTION_UP){
            performClick();
            invalidate();
            return true;
        }
        return super.onTouchEvent(event);
    }
}

1.activity_main.xml界面

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:gravity="center"
        android:orientation="horizontal">


</LinearLayout>
    <LinearLayout
        android:id="@+id/hw"
        android:layout_width="300dp"
        android:layout_height="500dp"
        android:layout_gravity="center"
        android:gravity="center"
        android:orientation="horizontal"></LinearLayout>

    <TextView
        android:id="@+id/txt_result"
        android:layout_gravity="center"
        android:layout_marginTop="@dimen/space"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="@dimen/txt_choose"
        android:textColor="@color/black"></TextView>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:layout_marginBottom="100dp"
        android:gravity="center"
        android:orientation="horizontal">

        <LinearLayout
            android:layout_width="@dimen/icon_loc"
            android:layout_height="match_parent"
            android:gravity="center"
            android:orientation="vertical">

            <Button
                android:id="@+id/icon_lasso"
                android:layout_width="@dimen/icon_size"
                android:layout_height="@dimen/icon_size"
                android:background="@drawable/ic_lasso"></Button>
            <TextView
                android:id="@+id/txt_lasso"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="@dimen/space"
                android:text="@string/txt_lasso"
                android:textColor="@color/black"
                android:textSize="@dimen/btn_txt_size"
                ></TextView>

        </LinearLayout>
        <LinearLayout
            android:layout_width="@dimen/icon_loc"
            android:layout_height="match_parent"
            android:gravity="center"
            android:orientation="vertical">
            <Button
                android:id="@+id/icon_pen"
                android:layout_width="@dimen/icon_size"
                android:layout_height="@dimen/icon_size"
                android:background="@drawable/ic_pen"></Button>
            <TextView
                android:id="@+id/txt_pen"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="@dimen/space"
                android:text="@string/txt_pen"
                android:textColor="@color/black"
                android:textSize="@dimen/btn_txt_size"
                ></TextView>
        </LinearLayout>

        <LinearLayout
            android:layout_width="@dimen/icon_loc"
            android:layout_height="match_parent"
            android:gravity="center"
            android:orientation="vertical">
            <Button
                android:id="@+id/icon_next"
                android:layout_width="@dimen/icon_size"
                android:layout_height="@dimen/icon_size"
                android:gravity="center"
                android:background="@drawable/ic_next"></Button>
            <TextView
                android:id="@+id/txt_next"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="@dimen/space"
                android:text="@string/txt_next"
                android:textColor="@color/black"
                android:textSize="@dimen/btn_txt_size"
                ></TextView>
        </LinearLayout>
    </LinearLayout>
</LinearLayout>

2.file_paths.xml图片存储路径

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <external-cache-path path="." name="take_photo"/>
</resources>

3.mainfest文件

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.buildmaterialapplication">
    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"
        tools:ignore="ProtectedPermissions"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.CAMERA"/>
    <uses-permission android:name="android.permission.INTERNET" />
    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_car"
        android:label="@string/app_name"
        android:roundIcon="@drawable/ic_car"
        android:supportsRtl="true"
        android:theme="@style/Theme.MyApplication"
        android:requestLegacyExternalStorage="true"
        android:usesCleartextTraffic="true"
        android:hardwareAccelerated="false"
        android:largeHeap="true">
        <activity
            android:name=".MainActivity"
            android:exported="true"
            tools:ignore="DuplicateActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <provider
            android:authorities="com.buildmaterialapplication.fileprovider"
            android:name="androidx.core.content.FileProvider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths"/>
        </provider>
    </application>
    <supports-screens android:resizeable="true" />
</manifest>

4.MainActivity

package com.buildmaterialapplication;

import android.Manifest;
import android.annotation.SuppressLint;
import android.content.DialogInterface;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.Matrix;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.MediaStore;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;

import org.jetbrains.annotations.Nullable;
import org.opencv.core.Mat;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.Objects;

import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.FileProvider;

//阅读前请查看README文件
public class MainActivity extends AppCompatActivity {

    //spinner用的列表
    private final static String[] items = new String[]{
            "拍照",
            "从相册中选择",
    };
    public static final int TAKE_PHOTO=1;//声明一个请求码,用于识别返回的结果
    private static final int SCAN_OPEN_PHONE = 2;// 相册
    private Uri imageUri;
    public String path=null;
    Bitmap bitmap=null;
    public int count=0;
    public String picpath=null;
    private LinearLayout handWrite=null;
    public HandWrite hd;
    public HandWrite ori_hd;    //当前显示的handwrite控件
    public boolean isLasso=false;   //是否选择范围
    int requestW=0;         //初始化页面显示的图片的宽高
    int requestH=0;
    int drawType=0;         //画笔类型 1画笔 2橡皮 3套索

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);


        askPermission();
        //用于设置图片显示的大小
        DisplayMetrics dm=getResources().getDisplayMetrics();
        requestW=(int) (dm.widthPixels*0.8);
        requestH=(int) (dm.heightPixels*0.6);
        //加载手写类控件
        handWrite=findViewById(R.id.hw);
        choosePic();
        //aiAlgorithm
        buttonEvent();
        if(drawType==0){
            TextView txt_pen=findViewById(R.id.txt_pen);

            txt_pen.setTextColor(Color.BLACK);

        }
    }
    /**
    * @name: askPermission
    * @param :null
    * @return :null
    * @describe:  请求相机等权限
    */
    private void askPermission(){
        ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE,
                    Manifest.permission.READ_EXTERNAL_STORAGE,
                    Manifest.permission.CAMERA
            },0);


    }
    /**
    * @name: buttonEvent
    * @param :null
    * @return null
    * @describe:  按钮点击事件
    */
    public void buttonEvent() {
        Button btn_pen;
        Button btn_lasso;
        Button btn_eraser;
        TextView txt_pen = findViewById(R.id.txt_pen);
        TextView txt_lasso = findViewById(R.id.txt_lasso);
        btn_pen = (Button) findViewById(R.id.icon_pen);
        //画笔事件
        btn_pen.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                txt_pen.setTextColor(Color.GRAY);
                txt_lasso.setTextColor(Color.BLACK);

                drawType = 1;
                ori_hd.setPen_type(drawType);
            }
        });

        //套索事件
        btn_lasso = (Button) findViewById(R.id.icon_lasso);
        btn_lasso.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                txt_lasso.setTextColor(Color.GRAY);
                txt_pen.setTextColor(Color.BLACK);
//                draw_type=0;
                drawType = 3;
                ori_hd.setPen_type(drawType);
                isLasso = true;

            }
        });

        //下一张
        Button ic_next = (Button) findViewById(R.id.icon_next);
        ic_next.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                startActivity(new Intent(com.buildmaterialapplication.MainActivity.this, com.buildmaterialapplication.MainActivity.class));
            }
        });
    }


    /**
    * @name: choosePic
    * @params:
    * @return
    * @describe:  选择照片或拍照
    */
    private void choosePic(){
        count=0;
        AlertDialog.Builder builder = new AlertDialog.Builder(com.buildmaterialapplication.MainActivity.this)
                .setTitle("请选择图片")//设置对话框 标题
                .setItems(items, new DialogInterface.OnClickListener() {
                    @RequiresApi(api = Build.VERSION_CODES.N)
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        if(which==0){
                            openCamera();
                        }
                        else{
                            openGallery();
                        }
                        return;
                    }
                });
        builder.create()
                .show();
    }
    /**
    * @name: openGallery
    * @params:
    * @return
    * @describe:  拍照
    */
    private void openGallery() {
        Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);

        //intent.setType("image/*");
        startActivityForResult(intent, SCAN_OPEN_PHONE);

    }
    /**
    * @name: openCamera
    * @params:
    * @return
    * @describe:  拍照
    */
    @RequiresApi(api = Build.VERSION_CODES.N)
    private void openCamera(){
        String imageName = new SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault()).format(new Date());
//        File outputImage=new File(Environment.getExternalStorageDirectory().getAbsolutePath()+"/data/com.example.woundapplication/"+imageName+".jpg");

        File outputImage = new File(getExternalCacheDir(), imageName+".jpg");
        Objects.requireNonNull(outputImage.getParentFile()).mkdirs();
//        Log.e("", outputImage.getAbsolutePath());
                /*
                创建一个File文件对象,用于存放摄像头拍下的图片,我们把这个图片命名为output_image.jpg
                并把它存放在应用关联缓存目录下,调用getExternalCacheDir()可以得到这个目录,为什么要
                用关联缓存目录呢?由于android6.0开始,读写sd卡列为了危险权限,使用的时候必须要有权限,
                应用关联目录则可以跳过这一步
                 */
        try//判断图片是否存在,存在则删除在创建,不存在则直接创建
        {
            if(outputImage.exists())
            {
                outputImage.delete();
            }
            boolean a = outputImage.createNewFile();
//            Log.e("createNewFile", String.valueOf(a));
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
        if(Build.VERSION.SDK_INT>=24)
            //判断安卓的版本是否高于7.0,高于则调用高于的方法,低于则调用低于的方法
            //把文件转换成Uri对象
                    /*
                    之所以这样,是因为android7.0以后直接使用本地真实路径是不安全的,会抛出异常。
                    FileProvider是一种特殊的内容提供器,可以对数据进行保护
                     */
        {
            imageUri= FileProvider.getUriForFile(com.buildmaterialapplication.MainActivity.this,
                    "com.buildmaterialapplication.fileprovider",outputImage);
//            imageUri=Uri.fromFile(outputImage);
            path=imageUri.getPath();
            Log.e(">7:",path);

                    /*
                    第一个参数:context对象
                    第二个参数:任意唯一的字符串
                    第三个参数:文件对象
                     */

        }
        else {
            imageUri= Uri.fromFile(outputImage);
            path=imageUri.getPath();
            Log.e("<7:",imageUri.getPath());
        }

        //使用隐示的Intent,系统会找到与它对应的活动,即调用摄像头,并把它存储
        Intent intent0=new Intent("android.media.action.IMAGE_CAPTURE");
//        Intent intent0=new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
        intent0.putExtra(MediaStore.EXTRA_OUTPUT,imageUri);
        startActivityForResult(intent0,TAKE_PHOTO);
        //调用会返回结果的开启方式,返回成功的话,则把它显示出来

        Log.e("pic",path);
    }
    //拍照或相册的响应事件
    @SuppressLint("SetTextI18n")
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        Bitmap bitmaptmp;
        switch (requestCode) {

            case TAKE_PHOTO:
                if (resultCode == RESULT_OK) {
                    //将图片解析成Bitmap对象,并把它显现出来
                    try {

                        bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(imageUri));
                    } catch (FileNotFoundException e) {
                        e.printStackTrace();
                    }
                    picpath= com.buildmaterialapplication.MainActivity.this.getExternalCacheDir().getPath()+imageUri.getPath();
//                        bitmap =BitmapFactory.decodeStream(filePath);
                    Log.e("filename",picpath);

                    @SuppressLint("SdCardPath") String fileName = picpath;
                    //缩放
                    bitmap=getScaleBitmap(bitmap,requestW,requestH);

                    Mat m=new Mat();

                    hd=new HandWrite(com.buildmaterialapplication.MainActivity.this,bitmap,0);
                    handWrite.addView(hd);
                    ori_hd=hd;
                }

                break;
                //相册的响应
            case SCAN_OPEN_PHONE:
                if (resultCode == RESULT_OK){

                    Uri selectImage=data.getData();
                    String[] FilePathColumn={MediaStore.Images.Media.DATA};
                    Cursor cursor = getContentResolver().query(selectImage,
                            FilePathColumn, null, null, null);
                    cursor.moveToFirst();
                    //从数据视图中获取已选择图片的路径
                    int columnIndex = cursor.getColumnIndex(FilePathColumn[0]);

                    picpath = cursor.getString(columnIndex);

                    Log.e("picpath",picpath);
                    cursor.close();
                    bitmaptmp=BitmapFactory.decodeFile(picpath);
                    bitmap=getScaleBitmap(bitmaptmp,requestW,requestH);
                    //预处理
                    hd=new HandWrite(com.buildmaterialapplication.MainActivity.this,bitmap,0);
                    handWrite.addView(hd);
                    ori_hd=hd;
                }
                break;
            default:
                break;
                }
    }
    /**
    * @name: getScaleBitmap
    * @params:
     *      sourceBitmap:原图
     *      width: 需要的宽
     *      height: 需要的高
    * @return
    * @describe:  bitmap缩放函数
    */
    private Bitmap getScaleBitmap(Bitmap sourceBitmap,float width,float height){
        Bitmap scaleBitmap;

        Matrix matrix = new Matrix();

        float scale= 0;
        if(sourceBitmap.getWidth()>sourceBitmap.getHeight()){
            scale=width/sourceBitmap.getWidth();
        }
        else{
            scale=height/ sourceBitmap.getHeight();
        }
//        float scale_x = width/sourceBitmap.getWidth();
//        float scale_y = height/sourceBitmap.getHeight();
        matrix.postScale(scale,scale);

        try {
            scaleBitmap = Bitmap.createBitmap(sourceBitmap,0,0,sourceBitmap.getWidth(),sourceBitmap.getHeight(),matrix,true);
        }catch (OutOfMemoryError e){
            scaleBitmap = null;
            System.gc();
        }
        return scaleBitmap;
    }
}


结语

最后只保留了涂鸦的部分,擦除有点bug
不想读取手机中的照片,想直接用电脑上的照片的话,改掉handwrite类里的构造方法就好了,注释中有写使用本地资源的方法,
代码还是有很多问题的,handwrite类的刷新写的也很麻烦,如果不是学习Android的话,尽量不要用这么底层的方法去写文章来源地址https://www.toymoban.com/news/detail-485067.html

到了这里,关于AndroidStudio实现在图片上涂鸦并记录涂鸦轨迹的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • AndroidStudio-图片的上传以及存进mysql数据库里

    参考文章: 如何简单地利用BITMAP为中介储存图片到数据库中 android开发实现头像上传功能 先添加Tiny框架的依赖 然后创建dialog的xml文件dialog_select_photo 然后创建一个空白的activity,在该activity的xml里添加一个按钮(btn_test)和一个ImagView(image) 然后是activity里的代码,有些依赖可能会

    2023年04月23日
    浏览(38)
  • 涂鸦智能打造专业家庭智能生活助手,实现人机交互升级

    近年来,智能家居设备的品类不断拓展,同时,人们对AI与智能家居的联动愈发憧憬。自然语言交互是未来人机交互的主要趋势之一,其关键在于使AI具备主动理解信息的能力,让用户的交互更轻松。如何将智能场景的交互变得更“善解人意”,成为行业亟需解决的难题。 面

    2024年02月16日
    浏览(49)
  • Unity绘画功能实现(包含涂鸦、颜色一键填充、撤销操作、保存图像)

    项目需要,要实现在图像上进行绘画,看来网上的很多Unity绘画代码,感觉挺复杂的而且功能不全,这里我自己实现了一个在图像上进行绘画的代码,包含了涂鸦、一键填充颜色、撤销上一次操作、保存图片功能。 本项目是在http://www.qb5200.com/article/391439.html上进行了魔改。 左

    2024年02月14日
    浏览(43)
  • 【RTB机器人工具箱学习记录】轨迹规划实例

    给定位置: 位姿插值: trinterp() trinterp(T0, T1, M) ​ T0:初始变换矩阵 ​ T1:结束变换矩阵 ​ M: 线性插值轨迹动画:(轨迹如上图左所示) 五次多项式插值轨迹动画:(轨迹如上图右所示,和上面用mtraj遍历方式的轨迹相同) 笛卡尔轨迹 ctraj() : TC = ctraj(T0, T1, N) ​ T0:初始变

    2023年04月22日
    浏览(54)
  • 微信小程序对上传图片进行裁剪实现记录

    媒体 / 图片 / wx.cropImage (qq.com) 小程序图片裁剪插件 image-cropper | 微信开放社区 (qq.com) 1、将插件项目中image-cropper文件内容复制到本地项目中的compoent中 wxml: js:  json: wxss:根据自己需求调整    2、然后在要引用插件的页面json文件中添加image-cropper 3、在引用插件的wxml文件中引

    2024年04月16日
    浏览(57)
  • 记录--前端实现文件预览(pdf、excel、word、图片)

    需求:实现一个在线预览pdf、excel、word、图片等文件的功能。 介绍:支持pdf、xlsx、docx、jpg、png、jpeg。 以下使用Vue3代码实现所有功能,建议以下的预览文件标签可以在外层包裹一层弹窗。 iframe标签能够将另一个HTML页面嵌入到当前页面中,我们的图片也能够使用iframe标签来

    2024年02月09日
    浏览(66)
  • 记录-JS简单实现购物车图片局部放大预览效果

    代码不多,先看一下 HTML 里面结构很简单,初始化 MagnifyingGlass 对象来关联一个 IMG 标签来实现放大。 再看一下 MagnifyingGlass 上面的就是全部逻辑,实现方法肯定不是最优的,但是其中可以联想到通过像素点的操作实现任意效果。 可以启动一个 node 本地服务,首先见一个

    2023年04月20日
    浏览(81)
  • DEJA_VU3D - Cesium功能集 之 078-对象材质:图片轨迹线

    编写这个专栏主要目的是对工作之中基于Cesium实现过的功能进行整合,有自己琢磨实现的,也有参考其他大神后整理实现的,初步算了算现在有实现120个左右的功能,后续也会不断的追加,所以暂时打算一周2-3更的样子来更新本专栏(每篇博文都会奉上完整demo的源代码,尽可

    2024年02月02日
    浏览(62)
  • 神舟电脑4年半的时间里 拆了N次,现在又 跳出 CMOS Message 问题,记录我的修机过程

    前言 这个国庆假期,坏了两个电子产品,分别为DIJ遥控器和已经陪伴我4年半的笔记本电脑(CMOS电压过低)。4 年半的时间里,这台神舟笔记本电脑拆了5 次以上,几次是日常扫灰保养,有一次是在21年12月固态坏了(当时进不了系统,开机屏幕都是暗黑的,还好之前重装系统时,

    2024年02月04日
    浏览(295)
  • 涂鸦wifi智能插座(BK7231N/BK7231T)刷开源固件,实现mqttt本地化接入homeassistant

    涂鸦智能插座(BK7231N/BK7231T)刷固件,实现mqtt本地化接入homeassistant 买了一个涂鸦插座,刚开始通过涂鸦开发者api接入homeassistant 正常使用后就没管它。 但是一个月后。。。。。。涂鸦开发授权到期了。看了下续费价格,果断放弃。 辗转论坛,首先找到了通过localtuya接入的方

    2024年02月11日
    浏览(76)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包