0

[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.

1 Answers1

1

Create an NSView subclass for your custom drawing:

@interface MyView : NSView
@end

@implementation MyView

- (void)drawRect:(NSRect)dirtyRect
{
    [[NSColor redColor] setFill];
    NSRectFill(dirtyRect);
}

@end

Make sure to insert that at the top-level of your AppDelegate.m - that is, outside any other @interface and @implementation blocks.

Make an instance of your view and assign it to the content view of your window:

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    // ... Existing code that sets the window frame

    MyView* myView = [[MyView alloc] initWithFrame:self.window.contentView.bounds];
    self.window.contentView = myView;
}

Am I doing something wrong here? Is my order of operations wrong?

applicationDidFinishLaunching: is a delegate method allowing you to respond to a specific part of the application lifecycle. It is not the proper place to attempt to draw to the screen. Consider that your application can have multiple windows, each containing multiple views or drawing surfaces.

Does that mean that it doesn't know its own drawRect function?

The application delegate does not have a drawRect function.

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

Some feedback that is intended as purely constructive: it seems like you would do well to start with a beginners introduction to C or Objective C specifically, rather than jumping straight into programming AppKit. Copy-pasting chunks of code into applicationDidFinishLaunching: without understanding what they are doing is unlikely to ever get the results you want.

TheNextman
  • 12,428
  • 2
  • 36
  • 75
  • If I have to back up and learn a bit more about Obj-C, is there any good literature you could recommend from personal experience that concerns learning the language and progressing towards 3D graphic rendering? I come from a place where instructors throw code at you and hope you can figure out the rest and get good grades. You may be right about my relationship with that language. I've done some programming in C back in college and have dealt with 3D rendering. I went through a lot of material on the Net in order to draw parallels between Obj-C and C/C++, hoping it would all make sense. – AndrewGreen Feb 12 '20 at 06:42
  • I think I figured it out. I employed your code and redefined the parameters of ```dirtyRect``` upon instantiation of the view. I was also messing around with ```self.window.contentView``` in order to understand for myself what kind of a gap it fills and how it fills that gap, but I think I'll get it later on. Thank you. – AndrewGreen Feb 12 '20 at 08:11
  • *Cocoa Programming for Mac OS X by Aaron Hillegass* was the bible years ago, I have no idea if it's still current but the concepts should still apply. If you are working towards 3D graphics rendering, I'm not sure there's a huge benefit in learning Objective C and Cocoa in-depth. Once you have a view set up that you can draw into, almost everything you'll want to do will be in C (or specific shader language(s)).. – TheNextman Feb 12 '20 at 21:35