Actual project scenario: remove the pure white background image of the picture and obtain a transparent bottom image for the jigsaw function

The following two ways of three treatments (do not know why think of kong Yiji), specific performance is not compared, if there is a big man can tell, grateful.

  • Core Image
  • Core Graphics/Quarz 2D

Core Image

Core Image is a very powerful framework. It allows you to manipulate images simply by applying various filters, such as changing vividness, color, or exposure. It uses the GPU (or CPU) to process image data and video frames very quickly, even in real time. In addition, it hides all the details of the underlying graphics processing, which can be easily used through the API provided. You don’t need to care about how OpenGL or OpenGL ES makes full use of GPU capabilities, and you don’t need to know how GCD plays a role in it. Core Image handles all the details.

The Chroma Key Filter Recipe is mentioned in the Core Image Programming Guide as an example of how to handle backgrounds

The HSV color model is used because the HSV model is more friendly than RGB for the representation of color ranges.

General processing process:

  1. Create a cubeMap that wants to remove the color value range from the cubeMap to the target colorAlphaSet to0.0 f
  2. useCIColorCubeFilters and cubeMap color the source image
  3. Get the passCIColorCubeTo deal with theCore ImageobjectCIImageConverting,Core GraphicsIn theCGImageRefObject, throughimageWithCGImage:Get the resulting picture

Note: in step 3, you should not directly use imageWithCIImage:, because the resulting UIImage is not a standard UIImage. If you use it directly, it will not display.

- (UIImage *)removeColorWithMinHueAngle:(float)minHueAngle maxHueAngle:(float)maxHueAngle image:(UIImage *)originalImage{ CIImage *image = [CIImage imageWithCGImage:originalImage.CGImage]; CIContext *context = [CIContext contextWithOptions:nil]; // kCIContextUseSoftwareRenderer : CPURender /** Note that UIImage initialized by CIimage is not a standard UIImage initialized by cgImage-like UIImage * so if you don't use context to render, Can't display properly * / CIImage * renderBgImage = [self outputImageWithOriginalCIImage: image minHueAngle: minHueAngle maxHueAngle:maxHueAngle]; CGImageRef renderImg = [context createCGImage:renderBgImage fromRect:image.extent]; UIImage *renderImage = [UIImage imageWithCGImage:renderImg];return renderImage;
}

struct CubeMap {
    int length;
    float dimension;
    float *data;
};

- (CIImage *)outputImageWithOriginalCIImage:(CIImage *)originalImage minHueAngle:(float)minHueAngle maxHueAngle:(float)maxHueAngle{
    
    struct CubeMap map = createCubeMap(minHueAngle, maxHueAngle);
    const unsigned int size = 64;
    // Create memory with the cube data
    NSData *data = [NSData dataWithBytesNoCopy:map.data
                                        length:map.length
                                  freeWhenDone:YES];
    CIFilter *colorCube = [CIFilter filterWithName:@"CIColorCube"];
    [colorCube setValue:@(size) forKey:@"inputCubeDimension"];
    // Set data for cube
    [colorCube setValue:data forKey:@"inputCubeData"];
    
    [colorCube setValue:originalImage forKey:kCIInputImageKey];
    CIImage *result = [colorCube valueForKey:kCIOutputImageKey];
    
    return result;
}

struct CubeMap createCubeMap(float minHueAngle, float maxHueAngle) {
    const unsigned int size = 64;
    struct CubeMap map;
    map.length = size * size * size * sizeof (float) * 4;
    map.dimension = size;
    float *cubeData = (float *)malloc (map.length);
    float rgb[3], hsv[3], *c = cubeData;
    
    for (int z = 0; z < size; z++){
        rgb[2] = ((double)z)/(size-1); // Blue value
        for (int y = 0; y < size; y++){
            rgb[1] = ((double)y)/(size-1); // Green value
            for (int x = 0; x < size; x ++){
                rgb[0] = ((double)x)/(size-1); // Red value
                rgbToHSV(rgb,hsv);
                // Use the hue value to determine which to make transparent
                // The minimum and maximum hue angle depends on
                // the color you want to remove
                floatalpha = (hsv[0] > minHueAngle && hsv[0] < maxHueAngle) ? 0.0 f: 1.0 f; // Calculate premultiplied alpha valuesfor the cube
                c[0] = rgb[0] * alpha;
                c[1] = rgb[1] * alpha;
                c[2] = rgb[2] * alpha;
                c[3] = alpha;
                c += 4; // advance our pointer into memory for the next color value
            }
        }
    }
    map.data = cubeData;
    return map;
}
Copy the code

RgbToHSV is not mentioned in the official documentation, I found the relevant conversion processing in the blog of the big guy mentioned below. Thank you

void rgbToHSV(float *rgb, float *hsv) {
    float min, max, delta;
    float r = rgb[0], g = rgb[1], b = rgb[2];
    float *h = hsv, *s = hsv + 1, *v = hsv + 2;
    
    min = fmin(fmin(r, g), b );
    max = fmax(fmax(r, g), b );
    *v = max;
    delta = max - min;
    if( max ! = 0 ) *s = delta / max;else {
        *s = 0;
        *h = -1;
        return;
    }
    if( r == max )
        *h = ( g - b ) / delta;
    else if( g == max )
        *h = 2 + ( b - r ) / delta;
    else
        *h = 4 + ( r - g ) / delta;
    *h *= 60;
    if( *h < 0 )
        *h += 360;
}
Copy the code

So let’s try it out. How does it look to remove the green background

Using the HSV tool, we can determine the approximate range of green HUE values from 50 to 170

Let’s call the method and try it out

[[SPImageChromaFilterManager sharedManager] removeColorWithMinHueAngle:50 maxHueAngle:170 image:[UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"nb" ofType:@"jpeg"]]]
Copy the code

