Objective-C: Display an Array of Floats as an Image - objective-c

In a Cocoa App I would like to display a 2d array of floats in an NSImageView. To make the code as simple as possible, start off by converting the data from float to NSData:
// dataArray: an Nx by Ny array of floats
NSMutableData *nsdata = [NSMutableData dataWithCapacity:0];
long numPixels = Nx*Ny;
for (int i = 0; i < numPixels; i++) {
[nsdata appendBytes:&dataArray[i] length:sizeof(float)];
}
and now try to display the data (the display is left blank):
[theNSImageView setImage:[[NSImage alloc] initWithData:nsdata]];
Is this the correct approach? Is a CGContext needed first? I was hoping to accomplish this with NSData.
I have noted the earlier Stack posts: 32 bit data, close but in reverse, almost worked but no NSData, color image data here, but not much luck getting variations on these working. Thanks for any suggestions.

You can use an NSBitmapImageRep to build up an NSImage float-by-float.
Interestingly, one of its initialisers has the longest method name in all of Cocoa:
- (id)initWithBitmapDataPlanes:(unsigned char **)planes
pixelsWide:(NSInteger)width
pixelsHigh:(NSInteger)height
bitsPerSample:(NSInteger)bps
samplesPerPixel:(NSInteger)spp
hasAlpha:(BOOL)alpha
isPlanar:(BOOL)isPlanar
colorSpaceName:(NSString *)colorSpaceName
bitmapFormat:(NSBitmapFormat)bitmapFormat
bytesPerRow:(NSInteger)rowBytes
bitsPerPixel:(NSInteger)
It's well documented at least. Once you've built it up by supplying float arrays in planes you can then get the NSImage to put in your view:
NSImage *image = [[NSImage alloc] initWithCGImage:[bitmapImageRep CGImage] size:NSMakeSize(width,height)];
Or, slightly cleaner
NSImage *image = [[[NSImage alloc] init] autorelease];
[im addRepresentation:bitmapImageRep];
There is an initialiser which just uses an NSData container:
+ (id)imageRepWithData:(NSData *)bitmapData
although that depends on your bitmapData containing one of the correct bitmap formats.

Ok got it to work. I had tried the NSBitmapImageRep before (thanks Tim) but the part I was missing was in properly converting my floating point data to a byte array. NSData doesn't do that and returns nil. So the solution was not so much in needing to build up an NSImage float-by-float. In fact, one can similarly build up a bitmapContext (using CGBitmapContextCreate (mentioned by HotLicks above)) and that works too, once the floating point data has been represented properly.

Related

Saving NSBitmapImageRep as image

I've done an exhaustive search on this and I know similar questions have been posted before about NSBitmapImageRep, but none of them seem specific to what I'm trying to do which is simply:
Read in an image from the desktop (but NOT display it)
Create an NSBitmap representation of that image
Iterate through the pixels to change some colours
Save the modified bitmap representation as a separate file
Since I've never worked with bitmaps before I thought I'd just try to create and save one first, and worry about modifying pixels later. That seemed really straightforward, but I just can't get it to work. Apart from the file saving aspect, most of the code is borrowed from another answer found on StackOverflow and shown below:
-(void)processBitmapImage:(NSString*)aFilepath
{
NSImage *theImage = [[NSImage alloc] initWithContentsOfFile:aFilepath];
if (theImage)
{
CGImageRef CGImage = [theImage CGImageForProposedRect:nil context:nil hints:nil];
NSBitmapImageRep *imageRep = [[NSBitmapImageRep alloc] initWithCGImage:CGImage];
NSInteger width = [imageRep pixelsWide];
NSInteger height = [imageRep pixelsHigh];
long rowBytes = [imageRep bytesPerRow];
// above matches the original size indicating NSBitmapImageRep was created successfully
printf("WIDE pix = %ld\n", width);
printf("HIGH pix = %ld\n", height);
printf("Row bytes = %ld\n", rowBytes);
// We'll worry about this part later...
/*
unsigned char* pixels = [imageRep bitmapData];
int row, col;
for (row=0; row < height; row++)
{
// etc ...
for (col=0; col < width; col++)
{
// etc...
}
}
*/
// So, let's see if we can just SAVE the (unmodified) bitmap first ...
NSData *pngData = [imageRep representationUsingType: NSPNGFileType properties: nil];
NSString *destinationStr = [self pathForDataFile];
BOOL returnVal = [pngData writeToFile:destinationStr atomically: NO];
NSLog(#"did we succeed?:%#", (returnVal ? #"YES": #"NO")); // the writeToFile call FAILS!
[imageRep release];
}
[theImage release];
}
While I like this code for its simplicity, another potential issue down the road might be that Apple docs advise us treat bitmaps returned with 'initWithCGImage' as read-only objects…
Can anyone please tell me where I'm going wrong with this code, and how I could modify it to work. While the overall concept looks okay to my non-expert eye, I suspect I'm making a dumb mistake and overlooking something quite basic. Thanks in advance :-)
That's a fairly roundabout way to create the NSBitmapImageRep. Try creating it like this:
NSBitmapImageRep* imageRep = [NSBitmapImageRep imageRepWithContentsOfFile:aFilepath];
Of course, the above does not give you ownership of the image rep object, so don't release it at the end.

