Can I render a texture at pixel resolution in Android? - android

I am trying to make a test image to check the tone curve on Android displays. The kotlin code below makes a Bitmap that fits the ImageView. The canvas is filled with the background colour. The central 50% is filled with alternate lightLine (white) and darkLine (black) 2-pixel wide horizontal stripes.
All the features are in the right places. The stripes should appear the same grey as the background. They sort-of do, but I think they are the wrong size, as though the BitmapShader was scaling from dp to px.
I could draw the stripes one at a time using myCanvas.drawRect(). If I am stuck, that is what I will do. But the BitmapShader would let me render general patterns if I could fix it.
class MainActivity : Activity() {
// Here are all the objects(instances)
// of classes that we need to do some drawing
lateinit var myImageView: ImageView
var background = Color.rgb(180, 180, 180)
var darkStripe = Color.rgb(0, 0, 0)
var lightStripe = Color.rgb(255, 255, 255)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Initialize the ImageView
myImageView = findViewById(R.id.imageView)
myImageView.doOnLayout {
val myBitmap = Bitmap.createBitmap(
it.measuredWidth,
it.measuredHeight,
Bitmap.Config.ARGB_8888
)
val w = (it.measuredWidth/4).toFloat()
val h = (it.measuredHeight/4).toFloat()
val myCanvas = Canvas(myBitmap)
myCanvas.drawColor(background)
val tex = createBitmap(
intArrayOf(lightStripe, lightStripe, darkStripe, darkStripe),
0, 1, 1, 4, Bitmap.Config.ARGB_8888
)
val shader: Shader = BitmapShader(tex, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT)
val myPaint = Paint()
myPaint.setAntiAlias(false)
myPaint.shader = shader
myCanvas.drawRect(w, h,w*3.0f,h*3.0f, myPaint)
myImageView.setImageBitmap(myBitmap)
}
}
(two hours later)
I replaced the BitmapShader with drawRect() calls like this...
myPaint.setAntiAlias(false)
myPaint.color = darkStripe
myCanvas.drawRect(xMin, yMin, xMax, yMax, myPaint)
myPaint.color = lightStripe
var y = yMin
while (y < yMax) {
myCanvas.drawRect(xMin, y, xMax, y+myStep, myPaint)
y += 2*myStep;
}
This still gave soft edges to my stripes. Maybe I was working in dp and not px. So I set the stripe width with....
myStep = resources.getDisplayMetrics().density
The stripes all look nice an uniform with hard edges. The step is 3.0, but if I look at the display through a microscope, I see its matrix has green dots on a square grid with alternate blue and red pixels at the square centres. The 3.0 step rectangle is four green dots high. I don't see how that translates into 3 of anything but it looks smooth. I expect the BitMap shader would have worked with a repeat of 3.

Related

Is there a way to save Android's Bitmap with ALPHA_8 config as a grayscale JPEG image? [duplicate]

