 |
 |
Create bitmap & display in a view (Cocoa Newbie :)
|
 |
|
 |
|
Professional Poster
Join Date: Nov 2000
Location: Tasmania, Australia
Status:
Offline
|
|
How do I create a bitmap graphic (ie, from scratch by specifying each pixel programatically, not from an existing graphic file or any other data), and then make it appear in an NSView (or custom view)?
I've had a look through a lot of documentation (NSImage, NSView, Core Graphics, etc) and every time I think I've nearly put all the pieces together, I find I'm missing something important.
I figured out how I should be able to build a bitmap using Core Graphics and store it in a memory address space, but I couldn't figure out how to display that in an NSView. Similarly, I could figure out how I should be able to display in image in an NSView from a file, but not from a bitmap I build on the fly.
I read the last chapter of "Learning Cocoa" (O'Reilly), but that just didn't explain enough for me. It only covered beziers (NSBezierPath and the Core Graphics equivalents). It didn't even touch on bitmaps.
From what I gather, I need to use an NSImage with an NSBitmapImageRep. But how do I tell the NSBitmapImageRep what each individual pixel should look like?
Can someone explain this to me in a very basic way (I'm new to all this).
Any advice would be greatly appreciated.
<small>[ 06-17-2002, 12:35 AM: Message edited by: Brass ]</small>
|
|
|
| |
|
|
|
 |
|
 |
|
Mac Elite
Join Date: Sep 2000
Location: Tempe, AZ
Status:
Offline
|
|
Create an NSBitmapImageRep using the
- (id)initWithBitmapDataPlanes  unsigned char **)planes pixelsWide  int)width pixelsHigh  int)height bitsPerSample  int)bps samplesPerPixel  int)spp hasAlpha  BOOL)alpha isPlanar  BOOL)isPlanar colorSpaceName  NSString *)colorSpaceName bytesPerRow  int)rowBytes bitsPerPixel  int)pixelBits
initializer. Pass null for planes so that it allocates its own memory. Use bitmapData to get a pointer to the data space. If you passed 8 for bps and 4 for spp, this will be in RGBA format. Write your values in, and then addRepresentation it into an NSImage. You've got a constructed-on-the-fly bitmap.
|
Geekspiff - generating spiffdiddlee software since before you began paying attention.
|
| |
|
|
|
 |
|
 |
|
Professional Poster
Join Date: Nov 2000
Location: Tasmania, Australia
Status:
Offline
|
|
</font><blockquote><font size="1" face="Geneva, Verdana, Arial, sans-serif">quote:</font><hr /><font size="1" face="Geneva, Verdana, Arial, sans-serif">Originally posted by smeger:
<strong>Create an NSBitmapImageRep using the
- (id)initWithBitmapDataPlanes  unsigned char **)planes pixelsWide  int)width pixelsHigh  int)height bitsPerSample  int)bps samplesPerPixel  int)spp hasAlpha  BOOL)alpha isPlanar  BOOL)isPlanar colorSpaceName  NSString *)colorSpaceName bytesPerRow  int)rowBytes bitsPerPixel  int)pixelBits
initializer. Pass null for planes so that it allocates its own memory. Use bitmapData to get a pointer to the data space. If you passed 8 for bps and 4 for spp, this will be in RGBA format. Write your values in, and then addRepresentation it into an NSImage. You've got a constructed-on-the-fly bitmap.</strong></font><hr /></blockquote><font size="1" face="Geneva, Verdana, Arial, sans-serif">Excellent! Thanks for this pointer. I couldn't make complete sense of your post (like where on earth "bitmapData" comes from) but after looking up the doco on this method it all makes sense now. I don't know why I couldn't find this method in my own search of the doco earlier (maybe my brain saw the number of arguments and skipped over it in fear). Thanks again for pointing me in the right direction - I really appreciate it!
|
|
|
| |
|
|
|
 |
|
 |
|
Mac Elite
Join Date: Sep 2000
Location: Tempe, AZ
Status:
Offline
|
|
My pleasure, hope it works out.
P.S. Sorry 'bout the cryptic post, my girlfriend was hanging over my shoulder telling me to hurry up 
|
Geekspiff - generating spiffdiddlee software since before you began paying attention.
|
| |
|
|
|
 |
