0

I have a new question here! As the real problem was not in the C++ conversion but rather that I need to convert the returned string data bytes into a CGImageRef. Anybody know how to do that please go to that link to answer the follow on to this question.

Thank you.


OK. Instead of muddying the question with protobuf stuff, I have simplified my test method to simulate the call that would be made to the protobuf stuff.

This test method does the following two parts. Part 1 takes a UIImage and converts it into a std::string.

  1. take a UIImage
  2. get the NSData from it
  3. convert the data to unsigned char *
  4. stuff the unsigned char * into a std::string

The string is what we would receive from the protobuf call. Part 2 takes the data from the string and converts it back into the NSData format to populate a UIImage. Following are the steps to do that:

  1. convert the std::string to char array
  2. convert the char array to a const char *
  3. put the char * into NSData
  4. return NSData
- (NSData *)testProcessedImage:(UIImage *)processedImage
{
    // UIImage to unsigned char *
    CGImageRef imageRef = processedImage.CGImage;
    NSData *data = (NSData *) CFBridgingRelease(CGDataProviderCopyData(CGImageGetDataProvider(imageRef)));
    unsigned char *pixels = (unsigned char *)[data bytes];
    unsigned long size = [data length];

    // ***************************************************************************
    // This is where we would call transmit and receive the bytes in a std::string
    // ***************************************************************************

    // unsigned char * to string
    std::string byteString(pixels, pixels + size);


    // string to char array to const char *
    char myArray[byteString.size()+1];//as 1 char space for null is also required
    strcpy(myArray, byteString.c_str());
    const char *bytes = (const char *)myArray;

    // put byte array back into NSData format
    NSUInteger usize = byteString.length();
    data = [NSData dataWithBytes:(const void *)bytes length:sizeof(unsigned char)*usize];
    NSLog(@"examine data");

    return data;
}

The is the code for when the data is returned:

    NSData *data = [self.messageCommand testProcessedImage:processedImage];

    // But when I try to alloc init a UIImage with the data, the image is nil
    UIImage *image = [[UIImage alloc] initWithData:data];
    NSLog(@"examine image");

Everything seems to go as planned until I try to create the UIImage with the data. Alloc initing the UIImage with that data returns nil. There must be some type of conversion that will make this work.

Community
  • 1
  • 1
Patricia
  • 5,019
  • 14
  • 72
  • 152
  • 1
    One suspects that your NSData object is not valid. Kind of convoluted code you have there, and not a clue what an IDMessageWrapper is. – Hot Licks Jul 02 '14 at 20:26
  • Why do you receive an NSData, convert it to a std:string, then back to NSData?? That's pretty much guaranteed to corrupt the data. – Hot Licks Jul 02 '14 at 20:30
  • The first NSData is an object returned using protobuf. The second NSData is the actual image in bytes. – Patricia Jul 02 '14 at 20:39
  • It sounds like either the original image data is not in a format UIImage supports, or your protobuf library is not returning the correct data. – Chuck Jul 02 '14 at 20:41
  • Converting with `c_str()` seems like a really bad idea to me. `c_str()` will give you a NULL terminated buffer, like a traditional C-string. If your original image data itself includes some zero value bytes, what happens? I wouldn't be surprised if your data just gets cut off at the first zero value byte. – Aaron Golden Jul 02 '14 at 20:42
  • 1
    @AaronGolden: Great point. The construction of the std::string is susceptible to this too, as it uses the `(const char*)` constructor rather than the `(const char*, size_t)` one. – Chuck Jul 02 '14 at 20:46
  • 1
    There is no way (other than using, say, Base64) you can convert an NSData to c_str and back without corrupting the data. – Hot Licks Jul 02 '14 at 20:54
  • @HotLicks - You may be onto something there. This is new territory for me. – Patricia Jul 02 '14 at 20:55
  • @Lucy: FYI the method you want instead of `c_str()` is `data()`. – Chuck Jul 02 '14 at 20:59
  • @Chuck - I got a little excited there.....but data() is the same as c_str() and image is still nil. – Patricia Jul 02 '14 at 21:14
  • @HotLicks - I've simplified my question. As a test I completely removed protobuf calls and simply tried a straight conversion in C++. The issue must be in the C++ conversions. I am not a C++ developer and thus I need help in figuring out why the conversion creates an NSData that is incompatible with creating a UIImage. Will you please take this question off hold? – Patricia Jul 03 '14 at 17:50
  • It's not clear why you're mucking around with C++ at all. – Hot Licks Jul 03 '14 at 18:02
  • @HotLicks - The Google protobuf class is a .cpp. I have to create a .mm class as a wrapper to C++. I don't have a choice. – Patricia Jul 03 '14 at 18:03
  • If you need the data in string format, use Base64. – Hot Licks Jul 03 '14 at 18:09
  • @HotLicks - Well, I was told that in C++ a string is an arbitrary collection of bytes. It's basically a specialized vector of characters and that the data contained by an std::string does not have to be anything other than a sequence of characters. But I'll give your suggestion a try since nothing else has worked. – Patricia Jul 03 '14 at 18:16
  • @HotLicks - I added code to encode and decode using base64. The encoding and decoding worked very well. But either the NSData is still the wrong format or there is something else going on with UIImage, because creating it still returns nil. – Patricia Jul 03 '14 at 19:08
  • Have you tried feeding the NSData directly through, with no modification? – Hot Licks Jul 03 '14 at 20:43
  • @HotLicks - Yes, I finally did that and discovered that I think the problem is that NSData is for png an jpeg formats and I'm creating a CGImageRef. So, I'm trying to get the correct format of NSData. Thanks – Patricia Jul 03 '14 at 21:00