I am new to this site, and I come with a question about Android.
Is there any way to convert a Bitmap to grayscale? I know how to draw a grayscale bitmap (using canvas operations: http://www.mail-archive.com/android-developers#googlegroups.com/msg38890.html) but I really need The actual bitmap in gray colors (or at least something that could be converted to a bitmap later on).
Do I have to implement it by hand (pixel by pixel operations)?
I've searched a lot, and still could not find. Anyone knows a easy/efficient way to do it?
Thanks a lot!
OH, yes, it does.
I was using it wrong, thanks for pointing it out to me.
(Sorry for the useless question)
Here is the end code (heavily based on the one linked) since it may help someone:
public Bitmap toGrayscale(Bitmap bmpOriginal)
{
int width, height;
height = bmpOriginal.getHeight();
width = bmpOriginal.getWidth();
Bitmap bmpGrayscale = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(bmpGrayscale);
Paint paint = new Paint();
ColorMatrix cm = new ColorMatrix();
cm.setSaturation(0);
ColorMatrixColorFilter f = new ColorMatrixColorFilter(cm);
paint.setColorFilter(f);
c.drawBitmap(bmpOriginal, 0, 0, paint);
return bmpGrayscale;
}
Any remarks or comments on it are very welcome.
Thanks
If you are going to show that Bitmap on ImageView. Then Instead of converting Bitmap to Gray Scale, you can try below code:
ColorMatrix matrix = new ColorMatrix();
matrix.setSaturation(0);
ColorMatrixColorFilter filter = new ColorMatrixColorFilter(matrix);
imageview.setColorFilter(filter);
For reference
Isn't that exactly what the code you're linking to does? It takes a color bitmap ("bmp"), creates a duplicate bitmap ("bm"), and then draws the color bitmap into "bm" using the filter to turn it into grayscale. From that point on, you can use "bm" as an actual grayscale bitmap and do whatever you want to do with it.
You'd need to tweak the sample a bit (it's using hard-coded sizes, you may want to just clone the size of the original bitmap), but other than that, this seems to be as ready-to-use as it gets, depending on what you want.
I'd like to mention that with this approach one important aspect must be taken in account. BitMap's on Android are stored in the NativeHeap. By just "creating bitmaps", you'll eventually clog the memory, getting an OutOfMemoryException (OOM).
Therefor, the bitmap must always be .recycled().
Here's a more efficient way, which I've made to support all versions of Android:
// https://xjaphx.wordpress.com/2011/06/21/image-processing-grayscale-image-on-the-fly/
#JvmStatic
fun getGrayscaledBitmapFallback(src: Bitmap, redVal: Float = 0.299f, greenVal: Float = 0.587f, blueVal: Float = 0.114f): Bitmap {
// create output bitmap
val bmOut = Bitmap.createBitmap(src.width, src.height, src.config)
// pixel information
var A: Int
var R: Int
var G: Int
var B: Int
var pixel: Int
// get image size
val width = src.width
val height = src.height
// scan through every single pixel
for (x in 0 until width) {
for (y in 0 until height) {
// get one pixel color
pixel = src.getPixel(x, y)
// retrieve color of all channels
A = Color.alpha(pixel)
R = Color.red(pixel)
G = Color.green(pixel)
B = Color.blue(pixel)
// take conversion up to one single value
B = (redVal * R + greenVal * G + blueVal * B).toInt()
G = B
R = G
// set new pixel color to output bitmap
bmOut.setPixel(x, y, Color.argb(A, R, G, B))
}
}
// return final image
return bmOut
}
#TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
#JvmStatic
fun getGrayscaledBitmap(context: Context, src: Bitmap): Bitmap {
// https://gist.github.com/imminent/cf4ab750104aa286fa08
// https://en.wikipedia.org/wiki/Grayscale
val redVal = 0.299f
val greenVal = 0.587f
val blueVal = 0.114f
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1)
return getGrayscaledBitmapFallback(src, redVal, greenVal, blueVal)
val render = RenderScript.create(context)
val matrix = Matrix4f(floatArrayOf(-redVal, -redVal, -redVal, 1.0f, -greenVal, -greenVal, -greenVal, 1.0f, -blueVal, -blueVal, -blueVal, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f))
val result = src.copy(src.config, true)
val input = Allocation.createFromBitmap(render, src, Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT)
val output = Allocation.createTyped(render, input.type)
// Inverts and do grayscale to the image
#Suppress("DEPRECATION")
val inverter =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
ScriptIntrinsicColorMatrix.create(render)
else
ScriptIntrinsicColorMatrix.create(render, Element.U8_4(render))
inverter.setColorMatrix(matrix)
inverter.forEach(input, output)
output.copyTo(result)
src.recycle()
render.destroy()
return result
}

Android Canvas PorterDuff not masking correctly

