From bac12c180830cab32bcca7a17672ee76f35d8cba Mon Sep 17 00:00:00 2001 From: xiajiid Date: Sun, 8 Feb 2026 16:33:44 +0800 Subject: [PATCH] Fix compilation errors: remove duplicate methods, add missing imports, fix location manager --- .../java/com/example/app/CameraActivity.kt | 578 +++++++----------- .../java/com/example/app/ImageCompositor.kt | 2 + 2 files changed, 239 insertions(+), 341 deletions(-) diff --git a/app/src/main/java/com/example/app/CameraActivity.kt b/app/src/main/java/com/example/app/CameraActivity.kt index 1c55ad9..66f3563 100644 --- a/app/src/main/java/com/example/app/CameraActivity.kt +++ b/app/src/main/java/com/example/app/CameraActivity.kt @@ -55,348 +55,14 @@ class CameraActivity : AppCompatActivity() { private lateinit var photoPreviewLayout: LinearLayout private var imageCapture: ImageCapture? = null private lateinit var cameraExecutor: ExecutorService + private var locationManagerHelper: LocationManagerHelper? = null + private var currentLocation: Location? = null // 存储拍摄的图片URI private val capturedImageUris = mutableListOf() // 图片合成器 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() - 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?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) @@ -451,6 +117,241 @@ class CameraActivity : AppCompatActivity() { 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() + + 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() { // 打开设置页面 val settingsFragment = SettingsFragment() @@ -472,9 +373,4 @@ class CameraActivity : AppCompatActivity() { } } } - - override fun onDestroy() { - super.onDestroy() - cameraExecutor.shutdown() - } } \ No newline at end of file diff --git a/app/src/main/java/com/example/app/ImageCompositor.kt b/app/src/main/java/com/example/app/ImageCompositor.kt index 9c510f9..fe47b02 100644 --- a/app/src/main/java/com/example/app/ImageCompositor.kt +++ b/app/src/main/java/com/example/app/ImageCompositor.kt @@ -1,6 +1,8 @@ package com.example.app import android.graphics.* +import android.text.StaticLayout +import android.text.Layout import java.io.File import java.text.SimpleDateFormat import java.util.*