Я создаю приложение для Android, использующее Camera2 API с Jetpack Compose для захвата изображений при нажатии кнопки. Моя цель — отобразить предварительный просмотр камеры с помощью TextView, захватывать изображения в формате JPEG с помощью ImageReader и, если это поддерживается, также захватывать данные о глубине в формате DEPTH16. Однако я столкнулся с проблемой, когда блок кода в ImageReader.setOnImageAvailableListener() вообще не выполняется, что не позволяет мне получить захваченное изображение.
В моем коде я настройте камеру и CaptureSession с помощью TextView для предварительного просмотра и ImageReader для захвата изображений. Я инициализирую ImageReader с соответствующими размерами, устанавливаю формат JPEG и настраиваю размер буфера, гарантируя, что они установлены перед созданием сеанса захвата. Когда кнопка запускает takePicture(), я создаю запрос захвата, нацеленный на ImageReader.surface, и ожидаю, что ImageReader.OnImageAvailableListener выполнится и позволит мне получить изображение. Однако блок кода прослушивателя не запускается, и ни один из операторов журналирования, которые я поместил в него, не достигается, что указывает на то, что он вообще не запускается. Я могу подтвердить, что сеанс захвата создан успешно, поскольку обратные вызовы конфигурации сеанса завершаются без ошибок, и takePicture(), похоже, срабатывает правильно, но OnImageAvailableListener просто не активируется.
Мой коды следующие:
package com.example.tutorial.camera
import android.Manifest
import android.app.Activity
import android.content.Context
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.ImageFormat
import android.graphics.SurfaceTexture
import android.hardware.camera2.CameraCaptureSession
import android.hardware.camera2.CameraCharacteristics
import android.hardware.camera2.CameraDevice
import android.hardware.camera2.CameraManager
import android.media.ImageReader
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.util.Size
import android.view.SurfaceHolder
import android.view.TextureView
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.*
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import java.lang.Exception
@Composable
fun CameraPreview(
onImageCaptured: (Bitmap) -> Unit,
onImageDCaptured: (ByteArray) -> Unit,
onDismiss: () -> Unit
) {
var cameraHandler: CameraHandler? = remember { null }
var errorMessage by remember { mutableStateOf(null) }
// Use TextureView to display the camera preview
AndroidView(factory = { context ->
TextureView(context).apply {
// Use SurfaceTextureListener to ensure the surface is created
surfaceTextureListener = object : TextureView.SurfaceTextureListener {
override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) {
cameraHandler =
CameraHandler(
context = context,
view = this@apply,
onImageCaptured = { bitmap ->
Log.d("CameraPreview", "Image captured successfully: ${bitmap.width}x${bitmap.height}")
onImageCaptured(bitmap)
},
onImageDCaptured = onImageDCaptured,
onDismiss = onDismiss,
onError = { message -> errorMessage = message })
cameraHandler?.startCamera()
}
override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture, width: Int, height: Int) {}
override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean {
cameraHandler?.stopCamera()
onDismiss()
return true
}
override fun onSurfaceTextureUpdated(surface: SurfaceTexture) {}
}
}
})
// Show error dialog
errorMessage?.let { message ->
AlertDialog(
onDismissRequest = { errorMessage = null; },
title = { Text("Tip") },
text = { Text(message) },
confirmButton = {
Button(onClick = { errorMessage = null; }) { Text("Confirm") }
}
)
}
// Capture button at the bottom of the screen
Box(modifier = Modifier.fillMaxSize()) {
Button(
onClick = {
cameraHandler?.takePicture()
onDismiss() // Close camera preview after taking the picture
},
modifier = Modifier.align(Alignment.BottomCenter)
) {
Text("Take Picture")
}
}
}
private const val REQUEST_CAMERA_PERMISSION = 1001 // Define the constant for permission request
class CameraHandler(
private val context: Context,
private val view: TextureView,
private val onImageCaptured: (Bitmap) -> Unit,
private val onImageDCaptured: (ByteArray) -> Unit,
private val onDismiss: () -> Unit, // Use onDismiss to close camera preview
private val onError: (String) -> Unit
) {
private val cameraManager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
private lateinit var cameraDevice: CameraDevice
private lateinit var captureSession: CameraCaptureSession
private lateinit var imageReader: ImageReader
private lateinit var depthImageReader: ImageReader
private val cameraId by mutableStateOf(getDepthSupportedCameraId())
private val supportsDepth: Boolean by lazy {
// Check if the device supports DEPTH16 format
val cameraId = cameraManager.cameraIdList[0]
val characteristics = cameraManager.getCameraCharacteristics(cameraId)
val capabilities = characteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES)
capabilities?.contains(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_DEPTH_OUTPUT) == true
}
private fun getDepthSupportedCameraId(): String? {
for (cameraId in cameraManager.cameraIdList) {
val characteristics = cameraManager.getCameraCharacteristics(cameraId)
// Check the direction of the camera
val facing = characteristics.get(CameraCharacteristics.LENS_FACING)
// Check if the device supports DEPTH16 format
val capabilities = characteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES)
if (capabilities?.contains(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_DEPTH_OUTPUT) == true) {
// Prefer the back camera
if (facing == CameraCharacteristics.LENS_FACING_BACK) {
return cameraId
}
}
}
// If no back camera is found, return other camera IDs that support DEPTH16
for (cameraId in cameraManager.cameraIdList) {
val characteristics = cameraManager.getCameraCharacteristics(cameraId)
val capabilities = characteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES)
if (capabilities?.contains(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_DEPTH_OUTPUT) == true) {
return cameraId
}
}
// If no camera supporting DEPTH16 format is found, return back camera ID
for (cameraId in cameraManager.cameraIdList) {
val characteristics = cameraManager.getCameraCharacteristics(cameraId)
val facing = characteristics.get(CameraCharacteristics.LENS_FACING)
if (facing == CameraCharacteristics.LENS_FACING_BACK) {
return cameraId
}
}
return null
}
fun startCamera() {
// Check and request camera permissions
if (ContextCompat.checkSelfPermission(
context,
Manifest.permission.CAMERA
) != PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(
context as Activity,
arrayOf(Manifest.permission.CAMERA),
REQUEST_CAMERA_PERMISSION
)
return
}
if (cameraId == null) {
showError("No usable camera")
return
}
try {
val handler = Handler(Looper.getMainLooper())
// Open the camera
cameraManager.openCamera(cameraId!!, object : CameraDevice.StateCallback() {
override fun onOpened(device: CameraDevice) {
cameraDevice = device
createCameraPreviewSession()
}
override fun onDisconnected(device: CameraDevice) {
device.close()
onDismiss() // Close camera preview when disconnected
}
override fun onError(device: CameraDevice, error: Int) {
device.close()
showError("Camera Error: $error")
}
}, handler)
} catch (e: Exception) {
showError("Failed to start camera: ${e.message}")
}
}
private fun createCameraPreviewSession() {
try {
// Get the best size for the camera and configure the preview
val characteristics = cameraId?.let { cameraManager.getCameraCharacteristics(it) }
val map = characteristics?.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
val previewSize =
map?.getOutputSizes(SurfaceHolder::class.java)?.maxByOrNull { it.width * it.height } ?: Size(640, 480)
val width = previewSize.width
val height = previewSize.height
Log.d("CameraHandler", "Initializing ImageReader with width=$width, height=$height")
imageReader = ImageReader.newInstance(width, height, ImageFormat.JPEG, 5)
val texture = view.surfaceTexture ?: throw IllegalStateException("SurfaceTexture is null")
texture.setDefaultBufferSize(previewSize.width, previewSize.height)
val previewSurface = android.view.Surface(texture)
val surfaces = mutableListOf(previewSurface, imageReader.surface)
if (supportsDepth) {
Log.d("CameraHandler", "Initializing DepthImageReader with width=$width, height=$height")
depthImageReader = ImageReader.newInstance(previewSize.width, previewSize.height, ImageFormat.DEPTH16, 5)
surfaces.add(depthImageReader.surface)
}
// Create CaptureSession
cameraDevice.createCaptureSession(
surfaces,
object : CameraCaptureSession.StateCallback() {
override fun onConfigured(session: CameraCaptureSession) {
captureSession = session
val captureRequest = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
captureRequest.addTarget(previewSurface)
captureSession.setRepeatingRequest(captureRequest.build(), null, null)
}
override fun onConfigureFailed(session: CameraCaptureSession) {
showError("Failed to configure camera session")
}
},
null
)
} catch (e: Exception) {
showError("Failed to create preview session: ${e.message}")
}
}
fun takePicture() {
try {
Log.d("CameraHandler", "At least tried successfully")
imageReader.setOnImageAvailableListener({ reader ->
val image = reader.acquireLatestImage()
Log.d("CameraHandler", "")
if (image != null) { // Check if the image was captured successfully
Log.d("CameraHandler", "Image captured")
val buffer = image.planes[0].buffer
val bytes = ByteArray(buffer.remaining())
buffer.get(bytes)
image.close()
val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
Log.d("CameraPreview", "Image captured successfully: ${bitmap.width}x${bitmap.height}")
onImageCaptured(bitmap)
} else {
Log.d("CameraHandler", "Failed to capture Image")
showError("Failed to acquire image from ImageReader")
}
// Check if depth image needs to be captured
if (supportsDepth) {
depthImageReader.setOnImageAvailableListener({ depthReader ->
val depthImage = depthReader.acquireLatestImage()
val depthBuffer = depthImage.planes[0].buffer
val depthBytes = ByteArray(depthBuffer.capacity())
depthBuffer.get(depthBytes)
depthImage.close()
// Call the callback for depth image
onImageDCaptured(depthBytes)
}, null)
// Trigger depth image capture request
val depthCaptureRequest = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE)
depthCaptureRequest.addTarget(depthImageReader.surface)
captureSession.capture(depthCaptureRequest.build(), null, null)
}
}, null)
// Trigger JPEG image capture request
val captureRequest = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE)
captureRequest.addTarget(imageReader.surface)
captureSession.capture(captureRequest.build(), null, null)
Log.d("CameraHandler", "Capture request for JPEG image sent")
} catch (e: Exception) {
showError("Failed to take picture: ${e.message}")
}
}
// Show error dialog
fun showError(message: String) {
onError(message)
}
fun stopCamera() {
try {
// Stop capture session
captureSession.close()
// Close camera device
cameraDevice.close()
// Release ImageReader
imageReader.close()
// If there is a depthImageReader, release it
if (supportsDepth) depthImageReader.close()
// (!supportsDepth) showError("This device doesn't support DEPTH16")
// onError("Success closing camera.")
} catch (e: Exception) {
onError("Error closing camera: ${e.message}")
}
}
}
Я ожидал, что прослушиватель в ImageReader.setOnImageAvailableListener() будет выполнять свой блок кода после каждого запроса на захват, что позволит мне получить изображение и обработать его. Для устранения неполадок я убедился, что TextureView, ImageReader и CaptureSession правильно инициализированы, а также удостоверился, что разрешения камеры предоставлены перед ее запуском. Я также поэкспериментировал с разными размерами буфера для ImageReader и подтвердил, что CaptureRequest включает imageReader.surface. Ведение журнала показывает, что takePicture() правильно инициирует запрос захвата и нацелен на ImageReader.surface, однако методacquireLatestImage() никогда не может получить изображение, поскольку прослушиватель не запускается. Никаких исключений или ошибок не возникает, и я остаюсь без каких-либо данных изображения.
Что может быть причиной того, что ImageReader.setOnImageAvailableListener() пропускает выполнение своего блока кода и какие шаги я могу предпринять для дальнейшего устранения этой проблемы? Есть ли что-то особенное в настройке CaptureRequest или ImageReader камеры 2, которое я, возможно, упустил из виду?
Кстати, я пробовал запускать это приложение на разных типах телефонов, но ни один из них не ответил поддержкой ГЛУБИНА 16 изображений. В этот список телефонов входит Samsung Galaxy S24 Ultra, который, я уверен, должен поддерживать измерение глубины. Я не знаю, действительно ли эти телефоны не поддерживают изображения такого типа, или что-то не так с тем, как я их запрашиваю.
Спасибо за помощь!< /п>
Я создаю приложение для Android, использующее Camera2 API с Jetpack Compose для захвата изображений при нажатии кнопки. Моя цель — отобразить предварительный просмотр камеры с помощью TextView, захватывать изображения в формате JPEG с помощью ImageReader и, если это поддерживается, также захватывать данные о глубине в формате DEPTH16. Однако я столкнулся с проблемой, когда блок кода в ImageReader.setOnImageAvailableListener() вообще не выполняется, что не позволяет мне получить захваченное изображение. В моем коде я настройте камеру и CaptureSession с помощью TextView для предварительного просмотра и ImageReader для захвата изображений. Я инициализирую ImageReader с соответствующими размерами, устанавливаю формат JPEG и настраиваю размер буфера, гарантируя, что они установлены перед созданием сеанса захвата. Когда кнопка запускает takePicture(), я создаю запрос захвата, нацеленный на ImageReader.surface, и ожидаю, что ImageReader.OnImageAvailableListener выполнится и позволит мне получить изображение. Однако блок кода прослушивателя не запускается, и ни один из операторов журналирования, которые я поместил в него, не достигается, что указывает на то, что он вообще не запускается. Я могу подтвердить, что сеанс захвата создан успешно, поскольку обратные вызовы конфигурации сеанса завершаются без ошибок, и takePicture(), похоже, срабатывает правильно, но OnImageAvailableListener просто не активируется. Мой коды следующие: [code]package com.example.tutorial.camera
@Composable fun CameraPreview( onImageCaptured: (Bitmap) -> Unit, onImageDCaptured: (ByteArray) -> Unit, onDismiss: () -> Unit ) { var cameraHandler: CameraHandler? = remember { null } var errorMessage by remember { mutableStateOf(null) }
// Use TextureView to display the camera preview AndroidView(factory = { context -> TextureView(context).apply { // Use SurfaceTextureListener to ensure the surface is created surfaceTextureListener = object : TextureView.SurfaceTextureListener { override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) { cameraHandler = CameraHandler( context = context, view = this@apply, onImageCaptured = { bitmap -> Log.d("CameraPreview", "Image captured successfully: ${bitmap.width}x${bitmap.height}") onImageCaptured(bitmap) }, onImageDCaptured = onImageDCaptured, onDismiss = onDismiss, onError = { message -> errorMessage = message }) cameraHandler?.startCamera() }
override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture, width: Int, height: Int) {} override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean { cameraHandler?.stopCamera() onDismiss() return true }
override fun onSurfaceTextureUpdated(surface: SurfaceTexture) {} } } })
// Capture button at the bottom of the screen Box(modifier = Modifier.fillMaxSize()) { Button( onClick = { cameraHandler?.takePicture() onDismiss() // Close camera preview after taking the picture }, modifier = Modifier.align(Alignment.BottomCenter) ) { Text("Take Picture") } } }
private const val REQUEST_CAMERA_PERMISSION = 1001 // Define the constant for permission request
class CameraHandler( private val context: Context, private val view: TextureView, private val onImageCaptured: (Bitmap) -> Unit, private val onImageDCaptured: (ByteArray) -> Unit, private val onDismiss: () -> Unit, // Use onDismiss to close camera preview private val onError: (String) -> Unit ) { private val cameraManager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager private lateinit var cameraDevice: CameraDevice private lateinit var captureSession: CameraCaptureSession private lateinit var imageReader: ImageReader private lateinit var depthImageReader: ImageReader
private val cameraId by mutableStateOf(getDepthSupportedCameraId()) private val supportsDepth: Boolean by lazy { // Check if the device supports DEPTH16 format val cameraId = cameraManager.cameraIdList[0] val characteristics = cameraManager.getCameraCharacteristics(cameraId) val capabilities = characteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES) capabilities?.contains(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_DEPTH_OUTPUT) == true }
private fun getDepthSupportedCameraId(): String? { for (cameraId in cameraManager.cameraIdList) { val characteristics = cameraManager.getCameraCharacteristics(cameraId) // Check the direction of the camera val facing = characteristics.get(CameraCharacteristics.LENS_FACING) // Check if the device supports DEPTH16 format val capabilities = characteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES) if (capabilities?.contains(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_DEPTH_OUTPUT) == true) { // Prefer the back camera if (facing == CameraCharacteristics.LENS_FACING_BACK) { return cameraId } } } // If no back camera is found, return other camera IDs that support DEPTH16 for (cameraId in cameraManager.cameraIdList) { val characteristics = cameraManager.getCameraCharacteristics(cameraId) val capabilities = characteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES) if (capabilities?.contains(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_DEPTH_OUTPUT) == true) { return cameraId } } // If no camera supporting DEPTH16 format is found, return back camera ID for (cameraId in cameraManager.cameraIdList) { val characteristics = cameraManager.getCameraCharacteristics(cameraId) val facing = characteristics.get(CameraCharacteristics.LENS_FACING) if (facing == CameraCharacteristics.LENS_FACING_BACK) { return cameraId } } return null }
fun startCamera() { // Check and request camera permissions if (ContextCompat.checkSelfPermission( context, Manifest.permission.CAMERA ) != PackageManager.PERMISSION_GRANTED ) { ActivityCompat.requestPermissions( context as Activity, arrayOf(Manifest.permission.CAMERA), REQUEST_CAMERA_PERMISSION ) return }
if (cameraId == null) { showError("No usable camera") return }
try { val handler = Handler(Looper.getMainLooper())
// Open the camera cameraManager.openCamera(cameraId!!, object : CameraDevice.StateCallback() { override fun onOpened(device: CameraDevice) { cameraDevice = device createCameraPreviewSession() }
override fun onDisconnected(device: CameraDevice) { device.close() onDismiss() // Close camera preview when disconnected }
private fun createCameraPreviewSession() { try { // Get the best size for the camera and configure the preview val characteristics = cameraId?.let { cameraManager.getCameraCharacteristics(it) } val map = characteristics?.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) val previewSize = map?.getOutputSizes(SurfaceHolder::class.java)?.maxByOrNull { it.width * it.height } ?: Size(640, 480) val width = previewSize.width val height = previewSize.height
Log.d("CameraHandler", "Initializing ImageReader with width=$width, height=$height") imageReader = ImageReader.newInstance(width, height, ImageFormat.JPEG, 5) val texture = view.surfaceTexture ?: throw IllegalStateException("SurfaceTexture is null") texture.setDefaultBufferSize(previewSize.width, previewSize.height) val previewSurface = android.view.Surface(texture)
val surfaces = mutableListOf(previewSurface, imageReader.surface) if (supportsDepth) { Log.d("CameraHandler", "Initializing DepthImageReader with width=$width, height=$height") depthImageReader = ImageReader.newInstance(previewSize.width, previewSize.height, ImageFormat.DEPTH16, 5) surfaces.add(depthImageReader.surface) }
override fun onConfigureFailed(session: CameraCaptureSession) { showError("Failed to configure camera session") } }, null ) } catch (e: Exception) { showError("Failed to create preview session: ${e.message}") } }
fun takePicture() { try { Log.d("CameraHandler", "At least tried successfully") imageReader.setOnImageAvailableListener({ reader -> val image = reader.acquireLatestImage() Log.d("CameraHandler", "") if (image != null) { // Check if the image was captured successfully Log.d("CameraHandler", "Image captured") val buffer = image.planes[0].buffer val bytes = ByteArray(buffer.remaining()) buffer.get(bytes) image.close()
val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size) Log.d("CameraPreview", "Image captured successfully: ${bitmap.width}x${bitmap.height}") onImageCaptured(bitmap) } else { Log.d("CameraHandler", "Failed to capture Image") showError("Failed to acquire image from ImageReader") }
// Check if depth image needs to be captured if (supportsDepth) { depthImageReader.setOnImageAvailableListener({ depthReader -> val depthImage = depthReader.acquireLatestImage() val depthBuffer = depthImage.planes[0].buffer val depthBytes = ByteArray(depthBuffer.capacity()) depthBuffer.get(depthBytes) depthImage.close()
// Call the callback for depth image onImageDCaptured(depthBytes) }, null)
// Trigger JPEG image capture request val captureRequest = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE) captureRequest.addTarget(imageReader.surface) captureSession.capture(captureRequest.build(), null, null) Log.d("CameraHandler", "Capture request for JPEG image sent") } catch (e: Exception) { showError("Failed to take picture: ${e.message}") } }
// Show error dialog fun showError(message: String) { onError(message) }
fun stopCamera() { try { // Stop capture session captureSession.close() // Close camera device cameraDevice.close() // Release ImageReader imageReader.close() // If there is a depthImageReader, release it if (supportsDepth) depthImageReader.close() // (!supportsDepth) showError("This device doesn't support DEPTH16") // onError("Success closing camera.") } catch (e: Exception) { onError("Error closing camera: ${e.message}") } } } [/code] Я ожидал, что прослушиватель в ImageReader.setOnImageAvailableListener() будет выполнять свой блок кода после каждого запроса на захват, что позволит мне получить изображение и обработать его. Для устранения неполадок я убедился, что TextureView, ImageReader и CaptureSession правильно инициализированы, а также удостоверился, что разрешения камеры предоставлены перед ее запуском. Я также поэкспериментировал с разными размерами буфера для ImageReader и подтвердил, что CaptureRequest включает imageReader.surface. Ведение журнала показывает, что takePicture() правильно инициирует запрос захвата и нацелен на ImageReader.surface, однако методacquireLatestImage() никогда не может получить изображение, поскольку прослушиватель не запускается. Никаких исключений или ошибок не возникает, и я остаюсь без каких-либо данных изображения. Что может быть причиной того, что ImageReader.setOnImageAvailableListener() пропускает выполнение своего блока кода и какие шаги я могу предпринять для дальнейшего устранения этой проблемы? Есть ли что-то особенное в настройке CaptureRequest или ImageReader камеры 2, которое я, возможно, упустил из виду? Кстати, я пробовал запускать это приложение на разных типах телефонов, но ни один из них не ответил поддержкой ГЛУБИНА 16 изображений. В этот список телефонов входит Samsung Galaxy S24 Ultra, который, я уверен, должен поддерживать измерение глубины. Я не знаю, действительно ли эти телефоны не поддерживают изображения такого типа, или что-то не так с тем, как я их запрашиваю. Спасибо за помощь!< /п>