Bitmap Image Filters
Pages: 1, 2, 3
The Way
Repeatedly, I've said that the filter will take a color image and convert it to gray-scale. This operation is a simple one, and in its most basic, it is no more complicated than finding a pixel, determining the values of the red, green, and blue samples, averaging those values, and then setting the average as the value of the white sample for the same in the gray-scale image representation.
This tiny bit of math must be done for every pixel in the image. To code this behavior we will be getting a heavy dose of C pointers and arrays. Since pointers and arrays are so closely related in C, I will use this discussion as an opportunity to show you how they are related. So, the first thing we need is a couple of loops that will scan through the entire image data buffer to access each pixel. This is where our x and y variables come in, as pixel numbers.
for ( y = 0; y < h; y++ ) {
for ( x = 0; x < w; x++ ) {
// Do the magic
}
}
The action that goes on inside the loop reads the values of the red, green, and blue components of the current pixel, averages them together, and then sets the corresponding destination pixel equal to that averaged value. To accomplish this we need pointers to the beginning of the source and destination image data buffers. These pointers are conveniently obtained with bitmapData messages to srcImageRep and destImageRep. bitmapData method returns a character pointer, which is type unsigned char *. This is convenient as unsigned char is the same size as our samples, 8-bits. Putting these last couple of pieces together with our existing code gives us the following:
- (NSImage *)filterImage:(NSImage *)srcImage
{
NSBitmapImageRep *srcImageRep = [NSBitmapImageRep
imageRepWithData:[srcImage TIFFRepresentation]];
NSImage *destImage = [[NSImage alloc] initWithSize:NSMakeSize(w,h)];
int w = [srcImageRep pixelsWide];
int h = [srcImageRep pixelsHigh];
int x, y;
NSBitmapImageRep *destImageRep = [[[NSBitmapImageRep alloc]
initWithBitmapDataPlanes:NULL
pixelsWide:w
pixelsHigh:h
bitsPerSample:8
samplesPerPixel:1
hasAlpha:NO
isPlanar:NO
colorSpaceName:NSCalibratedWhiteColorSpace
bytesPerRow:NULL
bitsPerPixel:NULL] autorelease];
unsigned char *srcData = [srcImageRep bitmapData];
unsigned char *destData = [destImageRep bitmapData];
unsigned char *p1, *p2;
for ( y = 0; y < h; y++ ) {
for ( x = 0; x < w; x++ ) {
}
}
[destImage addRepresentation:destImageRep];
return destImage;
}
In addition to declaring the pointers srcData and destData that point to the first element, or the head of the data buffers, we define the pointers p1 and p2, which will be used as working pointers. Think of them as moving cursors set to the location in the array of the pixel we're currently working on in the loop.
To understand how we work with the data, we have to understand more about how the data is organized. In a 24-bit image, where each color sample is 8-bits, with no alpha component, the data is arranged sequentially, where the first byte of the data buffer corresponds to the blue component of the first pixel. The second byte in the data array is the green component of the first pixel, and the third byte is the red component of the first pixel. This sequence continues for each pixel, across the first row of pixels (constant y, increasing x), and then, like a carriage return on a typewriter, we get to the next vertical row (incrementing y, reset x and go down the row again). This accounts for why the x for-loop is nested within the y for-loop.
A Bit About C-Pointer Arithmetic
In C a pointer is a variable that points to a place in memory where a meaningful value is stored. In our case, these meaningful values are the color samples for all of the pixels in our image. In our code, srcData is a pointer that points to the blue sample of the pixel in row 1, column 1. If we want to know the value at this memory location, we would use the dereferencing operator. Thus, *srcData is the value of the pixel sample.
Now, what if we want to know the value of the memory location adjacent to the one pointed to by srcData? What about the value stored in the memory location 2 or 3 or 300 slots beyond srcData? Simple, we just add the number of slots to the pointer and we get a pointer to that memory location. So the memory slot adjacent to srcData is srcData+1, two slots away is srcData+2, and so on. Again, to access the value at these locations use the dereferencing operator. So, *(srcData+1) is the adjacent value, or the value of the green sample of the first pixel, and so on. Notice how we used parentheses. Writing *srcData + 1 gives us the value of the first pixel's blue sample, plus one.
Now, let's see how we use this in the code below:
unsigned char *srcData = [srcImageRep bitmapData];
unsigned char *destData = [destImageRep bitmapData];
unsigned char *p1, *p2;
int n = [srcImageRep bitsPerPixel] / 8;
for ( y = 0; y < h; y++ ) {
for ( x = 0; x < w; x++ ) {
p1 = srcData + n * (y * w + x);
p2 = destData + y * w + x;
*p2 = (unsigned char)rint((*p1 + *(p1 + 1) + *(p1 + 2)) / 3);
}
}
Let's take our time and pick this thing apart. The first line in the for-loop says that p1, a pointer, will point to the location in the source image data buffer that corresponds to the xth pixel in the yth row. We get to that pixel by figuring out how many pixels into the data buffer we are, which is the y-value times the width of the image plus the x value, and we multiply that by the number of bytes in each pixel, n. In our code, we expect n to be 3. The resulting number is then added to the address pointed to by srcData.
Next we repeat this calculation for p2 in the destination data buffer. However, since the destination data is in the NSCalibratedWhiteColorspace colorspace, each pixel occupies only one byte of memory (because that is what's needed to represent a gray-scale value). So the calculation is the same as above, except n equals 1.
In the third line we access each of the red, green, and blue bytes of the pixel located by p1. The blue byte is pointed to by p1. Because the green and red bytes occur sequentially after the blue byte, we can increment the address of p1 by 1 to get the green byte location, and by 2 to get the blue byte location. To retrieve the actual value stored in those address locations we have to dereference the pointer. The * operator is applied only to addresses, and will return the value stored at the location pointed to by the address. So, p1, p1+1, and p1+2 are pointers to the red, green, and blue components, and *p1, *(p1+1), and *(p1+2) are the values of those components.
We sum up those three values, average them, round them off as an integer, and then cast it back as a unsigned char (since we're limited to 8-bits of storage space per component), and set the value of the memory pointed to by p2, which is *p2, to the result.
So, there you have it: C pointer arithmetic in a nutshell.
The above could have also been written using C's array notation. Writing our pointer arithmetic in pointer syntax is simple. For example, previously, *(p1+n) referred to the value at the memory location n steps past p1. In array syntax that would have be written as p1[n]. With this syntactical change the previous code can be rewritten as the following:
unsigned char *srcData = [srcImageRep bitmapData];
unsigned char *destData = [destImageRep bitmapData];
unsigned char *p1, *p2;
int n = [srcImageRep bitsPerPixel] / 8;
for ( y = 0; y < height; y++ ) {
for ( x = 0; x < width; x++ ) {
p1 = srcData + n * (y * w + x);
p2 = destData + y * w + x;
p2[0] = (unsigned char)rint((p1[0] + p1[1] + p1[2]) / 3);
}
}
Another way we can write the for-loop is to replace the n*y*w with the number of bytes per row. This would simplify the address arithmetic to the following:
unsigned char *srcData = [srcImageRep bitmapData];
unsigned char *destData = [destImageRep bitmapData];
unsigned char *p1, *p2;
int n = [srcImageRep bitsPerPixel] / 8;
int srcBPR = [srcImageRep bytesPerRow];
int destBPR = [destImageRep bytesPerRow];
for ( y = 0; y < height; y++ ) {
for ( x = 0; x < width; x++ ) {
p1 = srcData + y * srcBPR + n*x;
p2 = destData + y * destBPR + x;
p2[0] = (unsigned char)rint((p1[0] + p1[1] + p1[2]) / 3);
}
}
So, this is just another way of locating the data. When we put all the pieces together our final -filterImage: method looks like the following:
- (NSImage *)filterImage:(NSImage *)srcImage
{
NSBitmapImageRep *srcImageRep = [NSBitmapImageRep
imageRepWithData:[srcImage TIFFRepresentation]];
NSImage *destImage = [[NSImage alloc] initWithSize:NSMakeSize(w,h)];
int w = [srcImageRep pixelsWide];
int h = [srcImageRep pixelsHigh];
int x, y;
NSBitmapImageRep *destImageRep = [[[NSBitmapImageRep alloc]
initWithBitmapDataPlanes:NULL
pixelsWide:w
pixelsHigh:h
bitsPerSample:8
samplesPerPixel:1
hasAlpha:NO
isPlanar:NO
colorSpaceName:NSCalibratedWhiteColorSpace
bytesPerRow:NULL
bitsPerPixel:NULL] autorelease];
unsigned char *srcData = [srcImageRep bitmapData];
unsigned char *destData = [destImageRep bitmapData];
unsigned char *p1, *p2;
int n = [srcImageRep bitsPerPixel] / 8;
for ( y = 0; y < height; y++ ) {
for ( x = 0; x < width; x++ ) {
p1 = srcData + n * (y * w + x);
p2 = destData + y * w + x;
p2[0] = (unsigned char)rint((p1[0] + p1[1] + p1[2]) / 3);
}
}
[destImage addRepresentation:destImageRep];
return destImage;
}
With that we're ready to compile the code, and try it out.
Summary
As always, there are a number of enhancements you can make to this filter method. The implementation we created today is limited to 24-bit RGB images. One first enhancement would be to add support for images with an alpha channel. In the project you can download here you will find my implementation--it provides support for alpha.
You could also use this simple piece of code to make Altivec enhancements to the loop. I've haven't yet taken the time to dive into the Altivec libraries (I've only had a G4 for about six weeks now), but an image filter that uses the Altivec libraries is where I would start. It has all of the characteristics that make it a prime candidate for vectorization, and I imagine there'd be noticeable speed improvement. I leave that as an exercise for you folks.
So, we've seen how to make one image filter. There are hundreds of different operations you can perform on images that would fit well within the framework we've established here. That framework consists of this black-box method, -filterImage, where we pass an image in and get a new one back. The only thing that changes is what happens inside the for-loop. I'm not going to take the time to develop more filter operations, but in the next column I will show you how to define and implement an interface to a plug-in architecture for the application. This plug-in architecture will unload the burden of filter development from you to end users of the application. See you then.
Michael Beam is a software engineer in the energy industry specializing in seismic application development on Linux with C++ and Qt. He lives in Houston, Texas with his wife and son.
Read more Programming With Cocoa columns.
Return to the Mac DevCenter.
You must be logged in to the O'Reilly Network to post a talkback.
Showing messages 1 through 32 of 32.
-
confused by NSImage
2007-06-08 08:56:10 barryrp [Reply | View]
-
confused by NSImage
2009-03-16 03:37:52 Robin Forder [Reply | View]
I can reproduce the problem, indeed. The explanation is, I believe, that the filter is set up to convert RGB to grayscale. If you try to convert grayscale to grayscale, each of the destination pixels will be an average of the corresponding source pixel, the pixel to the right of that one, and the pixel to the right of that. It would result in an average leftwards shift by one pixel. This explains the stepping to the left, probably the breaking up of your vertical band, and also the accompanying blurring.
The first operation, rgb file to grayscale image, results in no leftwards move, which suggests that the effect is actually to do with the image formats.
-
Saving Image to Disk
2004-05-09 12:25:16 axronos [Reply | View]
Ok, so now that I know how to create my own custom filters, how do I save them to disk?
I have look far and wide, but I have not been able to find a way of taking my NSImage object (or any of the Reps thereof) and saving them to my harddrive as a specified image format. (And honestly, I'm surprised this series of tutorials falls short on that step.)
Thanks for the great tutorials, and the help.
Yianni
-
Categories, categories, categories....
2004-01-30 06:40:05 jlamarche [Reply | View]
Why create a whole new class for this one method. This method clearly belongs in a category on NSImage, so instead of initializing and allocating an object with no iVars, you can just call a method on your NSImage.
-
problem modifying filter
2003-05-08 13:23:14 anonymous2 [Reply | View]
Hi!
I hope it's ok to post this here. I have been playing around
with this filter. I used it as a template for making my own
filter, which is supposed to simply flip an image vertically.
Now, my filter works just fine, but if I try to apply it
twice to the same image it crashes. The crash occurs in
the loop which does the actual filtering. Now, I noticed that
the original grayscale filter provided in this lesson
doesn't crash, no matter how often I apply it. As I'm not
very good at pointer magic, I don't really see what the
reason could be. Maybe someone cares to look at this and
can suggest a solution. Note that I have changed the method
to a class method, but I don't see where that could be a
problem (I did the same to the grayscale filter).
Here is my code:
[code]
+ (NSImage *)filterImage: (NSImage *)srcImage
{
NSBitmapImageRep *srcImageRep = [NSBitmapImageRep
imageRepWithData: [srcImage TIFFRepresentation]];
int w = [srcImageRep pixelsWide];
int h = [srcImageRep pixelsHigh];
int x, y;
NSImage *destImage = [[NSImage alloc] initWithSize: NSMakeSize(w, h)];
NSBitmapImageRep *destImageRep = [[[NSBitmapImageRep alloc]
initWithBitmapDataPlanes: NULL // not planar but interlaced
pixelsWide: w
pixelsHigh: h
bitsPerSample: 8
samplesPerPixel: [srcImageRep samplesPerPixel]
hasAlpha: NO
isPlanar: NO
colorSpaceName: [srcImageRep colorSpaceName]
bytesPerRow: NULL
bitsPerPixel: NULL] autorelease];
// problem: this only works for rbg-images. unsigned char is 8-bit.
// how do I dynamically get a pointer type of the size
// (bitsPerSample * samplesPerPixel)?
unsigned char *srcData = [srcImageRep bitmapData];
unsigned char *destData = [destImageRep bitmapData];
unsigned char *p1, *p2;
int n = [srcImageRep bitsPerPixel] / 8;
for (y = 0; y < h; y++) {
for (x = 0; x < w; x++) {
p1 = srcData + n * (y * w + x); // pointer to pixel in row of src
p2 = destData + n * (y * w + (w - x)); // pointer to pixel in row of dest
*p2 = *p1; // the red bit
*(p2 + 1) = *(p1 + 1); // the green bit
*(p2 + 2) = *(p1 + 2); // the blue bit
}
}
[destImage addRepresentation: destImageRep];
return destImage;
}
[/code]
Thanks for help,
Knud -
problem modifying filter
2003-05-27 23:22:42 anonymous2 [Reply | View]
A small bug in your code is you should write:
p2 = destData + n*(y*w + w -x -1);
to calculate the p2 pointer.
This bug might result in the destData array out of bound durning the last transform operation. And if you were luck enough that there were any critical data, you application will crash.
However, I tested your code in my project. I was not so lucky to make it crash.
Maybe the real problem of yours is a bug in Mikebeam's framework. He said the object returned by filterImage: was autoreleased, and he did send a retain message to the returned object. But within the implemetation of filterImage:, the destImage was not sent such a message. A memory leakage!
-
Video Processing
2002-11-27 07:55:52 anonymous2 [Reply | View]
I would love to see also how a program very similar in functionality could be made to work on video clips (and process them as they play live)
A couple of pointers would be great if you don't plan anything similar to this...
Thanks
-
Expanding to cover 1bit images
2002-10-28 00:57:10 anonymous2 [Reply | View]
Can anyone cover this example to handle 1 bit images.
This would realy help me with one of my projects.
mark
-
Apple Laptop Keyboards Unsuitable for Unix Users
2002-09-18 02:30:50 anonymous2 [Reply | View]
Apple laptops are effectively unusable for unix users.
I am a long-time Unix user. That means I need to have the Ctrl key to the left of the A key. This is a genuine need, not merely a want; it is based upon ergonomics. The Ctrl key is heavily used in unix, and it must be easily accessable. It cannot be off in the lower left corner of the keyboard where it is difficult to get at, and where it distorts the position of your left hand such that you can't easily type other keys while holding the Ctrl key down.
Apple desktop keyboards are now all USB. They are all OK. The CapsLock key can be re-mapped into a Ctrl key.
Unfortunately, even in this modern age, all Apple laptops have built-in ADB keyboards. The ADB keyboard is broken-by-design. It is, in general, not possible to remap the CapsLock key into a Ctrl key.
There are some exceptions, but they are horrible kludges. They are
horrible kludges because the original design of the ADB keyboard was a horrible kludge. The correct solution would be for Apple to re-design their laptop motherboards to use built-in USB keyboards. This hasn't happened yet. If you run Linux, use Debian's solution. For Mac OS X users, uControl works. There are no solutions (that I know of) for either NetBSD or OpenBSD. Please note once again that the "solutions" above are in fact kludges, because of the original bad design of the ADB keyboard.
Apple is (currently) ignoring Unix users! This is not merely speculation on my part. In an on-going email exchange I am having with an Apple employee (whom I won't name) in their marketing department, the Apple marketing person directly stated to me that Apple was catering to their historic Mac customers, and is purposely ignoring the Unix market. He also claimed that Apple would soon start paying more attention to the Unix market. I won't hold my breath. Apple has been ignoring Unix users for more than 10 years. I expect that trend to continue. (Also note that my Apple contact indicated that Macs would never ship with a 3-button mouse, even though Apple intended to port almost all X-window software and deliver it either on a CD/DVD or installed directly on each Mac's hard drive. How Unix friendly is a 1-button mouse with X programs that often require 3 buttons?)
Apple has now lost two opportunities to sell me hardware. I really wanted an Apple laptop for their superior battery life, and for the PowerPC with Altivec CPU. (The Altivec is vastly superior to the x86 line for DSP.) Because I can't live with the broken-by-design built-in ADB keyboard in all Apple laptops, Sony and IBM sold me laptops instead. If Apple fixes this problem, they will sell me a PowerBook next year; if they don't, I'll still be running OpenBSD on x86 hardware, and wishing I could use a Mac.
-
motion deblurring-where is open FFT framework
2002-08-25 12:33:59 psheldon [Reply | View]
I bought a powershot s200 and wonder whether I could program up something to motion deblur. A G4 is so fast that I might not have to do a Cooley Tookey (sp?) algorithm. I would just have to get the way a glint got splotched and try a rough lateral inhibition in that direction. Starting little builds confidence for the long hauls. I took a nonflash time exposure on someone singing and playing the guitar in church, you see. The blur looked clean and with a glint on a guitar tuning nut(?) to track motion. I could see it was a curve.
I could save a file with that cropped out and figure out what the deconvolving filter was roughly, maybe.
I suppose photoshop has plugins that do that. Hey, wait a minute, I know they have gaussian blur. They might have motion deblur too. Wait a minute weren't we going to do plugins?
OK. Mike is going to make a speech and a book, so he might not want to fantacise about the next few columns, but I think I might hope from the vector processor nature of Altivec that fourier transform, maybe even FFT might just be already in the frameworks? I mean aren't frameworks built hand in hand with designing the processor, confustications of motorola "silicon compilers"? And Mike did just get a G4, right?
I spent a whole semester making up an algorithm with a pointer structure to do FFT's on a mac, but, if it is already built in a framework, then it might not take so long.
I downloaded something called cocoa browser from Japan. I haven't fooled with it yet.
How do I find such a framework? Maybe this is something that is open and free in opengl?
-
Building the NSBitmapImageRep from existing data.
2002-08-14 11:00:59 oscarmv [Reply | View]
What if I already had the image in raw RGBA 32-bit interlaced format, and wanted to use it without copying it? I assume the same init function with different parameters would work, but I don't know exactly how they should be set.
(BTW yes, I need to do exactly that in a program of mine ;P).
-
Centering the view.
2002-08-10 12:28:06 henrihansen [Reply | View]
Well, this question has already been asked in the "working with bitmaps" article but remained unanswered so far.
So, I wanted to take the following approach: when the window is resized (for example the user makes the window larger then the view should be placed in the center of the window) there should be an NSWindowDidResize notification flying around. My goal is to register the ImageView for this notification in order to catch it and make some adjustments on the placement of the ImageView.
I have no problems, registering and unregistering the ImageView with the default notificationCenter, but it looks like there is no NSWindowDidResize notification, well at least my method to handle the event is not invoked, which I checked with the help of NSLog.
The only idea I have, is that it is not sufficient to register the ImageView with the notificationCenter? Might it be, that the windows aren't automatically sending these notifications?
Any help is appreciated
greetings Henri -
Centering the view.
2002-08-12 05:02:38 infallible [Reply | View]
I am looking for a solution for that problem also.
1. Fixing lower-left corner problem is easy, just overwrite one method:
- (BOOL)isFlipped
{
return YES;
}
now about centering the view :/....
I have 1 idea:
1. make a new subclass from NSView called MyPositioningView or something.
2. add a MyImageView subview to MyPositioningView
3. then check if the clipview is larger than the image * scaleFactor. If it is set MyPositioningView's frame as big as clipView's frame, but if not then make it as big as your image * scaleFactor.
4. change MyImageView's frame origin.
I am gonna try it this week[when i got time], but maybe somebody knows a better way...
Oh easiest way would be just make a subclass of NSScrollView and overwrite the (void)tile method, but its not a good solution[ euw ugly!] :( ].
btw. sorry about my poor english. And good luck with your book Mike :).
-
Solution here:
2002-08-12 07:30:31 infallible [Reply | View]
Ok this is how you can do it:
1. Make a subclass of NSScrollView
2. overwrite drawRect, its not needed, but i wanted to make gray background for the image :]. If you won't overwrite it then it will be white
- (void)drawRect:(NSRect)rect
{
[[NSColor whiteColor] set];
NSRectFill(rect);
[[NSColor grayColor] set];
NSRectFill(NSMakeRect(0,0,rect.size.width - 15,rect.size.height - 15));
[self tile];
}
hmm 15 pixels is scroller's width ofcourse, you can make the code sigtly better ofcourse. Like: float scrollerWidth = NSHeight([[self horizontalScroller] frame]);
3. overwrite (void)tile
- (void)tile
{
NSRect newContentRect = [self frame];
NSRect documentRect = [[self documentView] frame];
float scrollerWidth = NSHeight([[self horizontalScroller] frame]); // 15 pixels for normal scrollers
// assuming that both scrollers are always visible! Modify if needed :]
newContentRect = NSMakeRect(0,0,newContentRect.size.width - scrollerWidth, newContentRect.size.height - scrollerWidth);
// use if([self hasHorizontalScroller])/if([self hasVerticalScroller]) also if you need to check if the scrollView has to draw the scrollers
[[self horizontalScroller] setFrame:NSMakeRect(0,newContentRect.size.height,newContentRect.size.width,scrollerWidth)];
[[self verticalScroller] setFrame:NSMakeRect(newContentRect.size.width,0,scrollerWidth,newContentRect.size.height)];
// Now check if centering is needed at all
if( newContentRect.size.width >= documentRect.size.width )
{
newContentRect.origin.x = (newContentRect.size.width - documentRect.size.width) / 2;
newContentRect.size.width = documentRect.size.width;
}
if( newContentRect.size.height >= documentRect.size.height )
{
newContentRect.origin.y = (newContentRect.size.height - documentRect.size.height) / 2;
newContentRect.size.height = documentRect.size.height;
}
// draw the content view
[[self contentView] setFrame:newContentRect];
}
/Infallible da n00b coder -
Oh almost forgot
2002-08-12 07:53:03 infallible [Reply | View]
This how it should look(without that popup button ofcourse)
http://www.hot.ee/priiware/shot0001.gif
and there may be some bugs when the image width or height isn't a whole number, i fixed that problem with the round() command.
/Infallible da n00b coder
-
Plugins for Cocoa Apps?
2002-08-10 11:31:31 kavan5 [Reply | View]
hey Mike- i know i'll probably be told to wait until the next article...=) but is CFPlugin the framework that you'll be using to demonstrate how to do a "plug-in" for ImageApp? i.e. is CFPlugin the "norm" on which Cocoa plug-ins are suggested to be based? i'd like to start looking into that. thanks!
-
Final code problem
2002-08-09 22:25:38 johnts [Reply | View]
In the final filterImage: code, there's a slight problem. At the top there are the declarations:
int w = [srcImageRep pixelsWide];
int h = [srcImageRep pixelsHigh];
But below in the for() loops, it's using 'height' and 'width'.
-
Final code problem
2002-08-10 21:52:55 johnts [Reply | View]
The final projects also has some warnings that can be very easily fixed. (I remember the warnings were around in the last version too):
IAWindowController.m needs:
#import "MyDocument.h"
and in IAWindowController.h this needs to be added:
- (void)setImageToDraw:(NSImage *)image; -
warnings can mean runtime errors later
2002-08-11 07:37:02 psheldon [Reply | View]
ie. that the compiler tried to guess what you meant which might not be correct during execution.
These warnings don't show up on a second compile (after a clean).
So, one should do a clean before a candidate final compile to catch them all if you want to be safe. -
I see why I and Mike didn't see
2002-08-10 10:25:16 psheldon [Reply | View]
The original partially filled out for loops did have w and h. Later loops didn't in the for lines controlling inside of the loops, the loop bodies, which did have w and h.
I only changed the inside of the loops not bothering to look at or paste the outside of the loop, so we only glanced at the for control lines for human, not computer, meaning.
I hope the above analysis of the mistake is useful.
Cool you caught that.
-
I made digital Laplacian image sharpener
2002-08-08 21:31:22 psheldon [Reply | View]
I sure hope this code doesn't pile up into one paragraph since there are no linefeed whitespaces.
We'll see.
Allocated another gray structure :
NSBitmapImageRep *destImageRepSharpened = same expression as destImageRep.
Then added :
unsigned char *destDataSharpened = [destImageRepSharpened bitmapData];
because I wasn't shown how to do the in place computation of the digital Laplacian.
Then I added to a line :
unsigned char *p1, *p2, *p2xp1, *p2xm1,*p2yp1,*p2ym1;
to capture pixels around on a cross of x±1 and y±1 from pixel at address p2.
After the grayifying loop, I put another loop :
//Implement digital Laplacian (see "Digital Picture Processing" p.185 by Rosenfeld and Kak copyright 1976)
for ( y = 0; y < h; y++ ) {
for ( x = 0; x < w; x++ ) {
// Get a pointer to the to be sharpened out pixel
p2Sharpened = destDataSharpened + y * w + x;
//Get terms of laplacian to fold or convolve into this pixel
p2 = destData + y * w + x;
p2xp1 = destData + y * w + (x+1);
p2xm1 = destData + y * w + (x-1);
p2yp1 = destData + (y+1) * w + x;
p2ym1 = destData + (y+1) * w + x;
//Average in color into an out gray
//(must cluge digital Laplacian with factor to insure that it is dark)
//this differencing of a pixel with the average of it and its surround
//is kind of like what goes on in the retina (reference too long ago to remember)
*p2Sharpened = (unsigned char)rint( - (*p2-(*p2+*p2xp1+*p2xm1+*p2yp1+*p2ym1) / 5) * 0.1 );
}
}
[destImage addRepresentation:destImageRepSharpened];
return destImage;
-
can get indents back if set prefs in PB
2002-08-08 21:37:16 psheldon [Reply | View]
There's an indentation selector fifth from left of the scrolling pane selectors in PB preferences.
That ought to make the code slightly readable. I need to get comfortable making c procedures in objective c to make things less cumbersome and surveyable than I wrote here.
-
handle alpha channel?
2002-08-08 21:12:34 psheldon [Reply | View]
"p1 = srcData + 3 * (y * w + x);" in IAGrayscaleFilter.m I believe does not as needs n in place of 3, I think.
I must be missing something because I don't see a change needed. Or maybe I learned enough to be of use in this article.
;-)
-
handle alpha channel?
2002-08-09 17:19:22 Michael Beam |
[Reply | View]
That's part of the solution. The other part has to do with the alpha channel occupying the first 8 bits of a pixel, so you have to offset your red, green, and blue indices by 1. So i had something like the following in the inner for-loop:
*p2 = (char)rint((*(p1 + ao) + *(p1 + 1 + ao) + *(p1 + 2 + ao)) / 3);
where ao (alpha offset) is determined at the start of the method as:
int ao = [srcImageRep hasAlpha] ? 1 : 0;
See if that works for you...
Mike -
ah you had given me support by giving me a problem to solve
2002-08-10 10:16:59 psheldon [Reply | View]
I do need some (easy) problems.
I don't get the syntax on right hand side of :
int ao = [srcImageRep hasAlpha] ? 1 : 0;
supposedly setting a0 to 1.
Are ? and : c operators and is this rhs c syntax?
My c pdf file from an old codewarrior has awful hypertexting. -
friend Dan Van Bose in Fort Worth explained syntax
2002-08-11 14:40:41 psheldon [Reply | View]
c = boolean ? a : b
c evaluates to first symbol after ? if boolean true and second expression if false.
So, this sentence commands skipping by an offset if Alpha is there and mixing 3 colors only.
I understand now.
-
soon getting to this, congrat's Mike
2002-08-07 15:53:44 psheldon [Reply | View]
I see "Mike Beam is a programmer for GeoCenter Inc., and he is working on a Cocoa book for O'Reilly."
I also see :
Mike Beam speaker at the O'Reilly Mac OS X Conference.
My brains have been fried the last two days studying F-Script article I hadn't gotten to for a long time. Evidencing my study is my belated work on threads to that column :
http://www.macdevcenter.com/pub/a/mac/2002/07/12/embed_fscript.html?page=last
That I can access object oriented frameworks through a scripting language brings to mind MEL scripts of Maya. Can we suppose that Maya creates os x objects on the fly too?
Now I must rest. Tomorrow your article.
-
soon getting to this, congrat's Mike
2002-08-08 14:06:43 Michael Beam |
[Reply | View]
Thanks! -
soon getting to this, congrat's Mike
2002-08-08 14:08:00 Michael Beam |
[Reply | View]
Hmmmm, that was bizarre. I suppose my toplevel post here doesn't mean much except in the context of psheldon's post.....
-
Grayscale conversion
2002-08-07 10:32:26 chieftypist [Reply | View]
The Y = (R + G + B) / 3 is a very rough approximation for grayscale. It's better to use:
Y = (0.222*R) + (0.707*G) + (0.071*B)
or
Y' = (0.299*R) + (0.587*G) + (0.114*B)
(depending on whose standard you want to follow :-)
With the speed of today's processors, the hit for doing the floating point aritmetic is negligible.
More information about colorspace conversions can be found at:
http://www.neuro.sfc.keio.ac.jp/~aly/polygon/info/color-space-faq.html
It's pretty hairy stuff, thankfully NSColor handles most of it transparently.
BTW, I'm loving this whole series. Really good work, Mike. Looking forward to the book!
-ch
-
Grayscale conversion
2002-08-08 14:06:03 Michael Beam |
[Reply | View]
Thanks for posting the additional information on colorspace conversions. Hopefully anyone who is interested in the details will follow you link. I'm glad that you're enjoying the series...thanks for reading (as well as thanks to everyone else following it)!






I need to produce an image filter on a mac. I've just acquired an intel mac complete with Xcode & am trying to learn cocoa asap (previous experience with VC & CodeWarrior). Came across this tutorial downloaded, converted to Xcode 2.4 and I'm a bit stuck! Some images seem to work - but then if you look closely repeated operations (you can Make Grayscale of a grayscale) move the image a pixel leftwards. I then created a TIFF - rectangle 200x100, all purple with white strip 10 pixels wide down righthand side. Tried this & the wihite band gets boken into strips & the resulting grayscale image has a black ban at the bottom. Looks as if each row is being shrunk. But stepping through the code all looks fine !
If you can reproduce this problem I would be very pleased (proves I'm not seeing things)!