summaryrefslogtreecommitdiff
path: root/utils/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'utils/src/main')
-rw-r--r--utils/src/main/AndroidManifest.xml17
-rw-r--r--utils/src/main/java/com/example/android/camera/utils/AutoFitSurfaceView.kt79
-rw-r--r--utils/src/main/java/com/example/android/camera/utils/CameraSizes.kt79
-rw-r--r--utils/src/main/java/com/example/android/camera/utils/ExifUtils.kt73
-rw-r--r--utils/src/main/java/com/example/android/camera/utils/GenericListAdapter.kt55
-rw-r--r--utils/src/main/java/com/example/android/camera/utils/OrientationLiveData.kt95
-rw-r--r--utils/src/main/java/com/example/android/camera/utils/Yuv.kt191
-rw-r--r--utils/src/main/java/com/example/android/camera/utils/YuvToRgbConverter.kt99
-rw-r--r--utils/src/main/res/drawable/ic_shutter.xml21
-rw-r--r--utils/src/main/res/drawable/ic_shutter_focused.xml28
-rw-r--r--utils/src/main/res/drawable/ic_shutter_normal.xml28
-rw-r--r--utils/src/main/res/drawable/ic_shutter_pressed.xml28
12 files changed, 793 insertions, 0 deletions
diff --git a/utils/src/main/AndroidManifest.xml b/utils/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..2e13c37
--- /dev/null
+++ b/utils/src/main/AndroidManifest.xml
@@ -0,0 +1,17 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<!--
3 ~ Copyright 2020 The Android Open Source Project
4 ~
5 ~ Licensed under the Apache License, Version 2.0 (the "License");
6 ~ you may not use this file except in compliance with the License.
7 ~ You may obtain a copy of the License at
8 ~
9 ~ https://www.apache.org/licenses/LICENSE-2.0
10 ~
11 ~ Unless required by applicable law or agreed to in writing, software
12 ~ distributed under the License is distributed on an "AS IS" BASIS,
13 ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 ~ See the License for the specific language governing permissions and
15 ~ limitations under the License.
16 -->
17<manifest package="com.example.android.camera.utils" />
diff --git a/utils/src/main/java/com/example/android/camera/utils/AutoFitSurfaceView.kt b/utils/src/main/java/com/example/android/camera/utils/AutoFitSurfaceView.kt
new file mode 100644
index 0000000..3d900d1
--- /dev/null
+++ b/utils/src/main/java/com/example/android/camera/utils/AutoFitSurfaceView.kt
@@ -0,0 +1,79 @@
1/*
2 * Copyright 2020 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * https://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.example.android.camera.utils
18
19import android.content.Context
20import android.util.AttributeSet
21import android.util.Log
22import android.view.SurfaceView
23import kotlin.math.roundToInt
24
25/**
26 * A [SurfaceView] that can be adjusted to a specified aspect ratio and
27 * performs center-crop transformation of input frames.
28 */
29class AutoFitSurfaceView @JvmOverloads constructor(
30 context: Context,
31 attrs: AttributeSet? = null,
32 defStyle: Int = 0
33) : SurfaceView(context, attrs, defStyle) {
34
35 private var aspectRatio = 0f
36
37 /**
38 * Sets the aspect ratio for this view. The size of the view will be
39 * measured based on the ratio calculated from the parameters.
40 *
41 * @param width Camera resolution horizontal size
42 * @param height Camera resolution vertical size
43 */
44 fun setAspectRatio(width: Int, height: Int) {
45 require(width > 0 && height > 0) { "Size cannot be negative" }
46 aspectRatio = width.toFloat() / height.toFloat()
47 holder.setFixedSize(width, height)
48 requestLayout()
49 }
50
51 override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
52 super.onMeasure(widthMeasureSpec, heightMeasureSpec)
53 val width = MeasureSpec.getSize(widthMeasureSpec)
54 val height = MeasureSpec.getSize(heightMeasureSpec)
55 if (aspectRatio == 0f) {
56 setMeasuredDimension(width, height)
57 } else {
58
59 // Performs center-crop transformation of the camera frames
60 val newWidth: Int
61 val newHeight: Int
62 val actualRatio = if (width > height) aspectRatio else 1f / aspectRatio
63 if (width < height * actualRatio) {
64 newHeight = height
65 newWidth = (height * actualRatio).roundToInt()
66 } else {
67 newWidth = width
68 newHeight = (width / actualRatio).roundToInt()
69 }
70
71 Log.d(TAG, "Measured dimensions set: $newWidth x $newHeight")
72 setMeasuredDimension(newWidth, newHeight)
73 }
74 }
75
76 companion object {
77 private val TAG = AutoFitSurfaceView::class.java.simpleName
78 }
79}
diff --git a/utils/src/main/java/com/example/android/camera/utils/CameraSizes.kt b/utils/src/main/java/com/example/android/camera/utils/CameraSizes.kt
new file mode 100644
index 0000000..6db01d3
--- /dev/null
+++ b/utils/src/main/java/com/example/android/camera/utils/CameraSizes.kt
@@ -0,0 +1,79 @@
1/*
2 * Copyright 2020 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * https://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.example.android.camera.utils
18
19import android.graphics.Point
20import android.hardware.camera2.CameraCharacteristics
21import android.hardware.camera2.params.StreamConfigurationMap
22import android.util.Size
23import android.view.Display
24import kotlin.math.max
25import kotlin.math.min
26
27/** Helper class used to pre-compute shortest and longest sides of a [Size] */
28class SmartSize(width: Int, height: Int) {
29 var size = Size(width, height)
30 var long = max(size.width, size.height)
31 var short = min(size.width, size.height)
32 override fun toString() = "SmartSize(${long}x${short})"
33}
34
35/** Standard High Definition size for pictures and video */
36val SIZE_1080P: SmartSize = SmartSize(1920, 1080)
37
38/** Returns a [SmartSize] object for the given [Display] */
39fun getDisplaySmartSize(display: Display): SmartSize {
40 val outPoint = Point()
41 display.getRealSize(outPoint)
42 return SmartSize(outPoint.x, outPoint.y)
43}
44
45/**
46 * Returns the largest available PREVIEW size. For more information, see:
47 * https://d.android.com/reference/android/hardware/camera2/CameraDevice and
48 * https://developer.android.com/reference/android/hardware/camera2/params/StreamConfigurationMap
49 */
50fun <T>getPreviewOutputSize(
51 display: Display,
52 characteristics: CameraCharacteristics,
53 targetClass: Class<T>,
54 format: Int? = null
55): Size {
56
57 // Find which is smaller: screen or 1080p
58 val screenSize = getDisplaySmartSize(display)
59 val hdScreen = screenSize.long >= SIZE_1080P.long || screenSize.short >= SIZE_1080P.short
60 val maxSize = if (hdScreen) SIZE_1080P else screenSize
61
62 // If image format is provided, use it to determine supported sizes; else use target class
63 val config = characteristics.get(
64 CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)!!
65 if (format == null)
66 assert(StreamConfigurationMap.isOutputSupportedFor(targetClass))
67 else
68 assert(config.isOutputSupportedFor(format))
69 val allSizes = if (format == null)
70 config.getOutputSizes(targetClass) else config.getOutputSizes(format)
71
72 // Get available sizes and sort them by area from largest to smallest
73 val validSizes = allSizes
74 .sortedWith(compareBy { it.height * it.width })
75 .map { SmartSize(it.width, it.height) }.reversed()
76
77 // Then, get the largest output size that is smaller or equal than our max size
78 return validSizes.first { it.long <= maxSize.long && it.short <= maxSize.short }.size
79} \ No newline at end of file
diff --git a/utils/src/main/java/com/example/android/camera/utils/ExifUtils.kt b/utils/src/main/java/com/example/android/camera/utils/ExifUtils.kt
new file mode 100644
index 0000000..561c14b
--- /dev/null
+++ b/utils/src/main/java/com/example/android/camera/utils/ExifUtils.kt
@@ -0,0 +1,73 @@
1/*
2 * Copyright 2020 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * https://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.example.android.camera.utils
18
19import android.graphics.Bitmap
20import android.graphics.Matrix
21import android.util.Log
22import androidx.exifinterface.media.ExifInterface
23
24private const val TAG: String = "ExifUtils"
25
26/** Transforms rotation and mirroring information into one of the [ExifInterface] constants */
27fun computeExifOrientation(rotationDegrees: Int, mirrored: Boolean) = when {
28 rotationDegrees == 0 && !mirrored -> ExifInterface.ORIENTATION_NORMAL
29 rotationDegrees == 0 && mirrored -> ExifInterface.ORIENTATION_FLIP_HORIZONTAL
30 rotationDegrees == 180 && !mirrored -> ExifInterface.ORIENTATION_ROTATE_180
31 rotationDegrees == 180 && mirrored -> ExifInterface.ORIENTATION_FLIP_VERTICAL
32 rotationDegrees == 270 && mirrored -> ExifInterface.ORIENTATION_TRANSVERSE
33 rotationDegrees == 90 && !mirrored -> ExifInterface.ORIENTATION_ROTATE_90
34 rotationDegrees == 90 && mirrored -> ExifInterface.ORIENTATION_TRANSPOSE
35 rotationDegrees == 270 && mirrored -> ExifInterface.ORIENTATION_ROTATE_270
36 rotationDegrees == 270 && !mirrored -> ExifInterface.ORIENTATION_TRANSVERSE
37 else -> ExifInterface.ORIENTATION_UNDEFINED
38}
39
40/**
41 * Helper function used to convert an EXIF orientation enum into a transformation matrix
42 * that can be applied to a bitmap.
43 *
44 * @return matrix - Transformation required to properly display [Bitmap]
45 */
46fun decodeExifOrientation(exifOrientation: Int): Matrix {
47 val matrix = Matrix()
48
49 // Apply transformation corresponding to declared EXIF orientation
50 when (exifOrientation) {
51 ExifInterface.ORIENTATION_NORMAL -> Unit
52 ExifInterface.ORIENTATION_UNDEFINED -> Unit
53 ExifInterface.ORIENTATION_ROTATE_90 -> matrix.postRotate(90F)
54 ExifInterface.ORIENTATION_ROTATE_180 -> matrix.postRotate(180F)
55 ExifInterface.ORIENTATION_ROTATE_270 -> matrix.postRotate(270F)
56 ExifInterface.ORIENTATION_FLIP_HORIZONTAL -> matrix.postScale(-1F, 1F)
57 ExifInterface.ORIENTATION_FLIP_VERTICAL -> matrix.postScale(1F, -1F)
58 ExifInterface.ORIENTATION_TRANSPOSE -> {
59 matrix.postScale(-1F, 1F)
60 matrix.postRotate(270F)
61 }
62 ExifInterface.ORIENTATION_TRANSVERSE -> {
63 matrix.postScale(-1F, 1F)
64 matrix.postRotate(90F)
65 }
66
67 // Error out if the EXIF orientation is invalid
68 else -> Log.e(TAG, "Invalid orientation: $exifOrientation")
69 }
70
71 // Return the resulting matrix
72 return matrix
73}
diff --git a/utils/src/main/java/com/example/android/camera/utils/GenericListAdapter.kt b/utils/src/main/java/com/example/android/camera/utils/GenericListAdapter.kt
new file mode 100644
index 0000000..a55af27
--- /dev/null
+++ b/utils/src/main/java/com/example/android/camera/utils/GenericListAdapter.kt
@@ -0,0 +1,55 @@
1/*
2 * Copyright 2020 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * https://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.example.android.camera.utils
18
19import android.view.LayoutInflater
20import android.view.View
21import android.view.ViewGroup
22import androidx.recyclerview.widget.RecyclerView
23
24/** Type helper used for the callback triggered once our view has been bound */
25typealias BindCallback<T> = (view: View, data: T, position: Int) -> Unit
26
27/** List adapter for generic types, intended used for small-medium lists of data */
28class GenericListAdapter<T>(
29 private val dataset: List<T>,
30 private val itemLayoutId: Int? = null,
31 private val itemViewFactory: (() -> View)? = null,
32 private val onBind: BindCallback<T>
33) : RecyclerView.Adapter<GenericListAdapter.GenericListViewHolder>() {
34
35 class GenericListViewHolder(val view: View) : RecyclerView.ViewHolder(view)
36
37 override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = GenericListViewHolder(when {
38 itemViewFactory != null -> itemViewFactory.invoke()
39 itemLayoutId != null -> {
40 LayoutInflater.from(parent.context)
41 .inflate(itemLayoutId, parent, false)
42 }
43 else -> {
44 throw IllegalStateException(
45 "Either the layout ID or the view factory need to be non-null")
46 }
47 })
48
49 override fun onBindViewHolder(holder: GenericListViewHolder, position: Int) {
50 if (position < 0 || position > dataset.size) return
51 onBind(holder.view, dataset[position], position)
52 }
53
54 override fun getItemCount() = dataset.size
55} \ No newline at end of file
diff --git a/utils/src/main/java/com/example/android/camera/utils/OrientationLiveData.kt b/utils/src/main/java/com/example/android/camera/utils/OrientationLiveData.kt
new file mode 100644
index 0000000..f9d9a47
--- /dev/null
+++ b/utils/src/main/java/com/example/android/camera/utils/OrientationLiveData.kt
@@ -0,0 +1,95 @@
1/*
2 * Copyright 2020 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * https://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.example.android.camera.utils
18
19import android.content.Context
20import android.hardware.camera2.CameraCharacteristics
21import android.view.OrientationEventListener
22import android.view.Surface
23import androidx.lifecycle.LiveData
24
25
26/**
27 * Calculates closest 90-degree orientation to compensate for the device
28 * rotation relative to sensor orientation, i.e., allows user to see camera
29 * frames with the expected orientation.
30 */
31class OrientationLiveData(
32 context: Context,
33 characteristics: CameraCharacteristics
34): LiveData<Int>() {
35
36 private val listener = object : OrientationEventListener(context.applicationContext) {
37 override fun onOrientationChanged(orientation: Int) {
38 val rotation = when {
39 orientation <= 45 -> Surface.ROTATION_0
40 orientation <= 135 -> Surface.ROTATION_90
41 orientation <= 225 -> Surface.ROTATION_180
42 orientation <= 315 -> Surface.ROTATION_270
43 else -> Surface.ROTATION_0
44 }
45 val relative = computeRelativeRotation(characteristics, rotation)
46 if (relative != value) postValue(relative)
47 }
48 }
49
50 override fun onActive() {
51 super.onActive()
52 listener.enable()
53 }
54
55 override fun onInactive() {
56 super.onInactive()
57 listener.disable()
58 }
59
60 companion object {
61
62 /**
63 * Computes rotation required to transform from the camera sensor orientation to the
64 * device's current orientation in degrees.
65 *
66 * @param characteristics the [CameraCharacteristics] to query for the sensor orientation.
67 * @param surfaceRotation the current device orientation as a Surface constant
68 * @return the relative rotation from the camera sensor to the current device orientation.
69 */
70 @JvmStatic
71 private fun computeRelativeRotation(
72 characteristics: CameraCharacteristics,
73 surfaceRotation: Int
74 ): Int {
75 val sensorOrientationDegrees =
76 characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION)!!
77
78 val deviceOrientationDegrees = when (surfaceRotation) {
79 Surface.ROTATION_0 -> 0
80 Surface.ROTATION_90 -> 90
81 Surface.ROTATION_180 -> 180
82 Surface.ROTATION_270 -> 270
83 else -> 0
84 }
85
86 // Reverse device orientation for front-facing cameras
87 val sign = if (characteristics.get(CameraCharacteristics.LENS_FACING) ==
88 CameraCharacteristics.LENS_FACING_FRONT) 1 else -1
89
90 // Calculate desired JPEG orientation relative to camera orientation to make
91 // the image upright relative to the device orientation
92 return (sensorOrientationDegrees - (deviceOrientationDegrees * sign) + 360) % 360
93 }
94 }
95}
diff --git a/utils/src/main/java/com/example/android/camera/utils/Yuv.kt b/utils/src/main/java/com/example/android/camera/utils/Yuv.kt
new file mode 100644
index 0000000..c476ad0
--- /dev/null
+++ b/utils/src/main/java/com/example/android/camera/utils/Yuv.kt
@@ -0,0 +1,191 @@
1package com.example.android.camera.utils
2
3import android.graphics.ImageFormat
4import android.media.Image
5import androidx.annotation.IntDef
6import java.nio.ByteBuffer
7
8/*
9This file is converted from part of https://github.com/gordinmitya/yuv2buf.
10Follow the link to find demo app, performance benchmarks and unit tests.
11
12Intro to YUV image formats:
13YUV_420_888 - is a generic format that can be represented as I420, YV12, NV21, and NV12.
14420 means that for each 4 luminosity pixels we have 2 chroma pixels: U and V.
15
16* I420 format represents an image as Y plane followed by U then followed by V plane
17 without chroma channels interleaving.
18 For example:
19 Y Y Y Y
20 Y Y Y Y
21 U U V V
22
23* NV21 format represents an image as Y plane followed by V and U interleaved. First V then U.
24 For example:
25 Y Y Y Y
26 Y Y Y Y
27 V U V U
28
29* YV12 and NV12 are the same as previous formats but with swapped order of V and U. (U then V)
30
31Visualization of these 4 formats:
32https://user-images.githubusercontent.com/9286092/89119601-4f6f8100-d4b8-11ea-9a51-2765f7e513c2.jpg
33
34It's guaranteed that image.getPlanes() always returns planes in order Y U V for YUV_420_888.
35https://developer.android.com/reference/android/graphics/ImageFormat#YUV_420_888
36
37Because I420 and NV21 are more widely supported (RenderScript, OpenCV, MNN)
38the conversion is done into these formats.
39
40More about each format: https://www.fourcc.org/yuv.php
41*/
42
43@kotlin.annotation.Retention(AnnotationRetention.SOURCE)
44@IntDef(ImageFormat.NV21, ImageFormat.YUV_420_888)
45annotation class YuvType
46
47class YuvByteBuffer(image: Image, dstBuffer: ByteBuffer? = null) {
48 @YuvType
49 val type: Int
50 val buffer: ByteBuffer
51
52 init {
53 val wrappedImage = ImageWrapper(image)
54
55 type = if (wrappedImage.u.pixelStride == 1) {
56 ImageFormat.YUV_420_888
57 } else {
58 ImageFormat.NV21
59 }
60 val size = image.width * image.height * 3 / 2
61 buffer = if (
62 dstBuffer == null || dstBuffer.capacity() < size ||
63 dstBuffer.isReadOnly || !dstBuffer.isDirect
64 ) {
65 ByteBuffer.allocateDirect(size) }
66 else {
67 dstBuffer
68 }
69 buffer.rewind()
70
71 removePadding(wrappedImage)
72 }
73
74 // Input buffers are always direct as described in
75 // https://developer.android.com/reference/android/media/Image.Plane#getBuffer()
76 private fun removePadding(image: ImageWrapper) {
77 val sizeLuma = image.y.width * image.y.height
78 val sizeChroma = image.u.width * image.u.height
79 if (image.y.rowStride > image.y.width) {
80 removePaddingCompact(image.y, buffer, 0)
81 } else {
82 buffer.position(0)
83 buffer.put(image.y.buffer)
84 }
85 if (type == ImageFormat.YUV_420_888) {
86 if (image.u.rowStride > image.u.width) {
87 removePaddingCompact(image.u, buffer, sizeLuma)
88 removePaddingCompact(image.v, buffer, sizeLuma + sizeChroma)
89 } else {
90 buffer.position(sizeLuma)
91 buffer.put(image.u.buffer)
92 buffer.position(sizeLuma + sizeChroma)
93 buffer.put(image.v.buffer)
94 }
95 } else {
96 if (image.u.rowStride > image.u.width * 2) {
97 removePaddingNotCompact(image, buffer, sizeLuma)
98 } else {
99 buffer.position(sizeLuma)
100 var uv = image.v.buffer
101 val properUVSize = image.v.height * image.v.rowStride - 1
102 if (uv.capacity() > properUVSize) {
103 uv = clipBuffer(image.v.buffer, 0, properUVSize)
104 }
105 buffer.put(uv)
106 val lastOne = image.u.buffer[image.u.buffer.capacity() - 1]
107 buffer.put(buffer.capacity() - 1, lastOne)
108 }
109 }
110 buffer.rewind()
111 }
112
113 private fun removePaddingCompact(
114 plane: PlaneWrapper,
115 dst: ByteBuffer,
116 offset: Int
117 ) {
118 require(plane.pixelStride == 1) {
119 "use removePaddingCompact with pixelStride == 1"
120 }
121
122 val src = plane.buffer
123 val rowStride = plane.rowStride
124 var row: ByteBuffer
125 dst.position(offset)
126 for (i in 0 until plane.height) {
127 row = clipBuffer(src, i * rowStride, plane.width)
128 dst.put(row)
129 }
130 }
131
132 private fun removePaddingNotCompact(
133 image: ImageWrapper,
134 dst: ByteBuffer,
135 offset: Int
136 ) {
137 require(image.u.pixelStride == 2) {
138 "use removePaddingNotCompact pixelStride == 2"
139 }
140 val width = image.u.width
141 val height = image.u.height
142 val rowStride = image.u.rowStride
143 var row: ByteBuffer
144 dst.position(offset)
145 for (i in 0 until height - 1) {
146 row = clipBuffer(image.v.buffer, i * rowStride, width * 2)
147 dst.put(row)
148 }
149 row = clipBuffer(image.u.buffer, (height - 1) * rowStride - 1, width * 2)
150 dst.put(row)
151 }
152
153 private fun clipBuffer(buffer: ByteBuffer, start: Int, size: Int): ByteBuffer {
154 val duplicate = buffer.duplicate()
155 duplicate.position(start)
156 duplicate.limit(start + size)
157 return duplicate.slice()
158 }
159
160 private class ImageWrapper(image:Image) {
161 val width= image.width
162 val height = image.height
163 val y = PlaneWrapper(width, height, image.planes[0])
164 val u = PlaneWrapper(width / 2, height / 2, image.planes[1])
165 val v = PlaneWrapper(width / 2, height / 2, image.planes[2])
166
167 // Check this is a supported image format
168 // https://developer.android.com/reference/android/graphics/ImageFormat#YUV_420_888
169 init {
170 require(y.pixelStride == 1) {
171 "Pixel stride for Y plane must be 1 but got ${y.pixelStride} instead."
172 }
173 require(u.pixelStride == v.pixelStride && u.rowStride == v.rowStride) {
174 "U and V planes must have the same pixel and row strides " +
175 "but got pixel=${u.pixelStride} row=${u.rowStride} for U " +
176 "and pixel=${v.pixelStride} and row=${v.rowStride} for V"
177 }
178 require(u.pixelStride == 1 || u.pixelStride == 2) {
179 "Supported" + " pixel strides for U and V planes are 1 and 2"
180 }
181 }
182 }
183
184 private class PlaneWrapper(width: Int, height: Int, plane: Image.Plane) {
185 val width = width
186 val height = height
187 val buffer: ByteBuffer = plane.buffer
188 val rowStride = plane.rowStride
189 val pixelStride = plane.pixelStride
190 }
191} \ No newline at end of file
diff --git a/utils/src/main/java/com/example/android/camera/utils/YuvToRgbConverter.kt b/utils/src/main/java/com/example/android/camera/utils/YuvToRgbConverter.kt
new file mode 100644
index 0000000..8dcd559
--- /dev/null
+++ b/utils/src/main/java/com/example/android/camera/utils/YuvToRgbConverter.kt
@@ -0,0 +1,99 @@
1/*
2 * Copyright 2020 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * https://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.example.android.camera.utils
18
19import android.content.Context
20import android.graphics.Bitmap
21import android.graphics.ImageFormat
22import android.media.Image
23import android.renderscript.Allocation
24import android.renderscript.Element
25import android.renderscript.RenderScript
26import android.renderscript.ScriptIntrinsicYuvToRGB
27import android.renderscript.Type
28import java.nio.ByteBuffer
29
30/**
31 * Helper class used to convert a [Image] object from
32 * [ImageFormat.YUV_420_888] format to an RGB [Bitmap] object, it has equivalent
33 * functionality to https://github
34 * .com/androidx/androidx/blob/androidx-main/camera/camera-core/src/main/java/androidx/camera/core/ImageYuvToRgbConverter.java
35 *
36 * NOTE: This has been tested in a limited number of devices and is not
37 * considered production-ready code. It was created for illustration purposes,
38 * since this is not an efficient camera pipeline due to the multiple copies
39 * required to convert each frame. For example, this
40 * implementation
41 * (https://stackoverflow.com/questions/52726002/camera2-captured-picture-conversion-from-yuv-420-888-to-nv21/52740776#52740776)
42 * might have better performance.
43 */
44class YuvToRgbConverter(context: Context) {
45 private val rs = RenderScript.create(context)
46 private val scriptYuvToRgb =
47 ScriptIntrinsicYuvToRGB.create(rs, Element.U8_4(rs))
48
49 // Do not add getters/setters functions to these private variables
50 // because yuvToRgb() assume they won't be modified elsewhere
51 private var yuvBits: ByteBuffer? = null
52 private var bytes: ByteArray = ByteArray(0)
53 private var inputAllocation: Allocation? = null
54 private var outputAllocation: Allocation? = null
55
56 @Synchronized
57 fun yuvToRgb(image: Image, output: Bitmap) {
58 val yuvBuffer = YuvByteBuffer(image, yuvBits)
59 yuvBits = yuvBuffer.buffer
60
61 if (needCreateAllocations(image, yuvBuffer)) {
62 val yuvType = Type.Builder(rs, Element.U8(rs))
63 .setX(image.width)
64 .setY(image.height)
65 .setYuvFormat(yuvBuffer.type)
66 inputAllocation = Allocation.createTyped(
67 rs,
68 yuvType.create(),
69 Allocation.USAGE_SCRIPT
70 )
71 bytes = ByteArray(yuvBuffer.buffer.capacity())
72 val rgbaType = Type.Builder(rs, Element.RGBA_8888(rs))
73 .setX(image.width)
74 .setY(image.height)
75 outputAllocation = Allocation.createTyped(
76 rs,
77 rgbaType.create(),
78 Allocation.USAGE_SCRIPT
79 )
80 }
81
82 yuvBuffer.buffer.get(bytes)
83 inputAllocation!!.copyFrom(bytes)
84
85 // Convert NV21 or YUV_420_888 format to RGB
86 inputAllocation!!.copyFrom(bytes)
87 scriptYuvToRgb.setInput(inputAllocation)
88 scriptYuvToRgb.forEach(outputAllocation)
89 outputAllocation!!.copyTo(output)
90 }
91
92 private fun needCreateAllocations(image: Image, yuvBuffer: YuvByteBuffer): Boolean {
93 return (inputAllocation == null || // the very 1st call
94 inputAllocation!!.type.x != image.width || // image size changed
95 inputAllocation!!.type.y != image.height ||
96 inputAllocation!!.type.yuv != yuvBuffer.type || // image format changed
97 bytes.size == yuvBuffer.buffer.capacity())
98 }
99}
diff --git a/utils/src/main/res/drawable/ic_shutter.xml b/utils/src/main/res/drawable/ic_shutter.xml
new file mode 100644
index 0000000..9bb91ab
--- /dev/null
+++ b/utils/src/main/res/drawable/ic_shutter.xml
@@ -0,0 +1,21 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!--
3 ~ Copyright 2020 The Android Open Source Project
4 ~
5 ~ Licensed under the Apache License, Version 2.0 (the "License");
6 ~ you may not use this file except in compliance with the License.
7 ~ You may obtain a copy of the License at
8 ~
9 ~ https://www.apache.org/licenses/LICENSE-2.0
10 ~
11 ~ Unless required by applicable law or agreed to in writing, software
12 ~ distributed under the License is distributed on an "AS IS" BASIS,
13 ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 ~ See the License for the specific language governing permissions and
15 ~ limitations under the License.
16 -->
17<selector xmlns:android="http://schemas.android.com/apk/res/android">
18 <item android:state_pressed="true" android:drawable="@drawable/ic_shutter_pressed" />
19 <item android:state_focused="true" android:drawable="@drawable/ic_shutter_focused" />
20 <item android:drawable="@drawable/ic_shutter_normal" />
21</selector> \ No newline at end of file
diff --git a/utils/src/main/res/drawable/ic_shutter_focused.xml b/utils/src/main/res/drawable/ic_shutter_focused.xml
new file mode 100644
index 0000000..9bf521d
--- /dev/null
+++ b/utils/src/main/res/drawable/ic_shutter_focused.xml
@@ -0,0 +1,28 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!--
3 ~ Copyright 2020 The Android Open Source Project
4 ~
5 ~ Licensed under the Apache License, Version 2.0 (the "License");
6 ~ you may not use this file except in compliance with the License.
7 ~ You may obtain a copy of the License at
8 ~
9 ~ https://www.apache.org/licenses/LICENSE-2.0
10 ~
11 ~ Unless required by applicable law or agreed to in writing, software
12 ~ distributed under the License is distributed on an "AS IS" BASIS,
13 ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 ~ See the License for the specific language governing permissions and
15 ~ limitations under the License.
16 -->
17<vector xmlns:android="http://schemas.android.com/apk/res/android"
18 android:width="24dp"
19 android:height="24dp"
20 android:viewportWidth="74"
21 android:viewportHeight="74">
22 <path android:fillColor="#FFFFFF" android:fillType="evenOdd"
23 android:pathData="M73.1,37C73.1,17.0637 56.9373,0.9 37,0.9C17.0627,0.9 0.9,17.0637 0.9,37C0.9,56.9373 17.0627,73.1 37,73.1C56.9373,73.1 73.1,56.9373 73.1,37"
24 android:strokeColor="#00000000" android:strokeWidth="1"/>
25 <path android:fillColor="#58A0C4" android:fillType="evenOdd"
26 android:pathData="M67.4,37C67.4,53.7895 53.7895,67.4 37,67.4C20.2105,67.4 6.6,53.7895 6.6,37C6.6,20.2105 20.2105,6.6 37,6.6C53.7895,6.6 67.4,20.2105 67.4,37"
27 android:strokeColor="#00000000" android:strokeWidth="1"/>
28</vector>
diff --git a/utils/src/main/res/drawable/ic_shutter_normal.xml b/utils/src/main/res/drawable/ic_shutter_normal.xml
new file mode 100644
index 0000000..cb50026
--- /dev/null
+++ b/utils/src/main/res/drawable/ic_shutter_normal.xml
@@ -0,0 +1,28 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!--
3 ~ Copyright 2020 The Android Open Source Project
4 ~
5 ~ Licensed under the Apache License, Version 2.0 (the "License");
6 ~ you may not use this file except in compliance with the License.
7 ~ You may obtain a copy of the License at
8 ~
9 ~ https://www.apache.org/licenses/LICENSE-2.0
10 ~
11 ~ Unless required by applicable law or agreed to in writing, software
12 ~ distributed under the License is distributed on an "AS IS" BASIS,
13 ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 ~ See the License for the specific language governing permissions and
15 ~ limitations under the License.
16 -->
17<vector xmlns:android="http://schemas.android.com/apk/res/android"
18 android:width="24dp"
19 android:height="24dp"
20 android:viewportWidth="74"
21 android:viewportHeight="74">
22 <path android:fillColor="#FFFFFF" android:fillType="evenOdd"
23 android:pathData="M73.1,37C73.1,17.0637 56.9373,0.9 37,0.9C17.0627,0.9 0.9,17.0637 0.9,37C0.9,56.9373 17.0627,73.1 37,73.1C56.9373,73.1 73.1,56.9373 73.1,37"
24 android:strokeColor="#00000000" android:strokeWidth="1"/>
25 <path android:fillColor="#CFD7DB" android:fillType="evenOdd"
26 android:pathData="M67.4,37C67.4,53.7895 53.7895,67.4 37,67.4C20.2105,67.4 6.6,53.7895 6.6,37C6.6,20.2105 20.2105,6.6 37,6.6C53.7895,6.6 67.4,20.2105 67.4,37"
27 android:strokeColor="#00000000" android:strokeWidth="1"/>
28</vector>
diff --git a/utils/src/main/res/drawable/ic_shutter_pressed.xml b/utils/src/main/res/drawable/ic_shutter_pressed.xml
new file mode 100644
index 0000000..9bf521d
--- /dev/null
+++ b/utils/src/main/res/drawable/ic_shutter_pressed.xml
@@ -0,0 +1,28 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!--
3 ~ Copyright 2020 The Android Open Source Project
4 ~
5 ~ Licensed under the Apache License, Version 2.0 (the "License");
6 ~ you may not use this file except in compliance with the License.
7 ~ You may obtain a copy of the License at
8 ~
9 ~ https://www.apache.org/licenses/LICENSE-2.0
10 ~
11 ~ Unless required by applicable law or agreed to in writing, software
12 ~ distributed under the License is distributed on an "AS IS" BASIS,
13 ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 ~ See the License for the specific language governing permissions and
15 ~ limitations under the License.
16 -->
17<vector xmlns:android="http://schemas.android.com/apk/res/android"
18 android:width="24dp"
19 android:height="24dp"
20 android:viewportWidth="74"
21 android:viewportHeight="74">
22 <path android:fillColor="#FFFFFF" android:fillType="evenOdd"
23 android:pathData="M73.1,37C73.1,17.0637 56.9373,0.9 37,0.9C17.0627,0.9 0.9,17.0637 0.9,37C0.9,56.9373 17.0627,73.1 37,73.1C56.9373,73.1 73.1,56.9373 73.1,37"
24 android:strokeColor="#00000000" android:strokeWidth="1"/>
25 <path android:fillColor="#58A0C4" android:fillType="evenOdd"
26 android:pathData="M67.4,37C67.4,53.7895 53.7895,67.4 37,67.4C20.2105,67.4 6.6,53.7895 6.6,37C6.6,20.2105 20.2105,6.6 37,6.6C53.7895,6.6 67.4,20.2105 67.4,37"
27 android:strokeColor="#00000000" android:strokeWidth="1"/>
28</vector>