NSBitmapImageRep from duration for animated GIF

I have two animated gifs:
http://d-32.com/uploads/gif1.gif and http://d-32.com/uploads/gif2.gif
When using NSBitmapImageRep I get a frame duration of 0.15s for the first gif and 0.1s for the second.
But 0.1 is way to slow for the second gif.
When using imagemagick I also get 0.15s for the first, but 0.03s for the second gif, which is correct.
Am I doing something wrong, or is NSBitmapImageRep, i.e. using NSImageView for displaying gifs useless and I would have to resort back to a webview (which can display both gifs correctly)?
So thanks to Heinrich I now know that the gif as two values, the real one and the clamped value. Sadly NSBitmapImageRep only provides the clamped value, but with CGImageProperty I can read both values. So I iterate through each frame, update the duration with the correct one and in the end create a CAAnimation like in: https://github.com/orta/GIFs/blob/master/objc/GIFs/ORImageView.m
So this is the code for updating the duration:
NSDictionary *properties = (__bridge_transfer NSDictionary *)CGImageSourceCopyPropertiesAtIndex(imageSource, i, nil);
float duration = [[[properties objectForKey:(__bridge NSString *)kCGImagePropertyGIFDictionary]
objectForKey:(__bridge NSString *) kCGImagePropertyGIFUnclampedDelayTime] doubleValue];
[bitmapRepresentation setProperty:NSImageCurrentFrame withValue:[NSNumber numberWithInt:i]];
[bitmapRepresentation setProperty:NSImageCurrentFrameDuration withValue:[NSNumber numberWithFloat:duration]];

NSBitmapImageRep bug

