[Edit:] Moving forward from phase 1 of my project here: How do I re-define size/origin of app window in code, overriding nib/xib file parameters, with Obj-C, Xcode 11.3.1 on Mac OS X 10.15.2 (Catalina)?
My current objective is pretty simple (at least in the abstract): I want to (re-)color a pixel in my blank Mac OS X application window. I want to do this economically, with as few lines of code as humanly possible, because looking at large chunks of code is a real eyesore.
I really did not want to deal with images and image buffers or draw the smallest visible lines and rectangles, but I was willing to give all that a try. I ended up going through more StackOverflow and Apple Kit articles than God can count.
The original agenda was to:
1) access my blank application window’s “content”
2) specify my pixel of interest within that content
3) access that pixel with its colorspace values
4) rewrite the values directly if possible
Seems pretty compact and simple, right?
This is what's happening in my AppDelegate. I open the constructing function with:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
/*
For a more complex app, it's probably better to not put a window into the Main Menu NIB.
Instead, make a separate NIB for the window.
Then, load it using a window controller object and ask that for its window.
*/
NSRect frame = [_window frame];
frame.origin.x = 0;
frame.origin.y = 0;
frame.size.width = 1425;
frame.size.height = 810;
[_window setFrame: frame display: YES];
What follows are my attempts at drawing some kind of something.
Tactic #1 - NSRectFill:
NSRect pxl;
pxl.origin.x = 0;
pxl.origin.y = 0;
pxl.size.width = 128;
pxl.size.height = 128;
//_window.draw(_ dirtyRect: pxl)
NSRectFill(pxl);
//draw(_ dirtyRect: pxl);
Tactic #2 (and I really did not want to try this one as it struck me as cheap and lazy) - NSBezierPath:
NSBezierPath * path = [NSBezierPath bezierPath];
[path setLineWidth: 4];
NSPoint startPoint = { 0, 0 };
NSPoint endPoint = { 128, 128 };
[path moveToPoint: startPoint];
[path lineToPoint: endPoint];
[[NSColor redColor] set];
[path stroke];
I know I'm drawing a line here, but every little bit helps.
Tactic #3 - using image and image rep objects:
NSImage * image;
//create and define a colorspace object:
NSColor * rgb = [NSColor colorWithDeviceRed:1.0f
green:1.0f
blue:1.0f
alpha:1.0f];
//load the defined colorspace data into image rep:
NSBitmapImageRep * imgRep = [[NSBitmapImageRep alloc] initWithData:[image TIFFRepresentation]];
[imgRep setColor:rgb atX:128.0 y:128.0];
Of course, the problem with this one is that the image object does not hold anything. So what do I put in there? Do I have to load a XIB file in there? I've seen some solutions on the web and they defeat the point of "writing as few lines as possible".
Tactic #4 - using a convoluted and verbose function:
NSColor *MyColorAtScreenCoordinate(CGDirectDisplayID displayID, NSInteger x, NSInteger y)
{ //Function definition is not allowed here
CGImageRef image = CGDisplayCreateImageForRect(displayID, CGRectMake(x, y, 1, 1));
NSBitmapImageRep *bitmap = [[NSBitmapImageRep alloc] initWithCGImage:image];
CGImageRelease(image);
NSColor *color = [bitmap colorAtX:0 y:0];
[bitmap release];
}
Then it complains:
Function definition is not allowed here
So I took this one out to the global scope with - (void)
but it complains anyway:
Expected method body
Tactic #5 - using bitmapImageRepForCachingDisplayInRect()
NSData *data;
NSBitmapImageRep *rep;
rep = [self bitmapImageRepForCachingDisplayInRect:[self frame]];
[self cacheDisplayInRect:[self frame] toBitmapImageRep:rep];
data = [rep TIFFRepresentation];
It gives me:
No visible @interface for 'AppDelegate' declares the selector 'frame'
Didn't I just define what frame is within the same scope?
I close my delegate constructing function with this:
[_window setFrame: frame display: YES];
}
Outside of that scope I tried
Tactic #6 - custom drawRect():
- (void)drawRect:(NSRect)dirtyRect {
// set any NSColor for filling, say white:
[[NSColor whiteColor] setFill];
NSRectFill(dirtyRect);
[super drawRect:dirtyRect];
}
No visible @interface for 'NSObject' declares the selector 'drawRect:'
Does that mean that it doesn't know its own drawRect function?
Tactic #7 - copying and pasting a large chunk from Mac OS Cocoa: Draw a simple pixel on a canvas :
NSInteger width = 128;
NSInteger height = 128;
NSInteger dataLength = width * height * 4;
UInt8 *data = (UInt8*)malloc(dataLength * sizeof(UInt8));
//Fill pixel buffer with color data
for (int j=0; j<height; j++) {
for (int i=0; i<width; i++) {
//Here I'm just filling every pixel with red
float red = 1.0f;
float green = 0.0f;
float blue = 0.0f;
float alpha = 1.0f;
int index = 4*(i+j*width);
data[index] =255*red;
data[++index]=255*green;
data[++index]=255*blue;
data[++index]=255*alpha;
}
}
// Create a CGImage with the pixel data
CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, data, dataLength, NULL);
CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
CGImageRef img = CGImageCreate(width, height, 8, 32, width * 4, colorspace, kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast,
provider, NULL, true, kCGRenderingIntentDefault);
//Clean up
CGColorSpaceRelease(colorspace);
CGDataProviderRelease(provider);
// Don't forget to free(data) when you are done with the CGImage
I've tried every one of those methods; nothing draws. Am I doing something wrong here? Is my order of operations wrong? I tried to keep it all syntax-compliant and understandable to the compiler and the builder.
Side note 1: I really wish that I could use as few classes/objects as possible to put something on the blank window.
Side note 2: I’ve seen so many MVC diagrams and read so many articles that it’s still unclear what does what. I’ve seen so many pieces of code as attempts to achieve the same goal that it’s hard to believe that there is more than one way to do something fundamental.
The simple act of coloring a pixel within the frame of a window has become a loaded adventure marked with over-exploiting buffers and objects Xcode “does not recognize”.
No visible @interface declares this selector.