|
 |
|
Professional Poster
Join Date: Nov 2000
Location: Tasmania, Australia
Status:
Offline
|
|
</font><blockquote><font size="1" face="Geneva, Verdana, Arial, sans-serif">quote:</font><hr /><font size="1" face="Geneva, Verdana, Arial, sans-serif">Originally posted by smeger:
<strong>My pleasure, hope it works out.
P.S. Sorry 'bout the cryptic post, my girlfriend was hanging over my shoulder telling me to hurry up  </strong></font><hr /></blockquote><font size="1" face="Geneva, Verdana, Arial, sans-serif">If it doesn't work out, I'll be sure to post back (when I eventually get another chance to try it).
I actually miss my girlfriend hanging over my shoulder, so I understand (she's been on the other side of the world for nearly a year... only 6 weeks to go! I'm going insane!).
|
|
|
| |
|
|
|
 |
|
 |
|
Professional Poster
Join Date: Nov 2000
Location: Tasmania, Australia
Status:
Offline
|
|
Okay, I'm getting there, but I'm stuck again. I've got:
• an NSImage with one NSBitmapImageRep all allocated and initialised
• 5 pointers pointing to the memory which should hold the pixel components (one for each plane) for the NSBitmapImageRep
• a window containing an NSImageView
• the NSImageView has it's image object set to the NSImage
How do I get the image represented by the NSBitmapImageRep (or NSImage) to appear in the NSImageView? I've had a look at NSImageView's "draw..." and "composite..." methods, as well as NSView's "setNeedsDisplay", but I can't seem to get them to do anything useful.
If anyone can help me to understand how this works, I'd be very grateful.
<small>[ 06-17-2002, 11:32 PM: Message edited by: Brass ]</small>
|
|
|
| |
|
|
|
 |
|
 |
|
Mac Elite
Join Date: Sep 2000
Location: Tempe, AZ
Status:
Offline
|
|
You set the NSImage to use the NSBitmapImageRep by doing [myNSImage addRepresentation: myNSBitmapImageRep]. You set the NSImageView to use the NSImage by doing [myNSImageView setImage: myNSImage]. That's it. You can force a redraw by minimizing your window to the dock and maximizing it again.
If you're not seeing anything, your image is probably screwy. You could try using an image that you loaded from a file as a test (see the NSImage initializers).
You mentioned five data planes. I've always set isPlanar to NO so I have a single data plane. If you do this and SPP=4 and BPS=8, each 32 bit value of your single data plane consists of an 8-bit R, 8-bit G, 8-bit B, and 8-bit Alpha.
I'm not sure what kind of color scheme would have five data planes?
|
Geekspiff - generating spiffdiddlee software since before you began paying attention.
|
| |
|
|
|
 |
|
 |
|
Professional Poster
Join Date: Nov 2000
Location: Tasmania, Australia
Status:
Offline
|
|
</font><blockquote><font size="1" face="Geneva, Verdana, Arial, sans-serif">quote:</font><hr /><font size="1" face="Geneva, Verdana, Arial, sans-serif">Originally posted by smeger:
<strong>You set the NSImage to use the NSBitmapImageRep by doing [myNSImage addRepresentation: myNSBitmapImageRep]. You set the NSImageView to use the NSImage by doing [myNSImageView setImage: myNSImage]. That's it. You can force a redraw by minimizing your window to the dock and maximizing it again.
If you're not seeing anything, your image is probably screwy. You could try using an image that you loaded from a file as a test (see the NSImage initializers).
You mentioned five data planes. I've always set isPlanar to NO so I have a single data plane. If you do this and SPP=4 and BPS=8, each 32 bit value of your single data plane consists of an 8-bit R, 8-bit G, 8-bit B, and 8-bit Alpha.
I'm not sure what kind of color scheme would have five data planes?</strong></font><hr /></blockquote><font size="1" face="Geneva, Verdana, Arial, sans-serif">Thanks for the tips. I seem to be doing it mostly right. My image itself must be screwy  I'll try an existing or known image like you suggested, I think.
From what I've looked at in the doco, I think the 5-planes are for CMYK with alpha (does alpha make sense with CMYK? maybe not on a printer, but maybe it's for theoretical devices).
|
|
|
| |
|
|
|
 |
