PDA

View Full Version : Using an Image Context




scottb99
Apr 15, 2009, 10:53 AM
I'm trying to use CGBitmapContextCreate to render text to bits.

It works (i get my rendered text) but then everything falls apart like I have heap corruption. Different crash every attempt.

The same thing that happened when I tried writing to the screen outside of drawRect (a year ago).

I know this is an interaction between the Cocoa framework and Core Graphics.

This render that I'm trying to accomplish has nothing to do with the screen or drawRect so I can't 'do it properly'.

I'm frustrated because this is an image context and it's totally illogical that it would cause problems.

Doe's anybody know what I can do to make this work?

This action is in response to a button press on a dialog box.

Scott Becker



kainjow
Apr 15, 2009, 12:57 PM
Well if you can post some code it would help, otherwise there's not much we can do since it sounds like it's crashing which probably means you're accessing invalid memory.

Do you require reading/writing at the pixel level? If not, drawing into an NSImage should be a lot simpler.

scottb99
Apr 15, 2009, 05:37 PM
my first attempt was along these lines:

/* read image src */
NSData *data = [NSData dataWithBytes:ms_bmp length:bytes];
NSImage *image = [[NSImage alloc] initWithData:data];
[image setFlipped:YES];
[image lockFocus];


/* now draw */ // this code works when drawing to the window
NSString *cocoa_string = [NSString stringWithUTF8String:text];
NSRect cocoa_rect;
NSSize cocoa_sz;

NSFont *font = [NSFont fontWithName:@"Arial" size:14.0];
NSMutableDictionary *attrsDictionary = [NSMutableDictionary dictionaryWithObject:font forKey:NSFontAttributeName];

cocoa_sz = [cocoa_string sizeWithAttributes:attrsDictionary];

cocoa_rect.origin.y = rect->top + ((( rect->bottom - rect->top ) - cocoa_sz.height ) / 2 );
cocoa_rect.size.height = cocoa_sz.height;

cocoa_rect.origin.x = rect->left;
cocoa_rect.size.width = rect->right - rect->left;

[cocoa_string drawInRect:cocoa_rect withAttributes:attrsDictionary];


/* cleanup */ // the code before and this did not seem to work
[image unlockFocus];

/* copy data back */
[data getBytes:ms_bmp];
[image release];



This attempt did not crash I got could not get my text on the image and I'm not sure if it's written right or not (real new at this.)





This the code I'm using right now. It works, I get the text in the image but then everything goes wrong afterwards:


CGContextRef context = NULL;
CGColorSpaceRef colorSpace;
unsigned char *bits, *in_row, *out_row;
int bitmapByteCount, bitmapBytesPerRow, x, y;

//pixmap is my custom structure
if ( pixmap->bit_count != 1 )
return -1;

/* render text to bits as grayscale */
bitmapBytesPerRow = pixmap->width;
bitmapByteCount = ( bitmapBytesPerRow * pixmap->height );

colorSpace = CGColorSpaceCreateWithName ( kCGColorSpaceGenericGray );
bits = malloc ( bitmapByteCount );
if ( bits == NULL)
{
fprintf (stderr, "Memory not allocated!");
return -1;
}

/* set to white */
memset ( bits, 255, bitmapByteCount );

context = CGBitmapContextCreate ( bits, pixmap->width,
pixmap->height, 8, bitmapBytesPerRow,
colorSpace, kCGImageAlphaNone );

CGColorSpaceRelease( colorSpace );
if (context== NULL)
{
free ( bits );
fprintf (stderr, "Context not created!");
return -1;
}

/* now we have context */
CGContextSelectFont ( context, "Arial", 10.f, kCGEncodingMacRoman );
CGContextSetRGBFillColor ( context, 0.0f, 0.0f, 0.f, 1.f );
CGContextSetShouldAntialias ( context, 0 );

CGContextShowTextAtPoint ( context, 0, 100, text, strlen ( text ));

CGContextRelease ( context );

// copy my data from bits


free ( bits );


This is based mostly on samples in the programming guide. This code is in a Safari Plugin.

Thanks for your help.

Scott Becker

kainjow
Apr 16, 2009, 11:50 AM
First, put your code in between ... tags - easier to read.

Since you're working with Quartz I'd suggest replacing that memset() with a CGContextFillRect().

Are you sure you're copying the bits properly, and not going beyond its length? I can't see anything else wrong memory wise with the code.

