I'm trying to compile an Apple sample : KMLViewer, but Xcode show this error:
variable 'lat' may be uninitializated when used here
The code is this:
// Convert a KML coordinate list string to a C array of CLLocationCoordinate2Ds
// KML coordinate lists are longitude,latitude[,altitude] tuples specified by whitespace.
static void strToCoords(NSString *str, CLLocationCoordinate2D **coordsOut, NSUInteger *coordsLenOut)
{
NSUInteger read = 0, space = 10;
CLLocationCoordinate2D *coords = malloc(sizeof(CLLocationCoordinate2D) * space);
NSArray *tuples = [str componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
for (NSString *tuple in tuples) {
if (read == space) {
space *= 2;
coords = realloc(coords, sizeof(CLLocationCoordinate2D) * space);
}
double lat, lon;
NSScanner *scanner = [[NSScanner alloc] initWithString:tuple];
[scanner setCharactersToBeSkipped:[NSCharacterSet characterSetWithCharactersInString:#","]];
BOOL success = [scanner scanDouble:&lon];
if (success)
success = [scanner scanDouble:&lat];
if (success) {
CLLocationCoordinate2D c = CLLocationCoordinate2DMake(lat, lon);
if (CLLocationCoordinate2DIsValid(c))
coords[read++] = c;
}
}
*coordsOut = coords;
*coordsLenOut = read;
}
The Xcode "solution" add 0.0 to double lat,lon (double lat = 0.0, lat), but does not work.
Any suggestion?
You need to have a default value for your variables as you only set them if certain conditions are met, but you use them regardless. This could lead to the variable not being set but you trying to use them. XCode is suggesting you default your variables to some value.
I.E:
double lat = 0.0;
double lon = 0.0;
Related
I am using a NSRulerView in MacOS in order to display line numbers next to a NSTextView.
Both views share the same font and the same font size, however while in NSTextView string rendering is automatically managed, in the NSRulerView I need to compute correct line number (and this part works fine) and then render the string inside a drawHashMarksAndLabelsInRect.
My issue is that I am unable to correctly align text between the two views. For some font it works fine while for other fonts there are visible differences.
The code I am actually using is:
#define BTF_RULER_WIDTH 40.0f
#define BTF_RULER_PADDING 5.0f
static inline void drawLineNumber(NSUInteger lineNumber, CGFloat y, NSDictionary *attributes, CGFloat ruleThickness) {
NSString *string = [[NSNumber numberWithUnsignedInteger:lineNumber] stringValue];
NSAttributedString *attString = [[NSAttributedString alloc] initWithString:string attributes:attributes];
NSUInteger x = ruleThickness - BTF_RULER_PADDING - attString.size.width;
[attString drawAtPoint:NSMakePoint(x, y)];
}
static inline NSUInteger countNewLines(NSString *s, NSUInteger location, NSUInteger length) {
CFStringInlineBuffer inlineBuffer;
CFStringInitInlineBuffer((__bridge CFStringRef)s, &inlineBuffer, CFRangeMake(location, length));
NSUInteger counter = 0;
for (CFIndex i=0; i < length; ++i) {
UniChar c = CFStringGetCharacterFromInlineBuffer(&inlineBuffer, i);
if (c == (UniChar)'\n') ++counter;
}
return counter;
}
#implementation BTFRulerView
- (instancetype)initWithBTFTextView:(BTFTextView *)textView {
self = [super initWithScrollView:textView.enclosingScrollView orientation:NSVerticalRuler];
if (self) {
self.clientView = textView;
// default settings
self.ruleThickness = BTF_RULER_WIDTH;
self.textColor = [NSColor grayColor];
}
return self;
}
- (void)drawHashMarksAndLabelsInRect:(NSRect)rect {
// do not use drawBackgroundInRect for background color otherwise a 1px right border with a different color appears
if (_backgroundColor) {
[_backgroundColor set];
[NSBezierPath fillRect:rect];
}
BTFTextView *textView = (BTFTextView *)self.clientView;
if (!textView) return;
NSLayoutManager *layoutManager = textView.layoutManager;
if (!layoutManager) return;
NSString *textString = textView.string;
if ((!textString) || (textString.length == 0)) return;
CGFloat insetHeight = textView.textContainerInset.height;
CGPoint relativePoint = [self convertPoint:NSZeroPoint fromView:textView];
NSDictionary *lineNumberAttributes = #{NSFontAttributeName: textView.font, NSForegroundColorAttributeName: _textColor};
NSRange visibleGlyphRange = [layoutManager glyphRangeForBoundingRect:textView.visibleRect inTextContainer:textView.textContainer];
NSUInteger firstVisibleGlyphCharacterIndex = [layoutManager characterIndexForGlyphAtIndex:visibleGlyphRange.location];
// line number for the first visible line
NSUInteger lineNumber = countNewLines(textString, 0, firstVisibleGlyphCharacterIndex)+1;
NSUInteger glyphIndexForStringLine = visibleGlyphRange.location;
// go through each line in the string
while (glyphIndexForStringLine < NSMaxRange(visibleGlyphRange)) {
// range of current line in the string
NSRange characterRangeForStringLine = [textString lineRangeForRange:NSMakeRange([layoutManager characterIndexForGlyphAtIndex:glyphIndexForStringLine], 0)];
NSRange glyphRangeForStringLine = [layoutManager glyphRangeForCharacterRange: characterRangeForStringLine actualCharacterRange:nil];
NSUInteger glyphIndexForGlyphLine = glyphIndexForStringLine;
NSUInteger glyphLineCount = 0;
while (glyphIndexForGlyphLine < NSMaxRange(glyphRangeForStringLine)) {
// check if the current line in the string spread across several lines of glyphs
NSRange effectiveRange = NSMakeRange(0, 0);
// range of current "line of glyphs". If a line is wrapped then it will have more than one "line of glyphs"
NSRect lineRect = [layoutManager lineFragmentRectForGlyphAtIndex:glyphIndexForGlyphLine effectiveRange:&effectiveRange withoutAdditionalLayout:YES];
// compute Y for line number
CGFloat y = NSMinY(lineRect) + relativePoint.y + insetHeight;
// draw line number only if string does not spread across several lines
if (glyphLineCount == 0) {
drawLineNumber(lineNumber, y, lineNumberAttributes, self.ruleThickness);
}
// move to next glyph line
++glyphLineCount;
glyphIndexForGlyphLine = NSMaxRange(effectiveRange);
}
glyphIndexForStringLine = NSMaxRange(glyphRangeForStringLine);
++lineNumber;
}
// draw line number for the extra line at the end of the text
if (layoutManager.extraLineFragmentTextContainer) {
CGFloat y = NSMinY(layoutManager.extraLineFragmentRect) + relativePoint.y + insetHeight;
drawLineNumber(lineNumber, y, lineNumberAttributes, self.ruleThickness);
}
}
I think that the issue is the y computation then passed to the drawLineNumber function. Any idea about how to correctly compute it?
I found a solution and I think it could be quite useful to others:
#define BTF_RULER_WIDTH 40.0f
#define BTF_RULER_PADDING 5.0f
static inline void drawLineNumberInRect(NSUInteger lineNumber, NSRect lineRect, NSDictionary *attributes, CGFloat ruleThickness) {
NSString *string = [[NSNumber numberWithUnsignedInteger:lineNumber] stringValue];
NSAttributedString *attString = [[NSAttributedString alloc] initWithString:string attributes:attributes];
NSUInteger x = ruleThickness - BTF_RULER_PADDING - attString.size.width;
// Offetting the drawing keeping into account the ascender (because we draw it without NSStringDrawingUsesLineFragmentOrigin)
NSFont *font = attributes[NSFontAttributeName];
lineRect.origin.x = x;
lineRect.origin.y += font.ascender;
[attString drawWithRect:lineRect options:0 context:nil];
}
static inline NSUInteger countNewLines(NSString *s, NSUInteger location, NSUInteger length) {
CFStringInlineBuffer inlineBuffer;
CFStringInitInlineBuffer((__bridge CFStringRef)s, &inlineBuffer, CFRangeMake(location, length));
NSUInteger counter = 0;
for (CFIndex i=0; i < length; ++i) {
UniChar c = CFStringGetCharacterFromInlineBuffer(&inlineBuffer, i);
if (c == (UniChar)'\n') ++counter;
}
return counter;
}
#implementation BTFRulerView
- (instancetype)initWithBTFTextView:(BTFTextView *)textView {
self = [super initWithScrollView:textView.enclosingScrollView orientation:NSVerticalRuler];
if (self) {
self.clientView = textView;
// default settings
self.ruleThickness = BTF_RULER_WIDTH;
self.textColor = [NSColor grayColor];
}
return self;
}
- (void)drawHashMarksAndLabelsInRect:(NSRect)rect {
// do not use drawBackgroundInRect for background color otherwise a 1px right border with a different color appears
if (_backgroundColor) {
[_backgroundColor set];
[NSBezierPath fillRect:rect];
}
BTFTextView *textView = (BTFTextView *)self.clientView;
if (!textView) return;
NSLayoutManager *layoutManager = textView.layoutManager;
if (!layoutManager) return;
NSString *textString = textView.string;
if ((!textString) || (textString.length == 0)) return;
CGFloat insetHeight = textView.textContainerInset.height;
CGPoint relativePoint = [self convertPoint:NSZeroPoint fromView:textView];
// Gettign text attributes from the textview
NSMutableDictionary *lineNumberAttributes = [[textView.textStorage attributesAtIndex:0 effectiveRange:NULL] mutableCopy];
lineNumberAttributes[NSForegroundColorAttributeName] = self.textColor;
NSRange visibleGlyphRange = [layoutManager glyphRangeForBoundingRect:textView.visibleRect inTextContainer:textView.textContainer];
NSUInteger firstVisibleGlyphCharacterIndex = [layoutManager characterIndexForGlyphAtIndex:visibleGlyphRange.location];
// line number for the first visible line
NSUInteger lineNumber = countNewLines(textString, 0, firstVisibleGlyphCharacterIndex)+1;
NSUInteger glyphIndexForStringLine = visibleGlyphRange.location;
// go through each line in the string
while (glyphIndexForStringLine < NSMaxRange(visibleGlyphRange)) {
// range of current line in the string
NSRange characterRangeForStringLine = [textString lineRangeForRange:NSMakeRange([layoutManager characterIndexForGlyphAtIndex:glyphIndexForStringLine], 0)];
NSRange glyphRangeForStringLine = [layoutManager glyphRangeForCharacterRange: characterRangeForStringLine actualCharacterRange:nil];
NSUInteger glyphIndexForGlyphLine = glyphIndexForStringLine;
NSUInteger glyphLineCount = 0;
while (glyphIndexForGlyphLine < NSMaxRange(glyphRangeForStringLine)) {
// check if the current line in the string spread across several lines of glyphs
NSRange effectiveRange = NSMakeRange(0, 0);
// range of current "line of glyphs". If a line is wrapped then it will have more than one "line of glyphs"
NSRect lineRect = [layoutManager lineFragmentRectForGlyphAtIndex:glyphIndexForGlyphLine effectiveRange:&effectiveRange withoutAdditionalLayout:YES];
// compute Y for line number
CGFloat y = ceil(NSMinY(lineRect) + relativePoint.y + insetHeight);
lineRect.origin.y = y;
// draw line number only if string does not spread across several lines
if (glyphLineCount == 0) {
drawLineNumberInRect(lineNumber, lineRect, lineNumberAttributes, self.ruleThickness);
}
// move to next glyph line
++glyphLineCount;
glyphIndexForGlyphLine = NSMaxRange(effectiveRange);
}
glyphIndexForStringLine = NSMaxRange(glyphRangeForStringLine);
++lineNumber;
}
// draw line number for the extra line at the end of the text
if (layoutManager.extraLineFragmentTextContainer) {
NSRect lineRect = layoutManager.extraLineFragmentRect;
CGFloat y = ceil(NSMinY(lineRect) + relativePoint.y + insetHeight);
lineRect.origin.y = y;
drawLineNumberInRect(lineNumber, lineRect, lineNumberAttributes, self.ruleThickness);
}
}
I use drawWithRect instead of drawAtPoint and I use the attributes directly from the connected textView.
I'm working with KML
https://developer.apple.com/library/ios/samplecode/KMLViewer/Listings/Classes_KMLParser_m.html#//apple_ref/doc/uid/DTS40010046-Classes_KMLParser_m-DontLinkElementID_4
It works perfectly fine, but now I'm using MapBox and I need the line coordinates for draw my route from KMLViewer. I can see the coordinates in this function:
static void strToCoords(NSString *str, CLLocationCoordinate2D **coordsOut, NSUInteger *coordsLenOut)
{
NSUInteger read = 0, space = 10;
CLLocationCoordinate2D *coords = malloc(sizeof(CLLocationCoordinate2D) * space);
NSArray *tuples = [str componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
for (NSString *tuple in tuples)
{
if (read == space)
{
space *= 2;
coords = realloc(coords, sizeof(CLLocationCoordinate2D) * space);
}
double lat, lon;
NSScanner *scanner = [[NSScanner alloc] initWithString:tuple];
[scanner setCharactersToBeSkipped:[NSCharacterSet characterSetWithCharactersInString:#","]];
BOOL success = [scanner scanDouble:&lon];
if (success)
success = [scanner scanDouble:&lat];
if (success)
{
NSLog(#"Coordenates: %f %f",lat,lon);
CLLocationCoordinate2D c = CLLocationCoordinate2DMake(lat, lon);
if (CLLocationCoordinate2DIsValid(c))
{
coords[read++] = c;
}
}
}
*coordsOut = coords;
*coordsLenOut = read;
}
How can I stract coordinates to my viewController? Thank you very much!
Im trying to get the least used color, and the most used color from MP3 file's album artwork for a music playing application. I need the colors to do an effect like the new itunes 11. Where the background color of the menu is the most used color, and the least used color is the color for song labels and artist name.
I am using
`- (UIColor*) getPixelColorAtLocation:(CGPoint)point {
UIColor* color = nil;
CGImageRef inImage = self.image.CGImage;
// Create off screen bitmap context to draw the image into. Format ARGB is 4 bytes for each pixel: Alpa, Red, Green, Blue
CGContextRef cgctx = [self createARGBBitmapContextFromImage:inImage];
if (cgctx == NULL) { return nil; /* error */ }
size_t w = CGImageGetWidth(inImage);
size_t h = CGImageGetHeight(inImage);
CGRect rect = {{0,0},{w,h}};
// Draw the image to the bitmap context. Once we draw, the memory
// allocated for the context for rendering will then contain the
// raw image data in the specified color space.
CGContextDrawImage(cgctx, rect, inImage);
// Now we can get a pointer to the image data associated with the bitmap
// context.
unsigned char* data = CGBitmapContextGetData (cgctx);
if (data != NULL) {
//offset locates the pixel in the data from x,y.
//4 for 4 bytes of data per pixel, w is width of one row of data.
int offset = 4*((w*round(point.y))+round(point.x));
int alpha = data[offset];
int red = data[offset+1];
int green = data[offset+2];
int blue = data[offset+3];
NSLog(#"offset: %i colors: RGB A %i %i %i %i",offset,red,green,blue,alpha);
color = [UIColor colorWithRed:(red/255.0f) green:(green/255.0f) blue:(blue/255.0f) alpha:(alpha/255.0f)];
}
// When finished, release the context
CGContextRelease(cgctx);
// Free image data memory for the context
if (data) { free(data); }
return color;
}
- (CGContextRef) createARGBBitmapContextFromImage:(CGImageRef) inImage {
CGContextRef context = NULL;
CGColorSpaceRef colorSpace;
void * bitmapData;
int bitmapByteCount;
int bitmapBytesPerRow;
// Get image width, height. We'll use the entire image.
size_t pixelsWide = CGImageGetWidth(inImage);
size_t pixelsHigh = CGImageGetHeight(inImage);
// Declare the number of bytes per row. Each pixel in the bitmap in this
// example is represented by 4 bytes; 8 bits each of red, green, blue, and
// alpha.
bitmapBytesPerRow = (pixelsWide * 4);
bitmapByteCount = (bitmapBytesPerRow * pixelsHigh);
// Use the generic RGB color space.
colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
if (colorSpace == NULL)
{
fprintf(stderr, "Error allocating color space\n");
return NULL;
}
// Allocate memory for image data. This is the destination in memory
// where any drawing to the bitmap context will be rendered.
bitmapData = malloc( bitmapByteCount );
if (bitmapData == NULL)
{
fprintf (stderr, "Memory not allocated!");
CGColorSpaceRelease( colorSpace );
return NULL;
}
// Create the bitmap context. We want pre-multiplied ARGB, 8-bits
// per component. Regardless of what the source image format is
// (CMYK, Grayscale, and so on) it will be converted over to the format
// specified here by CGBitmapContextCreate.
context = CGBitmapContextCreate (bitmapData,
pixelsWide,
pixelsHigh,
8, // bits per component
bitmapBytesPerRow,
colorSpace,
kCGImageAlphaPremultipliedFirst);
if (context == NULL)
{
free (bitmapData);
fprintf (stderr, "Context not created!");
}
// Make sure and release colorspace before returning
CGColorSpaceRelease( colorSpace );
return context;
}`
to get the color at the bottom of the image to make it blend in my view controller which uses the color for its background, and has a shadow to make it blended.
Question: So, as it says: How do I get the least and most used color from an image?
The method below takes an image and analyses it for its main colours, in the following steps:
1.) scale down the image and determine the main pixel colours.
2.) add some colour flexibility to allow for the loss during scaling
3.) distinguish colours, removing similar ones
4.) return the colours as an ordered array or with their percentages
You could adapt it to return a specific number of colours, e.g. top 10 colours in image if you needed a guaranteed number of colours returned, or just use the "detail" variable if you don't.
Larger images will take a long time to analyse at high detail.
No doubt the method could be cleaned up a bit but could be a good starting point.
Use like this:
NSDictionary * mainColours = [s mainColoursInImage:image detail:1];
-(NSDictionary*)mainColoursInImage:(UIImage *)image detail:(int)detail {
//1. determine detail vars (0==low,1==default,2==high)
//default detail
float dimension = 10;
float flexibility = 2;
float range = 60;
//low detail
if (detail==0){
dimension = 4;
flexibility = 1;
range = 100;
//high detail (patience!)
} else if (detail==2){
dimension = 100;
flexibility = 10;
range = 20;
}
//2. determine the colours in the image
NSMutableArray * colours = [NSMutableArray new];
CGImageRef imageRef = [image CGImage];
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
unsigned char *rawData = (unsigned char*) calloc(dimension * dimension * 4, sizeof(unsigned char));
NSUInteger bytesPerPixel = 4;
NSUInteger bytesPerRow = bytesPerPixel * dimension;
NSUInteger bitsPerComponent = 8;
CGContextRef context = CGBitmapContextCreate(rawData, dimension, dimension, bitsPerComponent, bytesPerRow, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
CGColorSpaceRelease(colorSpace);
CGContextDrawImage(context, CGRectMake(0, 0, dimension, dimension), imageRef);
CGContextRelease(context);
float x = 0;
float y = 0;
for (int n = 0; n<(dimension*dimension); n++){
int index = (bytesPerRow * y) + x * bytesPerPixel;
int red = rawData[index];
int green = rawData[index + 1];
int blue = rawData[index + 2];
int alpha = rawData[index + 3];
NSArray * a = [NSArray arrayWithObjects:[NSString stringWithFormat:#"%i",red],[NSString stringWithFormat:#"%i",green],[NSString stringWithFormat:#"%i",blue],[NSString stringWithFormat:#"%i",alpha], nil];
[colours addObject:a];
y++;
if (y==dimension){
y=0;
x++;
}
}
free(rawData);
//3. add some colour flexibility (adds more colours either side of the colours in the image)
NSArray * copyColours = [NSArray arrayWithArray:colours];
NSMutableArray * flexibleColours = [NSMutableArray new];
float flexFactor = flexibility * 2 + 1;
float factor = flexFactor * flexFactor * 3; //(r,g,b) == *3
for (int n = 0; n<(dimension * dimension); n++){
NSArray * pixelColours = copyColours[n];
NSMutableArray * reds = [NSMutableArray new];
NSMutableArray * greens = [NSMutableArray new];
NSMutableArray * blues = [NSMutableArray new];
for (int p = 0; p<3; p++){
NSString * rgbStr = pixelColours[p];
int rgb = [rgbStr intValue];
for (int f = -flexibility; f<flexibility+1; f++){
int newRGB = rgb+f;
if (newRGB<0){
newRGB = 0;
}
if (p==0){
[reds addObject:[NSString stringWithFormat:#"%i",newRGB]];
} else if (p==1){
[greens addObject:[NSString stringWithFormat:#"%i",newRGB]];
} else if (p==2){
[blues addObject:[NSString stringWithFormat:#"%i",newRGB]];
}
}
}
int r = 0;
int g = 0;
int b = 0;
for (int k = 0; k<factor; k++){
int red = [reds[r] intValue];
int green = [greens[g] intValue];
int blue = [blues[b] intValue];
NSString * rgbString = [NSString stringWithFormat:#"%i,%i,%i",red,green,blue];
[flexibleColours addObject:rgbString];
b++;
if (b==flexFactor){ b=0; g++; }
if (g==flexFactor){ g=0; r++; }
}
}
//4. distinguish the colours
//orders the flexible colours by their occurrence
//then keeps them if they are sufficiently disimilar
NSMutableDictionary * colourCounter = [NSMutableDictionary new];
//count the occurences in the array
NSCountedSet *countedSet = [[NSCountedSet alloc] initWithArray:flexibleColours];
for (NSString *item in countedSet) {
NSUInteger count = [countedSet countForObject:item];
[colourCounter setValue:[NSNumber numberWithInteger:count] forKey:item];
}
//sort keys highest occurrence to lowest
NSArray *orderedKeys = [colourCounter keysSortedByValueUsingComparator:^NSComparisonResult(id obj1, id obj2){
return [obj2 compare:obj1];
}];
//checks if the colour is similar to another one already included
NSMutableArray * ranges = [NSMutableArray new];
for (NSString * key in orderedKeys){
NSArray * rgb = [key componentsSeparatedByString:#","];
int r = [rgb[0] intValue];
int g = [rgb[1] intValue];
int b = [rgb[2] intValue];
bool exclude = false;
for (NSString * ranged_key in ranges){
NSArray * ranged_rgb = [ranged_key componentsSeparatedByString:#","];
int ranged_r = [ranged_rgb[0] intValue];
int ranged_g = [ranged_rgb[1] intValue];
int ranged_b = [ranged_rgb[2] intValue];
if (r>= ranged_r-range && r<= ranged_r+range){
if (g>= ranged_g-range && g<= ranged_g+range){
if (b>= ranged_b-range && b<= ranged_b+range){
exclude = true;
}
}
}
}
if (!exclude){ [ranges addObject:key]; }
}
//return ranges array here if you just want the ordered colours high to low
NSMutableArray * colourArray = [NSMutableArray new];
for (NSString * key in ranges){
NSArray * rgb = [key componentsSeparatedByString:#","];
float r = [rgb[0] floatValue];
float g = [rgb[1] floatValue];
float b = [rgb[2] floatValue];
UIColor * colour = [UIColor colorWithRed:(r/255.0f) green:(g/255.0f) blue:(b/255.0f) alpha:1.0f];
[colourArray addObject:colour];
}
//if you just want an array of images of most common to least, return here
//return [NSDictionary dictionaryWithObject:colourArray forKey:#"colours"];
//if you want percentages to colours continue below
NSMutableDictionary * temp = [NSMutableDictionary new];
float totalCount = 0.0f;
for (NSString * rangeKey in ranges){
NSNumber * count = colourCounter[rangeKey];
totalCount += [count intValue];
temp[rangeKey]=count;
}
//set percentages
NSMutableDictionary * colourDictionary = [NSMutableDictionary new];
for (NSString * key in temp){
float count = [temp[key] floatValue];
float percentage = count/totalCount;
NSArray * rgb = [key componentsSeparatedByString:#","];
float r = [rgb[0] floatValue];
float g = [rgb[1] floatValue];
float b = [rgb[2] floatValue];
UIColor * colour = [UIColor colorWithRed:(r/255.0f) green:(g/255.0f) blue:(b/255.0f) alpha:1.0f];
colourDictionary[colour]=[NSNumber numberWithFloat:percentage];
}
return colourDictionary;
}
Not sure about finding most color or least color, but here is a method to find out the average color.
- (UIColor *)averageColor {
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
unsigned char rgba[4];
CGContextRef context = CGBitmapContextCreate(rgba, 1, 1, 8, 4, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
CGContextDrawImage(context, CGRectMake(0, 0, 1, 1), self.CGImage);
CGColorSpaceRelease(colorSpace);
CGContextRelease(context);
if(rgba[3] > 0) {
CGFloat alpha = ((CGFloat)rgba[3])/255.0;
CGFloat multiplier = alpha/255.0;
return [UIColor colorWithRed:((CGFloat)rgba[0])*multiplier
green:((CGFloat)rgba[1])*multiplier
blue:((CGFloat)rgba[2])*multiplier
alpha:alpha];
}
else {
return [UIColor colorWithRed:((CGFloat)rgba[0])/255.0
green:((CGFloat)rgba[1])/255.0
blue:((CGFloat)rgba[2])/255.0
alpha:((CGFloat)rgba[3])/255.0];
}
}
You can probably follow a similar approach to find out the most used color.
Also check this answer about counting red color pixels in an image.
Thanks a lot for your code, #JohnnyRockex. It was really helpful in getting me started towards my goal (finding accent colors depending on the most predominant color in an image).
After going through it, I found the code could be simplified and made easier to read, so I'd like to give back to the community my own version; the -colors selector is in a UIImage extension.
- (NSArray *)colors {
// Original code by Johnny Rockex http://stackoverflow.com/a/29266983/825644
// Higher the dimension, the more pixels are checked against.
const float pixelDimension = 10;
// Higher the range, more similar colors are removed.
const float filterRange = 60;
unsigned char *rawData = (unsigned char*) calloc(pixelDimension * pixelDimension * kBytesPerPixel, sizeof(unsigned char));
NSUInteger bytesPerRow = kBytesPerPixel * pixelDimension;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(rawData, pixelDimension, pixelDimension, kBitsInAByte, bytesPerRow, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
CGColorSpaceRelease(colorSpace);
CGContextDrawImage(context, CGRectMake(0, 0, pixelDimension, pixelDimension), [self CGImage]);
CGContextRelease(context);
NSMutableArray * colors = [[NSMutableArray alloc] init];
float x = 0;
float y = 0;
const int pixelMatrixSize = pixelDimension * pixelDimension;
for (int i = 0; i < pixelMatrixSize; i++){
int index = (bytesPerRow * y) + x * kBytesPerPixel;
int red = rawData[index];
int green = rawData[index + 1];
int blue = rawData[index + 2];
int alpha = rawData[index + 3];
UIColor * color = [UIColor colorWithRed:(red / 255.0f) green:(green / 255.0f) blue:(blue / 255.0f) alpha:alpha];
[colors addObject:color];
y++;
if (y == pixelDimension){
y = 0;
x++;
}
}
free(rawData);
NSMutableDictionary * colorCounter = [[NSMutableDictionary alloc] init];
NSCountedSet *countedSet = [[NSCountedSet alloc] initWithArray:colors];
for (NSString *item in countedSet) {
NSUInteger count = [countedSet countForObject:item];
[colorCounter setValue:[NSNumber numberWithInteger:count] forKey:item];
}
NSArray *orderedColors = [colorCounter keysSortedByValueUsingComparator:^NSComparisonResult(id obj1, id obj2){
return [obj2 compare:obj1];
}];
NSMutableArray *filteredColors = [NSMutableArray new];
for (UIColor *color in orderedColors){
bool filtered = false;
for (UIColor *rangedColor in filteredColors){
if (abs(color.redRGBComponent - rangedColor.redRGBComponent) <= filterRange &&
abs(color.greenRGBComponent - rangedColor.greenRGBComponent) <= filterRange &&
abs(color.blueRGBComponent - rangedColor.blueRGBComponent) <= filterRange) {
filtered = true;
break;
}
}
if (!filtered) {
[filteredColors addObject:color];
}
}
return [filteredColors copy];
The code for UIColor's extension adding the -rgbComponent function can be found underneath, but I wrote it in Swift (trying to write all new classes in Swift, but this wasn't the case for the -colors selector):
extension UIColor {
open func redRGBComponent() -> UInt8 {
let colorComponents = cgColor.components!
return UInt8(colorComponents[0] * 255)
}
open func greenRGBComponent() -> UInt8 {
let colorComponents = cgColor.components!
return UInt8(colorComponents[1] * 255)
}
open func blueRGBComponent() -> UInt8 {
let colorComponents = cgColor.components!
return UInt8(colorComponents[2] * 255)
}
}
Enjoy!
I wrote this tool to do that.
https://github.com/623637646/UIImageColorRatio
// replace the UIImage() to yourself's UIImage.
let theMostUsedColor = UIImage().calculateColorRatio(deviation: 0)?.colorRatioArray.first?.color
let theLeastUsedColor = UIImage().calculateColorRatio(deviation: 0)?.colorRatioArray.last?.color
does anyone knows about a bug of calculating float values (in my case currency values converted from text to float). I've been testing it on the simulator and all calculated values are correct. Then testing on the iphone device itself, every value displayed with 0.59 more. On the iPad device I've got just 0.15 more on every value.
Here's my Code
- (void)viewDidLoad
{
[super viewDidLoad];
// current account balance from Coredata Dataset
if (!self.FSDataSet.konto || [self.FSDataSet.konto isEqualToString:#""]) {
self.txtKontostand.text = #"Nicht möglich!";
} else {
// fetch request for current changes of currency values
NSFetchRequest *fetchRequestFahrschueler = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass([FSKontoUnterTab class])];
NSPredicate *predicateKundenNr = [NSPredicate predicateWithFormat:#"kundennr == %#", self.FSDataSet.kundennr];
fetchRequestFahrschueler.predicate = predicateKundenNr;
// put datasets in array
NSArray *ergebnisFahrschueler= [[[CoreDataHelper sharedInstance] managedObjectContext] executeFetchRequest:fetchRequestFahrschueler error:nil];
// variables to calculate the new account balance
CGFloat soll;
CGFloat haben;
CGFloat konto;
// account balance string converting to a flot value
// comma replace with point
NSString *kontostand = [self.FSDataSet.konto stringByReplacingOccurrencesOfString:#"," withString:#"."];
CGFloat testKonto = kontostand.floatValue;
// NSArray Iterator workaround
// loop array and assign current dataset from entity (the managed object class)
for (NSInteger i = 0; i < [ergebnisFahrschueler count]; i++) {
FSKontoUnterTab *myset = [ergebnisFahrschueler objectAtIndex:i];
CGFloat gesamtpreis = [myset.preis stringByReplacingOccurrencesOfString:#"," withString:#"."].floatValue;
// positive currency values
CGFloat aktHaben = [myset.bar stringByReplacingOccurrencesOfString:#"," withString:#"."].floatValue;
// calculate
soll = soll + gesamtpreis;
haben = haben +aktHaben;
}
//konto = soll-haben;
konto = (testKonto - soll)+haben;
// convert back to string and display in textfield
self.txtKontostand.text = [[NSString stringWithFormat:#"%.2f", konto] stringByReplacingOccurrencesOfString:#"." withString:#","];
}
I have a sqlite database with a table full of geographic locations, each stored as a latitude and longitude value in degrees. I wanted to be able to perform a SQL SELECT on this table and ORDER BY each row’s distance from an arbitrary point. I’ve achieved this by using the defined custom sqlite function (distanceFunc) below.
Here’s the function, together with a convenience macro to convert from degrees to radians. This function is based on an online distance calculator, which makes use of the spherical law of cosines.
http://en.wikipedia.org/wiki/Spherical_law_of_cosines
"In spherical trigonometry, the law of cosines (also called the cosine rule for sides) is a theorem relating the sides and angles of spherical triangles, analogous to the ordinary law of cosines from plane trigonometry."
#define DEG2RAD(degrees) (degrees * 0.01745327) // degrees * pi over 180
The "distanceFunc" in (Location.m)
static void distanceFunc(sqlite3_context *context, int argc, sqlite3_value **argv) {
// check that we have four arguments (lat1, lon1, lat2, lon2)
assert(argc == 4);
// check that all four arguments are non-null
if (sqlite3_value_type(argv[0]) == SQLITE_NULL || sqlite3_value_type(argv[1]) == SQLITE_NULL || sqlite3_value_type(argv[2]) == SQLITE_NULL || sqlite3_value_type(argv[3]) == SQLITE_NULL) {
sqlite3_result_null(context);
return;
}
// get the four argument values
double lat1 = sqlite3_value_double(argv[0]);
double lon1 = sqlite3_value_double(argv[1]);
double lat2 = sqlite3_value_double(argv[2]);
double lon2 = sqlite3_value_double(argv[3]);
// convert lat1 and lat2 into radians now, to avoid doing it twice below
double lat1rad = DEG2RAD(lat1);
double lat2rad = DEG2RAD(lat2);
// apply the spherical law of cosines to our latitudes and longitudes, and set the result appropriately
// 6378.1 is the approximate radius of the earth in kilometres
sqlite3_result_double(context, acos(sin(lat1rad) * sin(lat2rad) + cos(lat1rad) * cos(lat2rad) * cos(DEG2RAD(lon2) - DEG2RAD(lon1))) * 6378.1);
}
I call my method "getDistanceBetweenLongLat" which is also in "Location.m" like this:
Used in my (AppDelegate.m):
Location *location = [[Location alloc] init];
location.latitude = -37.1134; //double property
location.longitude = 145.4254; //double property
[location getDistanceBetweenLongLat:[self dbPath]];
My "getDistanceBetweenLongLat" method in (Location.m):
- (void) getDistanceBetweenLongLat:(NSString *)dbPath {
AppDelegate *_ad = (AppDelegate *)[[UIApplication sharedApplication] delegate];
if (sqlite3_open([dbPath UTF8String], &database) == SQLITE_OK) {
sqlite3_create_function(database, "distance", 4, SQLITE_UTF8, NULL, &distanceFunc, NULL, NULL);
const char *sql = "select * from locations order by distance(latitude, longitude, ?, ?)";
sqlite3_stmt *selectstmt;
sqlite3_bind_double(selectStmt, 1, latitude);
sqlite3_bind_double(selectStmt, 2, longitude);
if(sqlite3_prepare_v2(database, sql, -1, &selectstmt, NULL) == SQLITE_OK) {
while(sqlite3_step(selectstmt) == SQLITE_ROW) {
NSInteger primaryKey = sqlite3_column_int(selectstmt, 0);
Location *location = [[Location alloc] initWithPrimaryKey:primaryKey];
location.latitude = sqlite3_column_double(selectstmt, 1);
location.longitude = sqlite3_column_double(selectstmt, 2);
location.title = [NSString stringWithUTF8String:(char *)sqlite3_column_text(selectstmt, 3)];
location.street1 = [NSString stringWithUTF8String:(char *)sqlite3_column_text(selectstmt, 4)];
location.street2 = [NSString stringWithUTF8String:(char *)sqlite3_column_text(selectstmt, 5)];
location.suburb = [NSString stringWithUTF8String:(char *)sqlite3_column_text(selectstmt, 6)];
NSInteger postCodeItem = sqlite3_column_int(selectstmt, 7);
location.postcode = postCodeItem;
location.isDirty = NO;
[_ad.locations addObject:location];
[location release];
}
}
} else {
//Even though the open call failed, close the database connection to release all the memory.
sqlite3_close(database);
}
}
This is calling the "distanceFunc" just fine. However when it gets to the statement where it checks that all four arguments are non-null they are all coming back as null.
However, when I change my sql statement above to the following:
const char *sql = "select * from locations order by distance(latitude, longitude, '?', '?')"; //single quotes added
The four arguments don't come back as null. However, the last two argument values are 0 when they should be the following, right?
location.latitude = -37.1134; //double property
location.longitude = 145.4254; //double property:
From "distanceFunc" in (Location.m):
double lat1 = sqlite3_value_double(argv[0]); // -17.7699 from 1st result in Locations table
double lon1 = sqlite3_value_double(argv[1]); // 245.1103 from 1st result in Locations table
double lat2 = sqlite3_value_double(argv[2]); // 0
double lon2 = sqlite3_value_double(argv[3]); // 0
I use pretty much the same method to get initial data to display and that particular array populates just fine. This time I just want my locations array (in _ad.locations) to contain the results ordered by distance instead so I can display them in a UITableView.
NOTE: The "distanceFunc" used can also be found at the following link: http://www.thismuchiknow.co.uk/?p=71&cpage=1#comment-30834
I use a variation, where latVal and longVal are my current location.:
NSString *sqlString = #"SELECT * FROM stores WHERE status = 1 AND distance(latitude, longitude, %f, %f, id) < 800";
sqlString = [NSString stringWithFormat:sqlString, latVal, longVal];
i also use a variable to read the distance value within the distanceFunc, changing the last two lines to the following:
float val = acos(sin(lat1rad) * sin(lat2rad) + cos(lat1rad) * cos(lat2rad) * cos(DEG2RAD(lon2) - DEG2RAD(lon1))) * 6378.1;
distanceVal = (float)val;
sqlite3_result_double(context, val);
I then save the distanceVal into my objects that fit the match, applying a sort to the items returned:
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"distance" ascending:YES];
[ locations sortUsingDescriptors: [ NSArray arrayWithObject: sortDescriptor ] ];
[sortDescriptor release];