|
 |
|
Professional Poster
Join Date: Nov 2000
Location: Tasmania, Australia
Status:
Offline
|
|
aha! There's something wrong with my pointers. After running the "initWithBitmapDataPlanes..." method, my pointers are all NULL. I'm not sure why that is, but I guess I've done something wrong in setting up or passing the pointers. I'll go through it and see if I can make sense of it. The whole pointer-to-pointers thing always confuses me.
|
|
|
| |
|
|
|
 |
|
 |
|
Mac Elite
Join Date: Sep 2000
Location: Tempe, AZ
Status:
Offline
|
|
If you pass NULL for planes in initWithBitmapDataPlanes..., the routine will allocate the memory for you. You can then do
</font><blockquote><font size="1" face="Geneva, Verdana, Arial, sans-serif">code:</font><hr /><pre style="font-size:x-small; font-family: monospace;">unsigned char *planes[5];
[myNSBitmapImageRep getBitmapDataPlanes: planes];
// planes[0] is the first plane, etc.</pre><hr /></blockquote><font size="1" face="Geneva, Verdana, Arial, sans-serif">No allocating necessary.
|
Geekspiff - generating spiffdiddlee software since before you began paying attention.
|
| |
|
|
|
 |
|
 |
|
Professional Poster
Join Date: Nov 2000
Location: Tasmania, Australia
Status:
Offline
|
|
hmmm... I did have my pointers declared and assigned a bit differently but I fixed that now, but I'm still having no luck. I'm setting pixel values like this (for RGB, no alpha, colour grey for all 100x100 pixels):
</font><blockquote><font size="1" face="Geneva, Verdana, Arial, sans-serif">code:</font><hr /><pre style="font-size:x-small; font-family: monospace;">
for ( y = 0; y < 100; y++ )
{
for ( x = 0; x < 100; x++ )
{
bitmapDataPlanes[0][100 * y + x] = (unsigned char) 64;
bitmapDataPlanes[1][100 * y + x] = (unsigned char) 64;
bitmapDataPlanes[2][100 * y + x] = (unsigned char) 64;
}
}
</pre><hr /></blockquote><font size="1" face="Geneva, Verdana, Arial, sans-serif">is this right? It's taking me a while to catch on, I think
When I use the debugger, it shows that the value 64 is actually at those locations (at least at the three I can check easily, for the pixel at point (0, 0) ).
<small>[ 06-19-2002, 01:31 AM: Message edited by: Brass ]</small>
|
|
|
| |
|
|
|
 |
|
 |
|
Mac Elite
Join Date: Sep 2000
Location: Tempe, AZ
Status:
Offline
|
|
Sorry, I'm out of ideas. As I said, I haven't done it with isPlanar=YES. But your assignment looks fine. What are you using for BPP, SPP, BPS, etc?
|
Geekspiff - generating spiffdiddlee software since before you began paying attention.
|
| |
|
|
|
 |
|
 |
