So I need help figuring out what code I am missing here. I have checked all over the place, but I need specifics on wether its the formulas used or a typo that i haven't noticed yet.
Here is the polygon class. I am trying to create random polygons with 8 vertices and then of course fill with a plain color. But I want them to continue to generate random position but leave them fixed. In a better way the poly's are my terrain.Ok revise: the polygons are there and my character interacts with them, but I cannot see them, and yes they are on the same layer. Oh but they don't keep generating at the bottom, which i am guessing i just need to delete the old ones once they go off the screen and it should make a new poly.
-(void) genBody:(b2World *)world pos:(CGPoint *)pos {
//Here we generate a somewhat random convex polygon by sampling
//the "eccentric anomaly" of an ellipse with randomly generated
//x and y scaling constants (a,b). The algorithm is limited by
//the parameter max_verts, and has a number of tunable minimal
//and scaling values.
// I need to change this to randomly choosing teh number of vertices between 3-8,
// then choosing random offsets from equally distributed t values.
// This will eliminate teh while loop.
screen_pos = ccp(pos->x, pos->y);
float cur_t;
float new_t;
float delta_t;
float min_delta_t = 0.5;
float t_scale = 1.5;
b2Vec2 *verts= new b2Vec2[m_maxVerts]; // this should be replaced by a private verts ... maybe ... hmm that will consume more ram though
float t_vec[m_maxVerts];
// Generate random vertices
int vec_len;
while (true) {
cur_t = 0.0;
for (vec_len=0; vec_len<m_maxVerts; vec_len++) {
//delta_t = t_scale*(float)rand()/(float)RAND_MAX; // wish they just had a randf method :/
delta_t = t_scale*floorf((double)arc4random()/ARC4RANDOM_MAX);
#ifdef POLY_DEBUG
CCLOG(#"delta_t %0.2f", delta_t);
#endif
if (delta_t < min_delta_t) {
delta_t = min_delta_t;
}
new_t = cur_t + delta_t;
if (new_t > 2*PI) {
break;
}
t_vec[vec_len] = new_t;
cur_t = new_t;
}
// We need at least three points for a triangle
if ( vec_len > 3 ) {
break;
}
}
At least where the body is being generated.
then...
float num_verts = vec_len;
b2BodyDef BodyDef;
BodyDef.type = b2_staticBody;
BodyDef.position.Set(pos->x/PTM_RATIO, pos->y/PTM_RATIO);
BodyDef.userData = self; // hope this is correct
m_polyBody = world->CreateBody(&BodyDef);
b2PolygonShape polyShape;
int32 polyVert = num_verts;
polyShape.Set(verts, polyVert);
b2FixtureDef FixtureDef;
FixtureDef.shape = &polyShape;
FixtureDef.userData = self; // hope this is correct
FixtureDef.density = 1.6f;
FixtureDef.friction = 0.4f;
FixtureDef.restitution = 0.5f;
m_polyBody->CreateFixture(&FixtureDef);
for (int i=0; i < num_verts; i++) {
// Convert from b2Vec2 to CCPoint and from physics units to pixels
m_verts[i] = ccp(verts[i].x*PTM_RATIO, verts[i].y*PTM_RATIO);
}
m_numVerts = num_verts;
delete verts;
}
-(void) setColor:(ccColor4F) color {
m_color = color;
}
-(BOOL) dirty {
return true;
}
-(void) draw {
//[super draw];
ccDrawPoly(m_verts, m_numVerts, YES);
CCLOG(#"Drawing?");
}
-(CGAffineTransform) nodeToParentTransform {
b2Vec2 pos = m_polyBody->GetPosition();
float x = pos.x * PTM_RATIO;
float y = pos.y * PTM_RATIO;
/*if ( ignoreAnchorPointForPosition_ ) {
x += anchorPointInPixels_.x;
y += anchorPointInPixels_.y;
}*/
// Make matrix
float radians = m_polyBody->GetAngle();
float c = cosf(radians);
float s = sinf(radians);
if( ! CGPointEqualToPoint(anchorPointInPixels_, CGPointZero) ){
x += c*-anchorPointInPixels_.x + -s*-anchorPointInPixels_.y;
y += s*-anchorPointInPixels_.x + c*-anchorPointInPixels_.y;
}
// Rot, Translate Matrix
transform_ = CGAffineTransformMake( c, s,
-s, c,
x, y );
return transform_;
}
there is some stuff in between but its less important. I can post it if asked.
Then the update function, which is based in my game scene class.
-(void)updateObstacles
{
//CCLOG(#"updating obstacles");
int xpos;
int ypos;
CGPoint pos;
for (int i=0; i<MAX_OBSTACLES; i++ ) {
// If there is no obstacle generate a new one
if ( obstacles[i] == NULL ) {
polyObstacleSprite *sprite = [[polyObstacleSprite alloc] init];
ypos = int(_winSize.width/2*(double)arc4random()/ARC4RANDOM_MAX) - _winSize.width/2;
xpos = int(_winSize.height/2*(double)arc4random()/ARC4RANDOM_MAX) - _winSize.height/2;
//CCLOG(#"generating obstacle at %d,%d", xpos, ypos);
pos = ccp(xpos, ypos);
[sprite genBody:_world pos:&pos];
[self addChild:sprite z:1];
obstacles[i] = sprite;
}
//CCLOG(#"position: %d, %d", obstacles[i]->screen, obstacles[i]->position.y); FINISH
}
}
Sorry if its sort of a mess I set this up quick, but pretty much what I want to do is have randomly generated polygons appear at the bottom of my iphone screen as my character moves down with gravity. I got everything else but the polygons working.
Thanks in advance for spending the time to read this.
I'm trying to show a preview of an image in 1-bit monochrome, as in, not grayscale, but bitonal black and white. It's supposed to be an indication of how the image will look if it were faxed. Formats as low as 1-bit per pixel aren't available on OS X, only 8-bit grayscale. Is there any way to achieve this effect using Core Graphics or another framework (ideally with dithering)?
I know there's a filter called CIColorMonochrome but this only converts the image to grayscale.
The creation of a 1 bit deep NSImageRep (and also in the CG-world) is AFAIK not supported, So we have to do it manually. It might be useful to use CIImage for this task. Here I go the classical (you may call it old-fashioned) way. Here is a code that shows how we can do it. First a gray image is created from an NSImageRep so we have a well defined and simple format whatever the source image will be formatted (could also be a PDF file). The resulting gray image is the source for the bitonal image. Here is the code for creating a gray image: (without respecting the size / resolution of the source image, only the pixels count!):
- (NSBitmapImageRep *) grayRepresentationOf:(NSImageRep *)aRep
{
NSBitmapImageRep *newRep =
[[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
pixelsWide:[aRep pixelsWide]
pixelsHigh:[aRep pixelsHigh]
bitsPerSample:8
samplesPerPixel:1
hasAlpha:NO //must be NO !
isPlanar:NO
colorSpaceName:NSCalibratedWhiteColorSpace
bytesPerRow:0
bitsPerPixel:0 ];
// this new imagerep has (as default) a resolution of 72 dpi
[NSGraphicsContext saveGraphicsState];
NSGraphicsContext *context = [NSGraphicsContext graphicsContextWithBitmapImageRep:newRep];
if( context==nil ){
NSLog( #"*** %s context is nil", __FUNCTION__ );
return nil;
}
[NSGraphicsContext setCurrentContext:context];
[aRep drawInRect:NSMakeRect( 0, 0, [newRep pixelsWide], [newRep pixelsHigh] )];
[NSGraphicsContext restoreGraphicsState];
return [newRep autorelease];
}
In the next method we create an NXBitmapImageRep (bits per pixel=1, samples per pixel=1) from a given NSImageRep (one of it's subclasses) and will use the method just given:
- (NSBitmapImageRep *) binaryRepresentationOf:(NSImageRep *)aRep
{
NSBitmapImageRep *grayRep = [aRep grayRepresentation];
if( grayRep==nil ) return nil;
NSInteger numberOfRows = [grayRep pixelsHigh];
NSInteger numberOfCols = [grayRep pixelsWide];
NSBitmapImageRep *newRep =
[[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
pixelsWide:numberOfCols
pixelsHigh:numberOfRows
bitsPerSample:1
samplesPerPixel:1
hasAlpha:NO
isPlanar:NO
colorSpaceName:NSCalibratedWhiteColorSpace
bitmapFormat:0
bytesPerRow:0
bitsPerPixel:0 ];
unsigned char *bitmapDataSource = [grayRep bitmapData];
unsigned char *bitmapDataDest = [newRep bitmapData];
// here is the place to use dithering or error diffusion (code below)
// iterate over all pixels
NSInteger grayBPR = [grayRep bytesPerRow];
NSInteger binBPR = [newRep bytesPerRow];
NSInteger pWide = [newRep pixelsWide];
for( NSInteger row=0; row<numberOfRows; row++ ){
unsigned char *rowDataSource = bitmapDataSource + row*grayBPR;
unsigned char *rowDataDest = bitmapDataDest + row*binBPR;
NSInteger destCol = 0;
unsigned char bw = 0;
for( NSInteger col = 0; col<pWide; ){
unsigned char gray = rowDataSource[col];
if( gray>127 ) {bw |= (1<<(7-col%8)); };
col++;
if( (col%8 == 0) || (col==pWide) ){
rowDataDest[destCol] = bw;
bw = 0;
destCol++;
}
}
}
// save as PNG for testing and return
[[newRep representationUsingType:NSPNGFileType properties:nil] writeToFile:#"/tmp/bin_1.png" atomically:YES];
return [newRep autorelease];
}
For error diffusion I used the following code which changes directly the bitmap of the gray image. This is allowed because the gray image itself is no longer used.
// change bitmapDataSource : use Error-Diffusion
for( NSInteger row=0; row<numberOfRows-1; row++ ){
unsigned char *currentRowData = bitmapDataSource + row*grayBPR;
unsigned char *nextRowData = currentRowData + grayBPR;
for( NSInteger col = 1; col<numberOfCols; col++ ){
NSInteger origValue = currentRowData[col];
NSInteger newValue = (origValue>127) ? 255 : 0;
NSInteger error = -(newValue - origValue);
currentRowData[col] = newValue;
currentRowData[col+1] = clamp(currentRowData[col+1] + (7*error/16));
nextRowData[col-1] = clamp( nextRowData[col-1] + (3*error/16) );
nextRowData[col] = clamp( nextRowData[col] + (5*error/16) );
nextRowData[col+1] = clamp( nextRowData[col+1] + (error/16) );
}
}
clamp is a macro defined before the method
#define clamp(z) ( (z>255)?255 : ((z<0)?0:z) )
This makes the unsigned char bytes to have valid values (0<=z<=255)
I've got a c-array of CGPoints in a struct. I need to replace this array when another CGPoint is added. I'd swear I'm doing this right and it seems to work fine a few times but eventually I'll get a EXC_BAD_ACCESS. What am I missing?
Here's the struct, which I've truncated to remove a lot of items that don't pertain.
typedef struct{
CGPoint **focalPoints;
NSUInteger focalPointCount;
CGRect boundingRect;
}FocalPoints;
Here's how I initialize it:
CGPoint *fPoints = (CGPoint *)malloc(sizeof(CGPoint));
FocalPoints focalInfo = {&fPoints, 0, rect};
Note that focalInfo is passed by reference to another function, like so: anotherFunction(&focalInfo).
Now here's the function that replaces the Points array with a new one:
void AddFocalPoint (CGPoint focalPoint, FocalPoints *focal){
if (focalPoint.x == CGFLOAT_MAX) return;
if (!CGRectContainsPoint(focal->boundingRect, focalPoint)) return;
int origCount = focal->focalPointCount;
int newCount = origCount + 1;
CGPoint *newPoints = (CGPoint *) malloc((newCount) * sizeof(CGPoint));
for (int i = 0; i < newCount; i++)
newPoints[i] = (i < origCount) ? *focal->focalPoints[i] : focalPoint; //error occurs here
free(*focal->focalPoints);
*focal->focalPoints = newPoints;
focal->focalPointCount = newCount;
}
The EXC_BAD_ACCESS error occurs in the above code on line 8: newPoints[i] = (i < origCount) ? *focal->focalPoints[i] : focalPoint;. So what exactly am I doing wrong?
This is a bit of a long shot, but maybe there's an issue with operator priority in *focal->focalPoints[i]. Have you try adding parentheses according to what you are trying to achieve ?
I believe the issue comes with where GCPoint *fPoints allocated as &fPoints evaluates to an address of that ... which is no longer valid once the function exits.
(The data to which it points was allocated fine with malloc.)
Aside from the suggestion I made in a comment, of using a linked list/NSMutableArray, my other suggestion would be that you use realloc() instead of constantly using malloc(), copying by hand, and then free()ing the old allocation.
void * realloc(void *ptr, size_t size);
The realloc() function tries to change the size of the allocation pointed to by ptr to size, and returns ptr. If there is not enough room to enlarge the memory allocation pointed to by ptr, realloc() creates a new allocation, copies as much of the old data pointed to by ptr as will fit to the new allocation, frees the old allocation, and returns a pointer to the allocated memory.
This is pretty much exactly what you are doing, but you can let the library handle it for you.
(May I also humbly suggest using the word "focal" slightly less to name variables in your function?) (Also also, I'm not really clear on why focalPoints in your struct is a pointer-to-pointer. You just want an array of structs -- a single pointer should be fine.)
Consider the following (somewhat extensive) rewrite; hope that it's helpful in some way.
typedef struct{
CGPoint *points; // Single pointer
NSUInteger count;
CGRect boundingRect;
} FocalPoints;
// Renamed to match Apple's style, like e.g. CGRectIntersectsRect()
void FocalPointsAddPoint (FocalPoints *, CGPoint);
void FocalPointsAddPoint (FocalPoints *f, CGPoint thePoint){
if (thePoint.x == CGFLOAT_MAX) return;
if (!CGRectContainsPoint(f->boundingRect, thePoint)) return;
NSUInteger origCount = f->count; // |count| is typed as NSUInteger; |origCount|
NSUInteger newCount = origCount + 1; // and |newCount| should be consistent
// Greatly simplified by using realloc()
f->points = (CGPoint *) realloc(f->points, newCount * sizeof(CGPoint));
(f->points)[newCount-1] = thePoint;
f->count = newCount;
}
int main(int argc, const char * argv[])
{
#autoreleasepool {
// Just for testing; any point should be inside this rect
CGRect maxRect = CGRectMake(0, 0, CGFLOAT_MAX, CGFLOAT_MAX);
// Can initialize |points| to NULL; both realloc() and free() know what to do
FocalPoints fp = (FocalPoints){NULL, 0, maxRect};
int i;
for( i = 0; i < 10; i++ ){
FocalPointsAddPoint(&fp, CGPointMake(arc4random() % 100, arc4random() % 100));
NSLog(#"%#", NSStringFromPoint(fp.points[i]));
}
}
return 0;
}
I think my algorithm has flawed logic somewhere. Calling the two functions should return the same image however it doesn't! Can anyone see where my logic goes wrong?
These functions are used on PNG-images, I have found that they store colors as follows: ALPHA, RED, GREEN, BLUE. Repeatingly for the whole image. "pixels" is just a long array of those values (like a list).
My intent is to do a lowpass filter on the image, which is a lot easier logic if you instead use a two dimentional array / matrix of the image.
// loading pixels
UIImage *image = imageView.image;
CGImageRef imageRef = image.CGImage;
NSData *data = (NSData *)CGDataProviderCopyData(CGImageGetDataProvider(imageRef));
char *pixels = (char *)[data bytes];
// editing image
char** matrix = [self mallocMatrix:pixels withWidth:CGImageGetWidth(imageRef) andHeight:CGImageGetHeight(imageRef)];
char* newPixels = [self mallocMatrixToList:matrix withWidth:CGImageGetWidth(imageRef) andHeight:CGImageGetHeight(imageRef)];
pixels = newPixels;
and the functions looks like this:
- (char**)mallocMatrix:(char*)pixels withWidth:(int)width andHeight:(int)height {
char** matrix = malloc(sizeof(char*)*height);
int c = 0;
for (int h=0; h < height; h++) {
matrix[h] = malloc(sizeof(char)*width*4);
for (int w=0; w < (width*4); w++) {
matrix[h][w] = pixels[c];
c++;
}
}
return matrix;
}
- (char*)mallocMatrixToList:(char**)matrix withWidth:(int)width andHeight:(int)height {
char* pixels = malloc(sizeof(char)*height*width*4);
int c = 0;
for (int h=0; h < height; h++) {
for (int w=0; w < (width*4); w++) {
pixels[c] = matrix[h][w];
c++;
}
}
return pixels;
}
Edit: Fixed the malloc as posters pointed out. Simplified the algorithm a bit.
I have not tested your code but it appears you are allocating the incorrect size for your matrix and low pass filter as well as not moving to the next pixel correctly.
- (char**) mallocMatrix:(char*)pixels withWidth:(int)width andHeight:(int)height {
//When using Objective-C do not cast malloc (only do so with Objective-C++)
char** matrix = malloc(sizeof(char*)*height);
for (int h=0; h < height; h++) {
//Each row needs to malloc the sizeof(char) not char *
matrix[h] = malloc(sizeof(char)*width*4);
for (int w=0; w < width; w++) {
// Varje pixel har ARGB
for (int i=0; i < 4; i++) {
matrix[h][w+i] = pixels[h*w+i];
}
}
}
return matrix;
}
- (char*) mallocLowPassFilter:(char**)matrix withWidth:(int)width andHeight:(int)height
{
//Same as before only malloc sizeof(char)
char* pixels = malloc(sizeof(char)*height*width*4);
for (int h=0; h < height; h++) {
for (int w=0; w < width; w++) {
// Varje pixel har ARGB
for (int i=0; i < 4; i++) {
// TODO: Lowpass here
pixels[h*w+i] = matrix[h][w+i];
}
}
}
return pixels;
}
Note: This code, as you know, is limited to ARGB images. If you would like to support more image formats there are additional functions available to get more information about your image such as CGImageGetColorSpace to find the pixel format (ARGB, RGBA, RGB, etc...), and CGImageGetBytesPerRow to get the number of bytes per row (you wouldn't have to multiply width by channels per pixel).
I have a question about the NSStatusItem for cocoa in mac osx. If you look at the mac app called snippets (see the movie at http://snippetsapp.com/). you will see that once you clicked your statusbar icon that a perfectly aligned view / panel or maybe even windows appears just below the icon.
My question is ... How to calculate the position to where to place your NSWindow just like this app does?
I have tried the following:
Subclass NSMenu
Set the view popery for the first item of the menu (Worked but enough)
Using addSubview instead of icon to NSStatusItem this worked but could not get higher then 20px
Give the NSStatusItem a view, then get the frame of that view's window. This technically counts as UndocumentedGoodness, so don't be surprised if it breaks someday (e.g., if they start keeping the window offscreen instead).
I don't know what you mean by “could not get heigher then 20px”.
To do this without the hassle of a custom view, I tried the following (that works). In the method that is set as the action for the status item i.e. the method that is called when the user clicks the status item, the frame of the status item can be retrieved by:
[[[NSApp currentEvent] window] frame]
Works a treat for me
Given an NSMenuItem and an NSWindow, you can get the point that centers your window right below the menu item like this:
fileprivate var centerBelowMenuItem: CGPoint {
guard let window = window, let barButton = statusItem.button else { return .zero }
let rectInWindow = barButton.convert(barButton.bounds, to: nil)
let screenRect = barButton.window?.convertToScreen(rectInWindow) ?? .zero
// We now have the menu item rect on the screen.
// Let's do some basic math to center our window to this point.
let centerX = screenRect.origin.x-(window.frame.size.width-barButton.bounds.width)/2
return CGPoint(x: centerX, y: screenRect.origin.y)
}
No need for undocumented API's.
Maybe another solution which works for me (swift 4.1) :
let yourStatusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
let frameOrigin = yourStatusItem.button?.window?.frame.origin
let yourPoint = CGPoint(x: (frameOrigin?.x)!, y: (frameOrigin?.y)! - 22)
yourWindow?.setFrameOrigin(yourPoint)
It seems that this app uses Matt's MAAttachedWindow. There's an sample application with the same layout & position.
NOTE: PLEASE DO NOT USE THIS, at least not for the purpose of locating an NSStatusItem.
Back when I posted this, this crazy image matching technique was the only way to solve this problem without undocumented API. Now, you should use Oskar's solution.
If you're willing to use image analysis to find the status item on a menu bar, here's a category for NSScreen which does exactly that.
It might seem crazy to do it this way, but it's fast, relatively small, and it's the only way of finding a status item without undocumented API.
If you pass in the current image for the status item, this method should find it.
#implementation NSScreen (LTStatusItemLocator)
// Find the location of IMG on the screen's status bar.
// If the image is not found, returns NSZeroPoint
- (NSPoint)originOfStatusItemWithImage:(NSImage *)IMG
{
CGColorSpaceRef csK = CGColorSpaceCreateDeviceGray();
NSPoint ret = NSZeroPoint;
CGDirectDisplayID screenID = 0;
CGImageRef displayImg = NULL;
CGImageRef compareImg = NULL;
CGRect screenRect = CGRectZero;
CGRect barRect = CGRectZero;
uint8_t *bm_bar = NULL;
uint8_t *bm_bar_ptr;
uint8_t *bm_compare = NULL;
uint8_t *bm_compare_ptr;
size_t bm_compare_w, bm_compare_h;
BOOL inverted = NO;
int numberOfScanLines = 0;
CGFloat *meanValues = NULL;
int presumptiveMatchIdx = -1;
CGFloat presumptiveMatchMeanVal = 999;
// If the computer is set to Dark Mode, set the "inverted" flag
NSDictionary *globalPrefs = [[NSUserDefaults standardUserDefaults] persistentDomainForName:NSGlobalDomain];
id style = globalPrefs[#"AppleInterfaceStyle"];
if ([style isKindOfClass:[NSString class]]) {
inverted = (NSOrderedSame == [style caseInsensitiveCompare:#"dark"]);
}
screenID = (CGDirectDisplayID)[self.deviceDescription[#"NSScreenNumber"] integerValue];
screenRect = CGDisplayBounds(screenID);
// Get the menubar rect
barRect = CGRectMake(0, 0, screenRect.size.width, 22);
displayImg = CGDisplayCreateImageForRect(screenID, barRect);
if (!displayImg) {
NSLog(#"Unable to create image from display");
CGColorSpaceRelease(csK);
return ret; // I would normally use goto(bail) here, but this is public code so let's not ruffle any feathers
}
size_t bar_w = CGImageGetWidth(displayImg);
size_t bar_h = CGImageGetHeight(displayImg);
// Determine scale factor based on the CGImageRef we got back from the display
CGFloat scaleFactor = (CGFloat)bar_h / (CGFloat)22;
// Greyscale bitmap for menu bar
bm_bar = malloc(1 * bar_w * bar_h);
{
CGContextRef bmCxt = NULL;
bmCxt = CGBitmapContextCreate(bm_bar, bar_w, bar_h, 8, 1 * bar_w, csK, kCGBitmapAlphaInfoMask&kCGImageAlphaNone);
// Draw the menu bar in grey
CGContextDrawImage(bmCxt, CGRectMake(0, 0, bar_w, bar_h), displayImg);
uint8_t minVal = 0xff;
uint8_t maxVal = 0x00;
// Walk the bitmap
uint64_t running = 0;
for (int yi = bar_h / 2; yi == bar_h / 2; yi++)
{
bm_bar_ptr = bm_bar + (bar_w * yi);
for (int xi = 0; xi < bar_w; xi++)
{
uint8_t v = *bm_bar_ptr++;
if (v < minVal) minVal = v;
if (v > maxVal) maxVal = v;
running += v;
}
}
running /= bar_w;
uint8_t threshold = minVal + ((maxVal - minVal) / 2);
//threshold = running;
// Walk the bitmap
bm_bar_ptr = bm_bar;
for (int yi = 0; yi < bar_h; yi++)
{
for (int xi = 0; xi < bar_w; xi++)
{
// Threshold all the pixels. Values > 50% go white, values <= 50% go black
// (opposite if Dark Mode)
// Could unroll this loop as an optimization, but probably not worthwhile
*bm_bar_ptr = (*bm_bar_ptr > threshold) ? (inverted?0x00:0xff) : (inverted?0xff:0x00);
bm_bar_ptr++;
}
}
CGImageRelease(displayImg);
displayImg = CGBitmapContextCreateImage(bmCxt);
CGContextRelease(bmCxt);
}
{
CGContextRef bmCxt = NULL;
CGImageRef img_cg = NULL;
bm_compare_w = scaleFactor * IMG.size.width;
bm_compare_h = scaleFactor * 22;
// Create out comparison bitmap - the image that was passed in
bmCxt = CGBitmapContextCreate(NULL, bm_compare_w, bm_compare_h, 8, 1 * bm_compare_w, csK, kCGBitmapAlphaInfoMask&kCGImageAlphaNone);
CGContextSetBlendMode(bmCxt, kCGBlendModeNormal);
NSRect imgRect_og = NSMakeRect(0,0,IMG.size.width,IMG.size.height);
NSRect imgRect = imgRect_og;
img_cg = [IMG CGImageForProposedRect:&imgRect context:nil hints:nil];
CGContextClearRect(bmCxt, imgRect);
CGContextSetFillColorWithColor(bmCxt, [NSColor whiteColor].CGColor);
CGContextFillRect(bmCxt, CGRectMake(0,0,9999,9999));
CGContextScaleCTM(bmCxt, scaleFactor, scaleFactor);
CGContextTranslateCTM(bmCxt, 0, (22. - IMG.size.height) / 2.);
// Draw the image in grey
CGContextSetFillColorWithColor(bmCxt, [NSColor blackColor].CGColor);
CGContextDrawImage(bmCxt, imgRect, img_cg);
compareImg = CGBitmapContextCreateImage(bmCxt);
CGContextRelease(bmCxt);
}
{
// We start at the right of the menu bar, and scan left until we find a good match
int numberOfScanLines = barRect.size.width - IMG.size.width;
bm_compare = malloc(1 * bm_compare_w * bm_compare_h);
// We use the meanValues buffer to keep track of how well the image matched for each point in the scan
meanValues = calloc(sizeof(CGFloat), numberOfScanLines);
// Walk the menubar image from right to left, pixel by pixel
for (int scanx = 0; scanx < numberOfScanLines; scanx++)
{
// Optimization, if we recently found a really good match, bail on the loop and return it
if ((presumptiveMatchIdx >= 0) && (scanx > (presumptiveMatchIdx + 5))) {
break;
}
CGFloat xOffset = numberOfScanLines - scanx;
CGRect displayRect = CGRectMake(xOffset * scaleFactor, 0, IMG.size.width * scaleFactor, 22. * scaleFactor);
CGImageRef displayCrop = CGImageCreateWithImageInRect(displayImg, displayRect);
CGContextRef compareCxt = CGBitmapContextCreate(bm_compare, bm_compare_w, bm_compare_h, 8, 1 * bm_compare_w, csK, kCGBitmapAlphaInfoMask&kCGImageAlphaNone);
CGContextSetBlendMode(compareCxt, kCGBlendModeCopy);
// Draw the image from our menubar
CGContextDrawImage(compareCxt, CGRectMake(0,0,IMG.size.width * scaleFactor, 22. * scaleFactor), displayCrop);
// Blend mode difference is like an XOR
CGContextSetBlendMode(compareCxt, kCGBlendModeDifference);
// Draw the test image. Because of blend mode, if we end up with a black image we matched perfectly
CGContextDrawImage(compareCxt, CGRectMake(0,0,IMG.size.width * scaleFactor, 22. * scaleFactor), compareImg);
CGContextFlush(compareCxt);
// Walk through the result image, to determine overall blackness
bm_compare_ptr = bm_compare;
for (int i = 0; i < bm_compare_w * bm_compare_h; i++)
{
meanValues[scanx] += (CGFloat)(*bm_compare_ptr);
bm_compare_ptr++;
}
meanValues[scanx] /= (255. * (CGFloat)(bm_compare_w * bm_compare_h));
// If the image is very dark, it matched well. If the average pixel value is < 0.07, we consider this
// a presumptive match. Mark it as such, but continue looking to see if there's an even better match.
if (meanValues[scanx] < 0.07) {
if (meanValues[scanx] < presumptiveMatchMeanVal) {
presumptiveMatchMeanVal = meanValues[scanx];
presumptiveMatchIdx = scanx;
}
}
CGImageRelease(displayCrop);
CGContextRelease(compareCxt);
}
}
// After we're done scanning the whole menubar (or we bailed because we found a good match),
// return the origin point.
// If we didn't match well enough, return NSZeroPoint
if (presumptiveMatchIdx >= 0) {
ret = CGPointMake(CGRectGetMaxX(self.frame), CGRectGetMaxY(self.frame));
ret.x -= (IMG.size.width + presumptiveMatchIdx);
ret.y -= 22;
}
CGImageRelease(displayImg);
CGImageRelease(compareImg);
CGColorSpaceRelease(csK);
if (bm_bar) free(bm_bar);
if (bm_compare) free(bm_compare);
if (meanValues) free(meanValues);
return ret;
}
#end
From the Apple NSStatusItem Class Reference:
Setting a custom view overrides all the other appearance and behavior settings defined by NSStatusItem. The custom view is responsible for drawing itself and providing its own behaviors, such as processing mouse clicks and sending action messages.