package com.runt.live.util
|
|
import android.content.ContentResolver
|
import android.content.Context
|
import android.graphics.Bitmap
|
import android.graphics.Canvas
|
import android.graphics.Color
|
import android.graphics.ImageFormat
|
import android.graphics.Matrix
|
import android.graphics.Paint
|
import android.graphics.Rect
|
import android.media.ExifInterface
|
import android.media.Image
|
import android.media.Image.Plane
|
import android.net.Uri
|
import android.provider.MediaStore
|
import android.renderscript.Allocation
|
import android.renderscript.Element
|
import android.renderscript.RenderScript
|
import android.renderscript.ScriptIntrinsicYuvToRGB
|
import android.renderscript.Type
|
import android.util.Log
|
import android.view.SurfaceHolder
|
import androidx.annotation.OptIn
|
import androidx.camera.core.ExperimentalGetImage
|
import androidx.camera.core.ImageProxy
|
import java.io.IOException
|
import java.nio.ByteBuffer
|
import kotlin.math.ceil
|
import kotlin.math.max
|
import kotlin.math.min
|
|
|
/**
|
* @author Runt(qingingrunt2010@qq.com)
|
* @purpose
|
* @date 9/28/24
|
*/
|
class BitmapUtils {
|
|
val TAG : String = "BitmapUtils"
|
|
|
companion object {
|
private var sInstance : BitmapUtils? = null
|
val instance : BitmapUtils?
|
get() {
|
if (sInstance == null) {
|
sInstance = BitmapUtils()
|
}
|
return sInstance
|
}
|
}
|
|
@OptIn(ExperimentalGetImage::class)
|
fun getBytes(image : ImageProxy) : ByteArray {
|
val nv21Buffer : ByteBuffer = yuv420ThreePlanesToNV21(image.image !!.planes , image.width , image.height)
|
nv21Buffer.rewind()
|
val imageInBuffer = ByteArray(nv21Buffer.limit())
|
nv21Buffer[imageInBuffer , 0 , imageInBuffer.size]
|
return imageInBuffer
|
}
|
|
fun yuv420ThreePlanesToNV21(planes : Array<Plane> , width : Int , height : Int) : ByteBuffer {
|
val imageSize = width * height
|
val out = ByteArray(imageSize + 2 * (imageSize / 4))
|
|
//Log.e("yuv420ThreePlanesToNV21", "imageSize: " +imageSize +" out:"+out.length);
|
if (areUVPlanesNV21(planes , width , height)) { // Copy the Y values.
|
planes[0].buffer[out , 0 , imageSize]
|
|
val uBuffer = planes[1].buffer
|
val vBuffer = planes[2].buffer // Get the first V value from the V buffer, since the U buffer does not contain it.
|
vBuffer[out , imageSize , 1] // Copy the first U value and the remaining VU values from the U buffer.
|
uBuffer[out , imageSize + 1 , 2 * imageSize / 4 - 1]
|
} else { // Fallback to copying the UV values one by one, which is slower but also works.
|
// Unpack Y.
|
unpackPlane(planes[0] , width , height , out , 0 , 1) // Unpack U.
|
unpackPlane(planes[1] , width , height , out , imageSize + 1 , 2) // Unpack V.
|
unpackPlane(planes[2] , width , height , out , imageSize , 2)
|
}
|
return ByteBuffer.wrap(out)
|
}
|
|
|
/** Checks if the UV plane buffers of a YUV_420_888 image are in the NV21 format. */
|
private fun areUVPlanesNV21(planes : Array<Plane> , width : Int , height : Int) : Boolean {
|
val imageSize = width * height
|
|
val uBuffer = planes[1].buffer
|
val vBuffer = planes[2].buffer
|
|
// Backup buffer properties.
|
val vBufferPosition = vBuffer.position()
|
val uBufferLimit = uBuffer.limit()
|
|
// Advance the V buffer by 1 byte, since the U buffer will not contain the first V value.
|
vBuffer.position(vBufferPosition + 1) // Chop off the last byte of the U buffer, since the V buffer will not contain the last U value.
|
uBuffer.limit(uBufferLimit - 1)
|
|
// Check that the buffers are equal and have the expected number of elements.
|
val areNV21 = (vBuffer.remaining() == (2 * imageSize / 4 - 2)) && (vBuffer.compareTo(uBuffer) == 0)
|
|
// Restore buffers to their initial state.
|
vBuffer.position(vBufferPosition)
|
uBuffer.limit(uBufferLimit)
|
|
return areNV21
|
}
|
|
fun rotateBitmap(source : Bitmap , angle : Float) : Bitmap {
|
val matrix = Matrix()
|
matrix.postRotate(if(angle > 0) angle-90 else 0f)
|
return Bitmap.createBitmap(source , 0 , 0 , source.width , source.height , matrix , true)
|
}
|
|
fun getBitmap(context : Context,imageInBuffer : ByteArray,width : Int,height : Int) : Bitmap{
|
|
// 初始化 RenderScript
|
val rs = RenderScript.create(context)
|
|
|
// 创建分配空间
|
val yuvAllocation = Allocation.createSized(rs , Element.U8(rs) , imageInBuffer.size)
|
val rgbaAllocation = Allocation.createTyped(rs , Type.createXY(rs , Element.RGBA_8888(rs) , width , height))
|
|
|
// 将裁剪后的 YUV 数据复制到分配中
|
yuvAllocation.copyFrom(imageInBuffer)
|
|
|
// 创建 YUV 到 RGB 转换脚本
|
val yuvToRgb = ScriptIntrinsicYuvToRGB.create(rs , Element.U8_4(rs))
|
yuvToRgb.setInput(yuvAllocation)
|
yuvToRgb.forEach(rgbaAllocation)
|
|
|
// 输出 Bitmap
|
val bitmap = Bitmap.createBitmap(width , height , Bitmap.Config.ARGB_8888)
|
rgbaAllocation.copyTo(bitmap)
|
return bitmap;
|
}
|
|
|
/** Rotates a bitmap if it is converted from a bytebuffer. */
|
private fun rotateBitmap(bitmap : Bitmap , rotationDegrees : Int , flipX : Boolean , flipY : Boolean) : Bitmap {
|
val matrix = Matrix()
|
|
// Rotate the image back to straight.
|
matrix.postRotate(rotationDegrees.toFloat())
|
|
// Mirror the image along the X or Y axis.
|
matrix.postScale(if (flipX) - 1.0f else 1.0f , if (flipY) - 1.0f else 1.0f)
|
val rotatedBitmap = Bitmap.createBitmap(bitmap , 0 , 0 , bitmap.width , bitmap.height , matrix , true)
|
|
// Recycle the old bitmap if it has changed.
|
if (rotatedBitmap != bitmap) {
|
bitmap.recycle()
|
}
|
return rotatedBitmap
|
}
|
|
@Throws(IOException::class)
|
fun getBitmapFromContentUri(contentResolver : ContentResolver , imageUri : Uri) : Bitmap? {
|
val decodedBitmap = MediaStore.Images.Media.getBitmap(contentResolver , imageUri) ?: return null
|
val orientation = getExifOrientationTag(contentResolver , imageUri)
|
|
var rotationDegrees = 0
|
var flipX = false
|
var flipY = false
|
when (orientation) {
|
ExifInterface.ORIENTATION_FLIP_HORIZONTAL -> flipX = true
|
ExifInterface.ORIENTATION_ROTATE_90 -> rotationDegrees = 90
|
ExifInterface.ORIENTATION_TRANSPOSE -> {
|
rotationDegrees = 90
|
flipX = true
|
}
|
|
ExifInterface.ORIENTATION_ROTATE_180 -> rotationDegrees = 180
|
ExifInterface.ORIENTATION_FLIP_VERTICAL -> flipY = true
|
ExifInterface.ORIENTATION_ROTATE_270 -> rotationDegrees = - 90
|
ExifInterface.ORIENTATION_TRANSVERSE -> {
|
rotationDegrees = - 90
|
flipX = true
|
}
|
|
ExifInterface.ORIENTATION_UNDEFINED , ExifInterface.ORIENTATION_NORMAL -> {}
|
else -> {}
|
}
|
return rotateBitmap(decodedBitmap , rotationDegrees , flipX , flipY)
|
}
|
|
private fun getExifOrientationTag(resolver : ContentResolver , imageUri : Uri) : Int { // We only support parsing EXIF orientation tag from local file on the device.
|
// See also:
|
// https://android-developers.googleblog.com/2016/12/introducing-the-exifinterface-support-library.html
|
if (ContentResolver.SCHEME_CONTENT != imageUri.scheme && ContentResolver.SCHEME_FILE != imageUri.scheme) {
|
return 0
|
}
|
|
var exif : ExifInterface
|
try {
|
resolver.openInputStream(imageUri).use { inputStream ->
|
if (inputStream == null) {
|
return 0
|
}
|
exif = ExifInterface(inputStream)
|
}
|
} catch (e : IOException) {
|
Log.e(TAG , "failed to open file to read rotation meta data: $imageUri" , e)
|
return 0
|
}
|
|
return exif.getAttributeInt(ExifInterface.TAG_ORIENTATION , ExifInterface.ORIENTATION_NORMAL)
|
}
|
|
@OptIn(ExperimentalGetImage::class)
|
fun yuv420ToNv21(image : ImageProxy) : ByteArray {
|
return yuv420ToNv21(image.image!!.planes)
|
}
|
fun yuv420ToNv21(image : Image) : ByteArray {
|
return yuv420ToNv21(image.planes)
|
}
|
private fun yuv420ToNv21(planes : Array<Plane>) : ByteArray {
|
|
// 获取 Y、U、V 平面数据
|
val yBuffer = planes[0].buffer
|
val uBuffer = planes[1].buffer
|
val vBuffer = planes[2].buffer
|
|
|
// 获取平面数据大小
|
val ySize = yBuffer.remaining()
|
val uSize = uBuffer.remaining()
|
val vSize = vBuffer.remaining()
|
|
|
// 创建 YUV 数组并将数据从各个平面复制到对应数组
|
val nv21Data = ByteArray(ySize + uSize + vSize)
|
|
|
// 将 Y、U、V 数据复制到 NV21 格式
|
yBuffer[nv21Data , 0 , ySize]
|
uBuffer[nv21Data , ySize , uSize]
|
vBuffer[nv21Data , ySize + uSize , vSize]
|
|
return nv21Data
|
}
|
|
fun convertImageToNV21(image : Image) : ByteArray { // 获取图像的各个平面
|
val planes = image.planes
|
// 获取 Y、U 和 V 平面数据
|
val yBuffer = planes[0].buffer // Y平面
|
val uBuffer = planes[1].buffer // U平面
|
val vBuffer = planes[2].buffer // V平面
|
// 获取平面大小
|
val ySize = yBuffer.remaining()
|
val uSize = uBuffer.remaining()
|
val vSize = vBuffer.remaining()
|
// 创建一个存放 NV21 数据的 byte 数组
|
val nv21Data = ByteArray(ySize + uSize + vSize)
|
// 拷贝 Y 数据
|
yBuffer[nv21Data , 0 , ySize]
|
// 获取 U 和 V 的交错数据
|
val uvData = ByteArray(uSize + vSize)
|
uBuffer[uvData , 0 , uSize]
|
vBuffer[uvData , uSize , vSize]
|
// 将交错的 U 和 V 数据拷贝到 NV21 数组
|
System.arraycopy(uvData , 0 , nv21Data , ySize , uvData.size)
|
return nv21Data
|
}
|
|
fun imageToBitmap(image : Image) : Bitmap {
|
|
|
// 获取 Image 的宽高
|
val width = image.width
|
val height = image.height
|
|
|
// 获取 YUV_420_888 格式的 YUV 数据平面
|
val planes = image.planes
|
val yBuffer = planes[0].buffer // Y 数据
|
val uBuffer = planes[1].buffer // U 数据
|
val vBuffer = planes[2].buffer // V 数据
|
|
|
// 计算每个平面的像素步长(stride)
|
val yRowStride = planes[0].rowStride
|
val uRowStride = planes[1].rowStride
|
val vRowStride = planes[2].rowStride
|
val uvPixelStride = planes[1].pixelStride // U 和 V 的 pixel stride
|
|
|
// 创建一个数组用于存储 ARGB 数据
|
val argbArray = IntArray(width * height)
|
|
|
// 将 YUV 数据转换为 ARGB 格式
|
for (y in 0 until height) {
|
for (x in 0 until width) {
|
val yIndex = y * yRowStride + x
|
val uvIndex = (y / 2) * uRowStride + (x / 2) * uvPixelStride
|
|
// 获取 Y、U、V 值
|
val yValue = yBuffer[yIndex].toInt() and 0xFF
|
val uValue = uBuffer[uvIndex].toInt() and 0xFF
|
val vValue = vBuffer[uvIndex].toInt() and 0xFF
|
|
// 将 YUV 转换为 ARGB
|
var r = (yValue + 1.370705 * (vValue - 128)).toInt()
|
var g = (yValue - 0.337633 * (uValue - 128) - 0.698001 * (vValue - 128)).toInt()
|
var b = (yValue + 1.732446 * (uValue - 128)).toInt()
|
|
// 限制 r、g、b 的范围在 [0, 255]
|
r = max(0.0 , min(r.toDouble() , 255.0)).toInt()
|
g = max(0.0 , min(g.toDouble() , 255.0)).toInt()
|
b = max(0.0 , min(b.toDouble() , 255.0)).toInt()
|
|
// 将 ARGB 值保存到数组中
|
argbArray[y * width + x] = 0xFF shl 24 or (r shl 16) or (g shl 8) or b
|
}
|
}
|
|
|
// 创建 Bitmap 并填充 ARGB 数据
|
val bitmap = Bitmap.createBitmap(width , height , Bitmap.Config.ARGB_8888)
|
bitmap.setPixels(argbArray , 0 , width , 0 , 0 , width , height)
|
return bitmap;
|
}
|
|
@Deprecated("耗时太长,弃用", ReplaceWith("decodeToNV21"))
|
fun decodeNV21(image : Image) : ByteArray {
|
val width = image.width
|
val height = image.height
|
|
|
// 获取 YUV_420_888 格式的 YUV 数据平面
|
val planes = image.planes
|
val yBuffer = planes[0].buffer // Y 数据
|
val uBuffer = planes[1].buffer // U 数据
|
val vBuffer = planes[2].buffer // V 数据
|
|
|
// 准备 NV21 格式的输出数组
|
val nv21 = ByteArray(width * height * 3 / 2)
|
|
val ySize = width * height
|
val uvSize = width * height / 4
|
|
|
// 填充 Y 数据
|
val yRowStride = planes[0].rowStride
|
for (row in 0 until height) {
|
yBuffer.position(row * yRowStride)
|
yBuffer[nv21 , row * width , width]
|
}
|
|
|
// 填充 UV 数据,NV21 中 U 和 V 分量交替排列,起始顺序为 VU
|
val uRowStride = planes[1].rowStride
|
val vRowStride = planes[2].rowStride
|
val uvPixelStride = planes[1].pixelStride
|
|
for (row in 0 until height / 2) {
|
var uvPos = ySize + row * width
|
for (col in 0 until width / 2) {
|
val uIndex = row * uRowStride + col * uvPixelStride
|
val vIndex = row * vRowStride + col * uvPixelStride
|
|
// NV21 格式中 V 在前,U 在后
|
nv21[uvPos ++] = vBuffer[vIndex]
|
nv21[uvPos ++] = uBuffer[uIndex]
|
}
|
}
|
|
return nv21
|
}
|
|
fun decodeToNV21(image : Image): ByteArray{
|
//return converToNV21(image.planes,image.cropRect,image.format,Image.Plane::class.java)
|
return converToNV21(image.planes,image.cropRect,image.format);
|
}
|
|
@OptIn(ExperimentalGetImage::class)
|
fun decodeToNV21(image: ImageProxy): ByteArray{
|
//return converToNV21(image.planes,image.cropRect,image.format,ImageProxy.PlaneProxy::class.java)
|
return converToNV21(image.image!!.planes,image.cropRect,image.format)
|
}
|
|
private fun <T> converToNV21(planes : Array<T>, crop : Rect , format : Int, clazz : Class<T>): ByteArray{
|
val width = crop.width()
|
val height = crop.height()
|
val data = ByteArray(width * height * ImageFormat.getBitsPerPixel(format) / 8)
|
val getRowStride = clazz.getDeclaredMethod("getRowStride")
|
val getBuffer = clazz.getMethod("getBuffer")
|
val getPixelStride = clazz.getMethod("getPixelStride")
|
val rowData = ByteArray(getRowStride.invoke(planes[0]) as Int)
|
var channelOffset = 0
|
var outputStride = 1
|
for (i in planes.indices) {
|
when (i) {
|
0 -> {
|
channelOffset = 0
|
outputStride = 1
|
}
|
1 -> {
|
channelOffset = width * height + 1
|
outputStride = 2
|
}
|
2 -> {
|
channelOffset = width * height
|
outputStride = 2
|
}
|
}
|
val buffer: ByteBuffer = getBuffer.invoke(planes[i]) as ByteBuffer
|
val rowStride: Int = getRowStride.invoke(planes[i]) as Int
|
val pixelStride: Int = getPixelStride.invoke(planes[i]) as Int
|
val shift = if (i == 0) 0 else 1
|
val w = width shr shift
|
val h = height shr shift
|
buffer.position(rowStride * (crop.top shr shift) + pixelStride * (crop.left shr shift))
|
for (row in 0 until h) {
|
var length: Int
|
if (pixelStride == 1 && outputStride == 1) {
|
length = w
|
buffer[data, channelOffset, length]
|
channelOffset += length
|
} else {
|
length = (w - 1) * pixelStride + 1
|
buffer[rowData, 0, length]
|
for (col in 0 until w) {
|
data[channelOffset] = rowData[col * pixelStride]
|
channelOffset += outputStride
|
}
|
}
|
if (row < h - 1) {
|
buffer.position(buffer.position() + rowStride - length)
|
}
|
}
|
}
|
return data
|
}
|
|
private fun converToNV21(planes : Array<Plane>, crop : Rect , format : Int): ByteArray{
|
val width = crop.width()
|
val height = crop.height()
|
val data = ByteArray(width * height * ImageFormat.getBitsPerPixel(format) / 8)
|
val rowData = ByteArray(planes[0].rowStride)
|
var channelOffset = 0
|
var outputStride = 1
|
for (i in planes.indices) {
|
when (i) {
|
0 -> {
|
channelOffset = 0
|
outputStride = 1
|
}
|
1 -> {
|
channelOffset = width * height + 1
|
outputStride = 2
|
}
|
2 -> {
|
channelOffset = width * height
|
outputStride = 2
|
}
|
}
|
val buffer: ByteBuffer = planes[i].buffer
|
val rowStride: Int = planes[i].rowStride
|
val pixelStride: Int = planes[i].pixelStride
|
val shift = if (i == 0) 0 else 1
|
val w = width shr shift
|
val h = height shr shift
|
buffer.position(rowStride * (crop.top shr shift) + pixelStride * (crop.left shr shift))
|
for (row in 0 until h) {
|
var length: Int
|
if (pixelStride == 1 && outputStride == 1) {
|
length = w
|
buffer[data, channelOffset, length]
|
channelOffset += length
|
} else {
|
length = (w - 1) * pixelStride + 1
|
buffer[rowData, 0, length]
|
for (col in 0 until w) {
|
data[channelOffset] = rowData[col * pixelStride]
|
channelOffset += outputStride
|
}
|
}
|
if (row < h - 1) {
|
buffer.position(buffer.position() + rowStride - length)
|
}
|
}
|
}
|
return data
|
}
|
|
fun convertImageToYUV(image : Image) : ByteArray { // 获取图像平面
|
val planes = image.planes
|
|
|
// 获取 Y、U、V 平面数据
|
val yBuffer = planes[0].buffer
|
val uBuffer = planes[1].buffer
|
val vBuffer = planes[2].buffer
|
|
|
// 获取平面数据大小
|
val ySize = yBuffer.remaining()
|
val uSize = uBuffer.remaining()
|
val vSize = vBuffer.remaining()
|
|
|
// 创建 YUV 数组并将数据从各个平面复制到对应数组
|
val nv21Data = ByteArray(ySize + uSize + vSize)
|
|
|
// 将 Y、U、V 数据复制到 NV21 格式
|
yBuffer[nv21Data , 0 , ySize]
|
uBuffer[nv21Data , ySize , uSize]
|
vBuffer[nv21Data , ySize + uSize , vSize]
|
|
return nv21Data
|
}
|
|
//Planar格式(P)的处理
|
private fun getuvBufferWithoutPaddingP(uBuffer : ByteBuffer,vBuffer : ByteBuffer,width : Int,height : Int,rowStride :Int,pixelStride : Int):ByteBuffer{
|
var pos = 0;
|
var byteArray = ByteArray(height*width/2);
|
for (row in 0..(height/2-1)){
|
for (col in 0..(width/2-1)){
|
var vuPos = col*pixelStride + row*rowStride;
|
byteArray[pos++] = vBuffer.get(vuPos);
|
byteArray[pos++] = uBuffer.get(vuPos);
|
}
|
}
|
var bufferWithoutPaddings = ByteBuffer.allocate(byteArray.size);
|
// 数组放到buffer中
|
bufferWithoutPaddings.put(byteArray);
|
//重置 limit 和postion 值否则 buffer 读取数据不对
|
bufferWithoutPaddings.flip();
|
return bufferWithoutPaddings;
|
}
|
|
//Semi-Planar格式(SP)的处理和y通道的数据
|
private fun getBufferWithoutPadding(buffer:ByteBuffer,width1 : Int,rowStride : Int, times:Int,isVBuffer:Boolean):ByteBuffer{
|
var width = width1;
|
if(width == rowStride) return buffer; //没有buffer,不用处理。
|
var bufferPos = buffer.position();
|
var cap = buffer.capacity();
|
var byteArray = ByteArray(times*width);
|
var pos = 0;
|
//对于y平面,要逐行赋值的次数就是height次。对于uv交替的平面,赋值的次数是height/2次
|
for (i in 0..(times-1)){
|
buffer.position(bufferPos);
|
//part 1.1 对于u,v通道,会缺失最后一个像u值或者v值,因此需要特殊处理,否则会crash
|
if(isVBuffer && i==times-1){
|
width -= 1;
|
}
|
buffer.get(byteArray, pos, width);
|
bufferPos+= rowStride;
|
pos = pos+width;
|
}
|
|
//nv21数组转成buffer并返回
|
var bufferWithoutPaddings=ByteBuffer.allocate(byteArray.size);
|
// 数组放到buffer中
|
bufferWithoutPaddings.put(byteArray);
|
//重置 limit 和postion 值否则 buffer 读取数据不对
|
bufferWithoutPaddings.flip();
|
return bufferWithoutPaddings;
|
}
|
|
/**
|
* Unpack an image plane into a byte array.
|
*
|
*
|
* The input plane data will be copied in 'out', starting at 'offset' and every pixel will be
|
* spaced by 'pixelStride'. Note that there is no row padding on the output.
|
*/
|
private fun unpackPlane(plane : Plane , width : Int , height : Int , out : ByteArray , offset : Int , pixelStride : Int) {
|
val buffer = plane.buffer
|
buffer.rewind()
|
|
// Compute the size of the current plane.
|
// We assume that it has the aspect ratio as the original image.
|
val numRow = (buffer.limit() + plane.rowStride - 1) / plane.rowStride
|
if (numRow == 0) {
|
return
|
}
|
val scaleFactor = height / numRow
|
val numCol = width / scaleFactor
|
|
// Extract the data in the output buffer.
|
var outputPos = offset
|
var rowStart = 0
|
for (row in 0 until numRow) {
|
var inputPos = rowStart
|
for (col in 0 until numCol) {
|
out[outputPos] = buffer[inputPos]
|
outputPos += pixelStride
|
inputPos += plane.pixelStride
|
}
|
rowStart += plane.rowStride
|
}
|
}
|
|
/**
|
* @param yuv420sp target to save yuv data, the length should be at least width*height*1.5
|
* @param rgb rgb data, every 3 bytes as a color, without alpha
|
* @param width image width
|
* @param height image height
|
* @param stride The number of bytes to skip between rows.
|
* Normally this value will be the same as the width, but it may be larger sometimes
|
*/
|
fun rgb2YUV420sp(yuv420sp : ByteArray , rgb : ByteArray , width : Int , height : Int , stride : Int) {
|
val frameSize = width * height
|
var yIndex = 0
|
var uvIndex = frameSize
|
|
var r : Int
|
var g : Int
|
var b : Int
|
var y : Int
|
var u : Int
|
var v : Int
|
for (i in 0 until height) {
|
for (j in 0 until width) {
|
var index = (i * stride + j) * 3
|
r = rgb[index ++].toInt()
|
g = rgb[index ++].toInt()
|
b = rgb[index].toInt()
|
|
//RGB to YUV
|
y = (76 * r + 150 * g + 29 * b + 128) shr 8 //y=0.299r+0.587g+0.114b
|
|
yuv420sp[yIndex ++] = (if (y < 0) 0 else if (y > 255) 255 else y).toByte()
|
if (i % 2 == 0 && j % 2 == 0) {
|
u = ((- 43 * r - 84 * g + 128 * b + 128) shr 8) + 128 //u=-0.169r-0.331g+0.5b+128
|
v = ((128 * r - 107 * g - 20 * b + 128) shr 8) + 128 //v=0.5r-0.419g-0.081b+128
|
yuv420sp[uvIndex ++] = (if (v < 0) 0 else if (v > 255) 255 else v).toByte()
|
yuv420sp[uvIndex ++] = (if (u < 0) 0 else if (u > 255) 255 else u).toByte()
|
}
|
}
|
}
|
}
|
|
/**
|
* Convert image format from i420sp(NV12) to ARGB
|
*
|
* @param pixels array to save pixels, it's length should be no less than width*height
|
* @param data YUV data(like YYYYUVUV) with <I>length=1.5*width*height</I>,
|
* **or** gray image that has only Y plane with <I>length=width*height</I>
|
* @param width width of image
|
* @param height height of image
|
*/
|
fun convertYuv2Rgb(pixels : IntArray , data : ByteArray , width : Int , height : Int) {
|
val len = width * height
|
if (len == data.size) { //only has Y
|
for (i in 0 until len) {
|
val y = data[i].toInt() and 0xff
|
pixels[i] = Color.argb(255 , y , y , y)
|
}
|
} else {
|
val rgb = IntArray(3)
|
var y : Int
|
var u : Int
|
var v : Int
|
var pixIndex = 0
|
var yStart : Int
|
var uvStart : Int
|
for (i in 0 until height) {
|
yStart = i * width //use stride, if stride!=width
|
//uv start from width*height, every 2 columns share 1 row of uv data with stride=width
|
uvStart = len + (i shr 1) * width
|
for (j in 0 until width) {
|
y = data[yStart + j].toInt() and 0xff
|
var uIndex = uvStart + (j shr 1 shl 1) //every 2 pixels shared a group of uv
|
u = (data[uIndex ++].toInt() and 0xff) - 128
|
v = (data[uIndex].toInt() and 0xff) - 128
|
rgb[0] = y + (v * 359 shr 8) //r=y+1.403v
|
rgb[1] = y - (u * 88 shr 8) - (v * 182 shr 8) //g=y-0.344u-0.714v
|
rgb[2] = y + (u * 453 shr 8) //b=y+1.77u
|
for (k in 0 .. 2) { //rgb value should be 0-255
|
rgb[k] = if (rgb[k] > 255) 255 else if (rgb[k] < 0) 0 else rgb[k]
|
}
|
pixels[pixIndex ++] = Color.argb(255 , rgb[0] , rgb[1] , rgb[2])
|
}
|
}
|
}
|
}
|
|
fun textToBitmap(text : String , textSize : Float , textColor : Int , bgColor : Int) : Bitmap { // 创建 Paint 对象设置文本参数
|
|
val paint = Paint(Paint.ANTI_ALIAS_FLAG)
|
paint.textSize = textSize.toFloat()
|
paint.color = textColor
|
|
val fontMetrics = paint.fontMetrics
|
val lineHeight : Float = fontMetrics.bottom - fontMetrics.top + 30
|
|
// 按换行符分割文本
|
val lines = text.split("\n")
|
|
|
// 计算最大宽度
|
var maxWidth = 0f
|
for (line in lines) {
|
val width = paint.measureText(line)
|
if (width > maxWidth) maxWidth = width
|
}
|
|
|
// 创建 Bitmap
|
val bmpWidth = ceil(maxWidth.toDouble()).toInt()
|
val bmpHeight = ceil((lineHeight * lines.size).toDouble()).toInt()
|
|
val bitmap = Bitmap.createBitmap(bmpWidth , bmpHeight , Bitmap.Config.ARGB_8888)
|
val canvas = Canvas(bitmap)
|
canvas.drawColor(bgColor) // 背景色
|
|
|
// 逐行绘制文本
|
var y = - fontMetrics.top
|
for (line in lines) {
|
canvas.drawText(line !! , 0f , y , paint)
|
y += lineHeight
|
}
|
|
return bitmap
|
}
|
|
fun argbToNV21(argb : IntArray , width : Int , height : Int) : ByteArray {
|
val yuv = ByteArray(width * height * 3 / 2)
|
val frameSize = width * height
|
|
var yIndex = 0
|
var uvIndex = frameSize
|
|
for (j in 0 until height) {
|
for (i in 0 until width) {
|
val argbIndex = j * width + i
|
val color = argb[argbIndex]
|
|
val r = (color shr 16) and 0xFF
|
val g = (color shr 8) and 0xFF
|
val b = color and 0xFF
|
|
// RGB to YUV conversion
|
val y = ((66 * r + 129 * g + 25 * b + 128) shr 8) + 16
|
val u = ((- 38 * r - 74 * g + 112 * b + 128) shr 8) + 128
|
val v = ((112 * r - 94 * g - 18 * b + 128) shr 8) + 128
|
|
yuv[yIndex ++] = max(0.0 , min(255.0 , y.toDouble())).toInt().toByte()
|
|
// 每 2x2 像素写一次 UV
|
if ((j % 2 == 0) && (i % 2 == 0)) {
|
yuv[uvIndex ++] = max(0.0 , min(255.0 , v.toDouble())).toInt().toByte()
|
yuv[uvIndex ++] = max(0.0 , min(255.0 , u.toDouble())).toInt().toByte()
|
}
|
}
|
}
|
return yuv
|
}
|
|
|
fun cavansSurface(surfaceHolder : SurfaceHolder ,bitmap : Bitmap){
|
var canvas : Canvas? = null;
|
try {
|
canvas = surfaceHolder.lockCanvas()
|
if (canvas != null) {
|
var src = Rect(0,0,bitmap.width,bitmap.height)
|
var des = Rect(0,0,surfaceHolder.surfaceFrame.width(),surfaceHolder.surfaceFrame.height())
|
canvas.drawBitmap(bitmap,src,des,null)
|
}
|
} catch (e : Exception) {
|
Log.e("Surface" , "绘制失败:" + e.message)
|
} finally {
|
if (canvas != null) {
|
surfaceHolder.unlockCanvasAndPost(canvas)
|
}
|
}
|
}
|
}
|