|
Professional Poster
Join Date: Nov 2000
Location: Tasmania, Australia
Status:
Offline
|
|
Thanks for you patience and persistence. Here's almost the entire code (doesn't do much, just for testing for me to get this working so far). In the mean time, I might try it with isPlanar:NO.
</font><blockquote><font size="1" face="Geneva, Verdana, Arial, sans-serif">code:</font><hr /><pre style="font-size:x-small; font-family: monospace;">
- (id)init
{
[super init];
bitmapRep = [[NSBitmapImageRep alloc]
initWithBitmapDataPlanes:NULL pixelsWide:100 pixelsHigh:100
bitsPerSample:8 samplesPerPixel:3 hasAlpha:NO isPlanar:YES
colorSpaceName:NSCalibratedRGBColorSpace
bytesPerRow:0 bitsPerPixel:0];
[bitmapRep getBitmapDataPlanes:bitmapDataPlanes];
//bitmapDataRed = bitmapDataPlanes[0];
//bitmapDataGreen = bitmapDataPlanes[1];
//bitmapDataBlue = bitmapDataPlanes[2];
graphImage = [[NSImage alloc] initWithSize:imageSize];
[graphImage addRepresentation:bitmapRep];
[graphImageView setImage:graphImage];
return self;
}
- (void)dealloc
{
[graphImage release];
[bitmapRep release];
[super release];
}
- (void)setAllPixels
{
int x, y;
for ( y = 0; y < 100; y++ )
{
for ( x = 0; x < 100; x++ )
{
bitmapDataPlanes[0][100 * y + x] = (unsigned char)64;
bitmapDataPlanes[1][100 * y + x] = (unsigned char)64;
bitmapDataPlanes[2][100 * y + x] = (unsigned char)64;
}
}
}
- (IBAction)drawGraph  id)sender
{
[self setAllPixels];
[graphImageView setNeedsDisplay:YES];
}
</pre><hr /></blockquote><font size="1" face="Geneva, Verdana, Arial, sans-serif">EDIT: While I'm at it, I may as well post the interface part as well:
</font><blockquote><font size="1" face="Geneva, Verdana, Arial, sans-serif">code:</font><hr /><pre style="font-size:x-small; font-family: monospace;">
{
IBOutlet NSImageView *graphImageView;
NSSize imageSize;
NSImage *graphImage;
NSBitmapImageRep *bitmapRep;
unsigned char *bitmapDataPlanes[5];
unsigned char *bitmapDataRed;
unsigned char *bitmapDataGreen;
unsigned char *bitmapDataBlue;
unsigned char *bitmapDataAlpha;
unsigned char *bitmapDataOther;
}
- (IBAction)drawGraph  id)sender;
- (id)init;
- (void)dealloc;
</pre><hr /></blockquote><font size="1" face="Geneva, Verdana, Arial, sans-serif">In Interface Builder, there's just one small window which contains a small NSImageView (CustomView) and one button. The button and image view are the IBAction and IBOutlet respectively, and the object for which the code is above is instantiated in Image Builder.
<small>[ 06-20-2002, 06:18 PM: Message edited by: Brass ]</small>
|
|
|
| |
|
|
|
 |
|
 |
|
Mac Elite
Join Date: Sep 2000
Location: Tempe, AZ
Status:
Offline
|
|
Your 'imageSize' class variable is never initialized, so it defaults to NSZeroSize,meaning that you've got an image that's 0 by 0 pixels. Also, it's good practive to wrap your init in a
</font><blockquote><font size="1" face="Geneva, Verdana, Arial, sans-serif">code:</font><hr /><pre style="font-size:x-small; font-family: monospace;">if (self = [super init]) {
}
return self;</pre><hr /></blockquote><font size="1" face="Geneva, Verdana, Arial, sans-serif">block in case super can't init.
Everything else seems fine.
|
Geekspiff - generating spiffdiddlee software since before you began paying attention.
|
| |
|
|
|
 |
|
 |
|
Professional Poster
Join Date: Nov 2000
Location: Tasmania, Australia
Status:
Offline
|
|
oh yes, I'd cut them out during some earlier fiddling around. I've put it back in now:
</font><blockquote><font size="1" face="Geneva, Verdana, Arial, sans-serif">code:</font><hr /><pre style="font-size:x-small; font-family: monospace;"> imageSize.width = 100.0;
imageSize.height = 100.0; </pre><hr /></blockquote><font size="1" face="Geneva, Verdana, Arial, sans-serif">But it doesn't help. No image ever appears in the view <img border="0" title="" alt="[Frown]" src="frown.gif" />
|
|
|
| |
|
|
|
 |
|
 |
|
Junior Member
Join Date: Jul 2001
Location: Mos Eisley Cantina
Status:
Offline
|
|
In your setAllPixels function, try putting a [graphImage recache] after you set the pixel values.
|
|
|
| |
|
|
|
 |
|
 |
