Add WatermarkCameraActivity with timestamp watermark and development plan

This commit is contained in:
xiajiid
2026-02-08 22:20:32 +08:00
parent b87d90e418
commit f1a7811d28
5 changed files with 362 additions and 4 deletions

View File

@@ -30,6 +30,10 @@
android:name=".SimpleCameraActivity"
android:exported="false"
android:screenOrientation="portrait" />
<activity
android:name=".WatermarkCameraActivity"
android:exported="false"
android:screenOrientation="portrait" />
</application>
</manifest>

View File

@@ -9,8 +9,8 @@ class MainActivity : AppCompatActivity() {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 启动极简相机Activity
startActivity(Intent(this, SimpleCameraActivity::class.java))
// 启动带水印相机Activity
startActivity(Intent(this, WatermarkCameraActivity::class.java))
finish() // 关闭MainActivity避免返回键回到空白页面
}
}

View File

@@ -138,8 +138,12 @@ class SimpleCameraActivity : AppCompatActivity() {
ContextCompat.getMainExecutor(this),
object : ImageCapture.OnImageSavedCallback {
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
val savedUri = outputFileResults.savedUri ?: return
val msg = "照片已保存: ${savedUri.lastPathSegment}"
val savedUri = outputFileResults.savedUri
val msg = if (savedUri != null) {
"照片已保存: ${savedUri.lastPathSegment}"
} else {
"照片保存成功"
}
Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
Log.d(TAG, msg)
}

View File

@@ -0,0 +1,248 @@
package com.example.app
import android.Manifest
import android.content.ContentValues
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Typeface
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.MediaStore
import android.util.Log
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.camera.core.CameraSelector
import androidx.camera.core.ImageCapture
import androidx.camera.core.ImageCaptureException
import androidx.camera.core.Preview
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.view.PreviewView
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.google.android.material.floatingactionbutton.FloatingActionButton
import java.io.OutputStream
import java.text.SimpleDateFormat
import java.util.*
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
class WatermarkCameraActivity : AppCompatActivity() {
companion object {
private const val TAG = "WatermarkCamera"
private const val REQUEST_CODE_PERMISSIONS = 10
private val REQUIRED_PERMISSIONS = arrayOf(
Manifest.permission.CAMERA
)
}
private lateinit var viewFinder: PreviewView
private lateinit var captureButton: FloatingActionButton
private var imageCapture: ImageCapture? = null
private lateinit var cameraExecutor: ExecutorService
private var cameraProvider: ProcessCameraProvider? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_simple_camera)
// 初始化视图
viewFinder = findViewById(R.id.viewFinder)
captureButton = findViewById(R.id.captureButton)
// 初始化线程池
cameraExecutor = Executors.newSingleThreadExecutor()
// 请求权限
if (allPermissionsGranted()) {
startCamera()
} else {
ActivityCompat.requestPermissions(
this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS
)
}
// 设置拍照按钮点击监听器
captureButton.setOnClickListener { takePhoto() }
}
override fun onDestroy() {
super.onDestroy()
cameraExecutor.shutdown()
cameraProvider?.unbindAll()
}
private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
ContextCompat.checkSelfPermission(baseContext, it) == PackageManager.PERMISSION_GRANTED
}
private fun startCamera() {
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
cameraProviderFuture.addListener({
try {
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
cameraProvider?.unbindAll()
cameraProvider?.bindToLifecycle(
this, cameraSelector, preview, imageCapture
)
Log.d(TAG, "相机启动成功")
} catch (e: Exception) {
Log.e(TAG, "相机初始化失败: ${e.message}", e)
Toast.makeText(this, "相机启动失败: ${e.message}", Toast.LENGTH_LONG).show()
finish()
}
}, ContextCompat.getMainExecutor(this))
}
private fun takePhoto() {
val imageCapture = this.imageCapture ?: run {
Toast.makeText(this, "相机未准备好", Toast.LENGTH_SHORT).show()
return
}
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) {
Toast.makeText(this, "创建文件失败", Toast.LENGTH_SHORT).show()
return
}
val outputOptions = ImageCapture.OutputFileOptions.Builder(contentResolver, uri, contentValues).build()
imageCapture.takePicture(
outputOptions,
ContextCompat.getMainExecutor(this),
object : ImageCapture.OnImageSavedCallback {
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
val savedUri = outputFileResults.savedUri
if (savedUri != null) {
// 添加水印
addWatermarkToImage(savedUri)
val msg = "照片已保存并添加水印: ${savedUri.lastPathSegment}"
Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
Log.d(TAG, msg)
} else {
Toast.makeText(baseContext, "照片保存成功", Toast.LENGTH_SHORT).show()
Log.d(TAG, "照片保存成功")
}
}
override fun onError(exception: ImageCaptureException) {
Log.e(TAG, "拍照失败: ${exception.message}", exception)
Toast.makeText(baseContext, "拍照失败: ${exception.message}", Toast.LENGTH_LONG).show()
}
}
)
}
private fun addWatermarkToImage(imageUri: Uri) {
try {
// 读取图片
val inputStream = contentResolver.openInputStream(imageUri)
val originalBitmap = BitmapFactory.decodeStream(inputStream)
inputStream?.close()
if (originalBitmap == null) {
Log.e(TAG, "无法读取图片: $imageUri")
return
}
// 添加水印
val watermarkedBitmap = addWatermark(originalBitmap)
// 保存回原文件
val outputStream: OutputStream? = contentResolver.openOutputStream(imageUri, "w")
if (outputStream != null) {
watermarkedBitmap.compress(Bitmap.CompressFormat.JPEG, 90, outputStream)
outputStream.close()
Log.d(TAG, "水印添加成功: $imageUri")
} else {
Log.e(TAG, "无法写入图片: $imageUri")
}
// 回收位图
originalBitmap.recycle()
watermarkedBitmap.recycle()
} catch (e: Exception) {
Log.e(TAG, "添加水印失败: ${e.message}", e)
}
}
private fun addWatermark(originalBitmap: Bitmap): Bitmap {
val timestamp = SimpleDateFormat("yyyy年-MM月-dd日 HH:mm:ss", Locale.getDefault()).format(Date())
// 创建可修改的位图副本
val mutableBitmap = originalBitmap.copy(Bitmap.Config.ARGB_8888, true)
val canvas = Canvas(mutableBitmap)
// 创建水印画笔
val paint = Paint().apply {
color = Color.WHITE
textSize = 48f
typeface = Typeface.DEFAULT_BOLD
setShadowLayer(5f, 0f, 0f, Color.BLACK)
}
// 计算文字尺寸
val textWidth = paint.measureText(timestamp)
val textHeight = paint.descent() - paint.ascent()
// 计算水印位置(左下角,留边距)
val margin = 20f
val x = margin
val y = mutableBitmap.height - textHeight - margin
// 绘制水印
canvas.drawText(timestamp, x, y, paint)
return mutableBitmap
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == REQUEST_CODE_PERMISSIONS) {
if (allPermissionsGranted()) {
startCamera()
} else {
Toast.makeText(this, "相机权限被拒绝,应用无法使用相机功能", Toast.LENGTH_LONG).show()
finish()
}
}
}
}