What I'm trying to do
I'm trying to implement a view that can be masked. Let's say the user has an image like this:
which gets masked by a custom shape like this:
The resulting image should only let the shoe shine through and make the background transparent.
Implementation
I have tried to implement it this way:
Make all black pixels in the mask transparent
Override the View's draw function to draw the mask over the original image using an xfermode (PorterDuffXfermode) of DST_IN. (See Android docs for PorterDuff.Mode)
This works perfectly fine and gives me this image (green pixels mean transparent):
While this works perfectly fine, I couldn't implement a custom "drawing" functionality to let the user draw or erase the mask. I only succeeded in doing one or the other, but not both at the same time. This is what I achieved right now:
note that erasing the mask works as expected, but trying to extend the mask doesn't work and paints white pixels (instead of letting the original image (shoe) shine through).
This is the code I'm using right now:
override fun draw(baseCanvas: Canvas) {
super.draw(baseCanvas)
val image = imageBitmap
val mask = maskBitmap
val drawingBitmap = drawingBitmap
if (image != null && mask != null && drawingBitmap != null) {
run {
val canvas = Canvas(drawingBitmap)
// 1. Fill with white
canvas.drawColor(Color.WHITE)
// 2. Draw mask and only let non-transparent pixels through
val paint = Paint(Paint.ANTI_ALIAS_FLAG)
paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_IN)
canvas.drawBitmap(mask, 0f, 0f, paint)
// 3. Draw all lines point to point with white color and custom xfermode
paint.xfermode = null
paint.style = Paint.Style.STROKE
paint.color = Color.WHITE
paint.strokeWidth = penSize
paint.isDither = true
paint.strokeJoin = Paint.Join.ROUND
paint.strokeCap = Paint.Cap.ROUND
paint.pathEffect = CornerPathEffect(10f)
paint.isAntiAlias = true
lines.forEach { line ->
paint.xfermode = when (line.drawMode) {
DrawMode.ERASE -> PorterDuffXfermode(PorterDuff.Mode.DST_OUT)
DrawMode.DRAW -> PorterDuffXfermode(PorterDuff.Mode.SRC)
}
val path = Path().also { path ->
val points = line.points
val range = points.size - 1
for (i in 1..range) {
path.moveTo(points[i - 1].x, points[i - 1].y)
path.lineTo(points[i].x, points[i].y)
}
}
canvas.drawPath(path, paint)
}
}
run {
val canvas = Canvas(image)
val paint = Paint(Paint.ANTI_ALIAS_FLAG)
paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_ATOP)
canvas.drawBitmap(drawingBitmap, 0f, 0f, paint)
}
}
}
Important variables in scope:
imageBitmap: The shoe bitmap
maskBitmap: The bitmap that contains white colors for the original shape to shine through, transparent for everything that should be transparent
drawingBitmap: An empty bitmap with the same size as imageBitmap, I use this for drawing only and then draw that result onto the imageBitmap (using a Canvas, see second run block)
lines: The lines I want to draw. A line consists of a drawMode (draw or erase) and a list of all points I have tracked.
Weird Observation
The thing that's confusing me right now is that when I fill the canvas black before adding the image:
run {
val canvas = Canvas(image)
canvas.drawColor(Color.BLACK) // <-- ADD THIS HERE
val paint = Paint(Paint.ANTI_ALIAS_FLAG)
paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_ATOP)
canvas.drawBitmap(drawingBitmap, 0f, 0f, paint)
}
it looks like it's working fine:
Same result if I fill the canvas RED, it lets the Red color shine through instead of Black. Why does it work with the color, but not the original bitmap?
It even looks correct if inspected in the debugger:
Question
Does anyone here know why this does not work as I expect it to work? I tried to play around with all kinds of different PorterDuff modes, but couldn't manage to get it working smoothly.
Any help appreciated!
Okay I got it working. I'm not sure if there's a more efficient way to achieve this, but this is what I did:
Create a fourth Bitmap called imageDrawingBitmap.
Set imageDrawingBitmap to the actual ImageView
As in the code snippet above, first draw the mask, then paths ontop of that
Then create a Canvas for the imageDrawingBitmap
Draw original image in that Canvas
Draw mask (maskDrawingBitmap from step 3.) in that Canvas with PorterDuff DST_ATOP.
That seems to be working fine.

Semi-transparent white bitmap is shown like opaque white

I am trying to create bitmap with semi-transparent background (not black). I use the next code:
val result = drawable.bitmap.copy(Bitmap.Config.ARGB_8888, true)
for (y in 0 until result.height) for (x in 0 until result.width) {
val dstColor = Color.argb(100, 255, 255, 255)
result.setPixel(x, y, dstColor)
}
But all I see is white opaque white color. I've tried to set alpha param to 0, use different colors (read, green), but it doesn't work. What the possible reasons?
After getting a copy of the bitmap, execute the following line to create an alpha channel:
result.setHasAlpha(true)
Everything else should work as is.
For example, take your code and make the changes as follows:
// Get the bitmap. What the bitmap is doesn't really matter. Here it is just a jpg.
val drawable = ResourcesCompat.getDrawable(resources, R.drawable.somebitmap, null) as BitmapDrawable
val result = drawable.bitmap.copy(Bitmap.Config.ARGB_8888, true)
result.setHasAlpha(true)
val dstColor = Color.argb(100, 255, 255, 255)
for (y in 0 until result.height) for (x in 0 until result.width) {
result.setPixel(x, y, dstColor)
}
image.setImageBitmap(result)
If result.setHasAlpha(true) is commented out, then we will see the following image. Here there is no translucence on the image.
If we uncomment result.setHasAlpha(true) then we can see the translucence:
I test the next approach creating a layout with only an image view (bitmap_container id), and it works, but i needed to change the background viewgroup color to see it:
val bitmap = Bitmap.createBitmap(
100,
100,
Bitmap.Config.ARGB_8888
)
val color = Color.argb(100, 255, 255, 255)
Canvas(bitmap).apply { drawColor(color) }
bitmap_container.setImageBitmap(bitmap)

