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
Related
I am trying to create a weighted heat map for my android app project. I had a look at the google documentation for it. I don't understand how to create a new gradient using colors array and starting points array. The starting array is denoted as
The starting point for each color, given as a percentage of the maximum intensity.
What does it mean? How to relate color array with starting point array?
int[] colors = {
Color.GREEN, // green(0-50)
Color.YELLOW, // yellow(51-100)
Color.rgb(255,165,0), //Orange(101-150)
Color.RED, //red(151-200)
Color.rgb(153,50,204), //dark orchid(201-300)
Color.rgb(165,42,42) //brown(301-500)
};
float[] startpoints = {
};
I need to fill this startpoints array.
Here are some assumptions:
the gradient colorMapSize is 1000 (default) but should be set to 500
the color values length is 6
the startPoints length is 6 (has to be the same length as the colors)
the colors array is specified in OP
the startingPoints array should be { 0.1F, 0.2F, 0.3F, 0.4F, 0.6F, 1.0F }
Here's a diagram to help the discussion:
ColorMap
The first thing to understand is the colorMap. This is generated by the
Gradient builder. It is an 'int' array with size by default of 1000 but is
customizable in one of the constructors - recommend 500 in your example. So every value in this array is a color value. (The size affects the resolution of your gradient.)
Color values in the colorMap are affected by 2 controls which produce color
intervals: the startPoints array and the colors array. In your example there
is 6 color intervals.
A color interval is defined as a starting color and end color and the number
of 'colorMap' slots in range. All colorMap values for any given interval
are interpolated using the start/end colors for that range.
If the first value of the startPoints array is 0 then the first color interval
is assumed to be solid - non-zero implies a transition from transparent to the first color which seems most desirable. (See example where the startPoints is set to 0 and notice the jaggedness of the outside areas.)
If the first value of the startPoints array is not 0 then the first color
interval is defined as starting with the first color (colors[0]) and a range
of the colorMapSize * the first starting point, e.g. 500 * 0.1 = 50
and ending with the same color.
For all remaining defined colors an interval is generated - again with a
starting colorMap slot, a starting color (which is the previous color end),
and ending color (which is the current color in the table) and a range.
If the last startingPoint is not 1.0, then the last color is used for start
and stop. In this example, 1.0 is probably best.
(Opacity applies to the whole layer and simply affects the alpha channel of the color.)
Tiles
This is where 'intensity' is appreciated and the effect a WeightedLatLng can play a part.
When the map is generated it divides the viewable area into tiles - the
number of tiles is a function of zoom level. At zoom level 0 there is 1 tile,
and the arbitrary zoom level tile count is 2^n (where n is the zoom level).
Each tile is further divided into buckets. The number of buckets is a function
of zoom level and Radius (blur). Each bucket corresponds to a geographical
area within the tile - think rectangle-grid.
The points inside the current tile are obtained from the data set.
For all of the points within the geographic bounds of the tile, the intensity value of the point is added to its corresponding bucket. The intensity value for a point by default is 1. The WeightedLatLng allows you to bias a point by changing this value from 1 to some number (larger to increase importance, smaller to decrease importance). So the result is the bucket intensity tally is affected from what it would be for just LatLngs.
Once the intensity buckets are tallied, the intensity values are colorized using
the colorMap determined in the first section. Note that the range of intensity values are scaled to the size of the colorMap such that the maximum intensity value maps to the last color.
The buckets are then applied to the tile and the tile is made into a bitmap
and is rendered.
Example
For an example I used some data on crime in Sacramento. I first created a non-weighted heatmap. And then created a weighted heatmap to give an importance to auto-thefts by specifying an intensity of 5.0 (verses the default of 1.0 for any point) for crimes with an NCIC code of 2404 (vehicle-theft). The 5.0 is somewhat arbitrary and really depends on the data and what you're trying to convey.
Non-Weighted / Weighted (by vehicle theft)
And an example where the first startPoint[0] is 0.0 - which shows the lack of a transition from transparent to initial color:
Here are the relevant portions of the MapActivity:
public void onMapReady(GoogleMap googleMap) {
mMap = googleMap;
mMap.getUiSettings().setZoomControlsEnabled(true);
// Add a marker in Sydney and move the camera
LatLng sydney = new LatLng(-34, 151);
mMap.addMarker(new MarkerOptions().position(sydney).title("Marker in Sydney"));
mMap.moveCamera(CameraUpdateFactory.newLatLng(sydney));
addHeatMapWeighted();
//addHeatMap();
}
public void addHeatMapWeighted() {
Gradient gradient = new Gradient(colors,startpoints);
List<WeightedLatLng> wDat = new CrimeData().getWeightedPositions();
HeatmapTileProvider provider = new HeatmapTileProvider.Builder().weightedData(wDat).gradient(gradient).build();
mMap.addTileOverlay(new TileOverlayOptions().tileProvider(provider));
}
public void addHeatMap() {
Gradient gradient = new Gradient(colors,startpoints);
List<LatLng> cDat = new CrimeData().getPositions();
HeatmapTileProvider provider = new HeatmapTileProvider.Builder().data(cDat).gradient(gradient).build();
mMap.addTileOverlay(new TileOverlayOptions().tileProvider(provider));
}
int[] colors = {
Color.GREEN, // green(0-50)
Color.YELLOW, // yellow(51-100)
Color.rgb(255,165,0), //Orange(101-150)
Color.RED, //red(151-200)
Color.rgb(153,50,204), //dark orchid(201-300)
Color.rgb(165,42,42) //brown(301-500)
};
float[] startpoints = {
0.1F, 0.2F, 0.3F, 0.4F, 0.6F, 1.0F
};
Start of Interval
OK, so you may have noticed that the start of each colorMap interval starts at a nice round number (0,50,100...) which doesn't quite match your requirements (51, 101...301). I would argue that your commented-ranges are not right since really that means there are 501 possible colors which is a bit odd. But if you really wanted that range as specified then you'd have to do some math on to come up with an alternate startPoints array of: (51/501,101/501,151/501,201/501,301/501,501/501) = (.101796407,.201596806,.301397206,.401197605,.600798403, 1.0)
Radius
The radius value is an input to the HeatMap's Gaussian Blur implementation. Sometimes a picture is the best explanation: this is an animated Gif which cycles through a HeatMap of radius 20 to 50 in steps of 10 (with the blurriest being radius 50).
Since the heat map is intended to convey meaning to the information, it's really left to the data presenter to assess what is the best radius effect. So for example, in the case of crime data, as a consumer of the data looking for a place to live, I'd probably gain more value from the data with some blur. On the other hand if the data was presenting fire stations then too much blur could very well lead one to believe they are covered when they are not.
Dots
Just using dots (circles) tells a more accurate story than the heat map and which at the broader zoom levels visually blurs just like a heat map albeit without the color. For example, here are two dot renderings, unweighted and weighted. The weighted bias is for drug crimes (NCIC codes [3500,3600)) which shows that drug crimes predominate this area.
HeatMap Trivia
From the referenced book (3):
The term "heat map" was trademarked in 1991 by software developer
Cormac Kinney. It was then acquired by SS&C Technologies, Inc. but
they did not extend the license and it was annulled in 2006.
References
Crime data: https://support.spatialkey.com/spatialkey-sample-csv-data/
Android code:
https://github.com/googlemaps/android-maps-utils/blob/master/library/src/com/google/maps/android/heatmaps/
HeatMap trademark: https://books.google.com/books?id=clIWDAAAQBAJ&pg=PA219&lpg=PA219&dq=is+heatmap+trademarked&source=bl&ots=XfcTsOc2pG&sig=EPaHG95M8uXiI5WAAwpa-e3zHXE&hl=en&sa=X&ved=0ahUKEwijtKSl2IfaAhUNy1kKHdBUAdsQ6AEIlwEwCg#v=onepage&q=is%20heatmap%20trademarked&f=false
I'm using OpenCV on Android to find circles of specific colour's in real time. My first step is to keep only pixels which corresponds to my defined color i'm looking for (red or green in this example). Example Image.
For this purpose i'm using the method inRange().
Here is my Question: What kind of color model (RGB, BGR, HSV, ..) is required as lower-/upper-bound color parameter's? And: what is a good practice to define these color bounds in respect to natural brightness changes?
matRgba = inputFrame.rgba();
Scalar lowerColorBound = Scalar(0.0, 0.0, 0.0); // Blue, Green, Red?
Scalar upperColorBound = Scalar(0.0, 0.0, 0.0);
// convert to HSV, necessary to use inRange()
Imgproc.cvtColor(matRgba, matRgba, Imgproc.COLOR_RGB2HSV);
// keep only the pixels defined by lower and upper bound range
Core.inRange(matRgba, lowerColorBound, upperColorBound, matRgba);
The required color model for the inRange(src, lowerb, upperb, dst) function in OpenCV is HSV.
The lowerb and upperb parameters specify the required lower and upper color bounds in the HSV format. In OpenCV, for HSV, Hue range is [0,179], Saturation range is [0,255] and Value range is [0,255].
For object tracking applications a possible practice (as suggested in the official documentation) to define these two color bounds can be:
Start from a color to track in RGB format.
Convert the color to the HSV format. Let (H, S, V) be its value.
Assign the value (H - deltaH, minS, minV) to lowerb and the value (H - deltaH, maxS, maxV) to upperb.
Possible starting values for the parameters defined in step 3 can be:
deltaH = 10
minS = 100, minV = 100
maxS = 255, maxV = 255
Then you can adjust them to narrow down or enlarge the H, S, V intervals as needed.
Android: How do I add a translucent geofence over a map. I.e., i can fill it with a color but the map below is still visible.
This is what I wish to achieve
Use Color.argb(). To learn more, go to the Color class reference page (http://developer.android.com/reference/android/graphics/Color.html). Here is an example of making the blue translucent fill:
Color.argb(
100, //This is your alpha. Adjust this to make it more or less translucent
Color.red(Color.BLUE), //Red component.
Color.green(Color.BLUE), //Green component.
Color.blue(Color.BLUE)); //Blue component.
This will make the blue translucent color you wanted. If you have a custom hex color (0xFFFFFFFF), just adjust the first byte (0xFF......) to make it more or less translucent.
EDIT
Here is a simple function that you can use to pass in the alpha and color to adjust it:
int alphaAdjust(int alpha, int color) {
return Color.argb(
alpha, //This is your alpha. Adjust this to make it more or less translucent
Color.red(color), //Red component.
Color.green(color), //Green component.
Color.blue(color)); //Blue component.
}
You can create a Circle, it is very easy:
Having your geofence details already in place (geofLocation and geofRadius) you can do:
// Instantiates a new CircleOptions object and defines the center and radius
CircleOptions circleOptions = new CircleOptions()
.strokeColor(Color.BLACK) //Outer black border
.fillColor(Color.TRANSPARENT) //inside of the geofence will be transparent, change to whatever color you prefer like 0x88ff0000 for mid-transparent red
.center(geofLocation) // the LatLng Object of your geofence location
.radius(geofRadius)); // The radius (in meters) of your geofence
// Get back the mutable Circle
Circle circle = myMap.addCircle(circleOptions);
Try the examples listed in the documentation. I'd suggest to use a translucent circle to indicate your geofence.
Something like this:
LatLng latLng = ...
CircleOptions circleOptions = new CircleOptions();
circleOptions.center(latLng)
.fillColor(Color.argb(64, 0, 255, 0))
.strokeColor(Color.GREEN)
.strokeWidth(1)
.radius(100);
Notice the first argument in Color.argb() method (value 64), that argument controls the alpha/transculency of the circle, in this case it will be a transculent green circle. change the first argument value from 0 to 255 to achieve the transculency you desire. Here is the Color.argb method javadoc - http://developer.android.com/reference/android/graphics/Color.html#argb(int, int, int, int)
I am creating a 2D game using OpenGL ES 2 in Android and included with this is a health bar. The health bar changes between 5 colors as health decreases (green, yellow-green, yellow, orange, red). The texture for the health bar is coming from a texture atlas, and I would rather not clutter the texture atlas with a bar for each color (it would also be easier in the future to alter these colors).
This is the original green that I would like to reproduce using GLSL. The base color for this is #77ee9a.
What I've tried is to create a black and white version of the texture. The base green becomes #aeaeae here.
Then, I multiply the color I want with this black and white image. I realized that this wouldn't work exactly right, so I took the green I wanted (#77ee9a) and divided it by its black and white equivalent (#aeaeae) before multiplying. This resulted in:
I realized that dividing by the gray caused the green portion of the rgb value for my original green to exceed 255, creating artifacts around the bright portions of the image.
I'm not sure if there is a better way to achieve this colorizing effect, but I hope there is a solution that would allow me to.
If there isn't I would be able to create two textures, 1 pure white in the shape of the bar, and another with the transparent effects. I could then overlay these but it would require both another texture and another sprite in the game.
Thank you!
You can compute 2 separate gradients using the GLSL mix function: one that goes from black up to the selected color, corresponding to grey values of 0 up to 0.68 (#aeaeae), and another that goes from the selected color up to white for grey values of 0.68 to 1.0.
Pass the tint color (e.g. green) in as a uniform:
uniform vec4 uTintColor;
Read the grey value and take any component (since they are all the same). Then compute where the grey value lies in each of the gradients and apply them. When it is in the lower gradient, upperGradientCol will equal uTintColor because upperGradientPos will be zero, so upperGradientCol can be used as the upper bound of the lower gradient.
// read grey scale value
vec4 texel = texture2D(uTexture, vTexCoord);
float greyValue = texel.r;
// compute position in gradients:
float upperGradientPos = clamp((greyValue - 0.68) / 0.32, 0.0, 1.0);
float lowerGradientPos = clamp(greyValue / 0.68, 0.0, 1.0);
// apply upper gradient from uTintColor up to white:
vec4 upperGradientCol = mix(uTintColor, vec4(1.0, 1.0, 1.0, 1.0), upperGradientPos);
// apply lower gradient from black up to uTintColor:
vec4 lowerGradientCol = mix(vec4(0.0, 0.0, 0.0, 1.0), upperGradientCol, lowerGradientPos);
gl_FragColor = vec4(lowerGradientCol.rgb, texel.a);
To make this more versatile you could pass in different colors instead of black and white as uniforms, and same for the grey value corresponding to the tint color (currently hardcoded at 0.68).
You can actually write a shader so you just need 1 texture which gets modified in a way like: the more heath is lost, the more gray it gets
To get the color value of the "graytexture" that shall be used is:
Grayvalue = 0,299 × Red + 0,587 × Green + 0,114 × Blue
So the formula in the shader would be like:
Healthpercantage * 0.711 + 0.299, Healthpercantage * 413 + 0,587, Healthpercantage * 0.886 + 0.114
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).