Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Trying to implement BitmapSRGB so that it can take in bytes from Webcam without needing to convert to TYPE_INT_ARGB_PRE #2256

Open
Kietyo opened this issue Jul 1, 2024 · 3 comments

Comments

@Kietyo
Copy link
Contributor

Kietyo commented Jul 1, 2024

The image looks all wrong:

image

Can you please take a look at my implementation and see if I'm missing anything?

import korlibs.image.bitmap.Bitmap
import korlibs.image.color.RGBA
import kotlin.math.pow

class BitmapSRGB(
    width: Int,
    height: Int,
    val data: ByteArray = ByteArray(width * height * 3),
): Bitmap(width, height, 8, true, data) {
    override fun getInt(x: Int, y: Int): Int {
        val idxStart = (x * 3) + (y * width * 3)
        val r = data[idxStart].toInt()
        val g = data[idxStart + 1].toInt()
        val b = data[idxStart + 2].toInt()

        val linearRgb = doubleArrayOf(r / 255.0, g / 255.0, b / 255.0)

        // Linearization
        for (i in 0..2) {
            linearRgb[i] = if (linearRgb[i] <= 0.04045) {
                linearRgb[i] / 12.92
            } else {
                ((linearRgb[i] + 0.055) / 1.055).pow(2.4)
            }
        }

        // sRGB to XYZ matrix transformation
        val srgbToXyzMatrix = arrayOf(
            doubleArrayOf(0.4124564, 0.3575761, 0.1804375),
            doubleArrayOf(0.2126729, 0.7151522, 0.0721750),
            doubleArrayOf(0.0193339, 0.1191920, 0.9503041)
        )

        val xyz = DoubleArray(3)
        for (i in 0..2) {
            for (j in 0..2) {
                xyz[i] += srgbToXyzMatrix[i][j] * linearRgb[j]
            }
        }

        // XYZ to Adobe RGB matrix transformation
        val xyzToAdobeRgbMatrix = arrayOf(
            doubleArrayOf(2.0413690, -0.5649464, -0.3446944),
            doubleArrayOf(-0.9692660, 1.8760108, 0.0415560),
            doubleArrayOf(0.0134474, -0.1183897, 1.0154096)
        )

        val linearAdobeRgb = DoubleArray(3)
        for (i in 0..2) {
            for (j in 0..2) {
                linearAdobeRgb[i] += xyzToAdobeRgbMatrix[i][j] * xyz[j]
            }
        }

        // Gamma correction
        for (i in 0..2) {
            linearAdobeRgb[i] = if (linearAdobeRgb[i] <= 0.0) {
                0.0
            } else {
                linearAdobeRgb[i].pow(1 / 2.19921875)
            }
        }

        // Convert back to integer bytes (0-255)
        val adobeRgb = IntArray(3)
        for (i in 0..2) {
            adobeRgb[i] = (linearAdobeRgb[i] * 255.0).toInt()
        }

//        return RGBA(adobeRgb[0], adobeRgb[1], adobeRgb[2]).value
        return (adobeRgb[0] shl 0) or (adobeRgb[1] shl 8) or (adobeRgb[2] shl 16) or (0xFF shl 24)
    }
    override fun setInt(x: Int, y: Int, color: Int) = TODO()

    override fun getRgbaRaw(x: Int, y: Int): RGBA = RGBA(getInt(x, y))
    
}

For context: The webcam image is in terms of sRGB:

https://github.com/Kietyo/webcam-capture-kotlin/blob/master/src/main/kotlin/com/github/sarxos/webcam/ds/buildin/WebcamDefaultDevice.kt#L394

Resources I followed for my implementation:

@Kietyo
Copy link
Contributor Author

Kietyo commented Jul 1, 2024

Okay I found out my issue, I needed to convert the Bytes to UBytes:

    val r = data[idxStart].toUByte().toInt()
    val g = data[idxStart + 1].toUByte().toInt()
    val b = data[idxStart + 2].toUByte().toInt()

I found this out by using the debugger and finding out that all the numbers were negative for some reason. Realized it was because of signed Byte limits.

image

@Kietyo
Copy link
Contributor Author

Kietyo commented Jul 1, 2024

Turns out I didn't even need to do all those transformations, this works as well:

    override fun getInt(x: Int, y: Int): Int {
        val idxStart = (x * 3) + (y * width * 3)
        val r = data[idxStart].toUByte().toInt()
        val g = data[idxStart + 1].toUByte().toInt()
        val b = data[idxStart + 2].toUByte().toInt()
        return (r shl 0) or (g shl 8) or (b shl 16) or (0xFF shl 24)
    }

@soywiz
Copy link
Member

soywiz commented Jul 2, 2024

Cool! Great to know you figure it out.

BTW: RGBA(r, g, b, 0xFF).value should also pack it in the right order.

I guess the thing is that you have as input a 24-bit interleaved RGB source right? I guess sRGB with 8-bit would only adjust the colors a bit.

Maybe a Bitmap24 could be added here? https://github.com/korlibs/korlibs/tree/main/korlibs-image/src/korlibs/image/bitmap

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Pending
Development

No branches or pull requests

2 participants