AssimilateTranslate/app/src/main/java/com/assimilate/alltrans/viewui/StillImageActivity.kt
2024-07-10 10:52:21 +08:00

388 lines
16 KiB
Kotlin

package com.assimilate.alltrans.viewui
import android.app.Activity
import android.content.ContentValues
import android.content.Intent
import android.content.res.Configuration
import android.graphics.Bitmap
import android.net.Uri
import android.os.Bundle
import android.provider.MediaStore
import android.util.Log
import android.util.Pair
import android.view.MenuItem
import android.view.View
import android.view.ViewTreeObserver
import android.widget.AdapterView
import android.widget.AdapterView.OnItemSelectedListener
import android.widget.ArrayAdapter
import android.widget.ImageView
import android.widget.PopupMenu
import android.widget.Spinner
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.assimilate.alltrans.R
import com.assimilate.alltrans.common.BitmapUtils
import com.assimilate.alltrans.common.TextRecognitionProcessor
import com.assimilate.alltrans.common.VisionImageProcessor
import com.assimilate.alltrans.curview.GraphicOverlay
import com.google.android.gms.common.annotation.KeepName
import com.google.mlkit.vision.text.chinese.ChineseTextRecognizerOptions
import com.google.mlkit.vision.text.devanagari.DevanagariTextRecognizerOptions
import com.google.mlkit.vision.text.japanese.JapaneseTextRecognizerOptions
import com.google.mlkit.vision.text.korean.KoreanTextRecognizerOptions
import com.google.mlkit.vision.text.latin.TextRecognizerOptions
import java.io.IOException
/** Activity demonstrating different image detector features with a still image from camera. */
@KeepName
class StillImageActivity : AppCompatActivity() {
private var preview: ImageView? = null
private var graphicOverlay: GraphicOverlay? = null
private var selectedMode = TEXT_RECOGNITION_CHINESE
private var selectedSize: String? = SIZE_SCREEN
private var isLandScape = false
private var imageUri: Uri? = null
// Max width (portrait mode)
private var imageMaxWidth = 0
// Max height (portrait mode)
private var imageMaxHeight = 0
private var imageProcessor: VisionImageProcessor? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_still_image)
findViewById<View>(R.id.select_image_button).setOnClickListener { view: View ->
// Menu for selecting either: a) take new photo b) select from existing
val popup = PopupMenu(this@StillImageActivity, view)
popup.setOnMenuItemClickListener { menuItem: MenuItem ->
val itemId = menuItem.itemId
if (itemId == R.id.select_images_from_local) {
startChooseImageIntentForResult()
return@setOnMenuItemClickListener true
} else if (itemId == R.id.take_photo_using_camera) {
startCameraIntentForResult()
return@setOnMenuItemClickListener true
}
false
}
val inflater = popup.menuInflater
inflater.inflate(R.menu.camera_button_menu, popup.menu)
popup.show()
}
preview = findViewById(R.id.preview)
graphicOverlay = findViewById(R.id.graphic_overlay)
populateFeatureSelector()
populateSizeSelector()
isLandScape = resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
if (savedInstanceState != null) {
imageUri = savedInstanceState.getParcelable(KEY_IMAGE_URI)
imageMaxWidth = savedInstanceState.getInt(KEY_IMAGE_MAX_WIDTH)
imageMaxHeight = savedInstanceState.getInt(KEY_IMAGE_MAX_HEIGHT)
selectedSize = savedInstanceState.getString(KEY_SELECTED_SIZE)
}
val rootView = findViewById<View>(R.id.root)
rootView.viewTreeObserver.addOnGlobalLayoutListener(
object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
rootView.viewTreeObserver.removeOnGlobalLayoutListener(this)
imageMaxWidth = rootView.width
imageMaxHeight = rootView.height - findViewById<View>(R.id.control).height
if (SIZE_SCREEN == selectedSize) {
tryReloadAndDetectInImage()
}
}
}
)
val settingsButton = findViewById<ImageView>(R.id.settings_button)
settingsButton.setOnClickListener {
// val intent = Intent(applicationContext, SettingsActivity::class.java)
// intent.putExtra(SettingsActivity.EXTRA_LAUNCH_SOURCE, LaunchSource.STILL_IMAGE)
// startActivity(intent)
}
}
public override fun onResume() {
super.onResume()
Log.d(TAG, "onResume")
createImageProcessor()
tryReloadAndDetectInImage()
}
public override fun onPause() {
super.onPause()
imageProcessor?.run { this.stop() }
}
public override fun onDestroy() {
super.onDestroy()
imageProcessor?.run { this.stop() }
}
private fun populateFeatureSelector() {
val featureSpinner = findViewById<Spinner>(R.id.feature_selector)
val options: MutableList<String> = ArrayList()
options.add(TEXT_RECOGNITION_CHINESE); // 识别中文文本
options.add(TEXT_RECOGNITION_LATIN); // 识别拉丁文本
options.add(TEXT_RECOGNITION_DEVANAGARI); // 识别梵文文本
options.add(TEXT_RECOGNITION_JAPANESE); // 识别日文文本
options.add(TEXT_RECOGNITION_KOREAN); // 识别韩文文本
// Creating adapter for featureSpinner
val dataAdapter = ArrayAdapter(this, R.layout.spinner_style, options)
// Drop down layout style - list view with radio button
dataAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
// attaching data adapter to spinner
featureSpinner.adapter = dataAdapter
featureSpinner.onItemSelectedListener =
object : OnItemSelectedListener {
override fun onItemSelected(
parentView: AdapterView<*>,
selectedItemView: View?,
pos: Int,
id: Long
) {
if (pos >= 0) {
selectedMode = parentView.getItemAtPosition(pos).toString()
createImageProcessor()
tryReloadAndDetectInImage()
}
}
override fun onNothingSelected(arg0: AdapterView<*>?) {}
}
}
private fun populateSizeSelector() {
val sizeSpinner = findViewById<Spinner>(R.id.size_selector)
val options: MutableList<String> = ArrayList()
options.add(SIZE_SCREEN)
options.add(SIZE_1024_768)
options.add(SIZE_640_480)
options.add(SIZE_ORIGINAL)
// Creating adapter for featureSpinner
val dataAdapter = ArrayAdapter(this, R.layout.spinner_style, options)
// Drop down layout style - list view with radio button
dataAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
// attaching data adapter to spinner
sizeSpinner.adapter = dataAdapter
sizeSpinner.onItemSelectedListener =
object : OnItemSelectedListener {
override fun onItemSelected(
parentView: AdapterView<*>,
selectedItemView: View?,
pos: Int,
id: Long
) {
if (pos >= 0) {
selectedSize = parentView.getItemAtPosition(pos).toString()
tryReloadAndDetectInImage()
}
}
override fun onNothingSelected(arg0: AdapterView<*>?) {}
}
}
public override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putParcelable(KEY_IMAGE_URI, imageUri)
outState.putInt(KEY_IMAGE_MAX_WIDTH, imageMaxWidth)
outState.putInt(KEY_IMAGE_MAX_HEIGHT, imageMaxHeight)
outState.putString(KEY_SELECTED_SIZE, selectedSize)
}
private fun startCameraIntentForResult() { // Clean up last time's image
imageUri = null
preview!!.setImageBitmap(null)
val takePictureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
if (takePictureIntent.resolveActivity(packageManager) != null) {
val values = ContentValues()
values.put(MediaStore.Images.Media.TITLE, "New Picture")
values.put(MediaStore.Images.Media.DESCRIPTION, "From Camera")
imageUri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri)
startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE)
}
}
private fun startChooseImageIntentForResult() {
val intent = Intent()
intent.type = "image/*"
intent.action = Intent.ACTION_GET_CONTENT
startActivityForResult(Intent.createChooser(intent, "Select Picture"), REQUEST_CHOOSE_IMAGE)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == Activity.RESULT_OK) {
tryReloadAndDetectInImage()
} else if (requestCode == REQUEST_CHOOSE_IMAGE && resultCode == Activity.RESULT_OK) {
// In this case, imageUri is returned by the chooser, save it.
imageUri = data!!.data
tryReloadAndDetectInImage()
} else {
super.onActivityResult(requestCode, resultCode, data)
}
}
private fun tryReloadAndDetectInImage() {
Log.d(TAG, "Try reload and detect image")
try {
if (imageUri == null) {
return
}
if (SIZE_SCREEN == selectedSize && imageMaxWidth == 0) {
// UI layout has not finished yet, will reload once it's ready.
return
}
val imageBitmap =
BitmapUtils.getBitmapFromContentUri(contentResolver, imageUri) ?: return
// Clear the overlay first
graphicOverlay!!.clear()
val resizedBitmap: Bitmap
resizedBitmap =
if (selectedSize == SIZE_ORIGINAL) {
imageBitmap
} else {
// Get the dimensions of the image view
val targetedSize: Pair<Int, Int> = targetedWidthHeight
// Determine how much to scale down the image
val scaleFactor =
Math.max(
imageBitmap.width.toFloat() / targetedSize.first.toFloat(),
imageBitmap.height.toFloat() / targetedSize.second.toFloat()
)
Bitmap.createScaledBitmap(
imageBitmap,
(imageBitmap.width / scaleFactor).toInt(),
(imageBitmap.height / scaleFactor).toInt(),
true
)
}
preview!!.setImageBitmap(resizedBitmap)
if (imageProcessor != null) {
graphicOverlay!!.setImageSourceInfo(
resizedBitmap.width,
resizedBitmap.height,
/* isFlipped= */ false
)
imageProcessor!!.processBitmap(resizedBitmap, graphicOverlay)
} else {
Log.e(
TAG,
"Null imageProcessor, please check adb logs for imageProcessor creation error"
)
}
} catch (e: IOException) {
Log.e(TAG, "Error retrieving saved image")
imageUri = null
}
}
private val targetedWidthHeight: Pair<Int, Int>
get() {
val targetWidth: Int
val targetHeight: Int
when (selectedSize) {
SIZE_SCREEN -> {
targetWidth = imageMaxWidth
targetHeight = imageMaxHeight
}
SIZE_640_480 -> {
targetWidth = if (isLandScape) 640 else 480
targetHeight = if (isLandScape) 480 else 640
}
SIZE_1024_768 -> {
targetWidth = if (isLandScape) 1024 else 768
targetHeight = if (isLandScape) 768 else 1024
}
else -> throw IllegalStateException("Unknown size")
}
return Pair(targetWidth, targetHeight)
}
private fun createImageProcessor() {
try {
when (selectedMode) {
TEXT_RECOGNITION_LATIN ->
imageProcessor =
TextRecognitionProcessor(this, TextRecognizerOptions.Builder().build())
TEXT_RECOGNITION_CHINESE ->
imageProcessor =
TextRecognitionProcessor(
this,
ChineseTextRecognizerOptions.Builder().build()
)
TEXT_RECOGNITION_DEVANAGARI ->
imageProcessor =
TextRecognitionProcessor(
this,
DevanagariTextRecognizerOptions.Builder().build()
)
TEXT_RECOGNITION_JAPANESE ->
imageProcessor =
TextRecognitionProcessor(
this,
JapaneseTextRecognizerOptions.Builder().build()
)
TEXT_RECOGNITION_KOREAN ->
imageProcessor =
TextRecognitionProcessor(
this,
KoreanTextRecognizerOptions.Builder().build()
)
else -> Log.e(TAG, "Unknown selectedMode: $selectedMode")
}
} catch (e: Exception) {
Log.e(TAG, "Can not create image processor: $selectedMode", e)
Toast.makeText(
applicationContext,
"Can not create image processor: " + e.message,
Toast.LENGTH_LONG
)
.show()
}
}
companion object {
private const val TAG = "StillImageActivity"
private const val TEXT_RECOGNITION_LATIN = "Text Recognition Latin"
private const val TEXT_RECOGNITION_CHINESE = "Text Recognition Chinese"
private const val TEXT_RECOGNITION_DEVANAGARI = "Text Recognition Devanagari"
private const val TEXT_RECOGNITION_JAPANESE = "Text Recognition Japanese"
private const val TEXT_RECOGNITION_KOREAN = "Text Recognition Korean"
private const val SIZE_SCREEN = "w:screen" // Match screen width
private const val SIZE_1024_768 = "w:1024" // ~1024*768 in a normal ratio
private const val SIZE_640_480 = "w:640" // ~640*480 in a normal ratio
private const val SIZE_ORIGINAL = "w:original" // Original image size
private const val KEY_IMAGE_URI = "com.google.mlkit.vision.demo.KEY_IMAGE_URI"
private const val KEY_IMAGE_MAX_WIDTH = "com.google.mlkit.vision.demo.KEY_IMAGE_MAX_WIDTH"
private const val KEY_IMAGE_MAX_HEIGHT = "com.google.mlkit.vision.demo.KEY_IMAGE_MAX_HEIGHT"
private const val KEY_SELECTED_SIZE = "com.google.mlkit.vision.demo.KEY_SELECTED_SIZE"
private const val REQUEST_IMAGE_CAPTURE = 1001
private const val REQUEST_CHOOSE_IMAGE = 1002
}
}