|
Forum Regular
Join Date: Sep 2000
Status:
Offline
|
|
Brass, in an attempt to help and teach myself a couple things at the same time, I took your code and used it as the basis for a subclass of NSView called "PixelView". I placed this custom view on a window, set it to auto-resize with the window, and implemented the code as follows:
Interface (PixelView.h):
</font><blockquote><font size="1" face="Geneva, Verdana, Arial, sans-serif">code:</font><hr /><pre style="font-size:x-small; font-family: monospace;">#import <Cocoa/Cocoa.h>
@interface PixelView : NSView
{
NSImage *pixelImage;
NSBitmapImageRep *bitmapRep;
unsigned char *bitmapDataPlanes[5];
}
- (void)setupImageWithSize  NSSize)size;
- (void)setPixelsWithSize  NSSize)size;
@end</pre><hr /></blockquote><font size="1" face="Geneva, Verdana, Arial, sans-serif"> Implementation (PixelView.m):
</font><blockquote><font size="1" face="Geneva, Verdana, Arial, sans-serif">code:</font><hr /><pre style="font-size:x-small; font-family: monospace;">#import "PixelView.h"
@implementation PixelView
- (id)initWithFrame  NSRect)frame
{
// initWithFrame: is the designated initializer for the NSView class
if (self = [super initWithFrame:frame]) {
[self setupImageWithSize:frame.size];
[self setPixelsWithSize:frame.size];
}
return self;
}
- (void)dealloc
{
[pixelImage release];
[bitmapRep release];
[super dealloc];
}
- (void)setupImageWithSize  NSSize)size
{
// Create a new bitmap of the appropriate size to place pixel data in
[bitmapRep release];
bitmapRep = [[NSBitmapImageRep alloc]
initWithBitmapDataPlanes:NULL
pixelsWide:size.width pixelsHigh:size.height
bitsPerSample:8 samplesPerPixel:3
hasAlpha:NO isPlanar:YES
colorSpaceName:NSCalibratedRGBColorSpace
bytesPerRow:0 bitsPerPixel:0];
// Get the array of pointers to the bitmap data
[bitmapRep getBitmapDataPlanes:bitmapDataPlanes];
// Create a new NSImage to contain this data
[pixelImage release];
pixelImage = [[NSImage alloc] initWithSize:size];
[pixelImage addRepresentation:bitmapRep];
}
- (void)setPixelsWithSize  NSSize)size
{
int width, height; // Integer versions of width/height
unsigned char rgbValues[3]; // RGB values for the current row
int x, y, pixelIndex; // Loop indices and current pixel index
width = (int)size.width;
height = (int)size.height;
// Set up the color values for the pixels to be drawn
for (y = 0; y < height; y++) {
rgbValues[0] = (unsigned char)(y / size.height * 64);
rgbValues[1] = (unsigned char)(y / size.height * 128);
rgbValues[2] = (unsigned char)(y / size.height * 192);
for (x = 0; x < width; x++) {
pixelIndex = width * y + x;
bitmapDataPlanes[0][pixelIndex] = rgbValues[0];
bitmapDataPlanes[1][pixelIndex] = rgbValues[1];
bitmapDataPlanes[2][pixelIndex] = rgbValues[2];
}
}
}
- (void)setFrame  NSRect)frameRect
{
// This gets called by AppKit when the view is resized
[super setFrame:frameRect];
[self setupImageWithSize:frameRect.size];
[self setPixelsWithSize:frameRect.size];
}
- (void)drawRect  NSRect)rect
{
// This gets called by AppKit when the image needs to be drawn
[pixelImage compositeToPoint:NSMakePoint(0.0, 0.0) operation:NSCompositeCopy];
}
- (BOOL)isOpaque
{
// Tell AppKit this view is opaque to increase performance
return YES;
}
@end</pre><hr /></blockquote><font size="1" face="Geneva, Verdana, Arial, sans-serif">The result is a manually-created gradient-filled rectangle that always fills the view. It probably isn't the most efficient code in the world, but it does work. Hope it helps.
[Edit: Fixed the dealloc method]
<small>[ 06-24-2002, 05:29 PM: Message edited by: Marshall ]</small>
|
|
|
| |
|
|
|
 |