I'd suggest going back to using the Cocoa methods unless you plan on porting this to the iPhone at a later point. If you have code drawing into an NSView you can use [NSBitmapImageRep initWithFocusedViewRect:] and then add that into an NSImage. I'm guessing the Cocoa method isn't working because one of two possible reasons: your NSImage is being created with raw bytes and *possibly* the colorspace is not setup properly. Instead I'd create a blank NSImage via [NSImage initWithSize:], draw in your image there and then draw the text on top. Or, the coordinates are off (you are flipping it too). Try just drawing at a known position first.

If you still want to use the CG methods, you can use normal NS* methods to draw by setting up the graphics context:[NSGraphicsContext setCurrentContext:
[NSGraphicsContext graphicsContextWithGraphicsPort:context flipped:NO]];
This makes it easier to draw text as well (the CG functions don't handle much more than the basics).

scottb99
Apr 24, 2009, 01:18 PM
Trying to understand how I would use
[NSBitmapImageRep initWithFocusedViewRect]

Currently I have this code functioning fine to draw to the webkit plugin's window ( NSView I suppose ):



NSString *cocoa_string = [NSString stringWithUTF8String:text];
NSRect cocoa_rect;
NSSize cocoa_sz;

NSFont *font = [NSFont fontWithName:@"Arial" size:14.0];
NSMutableDictionary *attrsDictionary = [NSMutableDictionary dictionaryWithObject:font forKey:NSFontAttributeName];

cocoa_sz = [cocoa_string sizeWithAttributes:attrsDictionary];

cocoa_rect.origin.y = rect->top + ((( rect->bottom - rect->top ) - cocoa_sz.height ) / 2 );
cocoa_rect.size.height = cocoa_sz.height;

cocoa_rect.origin.x = rect->left;
cocoa_rect.size.width = rect->right - rect->left;

[cocoa_string drawInRect:cocoa_rect withAttributes:attrsDictionary];



My goal is to get monochrome bits of the text to use as a mask so I can add the text to another image. So my render here is to a blank white image (I don't need to move in any image data beforehand).

My attempt with the CG code was to render to grayscale without AntiAlias because that was the closest to monochrome the colorspace choices would allow. I'll take any colorspace I can get and convert it.

So my above working code (if I understand it correctly) prepares a string object, a rectangle object and a dictionary object and tells the string object to draw itself to the rectangle using the dictionary of attributes for the details.



[cocoa_string drawInRect:cocoa_rect withAttributes:attrsDictionary];



And this draws to the window because it's being called from a drawRect call on an NSView object where focus has been set to make it draw to the window/screen.

The text render I'm trying to accomplish is not during drawRect (it's in a button press message on a dialog box).

So, (I'm guessing here) I take a freshly created NSBitmapImageRep object and call initWithFocusedViewRect. Is there anything else special about the NSBitmapImageRep object? Is the rect parameter for the init call the cocoa_rect from above? or is it a 0,0 width,height rect? do I set focus on the rect or the imageRep?

As you can see I'm struggling to ask an intelligent question. I've been programming dos and windows for 20 years but the cocoa framework and objective c is very new to me.

Thanks, Scott Becker

kainjow
Apr 24, 2009, 01:51 PM
Since you mentioned it's a WebKit plugin, I don't think initWithFocusedViewRect would be useful here (unless you have direct control over your view). initWithFocusedViewRect is used when you have a view (or another image) that is already lockFocus'd and you want to draw its content into your NSBitmapImageRep object instead of into the window.

An example (assuming self is an NSView):
if ([self lockFocusIfCanDraw]) {
rep = [[NSBitmapImageRep alloc] initWithFocusedViewRect:[self bounds]];
[self unlockFocus];
}

My goal is to get monochrome bits of the text to use as a mask so I can add the text to another image.

Maybe CGImageCreateWithMask() is what you want?

scottb99
Apr 24, 2009, 03:27 PM
I got the CG code to work, it just crashed weirdly afterwards.
Lets explore getting this to work.

I was reading about the NSImage and they spoke of multiple ImageReps inside it and updating one or more when drawn to.

Since my ms_bmp is 1 bit per pixel this may explain why I didn't get any results. My CG code needed at least 8 bits/grayscale to work. How do you test or set the colorspace in a NSImage?



/* read image src */
NSData *data = [NSData dataWithBytes:ms_bmp length:bytes];
NSImage *image = [[NSImage alloc] initWithData:data];

[image setFlipped:YES]; // does this make the coordinates top down?

[image lockFocus];


.....(set up sting and params, tested to screen)
[cocoa_string drawInRect:cocoa_rect withAttributes:attrsDictionary];



[image unlockFocus];

/* copy data back */
[data getBytes:ms_bmp];
[image release];



Maybe I need to feed it a grayscale image or don't init with data and just deal with the result: find out what image representation it has and grab it.

Is the above lockfocus and unlockfocus done properly?

scottb

scottb99
Apr 24, 2009, 06:16 PM
I'm reading a lot and learned why my first attempt didn't work.
Lockfocus on nsimage
draw to it
get bits
docs say that the original version is not modified, treat as immutable

I've been working on the "right" method when I ran across this:

In my second attempt using CoreGraphics I:

bits = malloc ( bitmapByteCount );

//then:
context = CGBitmapContextCreate ( bits...

//and at the end:
free ( bits );


If CGBitmapContextCreate takes ownership of bits (by intention or accident) then that would explain the random crashes. If the pointer was reassigned to a different and random block of memory then who knows what I was freeing.

kainjow
Apr 24, 2009, 06:39 PM
I don't think CGBitmapContextCreate() takes ownership. I have code that frees the data after the CGContextRef is released and it has never crashed on me.

scottb99
Apr 27, 2009, 10:40 AM
I just did my first test using my new Cocoa method and it crashed afterward just like the CG code did. So your right, the problem must be in the un-verified code which executes after this bit of code. I'll trace that down now.

thanks
scottb

scottb99
Apr 28, 2009, 12:55 PM
I found a nasty typo bug in my portable (win32 also) code. I works now.


The last thing I'm stuck on is turning off Anti-aliasing so I can convert to mono-chrome with the best quality. Here's my code, any ideas?



void fb_cocoa_window_draw_text ( void *instance, PRT rect, const char *text, int center, int bottom, int white, int word_break, int point_size )
{
NSString *cocoa_string = [NSString stringWithUTF8String:text];
NSRect cocoa_rect;
NSSize cocoa_sz;

NSFont *font = [NSFont fontWithName:@"Arial" size:point_size];
NSMutableDictionary *attrsDictionary = [NSMutableDictionary dictionaryWithObject:font forKey:NSFontAttributeName];

if ( white )
{
NSDictionary *attrsDictionary2 = [NSDictionary dictionaryWithObject:[NSColor whiteColor] forKey:NSForegroundColorAttributeName];
[attrsDictionary addEntriesFromDictionary:attrsDictionary2];
}

cocoa_sz = [cocoa_string sizeWithAttributes:attrsDictionary];


if ( word_break )
{
cocoa_rect.origin.y = rect->top;
cocoa_rect.size.height = rect->bottom - rect->top;
}
else
{
cocoa_rect.origin.y = rect->top + ((( rect->bottom - rect->top ) - cocoa_sz.height ) / 2 );
cocoa_rect.size.height = cocoa_sz.height;
}


cocoa_rect.origin.x = rect->left;
cocoa_rect.size.width = rect->right - rect->left;

[cocoa_string drawInRect:cocoa_rect withAttributes:attrsDictionary];

}












int fb_cocoa_pixmap_draw_text ( SBP pixmap, PRT rect, const char *text, int text_height, int center, int bottom, int white, int word_break )
{
NSImage* new_image;
NSBitmapImageRep* new_bitmap;
NSRect cocoa_rect;
int x, y, bits_pp, bytes_p_row, point_size;
unsigned char *in_bits, *out_bits, *in_row, *out_row;

/* prepare rect */
cocoa_rect.origin.x = 0;
cocoa_rect.origin.y = 0;
cocoa_rect.size.width = pixmap->width;
cocoa_rect.size.height = pixmap->height;

/* prepare image */
new_image = [[NSImage alloc] initWithSize:NSMakeSize ( pixmap->width, pixmap->height )]; //rect->right, rect->bottom )];
[new_image setFlipped:YES];
[new_image lockFocus];

/* draw on image */
[[NSColor whiteColor] setFill];
NSRectFill ( cocoa_rect );

/* window draw text assumes 72 dpi ( no way to specify ) so a pixel == a point */
/* this works fine, for now */
point_size = text_height;

/* function above */
fb_cocoa_window_draw_text ( 0, rect, text, center, bottom, white, word_break, point_size );

/* create bitmap of image */
new_bitmap = [[NSBitmapImageRep alloc] initWithFocusedViewRect:cocoa_rect];

/* clean up */
[new_image unlockFocus];
[new_image release];


/* process image... */





[new_bitmap release];

return 0;
}




Thanks
scottb

kainjow
Apr 28, 2009, 01:11 PM
Try this: [[NSGraphicsContext currentContext] setShouldAntialias:NO];

scottb99
Apr 28, 2009, 01:21 PM
Thankyou. That worked and looks WAY better.