Image Processing in iOS Part 1: Raw Bitmap Modification

Learn the basics of image processing on iOS via raw bitmap modification, Core Graphics, Core Image, and GPUImage in this 2-part tutorial series. By Jack Wu.

Leave a rating/review
Save for later
Share
You are currently viewing page 4 of 4 of this article. Click here to view the first page.

Black and White

One last effect to go. Try implementing the black and white filter yourself. To do this, set each pixel's red, green and blue components to the average of the three channels in the original, just like how you printed out Ghosty's brightness in the beginning.

Write this code before the // Create a new UIImage comment you added in the previous step.

Think you got it? Check your code here.
[spoiler title="Solution"]

// Convert the image to black and white
for (NSUInteger j = 0; j < inputHeight; j++) {
  for (NSUInteger i = 0; i < inputWidth; i++) {
    UInt32 * currentPixel = inputPixels + (j * inputWidth) + i;
    UInt32 color = *currentPixel;
      
    // Average of RGB = greyscale
    UInt32 averageColor = (R(color) + G(color) + B(color)) / 3.0;
      
    *currentPixel = RGBAMake(averageColor, averageColor, averageColor, A(color));
  }
}

[/spoiler]

The very last step is to cleanup your memory. ARC cannot manage CGImageRefs and CGContexts for you. Add this to the end of the function before the return statement:

// Cleanup!
 CGColorSpaceRelease(colorSpace);
CGContextRelease(context);
CGContextRelease(ghostContext);
free(inputPixels);
free(ghostPixels);

Build and run. Be prepared to be spooked out by the result:

BuildnRun-3

Where To Go From Here?

Congratulations! You just finished your first image-processing application. You can download a working version of the project at this point here.

That wasn't too hard, right? You can play around with the code inside the for loops to create your own effects, try to see if you can implement these ones:

  • Swap the red and blue channels of the image
  • Increase the brightness of the image by 10%
  • As a further challenge, try scaling Ghosty using only pixel-based methods. Here are the steps:
    1. Create a new CGContext with the target size for Ghosty.
    2. For each pixel in this new Context, calculate which pixel you should copy from in the original image.
    3. For extra coolness, try interpolating between nearby pixels if your calculations for the original coordinate lands in-between pixels. If you interpolate between the four nearest pixels, you have just implemented Bilinear scaling all on your own! What a boss!
  1. Create a new CGContext with the target size for Ghosty.
  2. For each pixel in this new Context, calculate which pixel you should copy from in the original image.
  3. For extra coolness, try interpolating between nearby pixels if your calculations for the original coordinate lands in-between pixels. If you interpolate between the four nearest pixels, you have just implemented Bilinear scaling all on your own! What a boss!

If you've completed the first project, you should have a pretty good grasp on the basic concepts of image processing. Now you can set out and explore simpler and faster ways to accomplish these same effects.

In the next part of the series, you replace -processUsingPixels: with three new functions that will perform the same task using different libraries. Definitely check it out!

In the meantime, if you have any questions or comments about the series so far, please join the forum discussion below!

Jack Wu

Contributors

Jack Wu

Author

Over 300 content creators. Join our team.