#android #kotlin
Вопрос:
Я пытаюсь сделать снимок с наложением, включенным в захват изображения. Я смог настроить наложение на previewView
использование cameraView.overlay.add(binding.textView)
. Как бы то ни было, это не спасло при попытке сохранить изображение, при imageCapture
этом была сохранена только картинка, а не наложение. Как сохранить изображение с включенным наложением с помощью PreviewView
камеры x.
Пожалуйста, не помечайте это как дубликат. Я много исследовал, и большинство примеров в Интернете используют старый camera
api, который не применяется к библиотеке camera x. Любая помощь будет признательна. Заранее спасибо.
Вот мой код
<FrameLayout
android:id="@ id/camera_wrapper"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="@id/space1"
app:layout_constraintBottom_toBottomOf="@id/space">
<androidx.camera.view.PreviewView
android:id="@ id/camera_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<TextView
android:id="@ id/text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="Hello world"
android:textSize="42sp"
android:textColor="@android:color/holo_green_dark"/>
</FrameLayout>
private lateinit var outputDirectory: File
private lateinit var cameraExecutor: ExecutorService
private var preview: Preview? = null
private var lensFacing: Int = CameraSelector.LENS_FACING_FRONT
private var imageCapture: ImageCapture? = null
private var camera: Camera? = null
private var cameraProvider: ProcessCameraProvider? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
outputDirectory = getOutputDirectory()
cameraExecutor = Executors.newSingleThreadExecutor()
}
private fun setupCamera() {
val cameraProviderFuture = ProcessCameraProvider.getInstance(requireContext())
cameraProviderFuture.addListener(
Runnable {
// Used to bind the lifecycle of cameras to the lifecycle owner
cameraProvider = cameraProviderFuture.get()
// Get screen metrics used to setup camera for full screen resolution
val metrics = DisplayMetrics().also { binding.cameraView.display.getRealMetrics(it) }
Timber.d("Screen metrics: ${metrics.widthPixels} x ${metrics.heightPixels}")
val screenAspectRatio = aspectRatio(metrics.widthPixels, metrics.heightPixels)
Timber.d("Preview aspect ratio: $screenAspectRatio")
val rotation = binding.cameraView.display.rotation
// CameraProvider
val cameraProvider = cameraProvider
?: throw IllegalStateException("Camera initialization failed.")
// CameraSelector
val cameraSelector = CameraSelector.Builder().requireLensFacing(lensFacing).build()
// add text overlay *---------*
binding.cameraView.overlay.add(binding.textView)
// Preview
preview = Preview.Builder()
// We request aspect ratio but no resolution
.setTargetAspectRatio(screenAspectRatio)
// Set initial target rotation
.setTargetRotation(rotation)
.build()
// ImageCapture
imageCapture = ImageCapture.Builder()
.setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
// We request aspect ratio but no resolution to match preview config, but letting
// CameraX optimize for whatever specific resolution best fits our use cases
.setTargetAspectRatio(screenAspectRatio)
// Set initial target rotation, we will have to call this again if rotation changes
// during the lifecycle of this use case
.setTargetRotation(rotation)
.build()
// Must unbind the use-cases before rebinding them
cameraProvider.unbindAll()
try {
// A variable number of use-cases can be passed here -
// camera provides access to CameraControl amp; CameraInfo
camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture)
// Attach the viewfinder's surface provider to preview use case
preview?.setSurfaceProvider(binding.cameraView.surfaceProvider)
} catch (exc: Exception) {
Toast.makeText(requireContext(), "Something went wrong. Please try again.", Toast.LENGTH_SHORT).show()
findNavController().navigateUp()
}
},
ContextCompat.getMainExecutor(requireContext())
)
}
private fun takePhoto() {
imageCapture?.let { imageCapture ->
// Create output file to hold the image
val photoFile = createFile(outputDirectory, FILENAME, PHOTO_EXTENSION)
// Setup image capture metadata
val metadata = ImageCapture.Metadata().apply {
// Mirror image when using the front camera
isReversedHorizontal = lensFacing == CameraSelector.LENS_FACING_FRONT
}
// Create output options object which contains file metadata
val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile)
.setMetadata(metadata)
.build()
// Setup image capture listener which is triggered after photo has been taken
imageCapture.takePicture(outputOptions, cameraExecutor, object : ImageCapture.OnImageSavedCallback {
override fun onError(exc: ImageCaptureException) {
Timber.e(exc, "Photo capture failed: ${exc.message}")
}
override fun onImageSaved(output: ImageCapture.OutputFileResults) {
val savedUri = output.savedUri ?: Uri.fromFile(photoFile)
Timber.d("Photo capture succeeded: $savedUri")
// Implicit broadcasts will be ignored for devices running API level >= 24
// so if you only target API level 24 you can remove this statement
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
requireActivity()
.sendBroadcast(Intent(android.hardware.Camera.ACTION_NEW_PICTURE, savedUri))
}
// If the folder selected is an external media directory, this is
// unnecessary but otherwise other apps will not be able to access our
// images unless we scan them using [MediaScannerConnection]
val mimeType = MimeTypeMap.getSingleton()
.getMimeTypeFromExtension(savedUri.toFile().extension)
MediaScannerConnection.scanFile(
context,
arrayOf(savedUri.toFile().absolutePath),
arrayOf(mimeType)
) { _, uri ->
Timber.d("Image capture scanned into media store: $uri")
}
}
})
}
}
Ответ №1:
Вы должны сами наложить текст поверх изображения. Я бы предложил использовать takePicture(Исполнитель,…), который помещает Jpeg в память; затем наложите текст с помощью одной из библиотек (не входит в состав платформы Android, ни в Jetpack) и сохраните результат в файле.
Если вы можете пойти на компромисс в отношении качества изображения, вы можете нарисовать Jpeg на растровом холсте и нарисовать текст сверху.
Ответ №2:
Перейдите с помощью этого плагина https://github.com/huangyz0918/AndroidWM .Кроме того, если вы хотите создать свой собственный, это может помочь вам обратиться к.
Простое использование, как показано ниже
WatermarkText watermarkText = new WatermarkText(inputText)
.setPositionX(0.5)
.setPositionY(0.5)
.setTextColor(Color.WHITE)
.setTextFont(R.font.champagne)
.setTextShadow(0.1f, 5, 5, Color.BLUE)
.setTextAlpha(150)
.setRotation(30)
.setTextSize(20);
val bmFinal: Bitmap = WatermarkBuilder
.create(applicationContext, capturedImageBitmap)
.loadWatermarkText(watermarkText)
.watermark
.outputImage
// ##### Then save it #########
fun saveImage(bitmap: Bitmap, photoFile: File) {
val output: OutputStream = FileOutputStream(photoFile)
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, output)
output.flush()
output.close()
Toast.makeText(
this@MainActivity,
"Imaged Saved at ${photoFile.absolutePath}",
Toast.LENGTH_LONG
).show()
}
val photoFile = File(
outputDirectory,
SimpleDateFormat(
FILENAME_FORMAT, Locale.US
).format(System.currentTimeMillis()) ".jpg"
)
saveImage(bmFinal, photoFile)
Ответ №3:
Возможно, вы можете использовать класс CameraSource и поместить свой предварительный просмотр/наложение внутрь :
val cameraSource = CameraSource.Builder(requireContext(),FakeDetector()).build()
cameraSource.start(your_preview_overlay)
И после того, как у вас будет API :
takePicture(CameraSource.ShutterCallback shutter, CameraSource.PictureCallback jpeg)
Источник камеры (https://developers.google.com/android/reference/com/google/android/gms/vision/CameraSource) предназначен для обнаружения, но вы можете создать поддельный детектор ( ничего не обнаруживать).
Ответ №4:
Ответ @alexcohn является предпочтительным, если вы не можете позволить себе потерять качество. Однако, если качество не имеет большого значения, вы можете это сделать.
<FrameLayout
android:id="@ id/camera_wrapper"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="@id/space1"
app:layout_constraintBottom_toBottomOf="@id/space">
<androidx.camera.view.PreviewView
android:id="@ id/camera_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ImageView
android:id="@ id/selfie"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:visibility="gone"
tools:visibility="visible"
tools:background="@color/gray" />
<ImageView
android:id="@ id/overlay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
tools:src="@drawable/full_frame_gd" />
</FrameLayout>
PreviewView
имеет встроенную функцию, которая дает вам растровое изображение предварительного просмотра
val bitmap = binding.cameraView.bitmap
binding.selfie.setImageBitmap(bitmap)
binding.selfie.visibility = View.VISIBLE
cameraExecutor.shutdown()
binding.cameraView.visibility = View.GONE
Теперь у вас есть два изображения, для просмотра одного selfie
и для overlay
другого . Вы не можете сделать снимок экрана предварительного просмотра. У этого есть некоторые ограничения, в которых я не слишком уверен, но я уверен, что может быть способ обойти это.
Отсюда вы можете просто сделать снимок экрана двух объединенных видов изображений, как это
private fun captureView(view: View, window: Window, bitmapCallback: (Bitmap)->Unit) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// Above Android O, use PixelCopy
val bitmap = Bitmap.createBitmap(view.width, view.height, Bitmap.Config.ARGB_8888)
val location = IntArray(2)
view.getLocationInWindow(location)
PixelCopy.request(
window,
Rect(
location[0],
location[1],
location[0] view.width,
location[1] view.height
),
bitmap,
{
if (it == PixelCopy.SUCCESS) {
bitmapCallback.invoke(bitmap)
}
},
Handler(Looper.getMainLooper()) )
} else {
val tBitmap = Bitmap.createBitmap(
view.width, view.height, Bitmap.Config.RGB_565
)
val canvas = Canvas(tBitmap)
view.draw(canvas)
canvas.setBitmap(null)
bitmapCallback.invoke(tBitmap)
}
}
и в функции takePhoto() вы можете удалить imageCapture.takePicture
логику и заменить ее на эту.
Handler(Looper.getMainLooper()).postDelayed({
captureView(binding.cameraWrapper, requireActivity().window) {
// your new bitmap with overlay is here and you can save it to file just like any other bitmaps.
}
}, 500)