The effect

The effect is ok.

If you look closely at the HSV model, you may notice that we are unable to specify grey, white and black by specifying Hue angles. Alphafloat alpha = (HSV [0] > minHueAngle && HSV [0] < maxHueAngle)? 0.0 f: 1.0 f; Let’s try it out there. As for the code why RGB and HSV so conversion, please baidu their conversion, because the writer also do not understand. Oh, a tanager)

For those of you who are interested in Core Image, check out the big Guy series

IOS8 Core Image In Swift: automatically improves images and uses built-in filters. IOS8 Core Image In Swift: Real-time video filter

Core Graphics/Quarz 2D

The OpengL-based Core Image mentioned above is obviously very powerful, as is Core Graphics, the other cornerstone of the view. To his exploration, let the writer more understanding of the picture related knowledge. So here is a summary for future reference.

If you are not interested in the exploration, please directly skip to the part Masking an Image with Color at the end of the article

Bitmap

BitMap is described as follows in the official Quarz 2D documentation

A bitmap image (or sampled image) is an array of pixels (or samples). Each pixel represents a single point in the image. JPEG, TIFF, and PNG graphics files are examples of bitmap images.

32-bit and 16-bit pixel formats for CMYK and RGB color spaces in Quartz 2D

Going back to our requirements, for removing the specified color in the image, if we can read the RGBA information on each pixel, determine their value separately, if it fits the target range, we change its Alpha value to 0, and then output it as a new image, then we have implemented similar processing to cubeMap above.

The powerful Quarz 2D provides us with the ability to do this. Here is a code example:

- (UIImage *)removeColorWithMaxR:(float)maxR minR:(float)minR maxG:(float)maxG minG:(float)minG maxB:(float)maxB minB:(floatConst int imageWidth = image.size. Width; const int imageHeight = image.size.height; size_t bytesPerRow = imageWidth * 4; uint32_t* rgbImageBuf = (uint32_t*)malloc(bytesPerRow * imageHeight); / / create the context CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB (); CGContextRef Context = CGBitmapContextCreate(rgbImageBuf, imageWidth, imageHeight, 8, bytesPerRow, colorSpace,kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipLast); CGContextDrawImage(context, CGRectMake(0, 0, imageWidth, imageHeight), image.CGImage); Int pixelNum = imageWidth * imageHeight; uint32_t* pCurPtr = rgbImageBuf;for (int i = 0; i < pixelNum; i++, pCurPtr++)
    {
        uint8_t* ptr = (uint8_t*)pCurPtr;
        if(ptr[3] >= minR && ptr[3] <= maxR && ptr[2] >= minG && ptr[2] <= maxG && ptr[1] >= minB && ptr[1] <= maxB) { ptr[0] = 0;  }else{
            printf("\n---->ptr0:%d ptr1:%d ptr2:%d ptr3:%d<----\n",ptr[0],ptr[1],ptr[2],ptr[3]); }} / / the memory into image CGDataProviderRef dataProvider = CGDataProviderCreateWithData (NULL, rgbImageBuf, bytesPerRow * imageHeight, nil); CGImageRef imageRef = CGImageCreate(imageWidth, imageHeight,8, 32, bytesPerRow, colorSpace,kCGImageAlphaLast |kCGBitmapByteOrder32Little, dataProvider,NULL,true,kCGRenderingIntentDefault); CGDataProviderRelease(dataProvider); UIImage* resultUIImage = [UIImage imageWithCGImage:imageRef]; / / release CGImageRelease (imageRef); CGContextRelease(context); CGColorSpaceRelease(colorSpace);return resultUIImage;
}
Copy the code

Remember the drawbacks of HSV mode that we mentioned in Core Image? Quarz 2D directly uses RGBA information for processing, which well avoids the unfriendly problem of black and white. We just need to set the RGB range (because black and white are easy to determine in RGB color mode), and we can roughly encapsulate it. The following

- (UIImage *)removeWhiteColorWithImage:(UIImage *)image{
    return [self removeColorWithMaxR:255 minR:250 maxG:255 minG:240 maxB:255 minB:240 image:image];
}
Copy the code
- (UIImage *)removeBlackColorWithImage:(UIImage *)image{
    return [self removeColorWithMaxR:15 minR:0 maxG:15 minG:0 maxB:15 minB:0 image:image];
}
Copy the code

Take a look at how we compare the white background

It looks good, but it’s not very friendly when it comes to gauze. Take a look at some of the photo tests I did

Apparently, if it weren’t for the white background, the “Raggedy” effect would be obvious. None of the three methods which I try is immune to this question, and it would be appreciated if any one of you knew a good one and could tell us about it. (Put both knees here first)

In addition to the problems mentioned above, this method of comparing each pixel causes errors in the values read during the drawing. But the error is almost invisible to the naked eye.

RGB

Masking an Image with Color

After trying to understand and use the previous method, I reread the document and discovered this method, which is almost like the gift of Father Apple. Go straight to code

- (UIImage *)removeColorWithMaxR:(float)maxR minR:(float)minR maxG:(float)maxG minG:(float)minG maxB:(float)maxB minB:(float)minB image:(UIImage *)image{

    const CGFloat myMaskingColors[6] = {minR, maxR,  minG, maxG, minB, maxB};
    CGImageRef ref = CGImageCreateWithMaskingColors(image.CGImage, myMaskingColors);
    return [UIImage imageWithCGImage:ref];
    
}
Copy the code

The official document can be found here

conclusion

Compared with RGB, HSV color mode is better for us to remove the color in the picture, while RGB is just the opposite. I chose the last option because I only needed to remove the white background in my project.