Fix compilation errors: remove duplicate methods, add missing imports, fix location manager
This commit is contained in:
@@ -55,348 +55,14 @@ class CameraActivity : AppCompatActivity() {
|
|||||||
private lateinit var photoPreviewLayout: LinearLayout
|
private lateinit var photoPreviewLayout: LinearLayout
|
||||||
private var imageCapture: ImageCapture? = null
|
private var imageCapture: ImageCapture? = null
|
||||||
private lateinit var cameraExecutor: ExecutorService
|
private lateinit var cameraExecutor: ExecutorService
|
||||||
|
private var locationManagerHelper: LocationManagerHelper? = null
|
||||||
|
private var currentLocation: Location? = null
|
||||||
|
|
||||||
// 存储拍摄的图片URI
|
// 存储拍摄的图片URI
|
||||||
private val capturedImageUris = mutableListOf<Uri>()
|
private val capturedImageUris = mutableListOf<Uri>()
|
||||||
// 图片合成器
|
// 图片合成器
|
||||||
private val compositor = ImageCompositor()
|
private val compositor = ImageCompositor()
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
setContentView(R.layout.activity_main)
|
|
||||||
|
|
||||||
// 初始化视图
|
|
||||||
viewFinder = findViewById(R.id.viewFinder)
|
|
||||||
captureButton = findViewById(R.id.captureButton)
|
|
||||||
settingsButton = findViewById(R.id.settingsButton)
|
|
||||||
photoPreviewLayout = findViewById(R.id.photoPreviewLayout)
|
|
||||||
|
|
||||||
// 请求权限
|
|
||||||
if (allPermissionsGranted()) {
|
|
||||||
startCamera()
|
|
||||||
} else {
|
|
||||||
ActivityCompat.requestPermissions(
|
|
||||||
this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置点击监听器
|
|
||||||
captureButton.setOnClickListener { takePhoto() }
|
|
||||||
settingsButton.setOnClickListener { openSettings() }
|
|
||||||
|
|
||||||
// 初始化线程池
|
|
||||||
cameraExecutor = Executors.newSingleThreadExecutor()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
|
|
||||||
ContextCompat.checkSelfPermission(baseContext, it) == PackageManager.PERMISSION_GRANTED
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun startCamera() {
|
|
||||||
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
|
|
||||||
|
|
||||||
cameraProviderFuture.addListener({
|
|
||||||
val cameraProvider = cameraProviderFuture.get()
|
|
||||||
|
|
||||||
val preview = Preview.Builder()
|
|
||||||
.build()
|
|
||||||
.also {
|
|
||||||
it.setSurfaceProvider(viewFinder.surfaceProvider)
|
|
||||||
}
|
|
||||||
|
|
||||||
imageCapture = ImageCapture.Builder()
|
|
||||||
.setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
|
|
||||||
|
|
||||||
try {
|
|
||||||
cameraProvider.unbindAll()
|
|
||||||
cameraProvider.bindToLifecycle(
|
|
||||||
this, cameraSelector, preview, imageCapture
|
|
||||||
)
|
|
||||||
|
|
||||||
} catch (exc: Exception) {
|
|
||||||
Log.e(TAG, "相机绑定失败", exc)
|
|
||||||
}
|
|
||||||
}, ContextCompat.getMainExecutor(this))
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun takePhoto() {
|
|
||||||
val imageCapture = imageCapture ?: return
|
|
||||||
|
|
||||||
val outputFileOptions = ImageCapture.OutputFileOptions.Builder(
|
|
||||||
createImageUri()
|
|
||||||
).build()
|
|
||||||
|
|
||||||
imageCapture.takePicture(
|
|
||||||
outputFileOptions,
|
|
||||||
ContextCompat.getMainExecutor(this),
|
|
||||||
object : ImageCapture.OnImageSavedCallback {
|
|
||||||
override fun onError(exception: ImageCaptureException) {
|
|
||||||
Log.e(TAG, "照片拍摄失败: ${exception.message}", exception)
|
|
||||||
Toast.makeText(
|
|
||||||
baseContext,
|
|
||||||
"照片拍摄失败: ${exception.message}",
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onImageSaved(output: ImageCapture.OutputFileResults) {
|
|
||||||
// 获取保存的URI
|
|
||||||
val savedUri = output.savedUri ?: return
|
|
||||||
|
|
||||||
// 添加水印到已保存的照片
|
|
||||||
addWatermarkToImage(savedUri)
|
|
||||||
|
|
||||||
// 添加到捕获的图片列表
|
|
||||||
capturedImageUris.add(savedUri)
|
|
||||||
|
|
||||||
// 更新预览
|
|
||||||
updatePhotoPreviews()
|
|
||||||
|
|
||||||
Toast.makeText(
|
|
||||||
baseContext,
|
|
||||||
"照片已保存",
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
|
|
||||||
// 刷新相册
|
|
||||||
refreshGallery(savedUri)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createImageUri(): Uri {
|
|
||||||
val contentValues = ContentValues().apply {
|
|
||||||
put(MediaStore.Images.Media.DISPLAY_NAME, "巡检_${System.currentTimeMillis()}.jpg")
|
|
||||||
put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
|
|
||||||
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
|
|
||||||
put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
|
|
||||||
?: throw IOException("无法创建图片URI")
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addWatermarkToImage(imageUri: Uri) {
|
|
||||||
try {
|
|
||||||
val inputStream = contentResolver.openInputStream(imageUri)
|
|
||||||
val originalBitmap = BitmapFactory.decodeStream(inputStream)
|
|
||||||
inputStream?.close()
|
|
||||||
|
|
||||||
val watermarkedBitmap = addWatermark(originalBitmap)
|
|
||||||
|
|
||||||
// 将带水印的图片保存回原URI
|
|
||||||
val outputStream = contentResolver.openOutputStream(imageUri, "rwt") // truncate and write
|
|
||||||
watermarkedBitmap.compress(Bitmap.CompressFormat.JPEG, 95, outputStream)
|
|
||||||
outputStream?.close()
|
|
||||||
|
|
||||||
// 回收位图以释放内存
|
|
||||||
if (watermarkedBitmap != originalBitmap) {
|
|
||||||
originalBitmap.recycle()
|
|
||||||
}
|
|
||||||
watermarkedBitmap.recycle()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(TAG, "添加水印失败: ${e.message}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addWatermark(bitmap: Bitmap): Bitmap {
|
|
||||||
val watermarkedBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true)
|
|
||||||
val canvas = Canvas(watermarkedBitmap)
|
|
||||||
val paint = Paint().apply {
|
|
||||||
color = Color.WHITE
|
|
||||||
textSize = 48f
|
|
||||||
typeface = Typeface.DEFAULT_BOLD
|
|
||||||
textAlign = Paint.Align.LEFT
|
|
||||||
}
|
|
||||||
|
|
||||||
// 绘制时间水印
|
|
||||||
val timestamp = formatDate(System.currentTimeMillis())
|
|
||||||
canvas.drawText(timestamp, 50f, watermarkedBitmap.height - 100f, paint)
|
|
||||||
|
|
||||||
// 绘制位置水印
|
|
||||||
getCurrentLocation()?.let { location ->
|
|
||||||
val address = getAddressFromLocation(location.latitude, location.longitude)
|
|
||||||
canvas.drawText(address, 50f, watermarkedBitmap.height - 50f, paint)
|
|
||||||
}
|
|
||||||
|
|
||||||
return watermarkedBitmap
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updatePhotoPreviews() {
|
|
||||||
// 清空当前预览
|
|
||||||
photoPreviewLayout.removeAllViews()
|
|
||||||
|
|
||||||
// 限制最多显示4张预览图
|
|
||||||
val displayCount = minOf(capturedImageUris.size, 4)
|
|
||||||
|
|
||||||
for (i in 0 until displayCount) {
|
|
||||||
val imageView = ImageView(this)
|
|
||||||
val params = LinearLayout.LayoutParams(
|
|
||||||
200, 200
|
|
||||||
).apply {
|
|
||||||
setMargins(8, 8, 8, 8)
|
|
||||||
}
|
|
||||||
imageView.layoutParams = params
|
|
||||||
imageView.scaleType = ImageView.ScaleType.CENTER_CROP
|
|
||||||
|
|
||||||
// 加载并设置图片
|
|
||||||
val uri = capturedImageUris[capturedImageUris.size - displayCount + i] // 显示最新的几张
|
|
||||||
imageView.setImageURI(uri)
|
|
||||||
|
|
||||||
// 添加点击事件以查看大图
|
|
||||||
imageView.setOnClickListener {
|
|
||||||
// 可以在这里实现查看大图的功能
|
|
||||||
}
|
|
||||||
|
|
||||||
photoPreviewLayout.addView(imageView)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果已有4张图片,添加合成按钮
|
|
||||||
if (capturedImageUris.size >= 4) {
|
|
||||||
addComposeButton()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addComposeButton() {
|
|
||||||
// 添加合成按钮
|
|
||||||
val composeButton = Button(this)
|
|
||||||
composeButton.text = "合成图片"
|
|
||||||
composeButton.setBackgroundColor(ContextCompat.getColor(this, R.color.purple_200))
|
|
||||||
composeButton.setTextColor(Color.WHITE)
|
|
||||||
|
|
||||||
val params = LinearLayout.LayoutParams(
|
|
||||||
LinearLayout.LayoutParams.WRAP_CONTENT,
|
|
||||||
LinearLayout.LayoutParams.WRAP_CONTENT
|
|
||||||
).apply {
|
|
||||||
setMargins(8, 8, 8, 8)
|
|
||||||
gravity = android.view.Gravity.CENTER_HORIZONTAL
|
|
||||||
}
|
|
||||||
composeButton.layoutParams = params
|
|
||||||
|
|
||||||
composeButton.setOnClickListener {
|
|
||||||
composeLastFourImages()
|
|
||||||
}
|
|
||||||
|
|
||||||
photoPreviewLayout.addView(composeButton)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun composeLastFourImages() {
|
|
||||||
// 获取最近的4张图片
|
|
||||||
val recentUris = if (capturedImageUris.size >= 4) {
|
|
||||||
capturedImageUris.takeLast(4)
|
|
||||||
} else {
|
|
||||||
capturedImageUris
|
|
||||||
}
|
|
||||||
|
|
||||||
// 将URI转换为Bitmap
|
|
||||||
val bitmaps = mutableListOf<Bitmap>()
|
|
||||||
for (uri in recentUris) {
|
|
||||||
try {
|
|
||||||
val inputStream = contentResolver.openInputStream(uri)
|
|
||||||
val bitmap = BitmapFactory.decodeStream(inputStream)
|
|
||||||
inputStream?.close()
|
|
||||||
bitmaps.add(bitmap)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(TAG, "加载图片失败: ${e.message}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bitmaps.isEmpty()) {
|
|
||||||
Toast.makeText(this, "没有图片可合成", Toast.LENGTH_SHORT).show()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 合成图片
|
|
||||||
val composedBitmap = compositor.composeImagesToGrid(bitmaps, 2000, 2000) // 目标尺寸2000x2000
|
|
||||||
|
|
||||||
// 添加标题和内容
|
|
||||||
val finalBitmap = compositor.addTextOverlay(
|
|
||||||
composedBitmap,
|
|
||||||
"巡检报告",
|
|
||||||
"这是巡检内容的详细描述"
|
|
||||||
)
|
|
||||||
|
|
||||||
// 保存合成后的图片
|
|
||||||
saveCompositedImage(finalBitmap)
|
|
||||||
|
|
||||||
// 回收位图
|
|
||||||
for (bitmap in bitmaps) {
|
|
||||||
if (bitmap != finalBitmap) {
|
|
||||||
bitmap.recycle()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (finalBitmap != composedBitmap) {
|
|
||||||
composedBitmap.recycle()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun saveCompositedImage(bitmap: Bitmap) {
|
|
||||||
try {
|
|
||||||
val fileName = compositor.generateFileName()
|
|
||||||
val contentValues = ContentValues().apply {
|
|
||||||
put(MediaStore.Images.Media.DISPLAY_NAME, fileName)
|
|
||||||
put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
|
|
||||||
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
|
|
||||||
put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val uri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
|
|
||||||
?: throw IOException("无法创建图片URI")
|
|
||||||
|
|
||||||
val outputStream = contentResolver.openOutputStream(uri)
|
|
||||||
bitmap.compress(Bitmap.CompressFormat.JPEG, 95, outputStream)
|
|
||||||
outputStream?.close()
|
|
||||||
|
|
||||||
Toast.makeText(this, "合成图片已保存", Toast.LENGTH_SHORT).show()
|
|
||||||
|
|
||||||
// 刷新相册
|
|
||||||
refreshGallery(uri)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(TAG, "保存合成图片失败: ${e.message}")
|
|
||||||
Toast.makeText(this, "保存合成图片失败", Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun refreshGallery(uri: Uri) {
|
|
||||||
// 通知相册更新
|
|
||||||
val contentValues = ContentValues().apply {
|
|
||||||
put(MediaStore.Images.Media.IS_PENDING, 0)
|
|
||||||
}
|
|
||||||
contentResolver.update(uri, contentValues, null, null)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 将时间戳转换为指定格式的日期时间字符串
|
|
||||||
fun formatDate(timestamp: Long): String {
|
|
||||||
val sdf = SimpleDateFormat("yyyy年-MM月-dd日 HH:mm:ss", Locale.getDefault())
|
|
||||||
return sdf.format(Date(timestamp))
|
|
||||||
}
|
|
||||||
|
|
||||||
// 使用Geocoder将经纬度转换为地址
|
|
||||||
private fun getAddressFromLocation(latitude: Double, longitude: Double): String? {
|
|
||||||
return try {
|
|
||||||
val geocoder = Geocoder(this, Locale.getDefault())
|
|
||||||
val addresses = geocoder.getFromLocation(latitude, longitude, 1)
|
|
||||||
if (addresses?.isEmpty() == false) {
|
|
||||||
val address = addresses[0]
|
|
||||||
val addressLine = address.getAddressLine(0)
|
|
||||||
addressLine ?: "${latitude}, ${longitude}"
|
|
||||||
} else {
|
|
||||||
"${latitude}, ${longitude}"
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(TAG, "无法解析地址: ${e.message}")
|
|
||||||
"${latitude}, ${longitude}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var locationManagerHelper: LocationManagerHelper? = null
|
|
||||||
private var currentLocation: Location? = null
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.activity_main)
|
setContentView(R.layout.activity_main)
|
||||||
@@ -451,6 +117,241 @@ class CameraActivity : AppCompatActivity() {
|
|||||||
cameraExecutor.shutdown()
|
cameraExecutor.shutdown()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
|
||||||
|
ContextCompat.checkSelfPermission(baseContext, it) == PackageManager.PERMISSION_GRANTED
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startCamera() {
|
||||||
|
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
|
||||||
|
|
||||||
|
cameraProviderFuture.addListener({
|
||||||
|
val cameraProvider = cameraProviderFuture.get()
|
||||||
|
|
||||||
|
// 预览
|
||||||
|
val preview = Preview.Builder()
|
||||||
|
.build()
|
||||||
|
.also {
|
||||||
|
it.setSurfaceProvider(viewFinder.surfaceProvider)
|
||||||
|
}
|
||||||
|
|
||||||
|
imageCapture = ImageCapture.Builder()
|
||||||
|
.setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
|
||||||
|
|
||||||
|
try {
|
||||||
|
cameraProvider.unbindAll()
|
||||||
|
cameraProvider.bindToLifecycle(
|
||||||
|
this, cameraSelector, preview, imageCapture
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "相机绑定失败", e)
|
||||||
|
}
|
||||||
|
}, ContextCompat.getMainExecutor(this))
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("MissingPermission")
|
||||||
|
private fun takePhoto() {
|
||||||
|
val imageCapture = this.imageCapture ?: return
|
||||||
|
|
||||||
|
val outputOptions = createOutputFileOptions()
|
||||||
|
|
||||||
|
imageCapture.takePicture(
|
||||||
|
outputOptions,
|
||||||
|
ContextCompat.getMainExecutor(this),
|
||||||
|
object : ImageCapture.OnImageSavedCallback {
|
||||||
|
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
|
||||||
|
val savedUri = outputFileResults.savedUri ?: return
|
||||||
|
capturedImageUris.add(savedUri)
|
||||||
|
showPhotoPreview(savedUri)
|
||||||
|
|
||||||
|
// 如果已经有4张照片,显示合成按钮
|
||||||
|
if (capturedImageUris.size >= 4) {
|
||||||
|
showComposeButton()
|
||||||
|
}
|
||||||
|
|
||||||
|
val msg = "照片已保存: ${savedUri.lastPathSegment}"
|
||||||
|
Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
|
||||||
|
Log.d(TAG, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onError(exception: ImageCaptureException) {
|
||||||
|
Log.e(TAG, "拍照失败: ${exception.message}", exception)
|
||||||
|
Toast.makeText(baseContext, "拍照失败", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createOutputFileOptions(): ImageCapture.OutputFileOptions {
|
||||||
|
val timestamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
|
||||||
|
|
||||||
|
val contentValues = ContentValues().apply {
|
||||||
|
put(MediaStore.MediaColumns.DISPLAY_NAME, "巡检_$timestamp")
|
||||||
|
put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
put(MediaStore.MediaColumns.RELATIVE_PATH, "Pictures/LogCam")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val contentResolver = contentResolver
|
||||||
|
val uri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
|
||||||
|
|
||||||
|
return ImageCapture.OutputFileOptions.Builder(contentResolver, uri).build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showPhotoPreview(uri: Uri) {
|
||||||
|
val imageView = ImageView(this)
|
||||||
|
val layoutParams = LinearLayout.LayoutParams(120, 120)
|
||||||
|
layoutParams.marginEnd = 8
|
||||||
|
imageView.layoutParams = layoutParams
|
||||||
|
imageView.scaleType = ImageView.ScaleType.CENTER_CROP
|
||||||
|
|
||||||
|
try {
|
||||||
|
val inputStream = contentResolver.openInputStream(uri)
|
||||||
|
val bitmap = BitmapFactory.decodeStream(inputStream)
|
||||||
|
inputStream?.close()
|
||||||
|
imageView.setImageBitmap(bitmap)
|
||||||
|
} catch (e: IOException) {
|
||||||
|
Log.e(TAG, "预览图片加载失败", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
photoPreviewLayout.addView(imageView)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showComposeButton() {
|
||||||
|
// 添加合成按钮到预览区域
|
||||||
|
val composeButton = Button(this)
|
||||||
|
composeButton.text = "合成图片"
|
||||||
|
composeButton.setOnClickListener { composeImages() }
|
||||||
|
|
||||||
|
val layoutParams = LinearLayout.LayoutParams(
|
||||||
|
LinearLayout.LayoutParams.WRAP_CONTENT,
|
||||||
|
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||||
|
)
|
||||||
|
layoutParams.marginStart = 16
|
||||||
|
composeButton.layoutParams = layoutParams
|
||||||
|
|
||||||
|
photoPreviewLayout.addView(composeButton)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun composeImages() {
|
||||||
|
if (capturedImageUris.size < 4) {
|
||||||
|
Toast.makeText(this, "需要至少4张图片进行合成", Toast.LENGTH_SHORT).show()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取最近4张图片
|
||||||
|
val recentUris = capturedImageUris.takeLast(4)
|
||||||
|
val bitmaps = mutableListOf<Bitmap>()
|
||||||
|
|
||||||
|
for (uri in recentUris) {
|
||||||
|
try {
|
||||||
|
val inputStream = contentResolver.openInputStream(uri)
|
||||||
|
val bitmap = BitmapFactory.decodeStream(inputStream)
|
||||||
|
inputStream?.close()
|
||||||
|
bitmap?.let { bitmaps.add(it) }
|
||||||
|
} catch (e: IOException) {
|
||||||
|
Log.e(TAG, "加载图片失败: $uri", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bitmaps.size < 4) {
|
||||||
|
Toast.makeText(this, "部分图片加载失败", Toast.LENGTH_SHORT).show()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 合成图片
|
||||||
|
val composedBitmap = compositor.composeImages(bitmaps, "巡检报告", "合成时间: ${Date()}")
|
||||||
|
|
||||||
|
// 保存合成图片
|
||||||
|
val timestamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
|
||||||
|
val contentValues = ContentValues().apply {
|
||||||
|
put(MediaStore.MediaColumns.DISPLAY_NAME, "巡检合成_$timestamp")
|
||||||
|
put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
put(MediaStore.MediaColumns.RELATIVE_PATH, "Pictures/LogCam")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val contentResolver = contentResolver
|
||||||
|
val uri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
|
||||||
|
|
||||||
|
if (uri != null) {
|
||||||
|
try {
|
||||||
|
val outputStream: OutputStream? = contentResolver.openOutputStream(uri)
|
||||||
|
if (outputStream != null) {
|
||||||
|
composedBitmap.compress(Bitmap.CompressFormat.JPEG, 90, outputStream)
|
||||||
|
outputStream.close()
|
||||||
|
|
||||||
|
Toast.makeText(this, "图片合成成功", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
} catch (e: IOException) {
|
||||||
|
Log.e(TAG, "保存合成图片失败", e)
|
||||||
|
Toast.makeText(this, "保存失败", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getAddressFromLocation(location: Location): String {
|
||||||
|
val geocoder = Geocoder(this, Locale.getDefault())
|
||||||
|
|
||||||
|
return try {
|
||||||
|
val addresses = geocoder.getFromLocation(
|
||||||
|
location.latitude,
|
||||||
|
location.longitude,
|
||||||
|
1
|
||||||
|
)
|
||||||
|
|
||||||
|
if (addresses != null && addresses.isNotEmpty()) {
|
||||||
|
val address = addresses[0]
|
||||||
|
"${address.locality}, ${address.subLocality}, ${address.thoroughfare}"
|
||||||
|
} else {
|
||||||
|
"${location.latitude.roundToInt()}, ${location.longitude.roundToInt()}"
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "无法解析地址: ${e.message}")
|
||||||
|
"${location.latitude.roundToInt()}, ${location.longitude.roundToInt()}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getLocationString(): String {
|
||||||
|
val location = getCurrentLocation()
|
||||||
|
return if (location != null) {
|
||||||
|
getAddressFromLocation(location)
|
||||||
|
} else {
|
||||||
|
"未知位置"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addWatermarkToBitmap(originalBitmap: Bitmap): Bitmap {
|
||||||
|
val timestamp = SimpleDateFormat("yyyy年-MM月-dd日 HH:mm:ss", Locale.getDefault()).format(Date())
|
||||||
|
val location = getLocationString()
|
||||||
|
|
||||||
|
val watermarkText = "$timestamp\n$location"
|
||||||
|
|
||||||
|
val mutableBitmap = originalBitmap.copy(Bitmap.Config.ARGB_8888, true)
|
||||||
|
val canvas = Canvas(mutableBitmap)
|
||||||
|
|
||||||
|
val paint = Paint().apply {
|
||||||
|
color = Color.WHITE
|
||||||
|
textSize = 32f
|
||||||
|
typeface = Typeface.DEFAULT_BOLD
|
||||||
|
setShadowLayer(5f, 0f, 0f, Color.BLACK)
|
||||||
|
}
|
||||||
|
|
||||||
|
val textWidth = paint.measureText(watermarkText)
|
||||||
|
val textHeight = paint.descent() - paint.ascent()
|
||||||
|
|
||||||
|
val x = 20f
|
||||||
|
val y = mutableBitmap.height - textHeight - 20f
|
||||||
|
|
||||||
|
canvas.drawText(watermarkText, x, y, paint)
|
||||||
|
|
||||||
|
return mutableBitmap
|
||||||
|
}
|
||||||
|
|
||||||
private fun openSettings() {
|
private fun openSettings() {
|
||||||
// 打开设置页面
|
// 打开设置页面
|
||||||
val settingsFragment = SettingsFragment()
|
val settingsFragment = SettingsFragment()
|
||||||
@@ -472,9 +373,4 @@ class CameraActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
|
||||||
super.onDestroy()
|
|
||||||
cameraExecutor.shutdown()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
package com.example.app
|
package com.example.app
|
||||||
|
|
||||||
import android.graphics.*
|
import android.graphics.*
|
||||||
|
import android.text.StaticLayout
|
||||||
|
import android.text.Layout
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|||||||
Reference in New Issue
Block a user