Overall Problem – I want to do fast image modification in Android, and my current naive solution is too slow
I’m building an Android app that will involve fast modification of streaming images. I am running it on a Samsung Galaxy S10. I’m new to Android, Java, and Kotlin, so please forgive any ignorance on my part.
I’ve successfully decoded the video such that each frame is read into a Bitmap
.
As a simple test, I’ve made the following function:
fun corruptBitmapInplace(bitmap: Bitmap){ println("Corrupting bitmap of size ${bitmap.width} x ${bitmap.height}, heheheh") val start = System.currentTimeMillis() for (x in 0..500){ for (y in 0..200){ bitmap.setPixel(x, y, bitmap.getPixel(x, y) and 0xffff00) // Remove blue channel } } println("Corruption took ${System.currentTimeMillis()-start}ms") }
Which outputs
I/System.out: Corrupting bitmap of size 1280 x 720, heheheh I/System.out: Corruption took 60ms
However it’s much slower than I expected – at around 0.6us/pixel, it would take around 0.5s per image (I use 500 x 200 in this demo because when I use the full image size, the thread running the function seems to be killed before completing)
For comparison, doing the same thing in Python…:
import numpy as np import time image = np.random.randint(255, size=(720, 1280)) tstart = time.time() image[:200, :500] &= 0xffff00 print(f'Elapsed: {(time.time()-tstart)*1000:.2f}ms')
… on my MacBook Air takes around 0.3ms (vs the 60ms on Galaxy 10 in Kotlin.).
So, question – what is the standard, efficient way to do such things in Android? Should I not be using native kotlin and instead something like MultiK? Or am I just doing things in an inefficient way natively?
Advertisement
Answer
So it looks like it is much faster to operate on the pixels as an array. This implementation is over 100x faster:
fun corruptBitmapInplace(bitmap: Bitmap){ println("Corrupting bitmap of size ${bitmap.width} x ${bitmap.height}, heheheh") val start = System.currentTimeMillis() val pixels = IntArray(bitmap.width*bitmap.height) bitmap.getPixels(pixels, 0, bitmap.width, 0, 0, bitmap.width, bitmap.height) for (i in 0 until pixels.size){ pixels[i] = pixels[i] and 0xffff00 // Remove blue channel } bitmap.setPixels(pixels, 0, bitmap.width, 0, 0, bitmap.width, bitmap.height) println("Corruption took ${System.currentTimeMillis()-start}ms")
… at around 4ms per call (doing every pixel) – as opposed to around 550ms per call when using BitMap.setPixel