2

I am working on a project that requires image pixel manipulation. What I am doing now is using UIImagePicker to receive a UIImage from the devices library. I then convert it to a CGImage, get the RBG values and change them slightly.

I then convert it back to a UIImage and write it out to disk. The issue is when I read the image back in, from the UIImagePicker, the RBG values are not the same. I have verified that the values are correct after I change them and before the image is actually written out. The pixel values only are different when I read it back in and then only by a few values. I am curious, why is this happening and are there any ways around this? I want to get back the exact same RBG values.

Here is a bit of code, if you want something specific please let me know.

- (void)imagePickerController:(UIImagePickerController *)picker
didFinishPickingMediaWithInfo:(NSDictionary *)info {

    // I only ever manipulate self.editingImage which is directly read from the photo library
    // self.editingImage is a UIImage property
    self.editingImage = [info objectForKey:@"UIImagePickerControllerOriginalImage"];
    self.imageView.image = self.editingImage;

    if (self.option == EDITPIXELS) {
        // After this method completes it automatically calls READPIXELS and the values are correct
        // converts self.editingImage to a CGImage before editing pixels
        [self editImage:self.editingImage];
    } else if (self.option == READPIXELS) {
        // converts self.editingImage to a CGImage before reading pixels, then logs RBG values
        [self readImage:self.editingImage];
    }

    [self.imagePickerPopoverController dismissPopoverAnimated:YES];
}

Edit: I am using the category from here to save my image, the code from the category:

-(void)saveImage:(UIImage*)image toAlbum:(NSString*)albumName withCompletionBlock:(SaveImageCompletion)completionBlock {

    //write the image data to the assets library (camera roll)
    [self writeImageToSavedPhotosAlbum:image.CGImage orientation:(ALAssetOrientation)image.imageOrientation 
                       completionBlock:^(NSURL* assetURL, NSError* error) {

                      //error handling
                      if (error!=nil) {
                          completionBlock(error);
                          return;
                      }
                      //add the asset to the custom photo album
                      [self addAssetURL: assetURL 
                                toAlbum:albumName 
                    withCompletionBlock:completionBlock];
                  }];
}

I am calling it on an action button:

- (IBAction)saveImage:(id)sender {
    // Called before saving to verify the RBG values, they are correct
    [self readImage:self.editingImage];

    // saving image
    [self.library saveImage:self.editingImage toAlbum:@"My Album" withCompletionBlock:^(NSError *error){}];
}

This is the code I am using to edit the RBG values:

