Because Unsolved Problems Bother Me
I’m working on some code to intelligently remove arbitrary border colors from an image for reasons vaguely related to a project at work. I’m close to a solution, but there’s some strangenesses I’m hitting in the core image filter stack that eludes me, so I don’t have a full code dump to share, but I’m particularly proud of how I structured the edge insets code to make it more functional.
private extension UIImage {
static func cropEdgeInsets(filterInfo: FilterInfo, startingSize: CGSize) -> UIEdgeInsets {
let inputHeight = Int(startingSize.height)
let inputWidth = Int(startingSize.width)
let topInset = filterInfo.calculateInset(1...inputHeight) { row in
return CGRect(x: 0, y: 0, width: inputWidth, height: row)
} ?? 0
let bottomRange = ((topInset + 1)...(inputHeight - 1)).reverse()
let bottomInset = filterInfo.calculateInset(bottomRange) { row in
return CGRect(x: 0, y: row, width: inputWidth, height: inputHeight - row)
} ?? 0
let leftInset = filterInfo.calculateInset(1...inputWidth) { col in
return CGRect(x: 0, y: 0, width: col, height: inputHeight)
} ?? 0
let rightRange = ((leftInset + 1)...(inputWidth - 1)).reverse()
let rightInset = filterInfo.calculateInset(rightRange) { col in
return CGRect(x: col, y: 0, width: inputWidth - col, height: inputHeight)
} ?? 0
return UIEdgeInsets(top: CGFloat(topInset), left: CGFloat(leftInset), bottom: CGFloat(bottomInset), right: CGFloat(rightInset))
}
struct FilterInfo {
let maxFilter :CIFilter
let minFilter: CIFilter
let context: CIContext
let maximumDelta: UInt
func calculateInset<U: CollectionType>(range: U, rectGenerator: (step: U.Generator.Element) -> CGRect) -> U.Generator.Element? {
var inset: U.Generator.Element?
for step in range {
let rect = rectGenerator(step: step)
self.maxFilter.updateFilterExtentsWithRect(rect)
self.minFilter.updateFilterExtentsWithRect(rect)
guard let rowDiff = self.minFilter.differenceFromFilter(self.maxFilter, context: self.context)
where rowDiff <= self.maximumDelta else {
break
}
inset = step
}
return inset
}
}
}
The first few passes at the code, I didn’t have a great idea to reduce the amount of duplication in iterating in from each of the sides, which I thought may hurt later as I’m assuming there’s room for optimizing the search for edges, probably involving subdividing and testing regions rather than a linear walk of the space.
I then realized that the only major change in each region was generating the rectangle and the range being worked on. Using a closure for the rectangle generation helped, but making a struct to shove all of the important values into and making the calculateInset
code a function on that struct also helped me keep the function signatures sane.
Finally, when I made the calculateInset
function properly generic, I also had to make the return type an optional value because I couldn’t set the initial value to 0
in the function body. This means I now added a ?? 0
after each call.
I’m much happier with this than the first few passes, the code doesn’t work yet, but all of the bits that I’ve tested outside of the core image black box for the filters are behaving properly