title: Chroma Key using Core Image filters leaves a halo around text
body: <p>I have been trying to come up with a simple way to use Core Image filters to key out the background of a slide and to key out text of a certain color. My current project works, but with one caveat: the text that I am trying to remove is gone, but a halo remains.</p>
  
  <p>Here is a screenshot showing the problem:</p>
  
  <p><img src="https://i.stack.imgur.com/JK0Xz.png" alt="enter image description here"></p>
  
  <p>From what I can tell, there is a blue halo around the "Main Slide Content", but this is probably okay since the main content is still on the screen. I want to remove the halo around the "Test Speaker Notes", though, because that's intended to be entirely keyed out.</p>
  
  <p>In my head, I feel like the solution would be to get a mask of the green text, and then expand that mask a little bit, and then use that in a multiply filter to key out the halos, but I don't know how to approach this with Core Image. I would appreciate any guidance on which path to take here. Here is my applicationDidFinishLaunching method and the keying and blending methods, which are the two methods that do the work. All code is in the project file on GitHub, found [here]: <a href="https://github.com/nspaul/chromakeytest" rel="nofollow noreferrer">https://github.com/nspaul/chromakeytest</a></p>
  
  <pre><code>- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
  {
      NSImage *img = [NSImage imageNamed:@"testSlide.png"];
      NSImage *actsBG = [NSImage imageNamed:@"background.png"];
      NSImage *keyed = img;
  
      //Key out the green
      keyed = [self keyImage:keyed minHue:0.25 maxHue:0.4];
  
      //Key out the blue background
      keyed = [self keyImage:keyed minHue:0.6 maxHue:0.7];
  
      //Put the test slide over the background slide
      keyed = [self blendImage:keyed withBackgroundImage:actsBG];
  
      [_topImageView setImage:img];
      [_backgroundImageView setImage:actsBG];
      [_keyedImageView setImage:keyed];
  }
  
  
  
  
  -(NSImage *)blendImage:(NSImage *)img withBackgroundImage:(NSImage *)bgimg {
      CIImage *inputImage = [[CIImage alloc] initWithData:[img TIFFRepresentation]];
      CIImage *bgInputImage = [[CIImage alloc] initWithData:[bgimg TIFFRepresentation]];
      CIFilter *blendFilter = [CIFilter filterWithName:@"CISourceOverCompositing"];
  
      [blendFilter setValue:inputImage forKey:@"inputImage"];
      [blendFilter setValue:bgInputImage forKey:@"inputBackgroundImage"];
  
      CIImage *outputImage = [blendFilter valueForKey:@"outputImage"];
      NSImage *resultImage = [[NSImage alloc] initWithSize:[outputImage extent].size];
      NSCIImageRep *rep = [NSCIImageRep imageRepWithCIImage:outputImage];
      [resultImage addRepresentation:rep];
  
      return resultImage;
  }
  
  
  
  -(NSImage *) keyImage:(NSImage *)imageToKey minHue:(float)minHueAngle maxHue:(float)maxHueAngle {
      CIImage *ciImage = [[CIImage alloc] initWithData:[imageToKey TIFFRepresentation]];
  
  
      // Allocate memory
      const unsigned int size = 64;
      float *cubeData = (float *)malloc (size * size * size * sizeof (float) * 4);
      float rgb[3], hsv[3], *c = cubeData;
  
      // Populate cube with a simple gradient going from 0 to 1
      for (int z = 0; z &lt; size; z++){
          rgb[2] = ((double)z)/(size-1); // Blue value
          for (int y = 0; y &lt; size; y++){
              rgb[1] = ((double)y)/(size-1); // Green value
              for (int x = 0; x &lt; size; x ++){
                  rgb[0] = ((double)x)/(size-1); // Red value
                  // Convert RGB to HSV
                  // You can find publicly available rgbToHSV functions on the Internet
                  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
                  float alpha = (hsv[0] &gt; minHueAngle &amp;&amp; hsv[0] &lt; maxHueAngle) ? 0.0f: 1.0f;
                  // Calculate premultiplied alpha values for 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
              }
          }
      }
  
      NSLog(@"cubeData: %p",cubeData);
  
      // Create memory with the cube data
      NSData *data = [NSData dataWithBytesNoCopy:cubeData
                                          length:(64 * 64 * 64 * sizeof (float) * 4)
                                    freeWhenDone:YES];
      CIFilter *colorCube = [CIFilter filterWithName:@"CIColorCube"];
      [colorCube setValue:[NSNumber numberWithInt:size] forKey:@"inputCubeDimension"];
      // Set data for cube
      [colorCube setValue:data forKey:@"inputCubeData"];
  
  
      [colorCube setValue:ciImage forKey:kCIInputImageKey];
  
      CIImage *outImage = [colorCube valueForKey:@"outputImage"];
  
      NSImage *resultImage = [[NSImage alloc] initWithSize:[outImage extent].size];
      NSCIImageRep *rep = [NSCIImageRep imageRepWithCIImage:outImage];
      [resultImage addRepresentation:rep];
  
      return resultImage;
  }
  </code></pre>