#kotlin #jpeg #inputstream #exif #outputstream
Вопрос:
Я работаю над приложением, одна из задач которого состоит в том, чтобы сделать снимки ( androidx.camera
библиотека), пометить его «EXIF «»LatLong», повернуть изображение с некоторым предопределенным значением (зависящим от устройства) Bitmap.createBitmap
, прежде чем закодировать его как Base64
String
, а затем загрузить его с помощью API.
Проблема в том, что информация «EXIF «»LatLong» теряется во время этого процесса, и как мне наилучшим образом сохранить ее (или как ее переписать) ?
Фрагмент кода, который делает снимок: Обратный вызов при сохранении файла изображения. Поймайте этот путь к файлу и добавьте EXIF с моим сохраненным местоположением устройства (из androidx
room
базы данных), этот фрагмент хорошо работает.
private fun takePhoto( orderResult: OrderResult?, categoryString: String ) { Log.i(TAG, "P100: takePhoto: current mediaDirectory = '${getMediaDirectory(requireActivity())}'") if(orderResult==null){ Message.error("Order not ready... try again later") return } val mediaDirectory = getMediaDirectory(requireActivity()) val filename = "${SimpleDateFormat(UPLOAD_MSLAM_FILENAME_FORMAT, Locale.ROOT).format(Date().time)}${categoryString}.jpg" val photoFile = File(mediaDirectory,filename) offlinePhotoViewModel.imageCaptureMLD.value?.takePicture( ImageCapture.OutputFileOptions.Builder(photoFile).build(), ContextCompat.getMainExecutor(requireContext()), object : ImageCapture.OnImageSavedCallback{ override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) { val savedUri = Uri.fromFile(photoFile) Log.i(TAG, "P100: onImageSaved: saved file attempt: ${outputFileResults}") Log.i(TAG, "P100: onImageSaved: savedUri = $savedUri") orderResult.apply { siteId?.let {safeSiteId-gt; assignmentId?.let {safeAssignmentId-gt; savedUri.lastPathSegment?.let {safeFilename-gt; savedUri.path?.let {safePath-gt; CoroutineScope(Dispatchers.IO).launch { val exif = ExifInterface(safePath) val deviceLocation = Repository.getDeviceLocationResult() deviceLocation?.apply { latitude?.let { safeLatitude-gt; longitude?.let { safeLongitude-gt; exif.setLatLong(safeLatitude,safeLongitude) } } altitude?.let { safeAltitude-gt; exif.setAltitude(safeAltitude) } } exif.saveAttributes() } offlinePhotoViewModel.insertReplacePictureEntity( PictureEntity( siteId = safeSiteId, assignmentId = safeAssignmentId, filename = safeFilename, uriString = safePath ) ) } } } } } } override fun onError(exception: ImageCaptureException) { Log.e(TAG, "onError: takePhoto error", exception) Message.error(exception.message?:"Unknown error capturing photo") //TODO: Consider record file store attempt as an error } } )?:let { Message.error("Camera not ready yet, please try again later") } }
…и я могу легко восстановить эту информацию при представлении изображения:
localPictureResult?.apply { val imgFile = File(uriString) if(imgFile.exists()) { listItemNewPicturesGalleryAppCompatImageView.apply { setImageURI(Uri.fromFile(imgFile)) visibility = View.VISIBLE val exif = ExifInterface(imgFile) exif.latLong?.let { listItemNewPicturesGalleryMaterialTextViewExifLocationContents.text = formatLatLngPosition(LatLng(it[0],it[1])) } } listItemNewPicturesGalleryCircularProgressIndicator.visibility = View.GONE } else { listItemNewPicturesGalleryAppCompatImageView.apply { setImageDrawable( ContextCompat.getDrawable(binding.root.context, R.drawable.ic_baseline_image_not_supported_24) ) visibility = View.VISIBLE } listItemNewPicturesGalleryCircularProgressIndicator.visibility = View.GONE } }
Before I upload the picture, I need to rotate it (the server side needs the picture in a particular direction, and only way to do that is to rotate it using Bitmap.createBitmap
method, but here I suspect the EXIF information to be stripped during that process.) The ‘EXIF’ ‘latLong’ should later be used when the pictures are further processed in the server.
private suspend fun postPictureWithPath( savedPath: String, siteId: Long, carId: Long?, assignmentId: Long, siteHash: String, assignmentHash: String, uploadFilename: String ):Int? { APIClientFactory.makeAPIInterfaceService()?.let {apiInterfaceService -gt; try { val bm = BitmapFactory.decodeFile(savedPath) val rotation = PreferenceManager .getDefaultSharedPreferences(applicationContext) .getString(applicationContext.resources.getString( R.string.preference_key_fragment_settings_camera_picture_gallery_drop_down_preference_rotation ),"0.0")?.toFloatOrNull()?:0.0f val rotatedBitmap = Bitmap.createBitmap( bm, 0, 0, bm.width, bm.height, Matrix().apply { postRotate(rotation) }, true ) val byteArrayOutputStream = ByteArrayOutputStream(); rotatedBitmap.compress(Bitmap.CompressFormat.JPEG,100,byteArrayOutputStream) //Consider value in settings val byteArray = byteArrayOutputStream.toByteArray() val encodedImage = Base64.encodeToString(byteArray,Base64.NO_WRAP) val req = apiInterfaceService.postSlamSiteSiteIdAssignmentAssignmentIdPictureOriginalFilename( siteId = siteId, assignmentId = assignmentId, carId = carId, originalFilename = uploadFilename, siteHash = siteHash, assignmentHash = assignmentHash, base64String = encodedImage ) if(req?.success==true) { if(req.data!=null) { req.data?.apply { appDatabase.withTransaction { appDatabase.pictureDao().insertReplacePictureInfo( PictureInfoEntity( siteId = siteId, assignmentId = assignmentId, originalFilename = originalFilename, fileDate = fileDate, hash = hash?:"no hash present", upload = true ) ) } } } else { Message.error("Missing data record") return -5 } } else if(req?.success==false) { Message.error(req.message?:"Unknown picture upload error") return -1 } else { Message.error(req?.message?:"Unknown connection error") return null } } catch (httpException:HttpException) { Log.e(TAG, "postPictureWithPath: httpException",httpException) Message.error(httpException.message()?:"Unknown HTTP error posting picture") return -2 } catch (throwable: Throwable) { Log.e(TAG, "postPictureWithPath: throwable", throwable) Message.error(throwable.message?:"Unknown throwable error posting picture") return -3 } catch (exception: Exception) { Log.e(TAG, "postPictureWithPath: exception", exception) Message.error(exception.message?:"Unknown exception posting picture info") return -4 } }?:let { return null } return 0 }
-gt; I think I have two methods to preserve the EXIF:
A. Extract-gt;rotate-gt;save picture to file-gt;add exif to file-gt;send picture
- save the EXIF before rotation.
- rotate picture
- save the picture to file
- add EXIF in the same way as the picture was taken.
- Send picture. (Actually the whole process without rotation)
This cause extra storing and file-handling, and slows the entire process down.
B. Extract-gt;rotate-gt;add exif to stream-gt;send picture
- Save EXIF before rotation
- rotate picture
- save EXIF to stream
- Отправить фотографию
Это было более многообещающе, но я обнаружил, что не было «сохранить EXIF в» OutputStream, но есть для InputStream.
Есть ли способ «преобразовать» поток из выходного во входной временный ?
Или есть другой способ повернуть изображение, сохранив EXIF на месте ?