package com.example.smtias_capture
import android.graphics.Bitmap
import java.io.ByteArrayOutputStream
import java.nio.ByteBuffer
import java.util.zip.CRC32
import java.util.zip.Deflater
import java.util.zip.DeflaterOutputStream
/**
* Bitmap を非圧縮 PNG(Deflater.NO_COMPRESSION)としてエンコードするユーティリティ.
*
* 圧縮率より速度を優先する研究用途向け.撮影フローのボトルネック解消のため
* 標準の `Bitmap.compress` を使わず自前で IDAT を非圧縮 zlib でラップする.
*/
internal object PngEncoder {
/** [bitmap] を非圧縮 PNG のバイト列に変換して返す. */
fun encodeUncompressed(bitmap: Bitmap): ByteArray {
val w = bitmap.width
val h = bitmap.height
val out = ByteArrayOutputStream(w * h * 3 + 1024)
// PNG シグネチャ
out.write(byteArrayOf(0x89.toByte(), 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A))
// IHDR チャンク
val ihdrData = ByteBuffer.allocate(13)
.putInt(w)
.putInt(h)
.put(8) // bit depth
.put(2) // color type: RGB
.put(0) // compression method
.put(0) // filter method
.put(0) // interlace method
.array()
writeChunk(out, "IHDR", ihdrData)
// IDAT チャンク: 非圧縮 zlib でラップした RGB データ
val idatStream = ByteArrayOutputStream(w * h * 3 + h + 64)
val deflater = Deflater(Deflater.NO_COMPRESSION)
val deflaterOut = DeflaterOutputStream(idatStream, deflater, 65536)
val pixels = IntArray(w)
val rowBytes = ByteArray(1 + w * 3) // フィルタバイト(0) + RGB
rowBytes[0] = 0 // フィルタ: None
for (y in 0 until h) {
bitmap.getPixels(pixels, 0, w, 0, y, w, 1)
for (x in 0 until w) {
val pixel = pixels[x]
rowBytes[1 + x * 3] = ((pixel shr 16) and 0xFF).toByte() // R
rowBytes[1 + x * 3 + 1] = ((pixel shr 8) and 0xFF).toByte() // G
rowBytes[1 + x * 3 + 2] = (pixel and 0xFF).toByte() // B
}
deflaterOut.write(rowBytes)
}
deflaterOut.finish()
deflaterOut.close()
deflater.end()
writeChunk(out, "IDAT", idatStream.toByteArray())
// IEND チャンク
writeChunk(out, "IEND", byteArrayOf())
return out.toByteArray()
}
/** PNG チャンクを書き込む(length + type + data + CRC). */
private fun writeChunk(out: ByteArrayOutputStream, type: String, data: ByteArray) {
val typeBytes = type.toByteArray(Charsets.US_ASCII)
// length (4 bytes, big-endian)
out.write(ByteBuffer.allocate(4).putInt(data.size).array())
// type (4 bytes)
out.write(typeBytes)
// data
out.write(data)
// CRC (type + data)
val crc = CRC32()
crc.update(typeBytes)
crc.update(data)
out.write(ByteBuffer.allocate(4).putInt(crc.value.toInt()).array())
}
}