Can I generate 1:1 pixel data within an Android application? - android

I wish to check the Android display is showing the correct tone curve. If I generate a patch with alternate black and white lines surrounded by a 50% grey, and the two grey levels match then the first stop of the tone curve is right. I can then generate a patch with alternate black and 50% grey lines surrounded by a 25% grey, and so on.
I can find the current display size. I can make RGB raster data that fits. If needs be I can make the image with 2x2 pixel replication for things like retina displays. But I cannot see how to get the raster data to the display without risking resizing.
The particular image I have described might be generated using a matte texture, or some other trick. I have other vision test images that more complicated, and I currently generate as raster data in another program. So, I am really looking for something that can take a rectangle of custom RGB data and stick it onto the screen.
Maybe the tool is there in Android Studio, staring me in the face. But I can't see it.
(the following day)
I have found the Bitmap class. This is probably what I wanted.
(several days later)
No. I am still not there. I can generate an ImageView. I would like to generate a region of bitmap data with alternate black and white lines.
My experiments are in Kotlin.
I have found the problems in getting the dimensions of an ImageView in pixels. The layout effectively works the other way: you say define your layout, and the library works out the dimensions and the resize parameters for you. resize and the size cannot usually be calculated until the view has been laid out. See for instance...
Why does setting view dimensions asynchronously not work in Kotlin?
There is a Java solution that uses viewTreeObserver(). I can use this to get the dimensions in pixels in Toast, but I can't get the value out of the observer and into the context.
The exact pixel size is not the issue here. I could make the ImageView 50% of the display height and width, and calculate the number of pixels as a fraction of the screen dimensions. This would be accurate enough for the general layout of the grey border and the block of stripes. I could use the to lay out the canvas, and then force the view to fit that at 1:1 scale.
It feels that these tools are not supposed to work this way. Is there some completely different way of doing this that I am missing?
Postscript:
There was a way to do this...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityToneBinding.inflate(layoutInflater)
setContentView(binding.root)
stripeColour = lastColour
step = resources.displayMetrics.density
// Initialize the ImageView
stripeView = findViewById(R.id.stripeView)
stripeView.doOnLayout {
val myBitmap = Bitmap.createBitmap(
it.measuredWidth,
it.measuredHeight,
Bitmap.Config.ARGB_8888
)
val myCanvas = Canvas(myBitmap)
val xMax = it.measuredWidth.toFloat()
val yMax = it.measuredHeight.toFloat()
val myPaint = Paint()
myPaint.isAntiAlias = false
myPaint.color = Color.rgb(0, 0, 0)
var y = 0.0f
while (y < yMax) {
myCanvas.drawRect(0.0f, y, xMax, y+step, myPaint)
y += 2.0f*step
}
stripeView.setImageBitmap(myBitmap)
}
The black stripes are opaque, and I could update the other stripes by changing the background. The stripes all had nice sharp edges, without the interpolation I had been getting at the edges. This is a rather specific solution for my particular problem, but it seemed to work fine.
If you want to try it yourself, be warned: I am now seeing something I do not understand. It seems the light and dark stripes are on average brighter then they should be, particularly with dark colours. The tone curve for the display seems to fit the sRGB standard well when I measure large patches, but the sum of the light and dark stripes isn't what it should be. So, this is not the test for the tone curve I was hoping for.

Once I found the terms 'Bitmap' and 'Canvas' I found the right sort of tools, but it was hard to see how to string them together to make something that works. Here is a simple exercise that helped me...
https://developer.android.com/codelabs/advanced-android-kotlin-training-canvas#10
Ignore the bit that sets SYSTEM_UI_FLAG_FULLSCREEN. This is no longer supported. There are other ways to do the same thing, but this example works perfectly well without it.

