AndroidStudio实现在图片上涂鸦并记录涂鸦轨迹
AndroidStudio實(shí)現(xiàn)在圖片上涂鴉,并保存涂鴉軌跡
開(kāi)個(gè)坑,終于有時(shí)間整理一下這個(gè)項(xiàng)目里用到的比較重要的技術(shù)
雖然最后甲方?jīng)]有采用(笑)
因?yàn)椴┲鲗W(xué)藝不精,有很多小bug
AndroidStudio版本:2020.3.1.25
實(shí)現(xiàn)效果:
本文通過(guò)重寫(xiě)view類(lèi),實(shí)現(xiàn)在選擇的圖片上涂鴉的功能,因?yàn)轫?xiàng)目需要?dú)埩袅艘恍┒嘤啻a
項(xiàng)目結(jié)構(gòu):
MainActivity為主程序類(lèi)
HandWrite類(lèi)為手寫(xiě)類(lèi),用于處理各種手勢(shì)
res/layout/activity_main.xml 為主界面
res/xml/file_path.xml 為圖片緩存路徑
0.Handwrite類(lèi)
劃重點(diǎn),此類(lèi)中重寫(xiě)了view類(lèi),畫(huà)出了手指按下的軌跡,并記錄在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; //定義畫(huà)筆Bitmap origBit = null; //存放原始圖像Bitmap new_1Bit = null; //存放從原始圖像復(fù)制的位圖圖像Bitmap new_2Bit = null; //存放處理后的圖像float startX = 0,startY = 0; //畫(huà)線的起點(diǎn)坐標(biāo)float clickX = 0, clickY = 0; //畫(huà)線的終點(diǎn)坐標(biāo)boolean isMove = false; //設(shè)置是否畫(huà)線的標(biāo)記boolean isDown =false;boolean isClear = false; //設(shè)置是否清除涂鴉的標(biāo)記int color = Color.BLUE; //設(shè)置畫(huà)筆的顏色float strokeWidth = 4.0f; //設(shè)置畫(huà)筆的寬度private int pen_type=0;ArrayList<Point> pointList =new ArrayList();Point point=new Point();public HandWrite(Context context, AttributeSet arrs){super(context,arrs);}//設(shè)置畫(huà)筆類(lèi)型public void setPen_type(int x) {pen_type = x;Log.e("penType",Integer.toString(pen_type));}//構(gòu)造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();}//設(shè)置畫(huà)筆樣式public void setSytle(float strokeWidth) {this.strokeWidth = strokeWidth;this.color=Color.BLUE;}//重寫(xiě)ondraw方法@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);canvas.drawBitmap(HandWriting(new_1Bit),0,0,null);}private Bitmap HandWriting(Bitmap origBit) { //記錄繪制圖形Canvas canvas = null; // 定義畫(huà)布if (isClear) { // 創(chuàng)建繪制新圖形的畫(huà)布canvas = new Canvas(new_2Bit);}else {canvas = new Canvas(origBit); //創(chuàng)建繪制原圖形的畫(huà)布}paint = new Paint();paint.setStyle(Paint.Style.STROKE);paint.setAntiAlias(true);paint.setColor(color);paint.setStrokeWidth(strokeWidth);//lassoif (isMove&&(pen_type==3)){canvas.drawLine(startX,startY,clickX,clickY,paint); // 在畫(huà)布上畫(huà)線條}//eraserif (isMove&&pen_type==2){paint.setAlpha(70);paint.setStrokeWidth(40.0f);paint.setStyle(Paint.Style.FILL);canvas.drawLine(startX,startY,clickX,clickY,paint); // 在畫(huà)布上畫(huà)線條}startX = clickX;startY = clickY;//penif (pen_type==1){if(isDown){paint.setColor(Color.GREEN);paint.setStyle(Paint.Style.FILL);canvas.drawCircle(clickX,clickY,20.0f,paint);}}//點(diǎn)擊的eraserif (isDown&&pen_type==2){paint.setColor(Color.BLUE);canvas.drawCircle(clickX,clickY,20.0f,paint);}if (isClear){return new_2Bit; // 返回新繪制的圖像}return origBit; // 若清屏,則返回原圖像}@Overridepublic 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());//獲取點(diǎn)集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(); // 獲取觸摸坐標(biāo)位置clickY = event.getY();if(event.getAction() == MotionEvent.ACTION_DOWN){isMove=false;isDown=true;invalidate();return true;}else if (event.getAction() == MotionEvent.ACTION_MOVE) { // 記錄在屏幕上劃動(dòng)的軌跡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"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="50dp"android:gravity="center"android:orientation="horizontal"></LinearLayout><LinearLayoutandroid:id="@+id/hw"android:layout_width="300dp"android:layout_height="500dp"android:layout_gravity="center"android:gravity="center"android:orientation="horizontal"></LinearLayout><TextViewandroid: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><LinearLayoutandroid:layout_width="match_parent"android:layout_height="100dp"android:layout_marginBottom="100dp"android:gravity="center"android:orientation="horizontal"><LinearLayoutandroid:layout_width="@dimen/icon_loc"android:layout_height="match_parent"android:gravity="center"android:orientation="vertical"><Buttonandroid:id="@+id/icon_lasso"android:layout_width="@dimen/icon_size"android:layout_height="@dimen/icon_size"android:background="@drawable/ic_lasso"></Button><TextViewandroid: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><LinearLayoutandroid:layout_width="@dimen/icon_loc"android:layout_height="match_parent"android:gravity="center"android:orientation="vertical"><Buttonandroid:id="@+id/icon_pen"android:layout_width="@dimen/icon_size"android:layout_height="@dimen/icon_size"android:background="@drawable/ic_pen"></Button><TextViewandroid: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><LinearLayoutandroid:layout_width="@dimen/icon_loc"android:layout_height="match_parent"android:gravity="center"android:orientation="vertical"><Buttonandroid: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><TextViewandroid: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圖片存儲(chǔ)路徑
<?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" /><applicationandroid: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"><activityandroid: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><providerandroid:authorities="com.buildmaterialapplication.fileprovider"android:name="androidx.core.content.FileProvider"android:exported="false"android:grantUriPermissions="true"><meta-dataandroid: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;//閱讀前請(qǐng)查看README文件 public class MainActivity extends AppCompatActivity {//spinner用的列表private final static String[] items = new String[]{"拍照","從相冊(cè)中選擇",};public static final int TAKE_PHOTO=1;//聲明一個(gè)請(qǐng)求碼,用于識(shí)別返回的結(jié)果private static final int SCAN_OPEN_PHONE = 2;// 相冊(cè)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; //當(dāng)前顯示的handwrite控件public boolean isLasso=false; //是否選擇范圍int requestW=0; //初始化頁(yè)面顯示的圖片的寬高int requestH=0;int drawType=0; //畫(huà)筆類(lèi)型 1畫(huà)筆 2橡皮 3套索@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);askPermission();//用于設(shè)置圖片顯示的大小DisplayMetrics dm=getResources().getDisplayMetrics();requestW=(int) (dm.widthPixels*0.8);requestH=(int) (dm.heightPixels*0.6);//加載手寫(xiě)類(lèi)控件handWrite=findViewById(R.id.hw);choosePic();//aiAlgorithmbuttonEvent();if(drawType==0){TextView txt_pen=findViewById(R.id.txt_pen);txt_pen.setTextColor(Color.BLACK);}}/*** @name: askPermission* @param :null* @return :null* @describe: 請(qǐng)求相機(jī)等權(quán)限*/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: 按鈕點(diǎn)擊事件*/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);//畫(huà)筆事件btn_pen.setOnClickListener(new View.OnClickListener() {@Overridepublic 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() {@Overridepublic 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() {@Overridepublic 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("請(qǐng)選擇圖片")//設(shè)置對(duì)話框 標(biāo)題.setItems(items, new DialogInterface.OnClickListener() {@RequiresApi(api = Build.VERSION_CODES.N)@Overridepublic 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());/*創(chuàng)建一個(gè)File文件對(duì)象,用于存放攝像頭拍下的圖片,我們把這個(gè)圖片命名為output_image.jpg并把它存放在應(yīng)用關(guān)聯(lián)緩存目錄下,調(diào)用getExternalCacheDir()可以得到這個(gè)目錄,為什么要用關(guān)聯(lián)緩存目錄呢?由于android6.0開(kāi)始,讀寫(xiě)sd卡列為了危險(xiǎn)權(quán)限,使用的時(shí)候必須要有權(quán)限,應(yīng)用關(guān)聯(lián)目錄則可以跳過(guò)這一步*/try//判斷圖片是否存在,存在則刪除在創(chuàng)建,不存在則直接創(chuàng)建{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,高于則調(diào)用高于的方法,低于則調(diào)用低于的方法//把文件轉(zhuǎn)換成Uri對(duì)象/*之所以這樣,是因?yàn)閍ndroid7.0以后直接使用本地真實(shí)路徑是不安全的,會(huì)拋出異常。FileProvider是一種特殊的內(nèi)容提供器,可以對(duì)數(shù)據(jù)進(jìn)行保護(hù)*/{imageUri= FileProvider.getUriForFile(com.buildmaterialapplication.MainActivity.this,"com.buildmaterialapplication.fileprovider",outputImage); // imageUri=Uri.fromFile(outputImage);path=imageUri.getPath();Log.e(">7:",path);/*第一個(gè)參數(shù):context對(duì)象第二個(gè)參數(shù):任意唯一的字符串第三個(gè)參數(shù):文件對(duì)象*/}else {imageUri= Uri.fromFile(outputImage);path=imageUri.getPath();Log.e("<7:",imageUri.getPath());}//使用隱示的Intent,系統(tǒng)會(huì)找到與它對(duì)應(yīng)的活動(dòng),即調(diào)用攝像頭,并把它存儲(chǔ)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);//調(diào)用會(huì)返回結(jié)果的開(kāi)啟方式,返回成功的話,則把它顯示出來(lái)Log.e("pic",path);}//拍照或相冊(cè)的響應(yīng)事件@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對(duì)象,并把它顯現(xiàn)出來(lái)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;//相冊(cè)的響應(yīng)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();//從數(shù)據(jù)視圖中獲取已選擇圖片的路徑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);//預(yù)處理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縮放函數(shù)*/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;} }結(jié)語(yǔ)
最后只保留了涂鴉的部分,擦除有點(diǎn)bug
不想讀取手機(jī)中的照片,想直接用電腦上的照片的話,改掉handwrite類(lèi)里的構(gòu)造方法就好了,注釋中有寫(xiě)使用本地資源的方法,
代碼還是有很多問(wèn)題的,handwrite類(lèi)的刷新寫(xiě)的也很麻煩,如果不是學(xué)習(xí)Android的話,盡量不要用這么底層的方法去寫(xiě)
總結(jié)
以上是生活随笔為你收集整理的AndroidStudio实现在图片上涂鸦并记录涂鸦轨迹的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 虚拟机安装LEDE之后如何配置连接互联网
- 下一篇: Android作业四