美文网首页
给图片添加位置信息,旋转角度处理

给图片添加位置信息,旋转角度处理

作者: 有点健忘 | 来源:发表于2018-09-18 17:04 被阅读273次

    今天有个需求,就是要点击一个按钮通过相机拍摄一张带位置信息的图片, 想了下直接打开相机的保存位置的功能选项即可,可咋才能直接打开这个功能,或者跳转到这个设置页面。

    搜了搜,貌似没找到。

    最简单的当然是用系统相机,直接跳到系统拍照界面,方便省事拉。可这个貌似没有太多 可以设置的地方
    后来想自己用camera这个东西吧,我看这里可以setParams,而params里是有经纬度参数可以设置的。
    就想着不用系统的,自己弄个页面来拍照吧,顺道把信息放进去。
    当然这个是可行的,我们这里没太大需求,我就直接跳到相机了,省事。
    关键是用camera还得分两种api21以上还得camera2.最讨厌这种适配的了。也许以后最低api21了可以再弄这种吧。

    注意事项

    ExifInterface only supports saving attributes on JPEG formats.
    这玩意只支持jpg的格式,png不支持的,

    后来就想着能不能直接给图片添加信息,还真找到了
    https://blog.csdn.net/goumiaoqiong/article/details/52136547

    我就正常用系统拍照,然后给图片文件添加额外信息,下边代码是错误的

     val exif=ExifInterface(mPhotoFile?.absolutePath)
                    val longitude = exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE)
                    val latitude = exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE)
                    if(TextUtils.isEmpty(longitude)||TextUtils.isEmpty(latitude)) {
                        exif.setAttribute(ExifInterface.TAG_GPS_LONGITUDE, "100");
                        exif.setAttribute(ExifInterface.TAG_GPS_LATITUDE, "100");
                        exif.saveAttributes();
                    }else{
                        println("==========$longitude======$latitude")
                    }
    

    结果看到了如下的错误信息

    错误:Given tag (GPSLongitude) value didn't match with one of expected formats: URATIONAL (guess: USHORT, ULONG)

    然后我看了下这个tag的描述,发现它对格式是有要求的,不是直接存的经纬度

     /** Type is rational. Format is "num1/denom1,num2/denom2,num3/denom3". */
        public static final String TAG_GPS_LONGITUDE = "GPSLongitude";
    

    然后又搜到了这个
    http://www.mamicode.com/info-detail-404391.html
    复制了下转换的方法,然后发现日志里还是提示格式不对,然后我打印了下带定位的图片的位置信息,发现人家最后也是整数,没有小数的,我们复制的那个最后一位还是小数,不符合要求

    我这里是适合android的,具体要求还是看参数的解释吧

    就像上边贴的Format is "num1/denom1,num2/denom2,num3/denom3" ,这样的“123/1,27/1,24/1”
    既然是分子,分母,应该都是整数了。而且我试了下分子弄成负数也提示格式不对。

    val exif = ExifInterface(mPhotoFile?.absolutePath)
                    val longitude = exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE)
                    val latitude = exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE)
                    if (TextUtils.isEmpty(longitude) || TextUtils.isEmpty(latitude)) {
                        val jd=123.456789
                        val wd=33.987654321
                        val longitude = gpsInfoConvert(jd)
                        val latitude = gpsInfoConvert(wd)
                        exif.setAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF, if (jd > 0) "E" else "W")//东经西经
                        exif.setAttribute(ExifInterface.TAG_GPS_LATITUDE_REF,if (wd > 0) "N" else "S")//北纬,南纬
                        exif.setAttribute(ExifInterface.TAG_GPS_LONGITUDE, longitude);
                        exif.setAttribute(ExifInterface.TAG_GPS_LATITUDE, latitude);
                        //因为格式化以后的经纬度,负数都没了,所以要设置ref来确定是正的还是负的。
                        exif.saveAttributes();
                        println("change============$longitude====$latitude")
                    } else {
                        println("==========$longitude======$latitude")
                    }
    

    最后的方法,我是根据下边这个方法来修改了一下
    Location.convert(gpsInfo, Location.FORMAT_SECONDS);

        fun gpsInfoConvert(coordinate: Double): String {
            var coordinate = coordinate
            if (coordinate < -180.0 || coordinate > 180.0 ||
                    java.lang.Double.isNaN(coordinate)) {
                throw IllegalArgumentException("coordinate=$coordinate")
            }
            val sb = StringBuilder()
            if (coordinate < 0) {
                coordinate = -coordinate
            }
            val degrees = Math.floor(coordinate).toInt()
            sb.append(degrees)
            sb.append("/1,")
            coordinate -= degrees.toDouble()
            coordinate *= 60.0
            val minutes = Math.floor(coordinate).toInt()
            sb.append(minutes)
            sb.append("/1,")
            coordinate -= minutes.toDouble()
            coordinate *= 60.0
            sb.append(Math.floor(coordinate).toInt())
            sb.append("/1")
            return sb.toString()
        }
    
    坐标系统 coordinate system

    decimal 小数,就是一个double值 -180到180
    sexagesimal 六十进制,这种格式的 100'22'123.333 这种,其实就是对上边的double值的小数部分乘以60取整就是第二位,然后再对剩下的小数部分乘以60就是第三位

    中间插播一下,如何处理旋转图片

    如果你拿到一张旋转的图片,那么android种显示的时候得处理
    根据图片的旋转角度,处理Matrix
    来源:https://mp.weixin.qq.com/s/1M6rlgxsZDupkugDePbGZw

        private fun loadRotatePicture(imageView: ImageView,uri: Uri){
            println("uri====${uri.path}============file path=${UriUtils.getFilePath(baseContext,uri)}")
               
                 var bitmap = BitmapFactory.decodeFile(uri.path)
                val matrix = genOrientationMatrix(ExifInterface(uri.path))
                bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
                imageView.setImageBitmap(bitmap)
    
        }
         fun genOrientationMatrix(exifInterface: ExifInterface): Matrix {
            val matrix = Matrix()
            try {
                var orientation =  exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)
                if (orientation > 0) {
                    orientation--
                    if (orientation and 0b100 != 0) { //对角线翻转
                        matrix.postScale(-1.0f, 1.0f)
                        matrix.postRotate(-90f)
                    }
                    if (orientation and 0b010 != 0) { //旋转180度
                        matrix.postRotate(180f)
                    }
                    if (orientation and 0b001 != 0) { //水平翻转
                        matrix.postScale(-1.0f, 1.0f)
                    }
                }
                return matrix
            } catch (e: IOException) {
                e.printStackTrace()
            }
            return matrix
        }
    

    先说第一种直接调用系统相机拍照

    这里也有分两种,一种是图片保存在我们提供的File里【原图大小】,一种是返回一个bitmap【小图】
    布局文件很简单,2个按钮测试两种方法,一个imageview显示结果

    <?xml version="1.0" encoding="utf-8"?>
    <android.support.constraint.ConstraintLayout 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:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".camera.ActivityCapturePic">
        <include layout="@layout/include_toolbar" />
    
        <Button
            android:id="@+id/btn_capture"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="100dp"
            android:text="capture"
            android:onClick="startCamera"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
        <Button
            android:id="@+id/btn_capture2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="100dp"
            android:text="capture2"
            android:onClick="startCamera2"
            app:layout_constraintLeft_toRightOf="@+id/btn_capture"
            app:layout_constraintTop_toTopOf="parent" />
    
        <ImageView
            android:id="@+id/iv_result"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/btn_capture" />
    
    </android.support.constraint.ConstraintLayout>
    

    代码也简单

       override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_capture_pic)
            defaultSetTitle("camera")
            requestCamera()
        }
        companion object {
            private val REQUEST_SAVE_TO_FILE = 123//将图片保存在我们提供的file里
            private val REQUEST_RETURN_BITMAP_DATA = 124//返回bitmap的data数据
        }
        var fileUri: Uri? = null;
        var mPhotoFile: File? = null
        /**save picture to our provided file*/
        fun startCamera(v: View) {
            mPhotoFile = File(getExternalFilesDir("externalfilespath"), "${System.currentTimeMillis()}.jpg")
            val captureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                //参数1 上下文, 参数2 Provider主机地址和清单文件中保持一致   参数3 共享的文件
                fileUri = FileProvider.getUriForFile(baseContext, "com.charlie.fileProvider", mPhotoFile!!)
                //添加这一句表示对目标应用临时授权该Uri所代表的文件
                captureIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
            } else{
                fileUri = Uri.fromFile(mPhotoFile)
            }
            
            captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri)
            startActivityForResult(captureIntent, REQUEST_SAVE_TO_FILE)
        }
    
        /**return bitmap data*/
        fun startCamera2(v: View) {
            val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
            startActivityForResult(intent, REQUEST_RETURN_BITMAP_DATA)
    
        }
    
        override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
            super.onActivityResult(requestCode, resultCode, data)
            if (resultCode != Activity.RESULT_OK) {
                return
            }
    
            when (requestCode) {
                REQUEST_SAVE_TO_FILE -> {
                    iv_result.setImageURI(fileUri)
                    mPhotoFile?.absolutePath?.apply {
                        addGPSInfo(this)
                    }
                }
                REQUEST_RETURN_BITMAP_DATA -> {
                    //  content://media/external/images/media/98430
                    data?.apply {
                        val bitmap2 = data.getParcelableExtra("data") as Bitmap
                        iv_result.setImageBitmap(bitmap2)
                        val filePath=UriUtils.getFilePath(baseContext,data.data)
                        filePath?.apply {
                            println("file path===============${filePath}")
                            addGPSInfo(filePath)
                        }
                    }
    
                }
            }
    
        }
        private fun addGPSInfo(path:String){
            val exif = ExifInterface(path)//根据文件完整名字获取一个exif实例
            val longitude = exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE)
            val latitude = exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE)
            if (TextUtils.isEmpty(longitude) || TextUtils.isEmpty(latitude)) {
                val jd = 123.456789
                val wd = 33.987654321
                val longitude = gpsInfoConvert(jd)
                val latitude = gpsInfoConvert(wd)
                //设置一下4个gps相关属性
                exif.setAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF, if (jd > 0) "E" else "W")
                exif.setAttribute(ExifInterface.TAG_GPS_LATITUDE_REF, if (wd > 0) "N" else "S")
                exif.setAttribute(ExifInterface.TAG_GPS_LONGITUDE, longitude);
                exif.setAttribute(ExifInterface.TAG_GPS_LATITUDE, latitude);
    
                exif.saveAttributes();//保存属性到文件
            } else {
                println("==========$longitude======$latitude")
            }
        }
        fun gpsInfoConvert(coordinate: Double): String {
            var coordinate = coordinate
            if (coordinate < -180.0 || coordinate > 180.0 ||
                    java.lang.Double.isNaN(coordinate)) {
                throw IllegalArgumentException("coordinate=$coordinate")
            }
            val sb = StringBuilder()
            if (coordinate < 0) {//符号位丢掉
                coordinate = -coordinate
            }
            val degrees = Math.floor(coordinate).toInt()
            sb.append(degrees)
            sb.append("/1,")
            coordinate -= degrees.toDouble()
            coordinate *= 60.0
            val minutes = Math.floor(coordinate).toInt()
            sb.append(minutes)
            sb.append("/1,")
            coordinate -= minutes.toDouble()
            coordinate *= 60.0
            sb.append(Math.floor(coordinate).toInt())
            sb.append("/1")
            return sb.toString()
        }
    
    
    
    看下 "return-data"为true的情况返回的intent里都有啥
     println("onActivityResult===========${data}")
    println("data===================${data.getParcelableExtra<Parcelable>("data")}")
    println("bounds===============${data.extras}")
    
    onActivityResult===========Intent { act=inline-data dat=content://media/external/images/media/98430 flg=0x1 (has extras) }
    data===================android.graphics.Bitmap@cf2ee70
    bounds===============Bundle[{data=android.graphics.Bitmap@cf2ee70, bitmap-data=true}]
    

    intent的extras也就是bounds里有2个,一个data就是bitmap,另外一个bitmap-data就是个true。
    然后看下整个intent的打印结果,可以看到两边有个uri =dat=content://media/external/images/media/98430
    这个就是小图的uri了,我们可以根据小图找到大图

    百度搜了个帖子https://blog.csdn.net/smileiam/rss/list
    其实以前用过一个库,里边代码也差不多,也有uri转换的适配代码的,那库不在手边。

    工具类如下

    import android.content.Context
    import android.database.Cursor
    import android.net.Uri
    import android.os.Build
    import android.provider.MediaStore
    import android.provider.DocumentsContract
    import android.content.ContentUris
    import android.os.Environment
    import android.support.annotation.RequiresApi
    import android.graphics.BitmapFactory
    import android.graphics.Bitmap
    import java.io.ByteArrayOutputStream
    import java.io.IOException
    import java.io.InputStream
    
    object UriUtils {
    
        fun getFilePath(context: Context, uri: Uri): String? {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                return getPath_above19(context, uri)
            } else {
                return getFilePath_below19(context, uri)
            }
        }
    
        fun getFilePath_below19(context: Context, uri: Uri): String? {
            return getDataColumn(context, uri, null, null)
        }
    
        @RequiresApi(Build.VERSION_CODES.KITKAT)
        private fun getPath_above19(context: Context, uri: Uri): String? {
            val pathHead = "file:///"
            // 1. DocumentProvider
            if (DocumentsContract.isDocumentUri(context, uri)) {
                // 1.1 ExternalStorageProvider
                if (isExternalStorageDocument(uri)) {
                    val docId = DocumentsContract.getDocumentId(uri)
                    val split = docId.split(":")
                    val type = split[0]
                    if ("primary".equals(type, ignoreCase = true)) {
                        return pathHead + Environment.getExternalStorageDirectory() + "/" + split[1]
                    }
                } else if (isDownloadsDocument(uri)) {
                    val id = DocumentsContract.getDocumentId(uri)
                    val contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), java.lang.Long.valueOf(id))
                    return pathHead + getDataColumn(context,
                            contentUri, null, null)
                } else if (isMediaDocument(uri)) {
                    val docId = DocumentsContract.getDocumentId(uri)
                    val split = docId.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
                    val type = split[0]
    
                    var contentUri: Uri? = null
                    if ("image" == type) {
                        contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
                    } else if ("video" == type) {
                        contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI
                    } else if ("audio" == type) {
                        contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
                    }
    
                    val selection = "_id=?"
                    val selectionArgs = arrayOf(split[1])
    
                    return pathHead + getDataColumn(context, contentUri, selection, selectionArgs)
                }// 1.3 MediaProvider
                // 1.2 DownloadsProvider
            } else if ("content".equals(uri.scheme!!, ignoreCase = true)) {
                return if (isGooglePhotosUri(uri)) {//判断是否是google相册图片
                    uri.lastPathSegment
                } else if (isGooglePlayPhotosUri(uri)) {//判断是否是Google相册图片
                    getImageUrlWithAuthority(context, uri)
                } else {//其他类似于media这样的图片,和android4.4以下获取图片path方法类似
                    getFilePath_below19(context, uri)
                }
            } else if ("file".equals(uri.scheme!!, ignoreCase = true)) {
                return pathHead + uri.path!!
            }// 3. 判断是否是文件形式 File
            // 2. MediaStore (and general)
            return ""
        }
    
        private fun getDataColumn(context: Context, uri: Uri?, selection: String?, selectionArgs: Array<String>?): String? {
            var cursor: Cursor? = null
            var path = ""
            try {
                val proj = arrayOf(MediaStore.Images.Media.DATA)
                //好像是android多媒体数据库的封装接口,具体的看Android文档
                cursor = context.getContentResolver().query(uri, proj, selection, selectionArgs, null)
                cursor?.apply {
                    //获得用户选择的图片的索引值
                    val column_index = getColumnIndexOrThrow(MediaStore.Images.Media.DATA)
                    //将光标移至开头 ,这个很重要,不小心很容易引起越界
                    moveToFirst()
                    //最后根据索引值获取图片路径   结果类似:/mnt/sdcard/DCIM/Camera/IMG_20151124_013332.jpg
                    path = getString(column_index)
                }
                println("p================$path")
            } catch (e: Exception) {
    
            } finally {
                cursor?.close()
            }
            return path
        }
    
        /**从相册中选择图片,如果手机安装了Google Photo,它的路径格式如下:
        content://com.google.android.apps.photos.contentprovider/0/1/mediakey%3A%2Flocal%253A821abd2f-9f8c-4931-bbe9-a975d1f5fabc/ORIGINAL/NONE/1754758324
        用原来的方式获取是不起作用的,path会是null,我们可以通过下面的形式获取*/
        fun getImageUrlWithAuthority(context: Context, uri: Uri): String? {
            var stream: InputStream? = null
            if (uri.authority != null) {
                try {
                    stream= context.contentResolver.openInputStream(uri)
                    val bmp = BitmapFactory.decodeStream(stream)
                    return writeToTempImageAndGetPathUri(context, bmp).toString()
                } catch (e: Exception) {
                    e.printStackTrace()
                } finally {
                    try {
                        stream?.close()
                    } catch (e: IOException) {
                        e.printStackTrace()
                    }
    
                }
            }
            return null
        }
    
        fun writeToTempImageAndGetPathUri(inContext: Context, inImage: Bitmap): Uri {
            val bytes = ByteArrayOutputStream()
            inImage.compress(Bitmap.CompressFormat.JPEG, 100, bytes)
            val path = MediaStore.Images.Media.insertImage(inContext.contentResolver, inImage, "Title", null)
            return Uri.parse(path)
        }
    
        /**
         * @param uri
         * The Uri to check.
         * @return Whether the Uri authority is ExternalStorageProvider.
         */
        private fun isExternalStorageDocument(uri: Uri): Boolean {
            return "com.android.externalstorage.documents" == uri.authority
        }
    
        /**
         * @param uri
         * The Uri to check.
         * @return Whether the Uri authority is DownloadsProvider.
         */
        private fun isDownloadsDocument(uri: Uri): Boolean {
            return "com.android.providers.downloads.documents" == uri.authority
        }
    
        /**
         * @param uri
         * The Uri to check.
         * @return Whether the Uri authority is MediaProvider.
         */
        private fun isMediaDocument(uri: Uri): Boolean {
            return "com.android.providers.media.documents" == uri.authority
        }
    
        /**
         * 判断是否是Google相册的图片,类似于content://com.google.android.apps.photos.content/...
         */
        fun isGooglePhotosUri(uri: Uri): Boolean {
            return "com.google.android.apps.photos.content" == uri.authority
        }
    
        /**
         * 判断是否是Google相册的图片,类似于content://com.google.android.apps.photos.contentprovider/0/1/mediakey:/local%3A821abd2f-9f8c-4931-bbe9-a975d1f5fabc/ORIGINAL/NONE/1075342619
         */
        fun isGooglePlayPhotosUri(uri: Uri): Boolean {
            return "com.google.android.apps.photos.contentprovider" == uri.authority
        }
    
    }
    

    问题

    使用模拟器测试的时候,发现return-data为true的那种,我拍照完点击ok对号按钮
    这边接收到的resultCode=0,intent=null,我拍照完点击cancel叉号按钮,接收到的resultCode=0,intent=Intent()一个啥都没有的Intent
    完事看下日志

    CAM_StateMachine: Failed to process event: com.android.camera.captureintent.event.EventTapOnConfirmPhotoButton@34298f1
    
    E/AndroidRuntime: FATAL EXCEPTION: main
        Process: com.android.camera2, PID: 12354
        java.lang.NullPointerException
            at com.google.common.base.Preconditions.checkNotNull(Preconditions.java:210)
            at com.google.common.base.Optional.of(Optional.java:85)
            at com.android.camera.captureintent.state.StateSavingPicture.onEnter(StateSavingPicture.java:77)
    

    然后看了下出错的代码的源码如下,第四行Optional.of 参数为空,肯定的啊,我们没有传uri的。

            Optional<Uri> saveUri = Optional.absent();
            final Bundle myExtras = mResourceConstructed.get().getIntent().getExtras();
            if (myExtras != null) {//感觉没有设置uri的情况下这里不应该走。
                saveUri = Optional.of((Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT));//of里边的参数是null
                String cropValue = myExtras.getString("crop");
            }
        //下边的有uri的返回的是空的new Intent(),而没有uri的返回的是bitmap,可现在上边就挂了
            if (saveUri.isPresent()) {
                OutputStream outputStream = null;
                try {
                    outputStream = mResourceConstructed.get().getContext().getContentResolver()
                            .openOutputStream(saveUri.get());
                    outputStream.write(mPictureData);
                    outputStream.close();
    
                    Log.v(TAG, "saved result to URI: " + saveUri);
                    return Optional.of((State) StateIntentCompleted.from(
                            this, mResourceConstructed, new Intent()));
                } catch (IOException ex) {
                    Log.e(TAG, "exception while saving result to URI: " + saveUri, ex);
                } finally {
                    CameraUtil.closeSilently(outputStream);
                }
            } else {
                /** Inline the bitmap into capture intent result */
                final Bitmap bitmap = CameraUtil.makeBitmap(
                        mPictureData, CaptureIntentConfig.INLINE_BITMAP_MAX_PIXEL_NUM);
                return Optional.of((State) StateIntentCompleted.from(
                        this, mResourceConstructed,
                        new Intent("inline-data").putExtra("data", bitmap)));
            }
    

    如果要保证不挂,那么就不能让方法走到if条件里,也就是getExtras必须为空,那我们跳转的时候啥额外参数都不能传,只能这么写

            val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
            startActivityForResult(intent, REQUEST_RETURN_BITMAP_DATA)
    

    返回的intent打印如下
    Intent { act=inline-data (has extras) },很明显没有图片的地址。
    试了下,真的不挂了,返回了一个bitmap,而intent.data返回的uri是空的。查了下相册里也没有新的图片,也不知道是模拟器这样还是跟手机有关。
    我三星6.0的测试机是没有问题的。返回的bitmap。intent.data是有值的,相册里有新的图片的。

    第二种,自己用surfaceview来显示相机预览图片以及拍照

    布局比较简单,一个surfaceview,2个按钮,一个用来拍照,一个用来重置,因为拍照完画面就不动了。

    <?xml version="1.0" encoding="utf-8"?>
    <android.support.constraint.ConstraintLayout 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:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".camera.ActivityCustomCamera">
    
        <!--<include layout="@layout/include_toolbar" />-->
    
        <com.charliesong.demo0327.camera.CameraSurface
            android:id="@+id/sf"
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    
        <Button
            android:id="@+id/btn_capture"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="takePIC"
            android:text="capture"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
        <Button
            android:id="@+id/btn_reset"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="resetSurface"
            android:text="reset"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    </android.support.constraint.ConstraintLayout>
    

    2个点击事件也简单,都在自定义的surfaceview里

        fun takePIC(v:View){
            val file=File(getExternalFilesDir(""),"${System.currentTimeMillis()}.jpg")
            sf.takePic(file)
    
        }
    
        fun resetSurface(v:View){
            sf.resetThis()
        }
    

    自定义的view如下

    import android.content.Context
    import android.hardware.Camera
    import android.view.SurfaceHolder
    import android.view.SurfaceView
    import android.graphics.ImageFormat
    import android.util.AttributeSet
    import java.io.File
    import java.io.FileOutputStream
    
    
    class CameraSurface : SurfaceView, SurfaceHolder.Callback2 {
    
        constructor(c: Context) : super(c) {
            initHolder()
        }
    
        constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
            initHolder()
        }
    
        constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {
            initHolder()
        }
    
        private fun initHolder() {
            val holder = getHolder()
            //指定回调接口
            holder.addCallback(this)
        }
    
        private var theCamera: Camera? = null
        override fun surfaceRedrawNeeded(holder: SurfaceHolder?) {
            println("========================surfaceRedrawNeeded")
        }
    
        override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) {
            println("===================surfaceChanged==$width/$height===========$format")
        }
    
        override fun surfaceDestroyed(holder: SurfaceHolder?) {
            println("=================surfaceDestroyed")
            theCamera?.stopPreview()
            theCamera = null;
        }
    
        override fun surfaceCreated(holder: SurfaceHolder) {
            println("====================surfaceCreated")
            if (theCamera == null) {
                theCamera = Camera.open(1)
            }
            theCamera?.apply {
                val myParameters = getParameters();
                val supportPicSize = myParameters.supportedPictureSizes
                supportPicSize.forEach {
                    println("support============${it.width}/${it.height}")
                }
                val support = myParameters.supportedPreviewSizes
                support.forEach {
                    println("preview size=========${it.width}/${it.height}")
                }
                val index=Math.min(supportPicSize.size-1,supportPicSize.size/2)
                val saveSize=supportPicSize[index]
                println("index===$index=====${saveSize.width}/${saveSize.height}")
                myParameters.setPictureFormat(ImageFormat.JPEG);
                myParameters.set("jpeg-quality", 85);
                //需要注意下边2个size的大小不能随便改,需要是上边打印的support的值才可以。
                //而且宽高也是当前屏幕方向的值,如果屏幕旋转了,宽高值就反过来了。
                myParameters.setPreviewSize(640, 480)//如果要改预览图片大小也可以改
                myParameters.setPictureSize(saveSize.width, saveSize.height);
                myParameters.setGpsLongitude(131.123456)
                myParameters.setGpsLatitude(34.5555)
                myParameters.setGpsTimestamp(System.currentTimeMillis())
                myParameters.setGpsAltitude(521.125)
                myParameters.setGpsProcessingMethod("gps")
                parameters = myParameters
                setPreviewDisplay(holder);
                startPreview()
            }
        }
    
        fun resetThis() {
            surfaceCreated(holder)
        }
    
        fun takePic(desFile: File) {
            theCamera?.takePicture(null, null, object : Camera.PictureCallback {
                override fun onPictureTaken(data: ByteArray?, camera: Camera?) {
                    try {
                        //弄成bitmap压缩以后,额外信息就丢失了,而且文件还比直接保存data的大一倍。
    //                    val a = BitmapFactory.decodeByteArray(data, 0, data?.size ?: 0)
    //                    val out = FileOutputStream(desFile)
    //                    val result = a?.compress(Bitmap.CompressFormat.JPEG, 100, out)
                        val fos = FileOutputStream(desFile)
                        fos.write(data)
                        fos.close()
                        println("f===============${desFile.absolutePath}")
                    } catch (e: Exception) {
                        e.printStackTrace()
                    }
                }
            })
        }
    }
    
    模拟器api28问题

    发生异常,在调用camera的setParameters方法重新设置参数的时候,挂了
    错误日志如下
    Camera2-Parameters: set: Incomplete set of GPS parameters provided
    而且测试了下就是加了这两行就挂了,注释掉就没事。

    myParameters.setGpsLongitude(131.123456)
    myParameters.setGpsLatitude(34.5555)
    

    从错误日志来看,好像是说数据不完整,难道要把其他gps的参数都添加进去吗?
    那就试试,貌似这5个少一个都不行。我测试机就弄经纬度也没事,模拟器就挂了,不知道是不是和版本有关

     myParameters.setGpsLongitude(131.123456)
                myParameters.setGpsLatitude(34.5555)
                myParameters.setGpsTimestamp(System.currentTimeMillis())
                myParameters.setGpsAltitude(521.125)
                myParameters.setGpsProcessingMethod("gps")
    

    camera2 待写

    相关文章

      网友评论

          本文标题:给图片添加位置信息,旋转角度处理

          本文链接:https://www.haomeiwen.com/subject/ptfsgftx.html