If you want to render something at pixel resolution, do the rendering in a doOnLayout() call. You can then get the view height and width. The ImageView layout width and height is 0dp (match_constraints). I am still not quite there: my double height stripe pattern is still being interpolated.
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)
// Toast.makeText(applicationContext, w.toString(), Toast.LENGTH_SHORT).show()
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)
}
}
}
Here's a revised version that gives my sharp stripes without the hardcoded 3.0 value...
step = resources.displayMetrics.density
// Initialize the ImageView
stripeView = findViewById(R.id.stripeView)
stripeView.doOnLayout {
val myBitmap = Bitmap.createBitmap(
it.measuredWidth,
it.measuredHeight,
Bitmap.Config.ARGB_8888
)
val myCanvas = Canvas(myBitmap)
val xMax = it.measuredWidth.toFloat()
val yMax = it.measuredHeight.toFloat()
val myPaint = Paint()
myPaint.isAntiAlias = false
myPaint.color = Color.rgb(0, 0, 0)
var y = 0.0f
while (y < yMax) {
myCanvas.drawRect(0.0f, y, xMax, y+step, myPaint)
y += 2.0f*step
}
stripeView.setImageBitmap(myBitmap)
}
I am drawing the black stripes as opaque rectangles. The background colour gives the other stripes. This means I can update the stripe colour without having to redraw.

Related

How to remove green border from google heatmap?

