UIImage with NSData initWithData returns nil

2019-09-21 19:39发布

问题:

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.

回答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");
    }
}


回答2:

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");
    }
}


回答3:

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.