Newer
Older
SmTIAS-Capture / android / app / src / main / kotlin / com / example / smtias_capture / YuvJpegConverter.kt
package com.example.smtias_capture

import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.ImageFormat
import android.graphics.Matrix
import android.graphics.Rect
import android.graphics.YuvImage
import java.io.ByteArrayOutputStream

/**
 * YUV_420_888 のプレーンデータを NV21 経由で JPEG に変換するユーティリティ.
 *
 * ライブプレビューのスナップショット保存に使用する(撮影本体の RAW とは独立).
 * 回転・左右反転にも対応する.
 */
internal object YuvJpegConverter {

    /**
     * YUV_420_888 のプレーン群を JPEG バイト列に変換する.
     *
     * [rotation] は時計回りの度数,[mirror] が true なら左右反転を適用する.
     */
    fun convert(
        width: Int,
        height: Int,
        yPlane: ByteArray,
        uPlane: ByteArray,
        vPlane: ByteArray,
        yRowStride: Int,
        uvRowStride: Int,
        uvPixelStride: Int,
        rotation: Int,
        mirror: Boolean,
        quality: Int,
    ): ByteArray {
        // YUV_420_888 → NV21 変換
        val nv21 = ByteArray(width * height * 3 / 2)

        // Y プレーンをコピー
        for (row in 0 until height) {
            System.arraycopy(yPlane, row * yRowStride, nv21, row * width, width)
        }

        // UV プレーンを NV21 形式(VUVU...)にインターリーブ
        val uvOffset = width * height
        for (row in 0 until height / 2) {
            for (col in 0 until width / 2) {
                val uvIndex = row * uvRowStride + col * uvPixelStride
                nv21[uvOffset + row * width + col * 2] = vPlane[uvIndex]
                nv21[uvOffset + row * width + col * 2 + 1] = uPlane[uvIndex]
            }
        }

        // NV21 → JPEG
        val yuvImage = YuvImage(nv21, ImageFormat.NV21, width, height, null)
        val jpegStream = ByteArrayOutputStream()
        yuvImage.compressToJpeg(Rect(0, 0, width, height), quality, jpegStream)

        // 回転・反転が必要な場合
        return if (rotation != 0 || mirror) {
            val bitmap = BitmapFactory.decodeByteArray(jpegStream.toByteArray(), 0, jpegStream.size())
            val matrix = Matrix()
            if (rotation != 0) matrix.postRotate(rotation.toFloat())
            if (mirror) matrix.postScale(-1f, 1f)
            val transformed = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
            bitmap.recycle()

            val outStream = ByteArrayOutputStream()
            transformed.compress(Bitmap.CompressFormat.JPEG, quality, outStream)
            transformed.recycle()
            outStream.toByteArray()
        } else {
            jpegStream.toByteArray()
        }
    }
}