Home » Swift » How to invert colors of a specific area of a UIView?

How to invert colors of a specific area of a UIView?

Posted by: admin November 30, 2017 Leave a comment

Questions:

It’s easy to blur a portion of the view, keeping in mind that if the contents of views behind change, the blur changes too in realtime.

My questions

  1. How to make an invert effect, and you can put it over a view and the contents behind would have inverted colors

  2. How to add an effect that would know the average color of the pixels behind?

In general, How to access the pixels and manipulate them?
My question is not about UIImageView, asking about UIView in general..

there are libraries that does something similar, but they are so slow and don’t run as smooth as blur!

Thanks.

Answers:

If you know how to code a CIColorKernel, you’ll have what you need.

  • Core Image has several blur filters, all of which use the GPU, which will give you the performance you need.

  • The CIAreaAverage will give you the average color for a specified rectangular area.

Core Image Filters

Here is about the simplest CIColorKernel you can write. It swaps the red and green value for every pixel in an image (note the “grba” instead of “rgba”):

kernel vec4 swapRedAndGreenAmount(__sample s) {
    return s.grba;
}

To put this into a CIColorKernel, just use this line of code:

let swapKernel = CIKernel(string:
    "kernel vec4 swapRedAndGreenAmount(__sample s) {" +
    "return s.grba;" +
    "}"

@tww003 has good code to convert a view’s layer into a UIImage. Assuming you call your image myUiImage, to execute this swapKernel, you can:

let myInputCi = CIImage(image: myUiImage)
let myOutputCi = swapKernel.apply(withExtent: myInputCi, arguments: myInputCi)
Let myNewImage = UIImage(ciImage: myOutputCi)

That’s about it. You can do alot more (including using CoreGraphics, etc.) but this is a good start.

One last note, you can chain individual filters (including hand-written color, warp, and general kernels). If you want, you can chain your color average over the underlying view with a blur and do whatever kind of inversion you wish as a single filter/effect.

Questions:
Answers:

I don’t think I can fully answer your question, but maybe I can point you in the right direction.

Apple has some documentation on accessing the pixels data from CGImages, but of course that requires that you have an image to work with in the first place. Fortunately, you can create an image from a UIView like this:

    UIGraphicsBeginImageContext(view.frame.size)
    view.layer.render(in: UIGraphicsGetCurrentContext()!)
    let image = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()

From this image you created, you’ll be able to manipulate the pixel data how you want to. It may not be the cleanest way to solve your problem, but maybe it’s something worth exploring.

Unfortunately, the link I provided is written in Objective-C and is a few years old, but maybe you can figure out how to make good use of it.

Questions:
Answers:

1st I will recommend you to extend UIImageView for this purpose. ref

Good ref by Joe

You have to override drawRect method

import UIKit

@IBDesignable class PortholeView: UIView {
  @IBInspectable var innerCornerRadius: CGFloat = 10.0
  @IBInspectable var inset: CGFloat = 20.0
  @IBInspectable var fillColor: UIColor = UIColor.grayColor()
  @IBInspectable var strokeWidth: CGFloat = 5.0
  @IBInspectable var strokeColor: UIColor = UIColor.blackColor()

  override func drawRect(rect: CGRect) {
    super.drawRect(rect:rect)
    // Prep constants
    let roundRectWidth = rect.width - (2 * inset)
    let roundRectHeight = rect.height - (2 * inset)

    // Use EvenOdd rule to subtract portalRect from outerFill
    // (See https://stackoverflow.com/questions/14141081/uiview-drawrect-draw-the-inverted-pixels-make-a-hole-a-window-negative-space)
    let outterFill = UIBezierPath(rect: rect)
    let portalRect = CGRectMake(
      rect.origin.x + inset,
      rect.origin.y + inset,
      roundRectWidth,
      roundRectHeight)
    fillColor.setFill()
    let portal = UIBezierPath(roundedRect: portalRect, cornerRadius: innerCornerRadius)
    outterFill.appendPath(portal)
    outterFill.usesEvenOddFillRule = true
    outterFill.fill()
    strokeColor.setStroke()
    portal.lineWidth = strokeWidth
    portal.stroke()
  }
}

Your answer is here