+ (UIImage *) fromImage:(UIImage *)source toRedColors:(NSArray *)redColorArray {

    // Code adapted from: http://brandontreb.com/image-manipulation-retrieving-and-updating-pixel-values-for-a-uiimage/
    CGContextRef ctx;
    CGImageRef imageRef = [source CGImage];
    NSUInteger width = CGImageGetWidth(imageRef);
    NSUInteger height = CGImageGetHeight(imageRef);
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    byte *rawData = malloc(height * width * 4);
    NSUInteger bytesPerPixel = 4;
    NSUInteger bytesPerRow = bytesPerPixel * width;
    NSUInteger bitsPerComponent = 8;
    CGContextRef context = CGBitmapContextCreate(rawData, width, height, 
                                     bitsPerComponent, bytesPerRow, colorSpace, 
                                     kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
    CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
    CGContextRelease(context);

    int byteIndex = 0;

    for (NSNumber *n in redColorArray) {
        rawData[byteIndex] = Clamp255([n integerValue]);
        byteIndex += 4;
    }
    ctx = CGBitmapContextCreate(rawData,
                            CGImageGetWidth( imageRef ),
                            CGImageGetHeight( imageRef ),
                            8,
                            bytesPerRow,
                            colorSpace,
                            kCGImageAlphaPremultipliedLast );
    CGColorSpaceRelease(colorSpace);
    imageRef = CGBitmapContextCreateImage (ctx);
    UIImage* rawImage = [UIImage imageWithCGImage:imageRef];
    CGImageRelease(imageRef);
    CGContextRelease(ctx);
    free(rawData);

    return rawImage;
}

Reading the values is using almost the exact same code except I store the values into an array and just return the array of RBG values (I do not return all of the RBG values just some of them). So instead of:

rawData[byteIndex] = Clamp255([n integerValue]);

I use:

[myMutableArray addObject:@(rawData[byteIndex])];
Firo
  • 15,448
  • 3
  • 54
  • 74
  • excuse me but i don't see you saving the picture anywhere. when you import the picture you have a copy of the picture not the actual picture you need to save the actual picture in the photo library to have it this is not done automatically. If i am wrong and actually save the picture in the photo library please post code – Radu May 10 '13 at 14:36
  • 1
    what format are you saving in (e.g., jpg, png)? – bobnoble May 10 '13 at 14:37
  • Post the code that contain reading RGB values and when you edit RGB values – Divyu May 10 '13 at 14:44
  • @bobnoble I am not sure, I have done almost nothing with images before and figured it was taken care of for me. – Firo May 10 '13 at 15:01
  • @DivyaSharma I added the code you asked for – Firo May 10 '13 at 15:02
  • @Firo like bobnoble mentioned, how are you saving the image - jpg, png? Changing the image type will also make a difference. – lostInTransit May 20 '13 at 10:31
  • @lostInTransit I show above how I am saving it, I also implemented the save option that Radu shows below. I did was not really aware that you could save out different file formats, I figured it would just use whatever it already was. – Firo May 21 '13 at 09:46

2 Answers2

1

You need to save the image to your library you are only modifing it in your app. Try this After you have the image try saving it to photo library add this line of code:

UIImageWriteToSavedPhotosAlbum(rawImage, self, @selector(image:didFinishSavingWithError:contextInfo:), self);

and to confirm the save add this method:

    - (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo{

        NSString *str = @"Saved!!!";

        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Saved." message:str delegate:self cancelButtonTitle:nil otherButtonTitles:@"OK", nil];

        [alert show];

    }

EDIT: I didn't see the full code For the save process, my mistake sorry. The code looks ok it should save the image can you check the RGB code for the image just before you save it? it might not be saving the correct image or it might be reset. other then that i can't see to find a fault

Radu
  • 3,434
  • 4
  • 27
  • 38
  • Now you will have 2 images the original and the modified image. if you want to modify the same image that is a little more complicated try this for now and tell me if it works – Radu May 13 '13 at 07:00
  • I tried using your code and have the same problem. The data isn't really changed. I can verify right before I save that the bits are changed and correct but when I manually read the image back in through a UIImagePicker they are unchanged. If the image is changed and I save it should I be using `UIImagePickerControllerOriginalImage`to read it in or something else? – Firo May 13 '13 at 20:49
  • Alright, using your code I save the image and my selector is called. I can even print the binary of the `image` given to me in this method and it is correct and it is added to the photo library. I then set the UIImagePicker to nil and re-initialize a new one. Yet, when I read in the newly added image and print out the binary it is unchanged! – Firo May 13 '13 at 22:35
  • 1
    Is the binary really completely unchanged? Using the imagePickerController the imagedata is compressed into a jpg and that alone should change the data. Have you simply looked at the saved photos? Having them saved to camera roll and edited them you should be able to just see a change to the saved picture on the phone right? – thomketler May 17 '13 at 08:46
  • @thomketler (sorry didn't see this until now). No, I meant it IS CHANGED. Thank you for your jpg compression insights. I was wondering if something like this was happening but could not find any information about how images were saved to the photo library. Do you know of anyway to prevent this compression from happening? Also if you make your comment an answer I will accept it, as it is more useful and informative than anything else I have. – Firo Jun 25 '13 at 13:09
1

Using the imagePickerController the imagedata is compressed into a jpg and that alone changes the data.

When you want to do some serious image manipulation you will need to use AVFoundation. The AVCam project in the documentation provides sample code you can use as a reference. The problem with iOS is that it only provides jpg and png as possibilities to save your data as a picture as far as I know.

For my application I use the OpenCV library to save the image data as a BMP on the phone. In this post you can see my solution. Also there you can see a link to a description of the "645 PRO" app. Those programmers faced the same problem and describe in general how their app works around it.

If you just need to save your data for a while to use it in your app again you could try saving the image data in a NSData object and write it on your phone.

Capture still UIImage without compression (from CMSampleBufferRef)? provides more insight to that matter. OpenCV imread() returns no image data on iOS.

When you search on stackoverflow you can find more posts like that. Those are just the ones I was involved.

Community
  • 1
  • 1
thomketler
  • 418
  • 4
  • 9
  • Thanks a bunch! This gives me lots of material to rummage through and will definitely provide some needed answers! – Firo Jun 27 '13 at 15:02