This code produces some very strange output. Why?
All I do is copying an image.
NSData *data = [NSData dataWithContentsOfFile: #"/Users/Jojo/Desktop/k2.jpg"];
NSBitmapImageRep *image = [NSBitmapImageRep imageRepWithData: data];
assert(image.samplesPerPixel == 3);
assert(image.isPlanar == NO);
uint8_t *buffer = [image bitmapData];
NSBitmapImageRep *rtn = [[NSBitmapImageRep alloc]
initWithBitmapDataPlanes:&buffer
pixelsWide:image.pixelsWide
pixelsHigh:image.pixelsHigh
bitsPerSample:8
samplesPerPixel:3
hasAlpha:NO
isPlanar:NO
colorSpaceName:NSDeviceRGBColorSpace
bytesPerRow: image.pixelsWide*3
bitsPerPixel: 8*3];
NSData *newJpg = [rtn representationUsingType:NSJPEGFileType properties:nil];
[newJpg writeToFile:#"/Users/Jojo/Desktop/and.jpg" atomically:YES];
Example:
I do not know what you want to achieve; only a copy of an existing image? Is a [image copy] not good enough? Besides this your code has at least two big bugs: you have to make a clear difference between the properties of an image and how these properties are stored. You forgot padding bytes, which may exist or not exist. This changed often from one OSX version to the next OSX version. So in your source image the number of samples per pixel is 3, but they are stored in 4 bytes (reason: storage access optimization). And therefore bitsPerPixel: 8*3 is wrong and so is bytesPerRow. So your source image has 1600 bytes per row and each pixel is stored in 32 bits (== 1 padding byte). You have to feed the new NSBitmapImageRep with exactly the same (buffer-)parameters as of the source imageRep. This means: the last two parameters (creating a new rep) should be:
bytesPerRow: [image bytesPerRow]
bitsPerPixel: [image bitsPerPixel]
And it could also be useful to use the parameter bitmapFormat which says if the components of a pixel are RGB or BGR and where the alpha value is (or the padding byte).
Maybe it should better to use a copy-method?

Populate an NSImageView box from NSString data

I am very new to Objective C and I've been searching Google for a number of hours trying to find a solution.
I have an NSString which looks like
273350/364D4D002A00041EB8F1E0CEF1E0CCF1E0CCF1E0CCF1E0CCF etc etc
which refers to a TIFF image (I guess in some sort of RAW string format), I want to populate an NSImageView with the data.
This is what I've attempted so far:
NSData *picdata = [NSData dataWithBytes:[albumArtStr UTF8String] length:[albumArtStr length]];
NSImage *myPicture = [[NSImage alloc] initWithData:picdata];
[_albumArtCell setImage:myPicture];
Where "albumArtCell" is the NSImageView
That data looks like hex encoded image with a length in front of it, not an unencoded TIFF, which is a tagged binary format. Perhaps you need to strip the number before the slash and decode e rest of the string from hex digits into NSData and then call [[NSImage alloc] initWithData] using that decided data.
You will need to decode it to binary before handing it to NSImage as it only understands the raw binary form of TIFF.
I believe the problem is due to the fact that [albumArtStr length] returns the number of "unicode character", and not number of "bytes".
So your NSData is probably not set-up to be the right size and so doesn't have the right format for a UIImage to be decoded properly.
Try this instead to create a NSData from NSString instance:
NSData* picData = [albumArtStr dataUsingEncoding: NSUTF8StringEncoding];

interacting with UIViews stored inside a NSMutableArray

a big noob needs help understanding things.
I have three UIViews stored inside a NSMutableArray
lanes = [[NSMutableArray arrayWithCapacity:3] retain];
- (void)registerLane:(Lane*)lane {
NSLog (#"registering lane:%i",lane);
[lanes addObject:lane];
}
in the NSLog I see: registering lane:89183264
The value displayed in the NSLog (89183264) is what I am after.
I'd like to be able to save that number in a variable to be able to reuse it elsewhere in the code.
The closest I could come up with was this:
NSString *lane0 = [lanes objectAtIndex:0];
NSString *description0 = [lane0 description];
NSLog (#"description0:%#",description0);
The problem is that description0 gets the whole UIView object, not just the single number (dec 89183264 is hex 0x550d420)
description0's content:
description0:<Lane: 0x550d420; frame = (127 0; 66 460); alpha = 0.5; opaque = NO; autoresize = RM+BM; tag = 2; layer = <CALayer: 0x550d350>>
what I don't get is why I get the correct decimal value with with NSLog so easily, but seem to be unable to get it out of the NSMutableArray any other way. I am sure I am missing some "basic knowledge" here, and I would appreciate if someone could take the time and explain what's going on here so I can finally move on. it's been a long day studying.
why can't I save the 89183264 number easily with something like:
NSInteger * mylane = lane.id;
or
NSInteger * mylane = lane;
thank you all
I'm really confused as to why you want to save the memory location of the view? Because that's what your '89183264' number is. It's the location of the pointer. When you are calling:
NSLog (#"registering lane:%i",lane);
...do you get what's actually being printed out there? What the number that's being printed means?
It seems like a really bad idea, especially when if you're subclassing UIView you've already got a lovely .tag property which you can assign an int of your choosing.
You're making life infinitely more complex than it needs to be. Just use a pointer. Say I have an array containing lots of UIViews:
UIView *viewToCompare = [myArray objectAtIndex:3];
for (id object in myArray) {
if (object == viewToCompare) {
NSLog(#"Found it!");
}
}
That does what you're trying to do - it compares two pointers - and doesn't need any faffing around with ints, etc.