How to draw a rectangle on the screen using coordinates with texture view?

I am previewing camera feed to texture view using cameraX. How to draw a rectangle on the screen using coordinates that i have? I don't want a complex function. I simply want to draw a rectangle.
I am using kotlin and android studio 4.0.
I would go for an ImageView overlapping on top of the Textureview at the same xml. This imageview will load a transparent bitmap that will have only the rectangle drawn. If you have the coordinates u have to do:
val myRectPaint = Paint()
myRectPaint.strokeWidth = 5F
myRectPaint.color = Color.RED
myRectPaint.style = Paint.Style.STROKE
// Create a Canvas object for drawing on the original bitmap provided
val tempBitmap =
Bitmap.createBitmap(bitmap!!.width, bitmap.height, Bitmap.Config.ARGB_8888)
val tempCanvas = Canvas(tempBitmap)
tempCanvas.drawBitmap(bitmap, 0F, 0F, null)
tempCanvas.drawRoundRect(
RectF(x1.toFloat(), y1.toFloat(), x2.toFloat(), y2.toFloat()),
2f,
2f,
myRectPaint
)
// Use this to widen picture on top or bottom
val croppedFaceBitmap =
Bitmap.createBitmap(tempBitmap, x1, y1, x2, y2)
In any case you can also follow this example from tensorflow github where round boxes are drawn when object is detected.
Hope I helped

Android Canvas: Draw only part of precalculated Path

Is it possible to draw only part of a Path? Let's say my Path is from x = 0 to x = 2000, the whole Path is calculated on start, and the Canvas is placed on HorizontalScrollView. When scroll x = 500, I want to draw only from 500 to 1000 of that Path; when x = 0 draw 0 to 1000, when x = 1500, draw 1000 to 1500 and when x = 2000 draw 1000 to 2000.
Path is a bezier curve, so if the calculation needs to be done all the time, it's damaging performance.
Thanks.
You can use getSegment method for this purpose, like this (Kotlin):
private fun getSubPath(path: Path, start: Float, end: Float): Path {
val subPath = Path()
val pathMeasure = PathMeasure(path, false)
pathMeasure.getSegment(start * pathMeasure.length, end * pathMeasure.length, subPath, true)
return subPath
}
Usage:
val subPath = getSubPath(path = originalPath, start = 0.2f, end = 0.8f)
I may have an answer for you.
The Picture class is used to store pictures that do not change and then write them to a canvas.
For example, you could have 4 different Picture objects, each with part of the Bezier curve, then write them when you want them.
Some code might look something like this:
Picture b1 = new Picture();
Canvas c1 = b1.beginRecording(500, height);
// draw
b1.endRecording();
Picture b2 = new Picture();
Canvas c2 = b2.beginRecording(500, height);
c2.translate(-500, 0);
// draw
b2.endRecording();
Picture b3 = new Picture();
Canvas c3 = b3.beginRecording(500, height);
c3.translate(-1000, 0);
// draw
b3.endRecording();
Picture b4; = new Picture();
Canvas c4 = b4.beginRecording(500, height);
c4.translate(-1500, 0);
// draw
b4.endRecording();
(if x < 500) {
// draw c1
}
...
There's probably a way to only draw it once, rather than 4 times, but I'm just posting what I know works. If you can find a way to partition the canvas, then you only need to draw it once.
Got the solution by drawing the whole line, and after that calculate Rect to draw on top of it, to hide particular part on visible part.
This is to demonstrate how I did this, I am drawing the whole Path, and to get it clip, I draw a rectangle on top of it to right position of screen, so it looks like the path is clipping.

Categories

Resources