RGB value to HSL converter - android

Google maps api v3 allows "styles" to be applied to the map, including setting the color of various features. However, the color format it uses is HSL (or what seems like it):
hue (an RGB hex string)
lightness (a floating point value between -100 and 100)
saturation (a floating point value between -100 and 100)
(from the docs)
I managed to find RGB to HSL converters online, but I am unsure how to specify the converted values in a way that google maps will accept. For instance, a typical HSL value given by a converter would be: 209° 72% 49%
How does that HSL value map to the parameters I specified from the google maps api? i.e. how does a hue degree value map to an RGB hex string and how does a percentage map to a floating point value between -100 and 100?
I am still uncertain how to do the conversion. I need to, given an RGB value, quickly convert it to what google maps expects so that the color will be identical...

Since the hue argument expects RGB, you can use the original color as the hue.
rgb2hsl.py:
#!/usr/bin/env python
def rgb2hsl(r, g, b):
#Hue: the RGB string
H = (r<<16) + (g<<8) + b
H = "0x%06X" % H
#convert to [0 - 1] range
r = float(r) / 0xFF
g = float(g) / 0xFF
b = float(b) / 0xFF
#http://en.wikipedia.org/wiki/HSL_and_HSV#Lightness
M = max(r,g,b)
m = min(r,g,b)
C = M - m
#Lightness
L = (M + m) / 2
#Saturation (HSL)
if L == 0:
S = 0
elif L <= .5:
S = C/(2*L)
else:
S = C/(2 - 2*L)
#gmaps wants values from -100 to 100
S = int(round(S * 200 - 100))
L = int(round(L * 200 - 100))
return (H, S, L)
def main(r, g, b):
r = int(r, base=16)
g = int(g, base=16)
b = int(b, base=16)
print rgb2hsl(r,g,b)
if __name__ == '__main__':
from sys import argv
main(*argv[1:])
Example:
$ ./rgb2hsl.py F0 FF FF
('0xF0FFFF', 100, 94)
Result:
Below is a screenshot showing the body set to a rgb background color (#2800E2 in this case), and a google map with styled road-geometry, using the values calculated as above ('0x2800E2', 100, -11).
It's pretty clear that google uses your styling to create around six different colors centered on the given color, with the outlines being closest to the input. I believe this is as close as it gets.
From experimentation with: http://gmaps-samples-v3.googlecode.com/svn/trunk/styledmaps/wizard/index.html
For water, gmaps subtracts a gamma of .5. To get the exact color you want, use the calculations above, and add that .5 gamma back.
like:
{
featureType: "water",
elementType: "geometry",
stylers: [
{ hue: "#2800e2" },
{ saturation: 100 },
{ lightness: -11 },
{ gamma: 0.5 },
]
}

We coded a tool which exactly does want you want. It takes hexadecimal RGB values and generates the needed HSL code. It comes with a preview and Google Map JavaScript API V3 code output. Enjoy ;D
http://googlemapscolorizr.stadtwerk.org/

From the linked page:
Note: while hue takes an HTML hex color value, it only uses this value to determine the basic color (its orientation around the color wheel), not its saturation or lightness, which are indicated separately as percentage changes. For example, the hue for pure green may be defined as "#00ff00" or "#000100" within the hue property and both hues will be identical. (Both values point to pure green in the HSL color model.) RGB hue values which consist of equal parts Red, Green and Blue — such as "#000000" (black) and "#FFFFFF" (white) and all the pure shades of grey — do not indicate a hue whatsoever, as none of those values indicate an orientation in the HSL coordinate space. To indicate black, white or grey, you must remove all saturation (set the value to -100) and adjust lightness instead.
At least as I read it, that means you need to convert your angle based on a color wheel. For example, let's assume 0 degrees is pure red, 120 degrees is pure blue and 240 degrees is pure green. You'd then take your angle, figure out which two primaries it falls between, and interpolate to determine how much of each primary to use. In theory you should probably use a quadratic interpolation -- but chances are that you can get by reasonably well with linear.
Using that, 90 degrees (for example) is 90/120 = 3/4ths of the way from red to blue, so your hex number for the hue would be 0x00010003 -- or any other number that had green set to 0, and a 1:3 ratio between red and blue.

I needed to match colors exactly. So I used the tool that #stadt.werk offers (http://googlemapscolorizr.stadtwerk.org/) to get close.
But then I ran into the problem explained by #bukzor where the Google Maps API creates variations on your shade, none of which seem to be exactly what I specified.
So I pulled up the map in a browser, took a screenshot of just the area with the two shades that weren't quite matching, opened it up in an image editor (pixlr.com, in my case), used the color-sucker tool to get the saturation and lightness for the shade, adjusted my saturation and/or lightness in the Google API call by 1, and repeated until I got something that seems to match perfectly.
It is possible, of course, that Google Maps API will do different things with the colors on different devices/browsers/etc., but so far, so good.
Tedious, yes, but it works.

Related

Weighted heat maps in android

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

OpenCV InRange parameter

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.

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.

OpenCV Android Green Color Detection

currently I'm making an app where user will detect green colors. I use this photo for testing:
My problem is that I can not detect any green pixel. Before I worked with blue color and everything worked fine. Now I can't detect anything though I tried different combinations of RGB. I wanted to know whether it's problem with green or my detection range, so I made an image in paint using (0, 255, 0) and it worked. Why it can't see this circle then? I use this code for detection:
Core.inRange(hsv_image, new Scalar([I change this value]), new Scalar(60, 255, 255), ultimate_blue);
It could have been that I set wrong Range, but I use Photoshop to get color of one of green pixels and convert RGB value of it into HSV. Yet it doesn't work. It don't detect even pixel that I've sampled. What's wrong? Thanks in advance.
Using Miki's answer:
Green color is HSV space has H = 120 and it's in range [0, 360].
OpenCV halves the H values to fit the range [0,255], so H value instead of being in range [0, 360], is in range [0, 180].
S and V are still in range [0, 255].
As a consequence, the value of H for green is 60 = 120 / 2.
You upper and lower bound should be:
// sensitivity is a int, typically set to 15 - 20
[60 - sensitivity, 100, 100]
[60 + sensitivity, 255, 255]
UPDATE
Since your image is quite dark, you need to use a lower bound for V. With these values:
sensitivity = 15;
[60 - sensitivity, 100, 50] // lower bound
[60 + sensitivity, 255, 255] // upper bound
the resulting mask would be like:
You can refer to this answer for the details.

android maps v2 polygon transparency

I'm using Google Maps v2 API for android and I don't manage to control the transparency of the fillColor.
I would like to be able to see the map under a filled polygon.
Is there a way to do that ?
Thanks for any help !
Well, let me describe how standard 4 bytes color is encoded:
Standard pixel color consists of 4 bytes:
A (alpha channel) - 0-255 (0 - fully transparent, 255 - fully opaque)
R (red color channel) - 0-255
G (green color channel) - 0-255
B (blue color channel) - 0-255
Each channel represents the saturation of particular color part. So if we need to create fully opaque red color, we need to specify following channel values:
255,255,0,0
If we want to make it half-transparent, we need to divide alpha channel value by 2 (255/2 = 127):
127,255,0,0
So let's go back to the android. In Android in most cases you can specify color by specifying it's hex value:
polygon.setFillColor(0xFF00FF00); //not transparent green color
In hex every channel can be specified by 2 hex digits:
A(FF)
R(00)
G(FF)
B(00)
If you want to make this color half-transparent, you need to divide FF by 2:
0xFF/2 = 0x7F //the same as 255/2 = 127
polygon.setFillColor(0x7F00FF00); //half-transparent green color
So basically what you need to do - is to play with alpha channel value in order to get transparency you are looking for
cir=map.addCircle(new CircleOptions().center(new LatLng(10.938341, 76.9548734))
.radius(150)
.strokeColor(Color.rgb(0, 50, 100))
.strokeWidth(5)
.fillColor(Color.argb(20, 50, 0, 255))
.zIndex(55));
cir.setVisible(true);
Basically what worked for Me is :
polygon = mGoogleMap.addPolygon(new PolygonOptions().
addAll(points)
.fillColor(0x33FF0000)
.strokeColor(Color.RED)
.strokeWidth(3))

Categories

Resources