/
ImageUtils.kt
122 lines (110 loc) · 3.97 KB
/
ImageUtils.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
/*
* Copyright 2020 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.google.mlkit.codelab.translate.util
import android.graphics.Bitmap
import android.graphics.ImageFormat
import android.graphics.Matrix
import android.graphics.Rect
import android.media.Image
import androidx.annotation.ColorInt
/**
* Utility class for manipulating images.
*/
object ImageUtils {
private val CHANNEL_RANGE = 0 until (1 shl 18)
fun convertYuv420888ImageToBitmap(image: Image): Bitmap {
require(image.format == ImageFormat.YUV_420_888) {
"Unsupported image format $(image.format)"
}
val planes = image.planes
// Because of the variable row stride it's not possible to know in
// advance the actual necessary dimensions of the yuv planes.
val yuvBytes = planes.map { plane ->
val buffer = plane.buffer
val yuvBytes = ByteArray(buffer.capacity())
buffer[yuvBytes]
buffer.rewind() // Be kind…
yuvBytes
}
val yRowStride = planes[0].rowStride
val uvRowStride = planes[1].rowStride
val uvPixelStride = planes[1].pixelStride
val width = image.width
val height = image.height
@ColorInt val argb8888 = IntArray(width * height)
var i = 0
for (y in 0 until height) {
val pY = yRowStride * y
val uvRowStart = uvRowStride * (y shr 1)
for (x in 0 until width) {
val uvOffset = (x shr 1) * uvPixelStride
argb8888[i++] =
yuvToRgb(
yuvBytes[0][pY + x].toIntUnsigned(),
yuvBytes[1][uvRowStart + uvOffset].toIntUnsigned(),
yuvBytes[2][uvRowStart + uvOffset].toIntUnsigned()
)
}
}
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
bitmap.setPixels(argb8888, 0, width, 0, 0, width, height)
return bitmap
}
fun rotateAndCrop(
bitmap: Bitmap,
imageRotationDegrees: Int,
cropRect: Rect
): Bitmap {
val matrix = Matrix()
matrix.preRotate(imageRotationDegrees.toFloat())
return Bitmap.createBitmap(
bitmap,
cropRect.left,
cropRect.top,
cropRect.width(),
cropRect.height(),
matrix,
true
)
}
@ColorInt
private fun yuvToRgb(nY: Int, nU: Int, nV: Int): Int {
var nY = nY
var nU = nU
var nV = nV
nY -= 16
nU -= 128
nV -= 128
nY = nY.coerceAtLeast(0)
// This is the floating point equivalent. We do the conversion in integer
// because some Android devices do not have floating point in hardware.
// nR = (int)(1.164 * nY + 2.018 * nU);
// nG = (int)(1.164 * nY - 0.813 * nV - 0.391 * nU);
// nB = (int)(1.164 * nY + 1.596 * nV);
var nR = 1192 * nY + 1634 * nV
var nG = 1192 * nY - 833 * nV - 400 * nU
var nB = 1192 * nY + 2066 * nU
// Clamp the values before normalizing them to 8 bits.
nR = nR.coerceIn(CHANNEL_RANGE) shr 10 and 0xff
nG = nG.coerceIn(CHANNEL_RANGE) shr 10 and 0xff
nB = nB.coerceIn(CHANNEL_RANGE) shr 10 and 0xff
return -0x1000000 or (nR shl 16) or (nG shl 8) or nB
}
}
private fun Byte.toIntUnsigned(): Int {
return toInt() and 0xFF
}