3 Answers3

1

OK, so the problem is most likely with the repeated conversions between Objective-C, C and C++ data structures. Overall, you need to make sure to initialize the string as a byte array rather than a textual C string, and you want to get back the raw bytes without a null terminator. I think this will preserve the data correctly:

- (void)onDataReceived:(NSNotification *)note {
    if ([note.name isEqualToString:@"DataReceived"]) {
        NSDictionary *userData = note.userInfo;
        NSData *imageData = [userData objectForKey:@"ImageData"];
 // Note the two-argument string constructor -- this is necessary for non-textual data!
        std::string byteString = std::string(static_cast<const char*>([imageData bytes]), imageData.length);


 // We get the image back as a std::string
        std::string imageStr = [self.message parseMessage:byteString ofSize:byteString.size()];
        NSLog(@"examine imageStr");

 // We get the data from the std::string
        char *imageCStr = new char[imageStr.size()];
        imageStr.copy(imageCStr, imageStr.size());
        NSData *data = [NSData dataWithBytes:imageCStr length:imageStr.size()];
        delete[] imageCStr;

 // But when I try to alloc init a UIImage with the data, the image is nil
        UIImage *image = [[UIImage alloc] initWithData:data];
        NSLog(@"examine image");
    }
}
Chuck
  • 234,037
  • 30
  • 302
  • 389
  • I tried your code. It worked pretty well, but I'm still getting nil for the UIImage. See my answer for the code. I had to change a couple of things. Can you take a look and see if my changes are not in line with what you were trying to provide? Thank you. – Patricia Jul 03 '14 at 00:12
  • @Lucy: You definitely got what I was saying. If I'm not familiar with the protocol buffer library you're using here, so I can't really tell whether the creation of imageStr is problematic, but that's the only thing I can think of. I made a little test program for the overall data->cstring->string and string->cstring->data flow here and ran it with some sample data and it seemed to preserve the data just fine. – Chuck Jul 03 '14 at 03:52
  • I've simplified the code and my original question to remove everything unrelated to just converting the image to a std::string and back to NSData to create a new UIImage. But the new UIImage is still nil. Can you take a look? Thank you. – Patricia Jul 03 '14 at 18:00
1

I tried the answer. There were some minor changes I needed to make to get rid of errors. Also, I had changed some variable names to minimize confusion. This is still returning nil for the UIImage.

- (void)onObjectReceived:(NSNotification *)note {
    if ([note.name isEqualToString:@"ObjectReceived"]) {
        NSDictionary *userData = note.userInfo;
        NSData *objectData = [userData objectForKey:@"ObjectData"];

        // Added this because bytes is used below.  Or did you mean something else?
        const char *bytes = (const char *)[objectData bytes];

        // Note the two-argument string constructor -- this is necessary for non-textual data!
        std::string byteString = std::string(static_cast<const char*>(bytes), objectData.length);

        // This is an out parameter in the parseMessage method.
        long unsigned int *msgSize = (long unsigned *)malloc(sizeof(long unsigned int));

        // We get the image back as a std::string
        std::string imageStr = [self.message parseMessage:byteString outMsgSize:msgSize];
        NSLog(@"examine imageStr");

        // We get the data from the std::string
        char *imageCStr = new char[imageStr.size()];
        imageStr.copy(imageCStr, imageStr.size());
        NSData *data = [NSData dataWithBytes:imageCStr length:imageStr.size()];
        delete[] imageCStr;

        // But when I try to alloc init a UIImage with the data, the image is nil
        UIImage *image = [[UIImage alloc] initWithData:data];
        NSLog(@"examine image");
    }
}
Patricia
  • 5,019
  • 14
  • 72
  • 152
0

I tried removing everything in between and it worked. Here's the code:

- (NSData *)testProcessedImage:(UIImage *)processedImage
{
    // UIImage to unsigned char *
    CGImageRef imageRef = processedImage.CGImage;
    NSData *data1 = (NSData *) CFBridgingRelease(CGDataProviderCopyData(CGImageGetDataProvider(imageRef)));

    UIImage *image = [UIImage imageWithCGImage:imageRef];

This tells me that the NSData dataWithBytes is not going to work, because I am using a CGImageRef. My image is raw data coming from the camera (not a PNG or JPEG).

I found this answer on SO that was helpful. The asker even posted this comment, "It looks quite simple to wrap (the) data in a CFData object, and then CGDataProviderCreateWithCFData."

I found another answer on SO that was also helpful. It shows how to create a CFDataRef using the string.

There is a lot of helpful information out there but still not finding what I need. I'm going to ask another question and reference it back here when I get an answer.

Thank you.

Community
  • 1
  • 1
Patricia
  • 5,019
  • 14
  • 72
  • 152