Become a MacRumors Supporter for $50/year with no ads, ability to filter front page stories, and private forums.

xArtx

macrumors 6502a
Original poster
Mar 30, 2012
764
1
Hi Guys,
Seemingly the NSData object is kind of tight with giving up the data.
Is there are way to index a single byte as you would in a C array
without check in range, or some other slowy downy sounding thing?

Currently, I'm modyfying pixel data in a C array, copying to NSData,
and then slapping a BMP file header on that for a UIImage.

It seems like it should be easier and quicker to access bytes in the
NSData array, and not have to copy the C array to the NSData array
each time the array is modified.

No need for code, what I'm doing works,
but the NSData class reference looks a little sad when going to access
the bytes in a for loop.

(Try to get useless demo in App Store)

Cheers, Art.
 
You can access the bytes property of the NSData object direct.

Do you have an example of just getting one byte as you would in a for loop in C?

Code:
unsigned char data[256];

for(int i=0; i < 256; ++i) { // limit array values to 255
if (data[i] > 255) {data[i] = 255;}
} // i
 
Code:
NSMutableString *result = [NSMutableString string];
const char *bytes = [myData bytes];
for (int i = 0; i < [myData length]; i++)
{
    [result appendFormat:@"%02hhx", (unsigned char)bytes[i]];
}

**robbed from stackoverflow ;)
 
Code:
NSMutableString *result = [NSMutableString string];
const char *bytes = [myData bytes];
for (int i = 0; i < [myData length]; i++)
{
    [result appendFormat:@"%02hhx", (unsigned char)bytes[i]];
}

**robbed from stackoverflow ;)

Thanks, I'll try it.
I still don't know if it's going to be faster than one full copy of the C array to the NSData array, but will find out.
It might also be possible to assign the NSData object to the C array if it was created with malloc (without having to copy it) That would be good if you could still sneak in behind it and address the C array it was pointing to.
 
Thanks, I'll try it.
I still don't know if it's going to be faster than one full copy of the C array to the NSData array, but will find out.
It might also be possible to assign the NSData object to the C array if it was created with malloc (without having to copy it) That would be good if you could still sneak in behind it and address the C array it was pointing to.

The above is accessing the C array.
 
Yea, so the above is accessing the c array,
Code:
const char *bytes = [myData bytes];
here you are getting a pointer to the c array containing the actual data.

This is direct access to the data and does not include the overhead of making a copy of the same.
 
Yea, so the above is accessing the c array,
Code:
const char *bytes = [myData bytes];
here you are getting a pointer to the c array containing the actual data.

This is direct access to the data and does not include the overhead of making a copy of the same.

Bah! :D
So I get to save a copy, thanks :)
 
Does this actually copy the C array at all, or just create the pointer to it?

Code:
unsigned char rawdata[10000]; // explicitly declared C array
int datalength = 10000; // default data length
NSData* myData; // iOS data object

myData = [NSData dataWithBytes:(const void *)rawdata length:sizeof(char)*datalength];

I could probably find out by only doing it once,
changing the C array, and seeing if the object contents have changed..
 
Take a look at these nocopy version.