How to remove border color from android google heatmap? I'm using this code to draw:
fun drawPolygons(polygons: Array<NetworkMapPolygonModel>, scale: Float) {
map?.let { map ->
val points = ArrayList<WeightedLatLng>()
polygons.forEach {
val intensity = ((it.signalStrength - 12) * -1).toDouble()
points.add(WeightedLatLng(
LatLng(it.aLatitude, it.aLongitude), intensity
))
}
val radius = 40
val provider = HeatmapTileProvider.Builder()
.weightedData(points)
.radius(radius)
.maxIntensity(48.0)
.build()
map.addTileOverlay(TileOverlayOptions().tileProvider(provider))
}
}
But google map drawing me this map:
I want to remove outer green border (in red square in the screenshot). But cannot find how to do this.
Please help!
To simply eliminate the green (and therefore the representation of data) use the gradient property of the HeatmapFileProvider.Builder to change the initial color from green (default) to yellow and change the starting threshold from the threshold that corresponded to green (0.2 default) to approximately (0.4) (you'll have to experiment with this number - at the bottom of this answer I show how to determine this and it is 0.454). And rather than a gradual fade-in from transparent I'll show how to start fully opaque at the desired color.
Before diving into modifications, understand that there is a default gradient used without being specified and it is this:
// Create the gradient.
val colors = intArrayOf(
Color.rgb(120, 225, 0), // green
Color.rgb(255, 0, 0) // red
)
val startPoints = floatArrayOf(0.2f, 1f)
val gradient = Gradient(colors, startPoints)
// Create the tile provider.
val provider = HeatmapTileProvider.Builder()
.data(latLngs)
.gradient(gradient)
.build()
In what follows, the colors array and startPoints are modified to demonstrate the point for each adjustment. So in this snippet, it shows eliminating green but transitioning from transparent to yellow (not what you are looking for but just an example).
// Create the gradient.
val colors = intArrayOf(
Color.rgb(255, 225, 0), // yellow
Color.rgb(255, 0, 0) // red
)
val startPoints = floatArrayOf(0.4f, 1f)
val gradient = Gradient(colors, startPoints)
In this answer I've used my own data representing crime statistics in Sacremento. You'll soon see why green is a good choice for the edge data.
The gradient property is composed of two controls:
colors
starting points (0.0 to 1.0)
By default colors is (GREEN,RED) and starting points is (0.2, 1.0). One thing to note is if the first starting point is non-zero (as in the defaults), the gradient from pts 0.0 to 0.2 transition from transparent to the color; otherwise it starts with the first color at pt 0.0.
Image (A) is the default settings for my data.
I then wanted to see where the green actually started (0.2) without the transition from transparent to green (0.0 - 0.2). To do this I modified the gradient to be transparent up to close to 2.0 - then introduce a border (black) from close-to-2.0 to 2.0 and the rest is defaults.
// Create the gradient.
val colors = intArrayOf(
Color.argb(0, 0, 0, 0), // transparent
Color.argb(0, 0, 0, 0), // transparent
Color.rgb(0, 0, 0), // black
Color.rgb(120, 255, 0), // green
Color.rgb(255, 0, 0) // red
)
val startPoints = floatArrayOf(0.0f, 0.15f, 0.18f, 0.2f, 1f)
val gradient = Gradient(colors, startPoints)
Image (B) is this "border" added to show where the pure green data (2.0+) starts:
And now to address the issue of removing green. The default interpolation is summarized as follows: 0 - 0.2f (transparent to green) and 0.2f - 1.0f (green to red). So somewhere in there is the interpolated yellow. For this answer, an approximation is made that the yellow is roughly 0.4 (but I'll follow up with a calculation to show how to figure that out). Again I add the border to show exactly where the yellow (0.4) is starting:
// Create the gradient.
val colors = intArrayOf(
Color.argb(0, 0, 0, 0), // transparent
Color.argb(0, 0, 0, 0), // transparent
Color.rgb(0, 0, 0), // black
Color.rgb(255, 255, 0), // yellow
Color.rgb(255, 0, 0) // red
)
val startPoints = floatArrayOf(0.0f, 0.35f, 0.38f, 0.4f, 1f)
val gradient = Gradient(colors, startPoints)
This answer demonstrates how to control the color results; the more pertinent question which I cannot answer is what information are you trying to convey: eliminating the "green" is eliminating data, whereas turning the data into a transition from yellow data using default starting points would not eliminate data but just eliminate green. (I did not post an example of this but worth considering.)
(A)
(B)
(C)
This next part of the answer focuses on color; hopefully not too far off topic.
Again, in the default gradient there are 2 percentiles specified (0.2, 1.0) and one implied starting at 0.0. And again, the colors for these three are: (0x0078E100 (fully transparent green(120,225,0)), 0xFF78E100 (opaque green), 0xFFFF0000 (opaque red)).
In the OP the question is in terms of color ("remove green border") which leads to having to make an assumption: up to what point should the removal stop. I chose to assume yellow - but since the colors represent percentiles of data really the question should be phrased in terms of percentiles to be precise.) But looking at the data representation in terms of color presents a problem: Where is the data percentile for yellow given the default gradient.
So to help this discussion I created a simple TileProvider whose purpose is to display the gradient of color in each tile rendered. Here's a sample:
This image shows one full tile and two partial tiles at the top and bottom; so here focus on the one full tile in the middle.
Some key points: (1) the tile starts at fully transparent (data percentile 0.0) and transitions to the first color in the default gradient at which point a black line segment is drawn representing the 20th percentile. From there the tile transitions from green to the second color in the default gradient (red) representing the 100th percentile. Along the way a second black line segment is drawn for the color in the gradient "closest" to "yellow".
In order to discover the closest data percentile to yellow, some knowledge of how the gradient is created is necessary. In short, the endpoints of each color segment in RGB values of the colors provided () are converted to HSV values. From these HSL values the ratio between the start and end points is applied the HSV value and then converted back to RGB.
Once the RGB color within the gradient segment is determined it's "distance" to the target ("tgt") color (YELLOW) is computed and the minimum distance found:
int deltaR = Color.red(tgt) - Color.red(ic);
int deltaG = Color.green(tgt) - Color.green(ic);
int deltaB = Color.blue(tgt) - Color.blue(ic);
double d = Math.sqrt(deltaR*0.3F*deltaR*0.3F + deltaG*0.59F*deltaG*0.59F + deltaB*0.11*deltaB*0.11);
As it turns out the data percentile closest to yellow is 45.4%. So the data presented in the final image above (3) represents the upper 54.6% of the data.
For reference here is the implementation of the TileProvider used. The mColors array is the 1000 element colors map which the heat map generates by default:
private class MyTileProvider implements TileProvider {
public MyTileProvider() {
}
#Override
public Tile getTile(int x, int y, int zoom) {
Bitmap tile = Bitmap.createBitmap(512,512,Bitmap.Config.ARGB_8888);
tile.setPixels(mColors,0,Math.max(512,mPixelsPerColor),0,0,512,512);
Log.d(TAG,"Tile gen done: "+x+","+y+" "+zoom);
return convertBitmap(tile);
}
}
For reference on gradients and heat maps use this answer:
Weighted heat maps in android
For reference on computing "color distance": https://stackoverflow.com/a/1847112/2711811
For reference on the map utils heat map implementation (a subdirectory within the andoid-maps-utils repo): https://github.com/googlemaps/android-maps-utils/tree/ac9684d627905587b020a0eac301e94478804a48/library/src/main/java/com/google/maps/android/heatmaps

How to get to edge pixel of an image

I'm trying to get the background color of image that I download with glide.
I think(please correct me if I'm wrong) that the best way is to get the edge pixel(top - left most, bottom - right most etc, my images are usually center image with solid background color)
I'm getting specific pixel color im my recycler view like this(based on the accepted answer here: How to Get Pixel Color in Android):
val bitmap = (binding.image.drawable as BitmapDrawable).bitmap
val pixel = bitmap.getPixel(x,y)
My question is how can I get the edge pixel of the image so I can determine the color of the background?
If you have the bitmap, you can use getWidth() and getHeight() (or just bitmap.width and bitmap.height in Kotlin) to get the X and Y coordinates of the far edges (it starts at 0 so it will be height - 1 etc).
Then you can plug those into getColor
with(bitmap) {
// origin (0,0) is the top left corner
val topLeft = getColor(0, 0)
val bottomRight = getColor(width-1, height-1)
....
}
(I used with to avoid going bitmap.whatever over and over)
This might not be the best way to get the actual background colour, it really depends (what if the image has a thin border?) and this could be a tricky problem! Just in case it helps, Android has the Palette library which lets you generate a bunch of colour swatches from a Bitmap, so that might be useful if you want to kind of pull out the main colours from an image and pick one

iOS and Android Algorithm or library for feathering edges of the images similar to photoshop's

I am looking for iOS and Android library for (preferably) or algorithm that would help me to feather edges of the image in similar way how it is handled in Photoshop. The illustration below shows the desired effect of the algorithm. I am not interested feathering bounds of the image, just alpha edges. I have been searching for algorithm that can accomplish it for few days without luck. Any help will be appreciated.
Assuming that you have alpha channel (like on photo with transparent background) it seems that regular convultion blur matrix should satisfy you.
However instead of going through RGB channels - you should go through ALPHA channel only.
Check the blur filter here:
https://en.wikipedia.org/wiki/Kernel_%28image_processing%29
You are interested in box blur/gaussian blur. However to make this effect more smooth - you should use matrix of bigger size.
The reason that algorithm will satisfy your needs is that if all surrounding pixels have alpha 0 - it will be still 0. If 255 - it will stay 255. Just pixels in area of border between alpha 0/255 will be affected.
Edit:
Please check this fiddle with chrome (in ff that's really slow):
http://jsfiddle.net/5L40ms65/
You can take a look into algorithm in the end of code. Since implementation i noted that:
- no need to blur if all neigbour pixels are 255 or 0 (alpha channel)
- it is required to blur also RGB in other case
In general:
RADIUS = 2 (makes total width of matrix = 5)
For x = 0..width
for y = 0..width
if all pixels in square of radius 2 are alpha = 0
do nothing
elsif all pixels in square have alpha = 255
do nothing
else
pixel[x][y].RGB = average RGB of adjacent pixels where alpha != 0
pixel[x][y].ALPHA = average ALPHA in square
Example result with radius=2
Of course this is rather concept program, there is a lot of place for memoization and tuning this script however it should make a big picture clear
You could change the alpha of your border based on the background color of the view.
var r:Float!
var g:Float!
var b:Float!
var a:Float!
if self.view.backgroundColor.getRed(red:r, green:g, blue:b, alpha:a) {
var imgv = UIImageView(frame: CGRect(x:100, y:100, width:100, height:100))
imgv.image = UIImage(named:"name")
imgv.layer.borderWidth = 2.0
imgv.layer.borderColor = UIColor(red:r, green:g, blue:b, alpha:0.5).CGColor
imgv.layer.cornerRadius = imgv.frame.size.width / 2
imgv.clipsToBounds = true
}else{
//Could not get the RGB of background color
}
You may see errors in this code because I have not yet tested it.

Using a gradient along a path

I'm trying to create a 'glow' effect using the Android Path class. However, the gradient is not being warped to fit around the path. Instead, it is simply being display 'above' it and clipped to the path's stroke. Using a square path, the image below shows what I mean:
Instead, that should look more like this:
In other words, the gradient follows the path, and in particular wraps around the corners according to the radius set in the CornerPathEffect.
Here is the relevant part of the code:
paint = new Paint();
paint.setStyle(Style.STROKE);
paint.setStrokeWidth(20);
paint.setAntiAlias(true);
LinearGradient gradient = new LinearGradient(30, 0, 50, 0,
new int[] {0x00000000, 0xFF0000FF, 0x00000000}, null, Shader.TileMode.MIRROR);
paint.setShader(gradient);
PathEffect cornerEffect = new CornerPathEffect(10);
paint.setPathEffect(cornerEffect);
canvas.drawPath(boxPath, paint);
Any ideas?
Another alternative is to get a 'soft-edged brush' effect when defining the stroke width. I've experimented with BlurMaskFilters, but those give a uniform blur rather than a transition from opaque to transparent. Does anyone know if that's possible?
How about drawing with a soft brush bitmap? Make a soft circular brush with opacity decreasing radially outward using image editing software like Photoshop. Save as drawable, load it in a bitmap and draw it evenly spaced along your path. Make the bitmap with white coloured brush. This way you can simply multiply the given colour(Here blue) to your bitmap using PorterDuffColorFilter.
brush1=BitmapFactory.decodeResource(getResources(), R.drawable.brush_custom_one);
//This contains radially decreasing opacity brush
porter_paint.setColorFilter(new PorterDuffColorFilter(paint.getColor(), Mode.MULTIPLY));
for (int i=1;i<matrix.size();i++) {
//matrix contains evenly spaced points along path
Point point = matrix.get(matrix.get(i));
canvas.drawBitmap(brush1, point.x,point.y, porter_paint);}
The brush used is (It's there):
The final result is:
Turns out there was a stupidly obvious way of doing this. Simply re-use the same path, and adjust the stroke width and alpha on each drawing pass. Example code:
float numberOfPasses = 20;
float maxWidth = 15;
for (float i = 0; i <= numberOfPasses; i++){
int alpha = (int) (i / numberOfPasses * 255f);
float width = maxWidth * (1 - i / numberOfPasses);
paint.setARGB(alpha, 0, 0, 255);
paint.setStrokeWidth(width);
canvas.drawPath(path, paint);
}
See below for an example of the result. The left path was drawn using this method, the right path, for comparison, is drawn in a single stroke with maxWidth and 255 alpha.
This mainly works. There are two problems:
The gradient isn't as smooth as it could be. This is because each pass being drawn over the previous one results in the alpha building up too quickly, reaching 255 before the final strokes. Experimenting a bit with the line int alpha = (int) (i / numberOfPasses * 125f); (note the change to 125f rather than 255f) helps.
The path looks like it has been 'cut' on the insides of the corners. Probably some result of the CornerPathEffect applied.
What you're wanting to do, if I understand it right, is to have the gradient effectively form a "brush" for the stroke.
This is exactly what I also was trying to achieve recently, but as far as I can tell the API doesn't provide any straightforward means to do it. I have recently created an SVG to Android Canvas converter class and so I am working a lot in Inkscape lately, too. So, when I was looking into it, I wondered if it's even possible to do it in Inkscape. However, even in Inkscape it's a very non-trivial thing to do. After some searching I eventually came across this image of a gradient being applied along the course of a path, together with a download link for a tutorial beneath:
http://www.flickr.com/photos/35772571#N03/3312087295/
What I was personally trying to do at the time was to create some semi-circles where the path is a kind of neon glow as opposed to a flat colour. Talking in terms of both the Android API and the SVG standard, it seems that the only way to to do this is to create a radial gradient that's centred perfectly on the circle, and position a series of color stops in exactly the right places. Pretty tricky to do, and I certainly don't know how you'd do it to a shape like a square.
Sorry that this is a bit of a 'I couldn't do it either' rather than a useful answer! I'll follow this with interest as I'm eager to know a solution for a kind of 'soft brush' effect too.
Can be very complicated to draw a gradient than follow a path.
So I suggest you to use some library already done than make it for you.
One can be Sc-Gauges.
Have some usefully classe than you can use for your goal.
For first include the library:
dependencies {
...
compile 'com.github.paroca72:sc-gauges:3.0.7'
}
After create an image or what you want with a canvas where draw:
<ImageView
android:id="#+id/image"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
Now the code:
// Dimensions
int padding = 24;
Rect drawArea = new Rect(padding, padding, 700 - padding, 500 - padding);
// Get the main layout
ImageView imageContainer = (ImageView) this.findViewById(R.id.image);
assert imageContainer != null;
// Create a bitmap and link a canvas
Bitmap bitmap = Bitmap.createBitmap(
drawArea.width() + padding * 2, drawArea.height() + padding * 2,
Bitmap.Config.ARGB_8888
);
Canvas canvas = new Canvas(bitmap);
canvas.drawColor(Color.parseColor("#f5f5f5"));
// Create the path building a bezier curve from the left-top to the right-bottom angles of
// the drawing area.
Path path = new Path();
path.moveTo(drawArea.left, drawArea.top);
path.quadTo(drawArea.centerX(), drawArea.top, drawArea.centerX(), drawArea.centerY());
path.quadTo(drawArea.centerX(), drawArea.bottom, drawArea.right, drawArea.bottom);
// Feature
ScCopier copier = new ScCopier();
copier.setPath(path);
copier.setColors(Color.RED, Color.GREEN, Color.BLUE);
copier.setWidths(20);
copier.draw(canvas);
// Add the bitmap to the container
imageContainer.setImageBitmap(bitmap);
And this the result:
The first part of the code is just for create a bitmap where draw.
What you interest is the second part where use ScCopier.
Just give the path, the color and the with.
Note than is you are inside a view you can use onDraw for draw directly on the view canvas.
This library can used to create gauge of every kind.
If you want take a look to this site ScComponents have some free and not gauges components.

Android bitmap mask color, remove color

I am creating bitmap, next i am drawing second solid color bitmap on top of it.
And now i want to change first bitmap, so solid color that i drawed on it will be transparent.
Or simply, i want to remove all pixels of one color from bitmap.
I havie tried every colorfilter, and xfermode with no luck, is there any other possibility to remove color other that doing it pixel by pixel?
This works for removing a certain color from a bitmap. The main part is the use of AvoidXfermode. It should also work if trying to change one color to another color.
I should add that this answers the question title of removing a color from a bitmap. The specific question is probably better solved using PorterDuff Xfermode like the OP said.
// start with a Bitmap bmp
// make a mutable copy and a canvas from this mutable bitmap
Bitmap mb = bmp.copy(Bitmap.Config.ARGB_8888, true);
Canvas c = new Canvas(mb);
// get the int for the colour which needs to be removed
Paint p = new Paint();
p.setARGB(255, 255, 0, 0); // ARGB for the color, in this case red
int removeColor = p.getColor(); // store this color's int for later use
// Next, set the alpha of the paint to transparent so the color can be removed.
// This could also be non-transparent and be used to turn one color into another color
p.setAlpha(0);
// then, set the Xfermode of the pain to AvoidXfermode
// removeColor is the color that will be replaced with the pain't color
// 0 is the tolerance (in this case, only the color to be removed is targetted)
// Mode.TARGET means pixels with color the same as removeColor are drawn on
p.setXfermode(new AvoidXfermode(removeColor, 0, AvoidXfermode.Mode.TARGET));
// draw transparent on the "brown" pixels
c.drawPaint(p);
// mb should now have transparent pixels where they were red before
user487252's solution works like a charm up until API level 16 (Jelly Bean), after which AvoidXfermode does not seem to work at all.
In my particular use case, I have rendered a page of a PDF (via APV PDFView) into a pixel array int[] that I am going to pass into Bitmap.createBitmap( int[], int, int, Bitmap.Config ). This page contains line art drawn onto a white background, and I need to remove the background while preserving the anti-aliasing.
I couldn't find a Porter-Duff mode that did exactly what I wanted, so I ended up buckling and iterating through the pixels and transforming them one by one. The result was surprisingly simple and performant:
int [] pixels = ...;
for( int i = 0; i < pixels.length; i++ ) {
// Invert the red channel as an alpha bitmask for the desired color.
pixels[i] = ~( pixels[i] << 8 & 0xFF000000 ) & Color.BLACK;
}
Bitmap bitmap = Bitmap.createBitmap( pixels, width, height, Bitmap.Config.ARGB_8888 );
This is perfect for drawing line art, since any color can be used for the lines without losing the anti-aliasing. I'm using the red channel here, but you can use green by shifting 16 bits instead of 8, or blue by shifting 24.
Pixel by pixel is not a bad option. Just don't call setPixel inside your loop. Fill an array of argb ints with getPixels, modify it in place if you don't need to preserve the original, and then call setPixels at the end. You can do this row-by-row if memory is a concern, or you can just do the whole thing in one shot. You don't need to fill a whole bitmap for your overlay color since you'd just be doing a simple replace (if current pixel is color1, set to color2).

Categories

Resources