|
 |
|
Professional Poster
Join Date: Nov 2000
Location: Tasmania, Australia
Status:
Offline
|
|
Guys,
Thanks for all your tips and help so far. It's really encouraging to have this kind of support.
PipelineStall - unfortunately the "recache" didn't help me in this case.
Marshall - your code reminds me that I've got a lot to learn about good Objective C style. It's a completely different approach to the way I was doing things. I'll give it a go and when I get that working, I'll try to adapt it to what I was originally aiming for (which was drawing fractals!).
Out of curiosity, why did you subclass NSView rather than NSImageView?
<small>[ 06-24-2002, 06:26 PM: Message edited by: Brass ]</small>
|
|
|
| |
|
|
|
 |
|
 |
|
Forum Regular
Join Date: Sep 2000
Status:
Offline
|
|
</font><blockquote><font size="1" face="Geneva, Verdana, Arial, sans-serif">quote:</font><hr /><font size="1" face="Geneva, Verdana, Arial, sans-serif">Originally posted by Brass:
<strong>Out of curiosity, why did you subclass NSView rather than NSImageView?</strong></font><hr /></blockquote><font size="1" face="Geneva, Verdana, Arial, sans-serif">NSImageView strikes me as the sort of thing I'd use for holding static images, like an icon on a dialog, whereas if I were modifying pixel data directly it would probably be for some direct-to-screen animated effect or custom UI element. Since most of the examples I've seen of this sort of thing subclass NSView (including Apple's DotView, CircleView, CompositeView, etc. examples), I figured I'd follow the pattern. It just seems like a more lightweight solution since I didn't need the extra features of the NSImageView just to get pixels to the screen.
|
|
|
| |
|
|
|
 |
|
 |
|
Forum Regular
Join Date: Sep 2000
Status:
Offline
|
|
A bit more info that I just found out: NSBitmapImageRep can draw itself without needing to be wrapped in an NSImage. It won't alpha-blend the image or do any other compositing effects, but if you're just trying to get a block of pixel data to the view, it seems to be a more efficient way of doing so.
To draw from the NSBitmapImageRep using the code I posted above, replace the line in drawRect:
</font><blockquote><font size="1" face="Geneva, Verdana, Arial, sans-serif">code:</font><hr /><pre style="font-size:x-small; font-family: monospace;">[pixelImage compositeToPoint:NSMakePoint(0.0, 0.0) operation:NSCompositeCopy];</pre><hr /></blockquote><font size="1" face="Geneva, Verdana, Arial, sans-serif">With this:
</font><blockquote><font size="1" face="Geneva, Verdana, Arial, sans-serif">code:</font><hr /><pre style="font-size:x-small; font-family: monospace;">[bitmapRep draw];</pre><hr /></blockquote><font size="1" face="Geneva, Verdana, Arial, sans-serif">That will draw the bitmap data at (0.0, 0.0) in the view. NSImageRep also defines drawAtPoint: and drawInRect: methods if you want to place the bitmap at a different location.
You can then remove all references to the NSImage from the interface and implementation.
|
|
|
| |
|
|
|
 |
|
 |
|
Professional Poster
Join Date: Nov 2000
Location: Tasmania, Australia
Status:
Offline
|
|
Marshall,
Thanks for all the info. It's been great. I've got the application working fairly well now, drawing nice fractals (greyscale so far, colour will be next). I might try the bitmap image representation without the NSImage like you suggested (I've already completely changed the code you gave above). I was just about to change the drawRect to only draw the actual rectange given as the parameter, but I might leave that until after I've removed NSImage from the equation.
I'm a bit dissappointed I couldn't figure out why my original implementation didn't work, but the current solution is a better one anyway.
If I ever release the software I'll be sure to give you and "smeger" credit for helping get on my feet with Cocoa and bitmap image creation.
|
|
|
| |
|
|
|
 |
 |
|
 |
|
|
|
|
|

|
|
 |
Forum Rules
|
 |
 |
|
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts
|
HTML code is Off
|
|
|
|
|
|
 |
 |
 |
 |
|
 |
|