Code:
    [NSData dataWithBytesNoCopy:<#(void *)#> length:<#(NSUInteger)#>]
    [NSData dataWithBytesNoCopy:<#(void *)#> length:<#(NSUInteger)#> freeWhenDone:<#(BOOL)#>]
 
Does this actually copy the C array at all, or just create the pointer to it?

Code:
unsigned char rawdata[10000]; // explicitly declared C array
int datalength = 10000; // default data length
NSData* myData; // iOS data object

myData = [NSData dataWithBytes:(const void *)rawdata length:sizeof(char)*datalength];

I could probably find out by only doing it once,
changing the C array, and seeing if the object contents have changed..


The documentation is a wonderful thing:




dataWithBytes:length:
Creates and returns a data object containing a given number of bytes copied from a given buffer.


dataWithBytesNoCopy:length:
Creates and returns a data object that holds length bytes from the buffer bytes.

+ (id)dataWithBytesNoCopy:(void *)bytes length:(NSUInteger)length
Parameters
bytes
A buffer containing data for the new object. bytes must point to a memory block allocated with malloc.
length
The number of bytes to hold from bytes. This value must not exceed the length of bytes.
Return Value
A data object that holds length bytes from the buffer bytes. Returns nil if the data object could not be created.

Discussion
The returned object takes ownership of the bytes pointer and frees it on deallocation. Therefore, bytes must point to a memory block allocated with malloc.
 
The documentation is a wonderful thing:

You can already see I kind of read docs, or I wouldn't know what I posted above.

There is some problem with it, but in a little time I can post entire source
which is just a little plasma demo ported originally from Rockbox, iPodLinux,
and then the Sony PSP.. probably been a lot of other platforms too.

It appears if the NSData object is used for a UIImage, the call to create the
UIImage object tries to free the data behind the NSData object.
This is the version that works.

Code:
    myData = [NSData dataWithBytes:(const void *)imgdata length:sizeof(char)*imgfilelength];
    myImage = [UIImage imageWithData: myData];
    [myImage drawInRect:CGRectMake(posx, posy, widx, widy)];
 
You can already see I kind of read docs, or I wouldn't know what I posted above.

There is some problem with it, but in a little time I can post entire source
which is just a little plasma demo ported originally from Rockbox, iPodLinux,
and then the Sony PSP.. probably been a lot of other platforms too.

It appears if the NSData object is used for a UIImage, the call to create the
UIImage object tries to free the data behind the NSData object.
This is the version that works.

Code:
    myData = [NSData dataWithBytes:(const void *)imgdata length:sizeof(char)*imgfilelength];
    myImage = [UIImage imageWithData: myData];
    [myImage drawInRect:CGRectMake(posx, posy, widx, widy)];

I'm confused as to what you're saying.

Are you saying that imageWithData tries to free the data that backs the NSData object? But then you say that the code above works. Or are you saying that some other code seems to be freeing the NSData backing buffer but the code above works correct?

Apple's docs for methods is usually quite clear about transfers of ownership. Unless it states explicitly that it takes ownership of the data, it won't.
 
I'm confused as to what you're saying.

Are you saying that imageWithData tries to free the data that backs the NSData object? But then you say that the code above works. Or are you saying that some other code seems to be freeing the NSData backing buffer but the code above works correct?

Apple's docs for methods is usually quite clear about transfers of ownership. Unless it states explicitly that it takes ownership of the data, it won't.

The three line code does work, but it's the version that does the copy,
so I assume it takes more time.
I can avoid that by only ever accessing the NSData object instead of ever
looking at the C array (suggested above), then I don't have to even use the
first line of that three line code to send the data to an image object.
So the actual practical problem for this app is already solved.

It's when I use the no copy version that something is going on.
It compiles and then crashes at run time on the second line.
I din't create the C array with malloc, but I don't want it freed either.
It's to be used for every frame, and then I'd lose the time allocating anyway.

It will be clearer when I can post the source, it's really just a small program.
It's intention is to help others, so it's important that the iOS code isn't sloppy.
I plan to maintain the same demo on the Sony PSP, where you address
absolute pixel values.
The iOS one draws to a bitmap screen buffer, which is what I really needed
to know when I started.
 
It's when I use the no copy version that something is going on.
It compiles and then crashes at run time on the second line.
I din't create the C array with malloc, but I don't want it freed either.
It's to be used for every frame, and then I'd lose the time allocating anyway.

I am guessing it is something to do with the stack.

How do you declare imgdata and where?
 
I am guessing it is something to do with the stack.

How do you declare imgdata and where?


The NSData and UIImage aren't used anywhere else so I can do it right there:
Code:
    NSData* myData = [NSData dataWithBytesNoCopy:(void *)imgdata length:sizeof(char)*imgfilelength];
   //NSData* myData = [NSData dataWithBytes:(const void *)imgdata length:sizeof(char)*imgfilelength];
    UIImage *myImage = [UIImage imageWithData: myData];

The error is "pointer being freed was not allocated" (at run time).

So what is working is this:
Code:
UIImage *myImage = [UIImage imageWithData: [NSData dataWithBytes:(const void *)imgdata length:sizeof(char)*imgfilelength]];
 
Last edited:
The NSData and UIImage aren't used anywhere else so I can do it right there:
Code:
    NSData* myData = [NSData dataWithBytesNoCopy:(void *)imgdata length:sizeof(char)*imgfilelength];
   //NSData* myData = [NSData dataWithBytes:(const void *)imgdata length:sizeof(char)*imgfilelength];
    UIImage *myImage = [UIImage imageWithData: myData];

The error is "pointer being freed was not allocated" (at run time).

So what is working is this:
Code:
UIImage *myImage = [UIImage imageWithData: [NSData dataWithBytes:(const void *)imgdata length:sizeof(char)*imgfilelength]];

The version of dataWithBytesNoCopy seems to take ownership of the C array
You can try this version instead: (with freeWhenDone set to NO)
Code:
[NSData dataWithBytesNoCopy:<#(void *)#> length:<#(NSUInteger)#> freeWhenDone:<#(BOOL)#>]
 
Looks like bingo! that's some fast plasma :)

Code:
    myData = [NSData dataWithBytesNoCopy:(void *)imgdata length:sizeof(char)*imgfilelength freeWhenDone:NO];
    myImage = [UIImage imageWithData: myData];
    [myImage drawInRect:CGRectMake(posx+fsizea, posy, widx+fsizeb, widy)];

So now I get to keep the C array right, since there's no overhead?
The NSData and UIImage objects are not used at all until these three lines.
I'm writing into a bitmap file that has a standard header,
so it's ready to go for the UIImage object.

If you are doing fire or plasma, you can reverse the order of lines to
loose the overhead of the transformation on the bitmap to flip it both ways
...if there was any overhead.. sounds like it could be a gfx chip's job.










The version of dataWithBytesNoCopy seems to take ownership of the C array
You can try this version instead: (with freeWhenDone set to NO)
Code:
[NSData dataWithBytesNoCopy:<#(void *)#> length:<#(NSUInteger)#> freeWhenDone:<#(BOOL)#>]
 
Got the Fire demo working as well :)
I got both of these for the Sony PSP back in 2006!

 
Last edited by a moderator:
The version of dataWithBytesNoCopy seems to take ownership of the C array
You can try this version instead: (with freeWhenDone set to NO)
Code:
[NSData dataWithBytesNoCopy:<#(void *)#> length:<#(NSUInteger)#> freeWhenDone:<#(BOOL)#>]

Cool. I hadn't seen the dataWithBytesNoCopy:freeWhenDone: variant before.

That's exactly what the OP needs. It lets you pass in a memory buffer that was NOT created with malloc(), or that for some other reason you don't want NSData to take ownership of.
 
Cool. I hadn't seen the dataWithBytesNoCopy:freeWhenDone: variant before.

That's exactly what the OP needs. It lets you pass in a memory buffer that was NOT created with malloc(), or that for some other reason you don't want NSData to take ownership of.

I still don't get out of the copy to the UIImage object if I understand correctly,
as that object has it's own copy of image data behind it.
Also, it is possible to email out a bitmap screenshot directly,
...bypassing the photo album.

The idea is the app is open sourced as soon as it goes up,
and so are both original Sony PSP demos which also do their work in C.
The code for both platforms gets to stay largely identical now.

The PSP 2D libs address absolute pixel values, their screen resolution never changed.
I needed to know how to do this when I first started out.

I would also appreciate critique on the iOS side.
because much more important than helping others, is helping myself :)
I would likely redo my fractal generator with this.
 
I still don't get out of the copy to the UIImage object if I understand correctly,
as that object has it's own copy of image data behind it.
Also, it is possible to email out a bitmap screenshot directly,
...bypassing the photo album.

There are possibly 2 copies for the original version.
- One from the c byte array to the NSData
- And another from the NSData to the UIImage (My guess is UIImage tries to decode the bitmap image, as a BMP header is stitched to it).

Now the first copy is avoid using NoCopy version of NSData initializer.
To avoid the second copy/decode in UIImage, may be you can take a look into CoreGraphics to create a bitmap context directly (avoiding the decode) and have it draw to a sub CALayer.

eg:

Code:
    CGBitmapContextCreate(<#void *data#>, <#size_t width#>, <#size_t height#>, <#size_t bitsPerComponent#>, <#size_t bytesPerRow#>, <#CGColorSpaceRef space#>, <#CGBitmapInfo bitmapInfo#>);
    CGBitmapContextCreateImage(<#CGContextRef context#>);

  and CALayer's delegate function :
   - (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx;

but it may or may not work, since the description for CGBitmapContextCreateImage says copy-on-write is on some cases only. :(

Creates and returns a Quartz image from the pixel data in a bitmap graphics context.
The CGImage object returned by this function is created by a copy operation. Subsequent changes to the bitmap graphics context do not affect the contents of the returned image. In some cases the copy operation actually follows copy-on-write semantics, so that the actual physical copy of the bits occur only if the underlying data in the bitmap graphics context is modified. As a consequence, you may want to use the resulting image and release it before you perform additional drawing into the bitmap graphics context. In this way, you can avoid the actual physical copy of the data.
 
I have used the bitmap context before, and just suspected it's the same thing.
Otherwise I don't see why the pixel data would be stored the same way
as a bitmap file, upside down and with rows back to front.

ie. whenever you do anything with it, iOS still needs to read it's dimensions
from somewhere to align the rows and columns.


There are possibly 2 copies for the original version.
- One from the c byte array to the NSData
- And another from the NSData to the UIImage (My guess is UIImage tries to decode the bitmap image, as a BMP header is stitched to it).

Now the first copy is avoid using NoCopy version of NSData initializer.
To avoid the second copy/decode in UIImage, may be you can take a look into CoreGraphics to create a bitmap context directly (avoiding the decode) and have it draw to a sub CALayer.

eg:

Code:
    CGBitmapContextCreate(<#void *data#>, <#size_t width#>, <#size_t height#>, <#size_t bitsPerComponent#>, <#size_t bytesPerRow#>, <#CGColorSpaceRef space#>, <#CGBitmapInfo bitmapInfo#>);
    CGBitmapContextCreateImage(<#CGContextRef context#>);

  and CALayer's delegate function :
   - (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx;

but it may or may not work, since the description for CGBitmapContextCreateImage says copy-on-write is on some cases only. :(
 
I have used the bitmap context before, and just suspected it's the same thing.
Otherwise I don't see why the pixel data would be stored the same way
as a bitmap file, upside down and with rows back to front.

Could it be the difference in coordinate system in UIKit and CoreGraphics?

flipped_coordinates-2.jpg


as in here http://developer.apple.com/library/...sDrawingOverview/GraphicsDrawingOverview.html

some CTM has to be applied to the context so it display correctly.
 
I'll see what I can do.
I should be able to still draw the image so that the orientation fixes it,
and not have to do the transform.
 
Register on MacRumors! This sidebar will go away, and you'll see fewer ads.