Skip to content

Instantly share code, notes, and snippets.

@elliottwilliams
Created February 1, 2018 17:34
Show Gist options
  • Select an option

  • Save elliottwilliams/dc0671705af69ac3132cdf7660547ca9 to your computer and use it in GitHub Desktop.

Select an option

Save elliottwilliams/dc0671705af69ac3132cdf7660547ca9 to your computer and use it in GitHub Desktop.
B-spline interpolation between multi-point color schemes
//
// ColorBrewer.swift
// Proper
//
// Created by Elliott Williams on 10/2/17.
// Copyright © 2017 Elliott Williams. All rights reserved.
//
import Foundation
import UIKit
import CoreGraphics
class ColorBrewer {
let scheme: [(red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat)]
private let rSpline: (CGFloat) -> (CGFloat)
private let gSpline: (CGFloat) -> (CGFloat)
private let bSpline: (CGFloat) -> (CGFloat)
private let aSpline: (CGFloat) -> (CGFloat)
init(scheme: [(red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat)]) {
self.scheme = scheme
rSpline = Interpolation.makeUniform(controlPoints: scheme.map({ $0.red }), degree: 2)
gSpline = Interpolation.makeUniform(controlPoints: scheme.map({ $0.green }), degree: 2)
bSpline = Interpolation.makeUniform(controlPoints: scheme.map({ $0.blue }), degree: 2)
aSpline = Interpolation.makeUniform(controlPoints: scheme.map({ $0.alpha }), degree: 2)
}
convenience init(uiColors scheme: [UIColor]) {
self.init(scheme: scheme.map({ ($0.red(), $0.green(), $0.blue(), $0.alpha()) }))
}
func interpolatedColor(at pos: CGFloat) -> UIColor {
return UIColor(red: CGFloat(rSpline(pos)),
green: CGFloat(gSpline(pos)),
blue: CGFloat(bSpline(pos)),
alpha: CGFloat(aSpline(pos)))
}
// Add schemes here, like:
static let purpleRed = ColorBrewer(uiColors: [
UIColor(hex: "df65b0"), UIColor(hex: "e7298a"), UIColor(hex: "ce1256"),
UIColor(hex: "980043"), UIColor(hex: "67001f"),
])
}
//
// Interpolation.swift
// Proper
//
// Created by Elliott Williams on 10/3/17.
// Copyright © 2017 Elliott Williams. All rights reserved.
//
import Foundation
public class Interpolation {
public static func makeBSpline<Number: FloatingPoint>(knots t: [Number],
controlPoints c: [Number],
degree p: Int,
shouldPad: Bool = true) -> (Number) -> Number
{
let t = shouldPad ? pad(t, degree: p) : t
let c = shouldPad ? pad(c, degree: p) : c
return { x in
var k = t.count-p-1
for i in t.indices.reversed().dropFirst() {
if t[i] <= x && x < t[i+1] {
k = i
break
}
}
var d = (0...(p+1)).map({ j -> Number in c[max(j + k - p,0)] })
for r in 1...p {
for j in stride(from: p, through: r, by: -1) {
let alpha = (x - t[j+k-p]) / (t[j+1+k-r] - t[j+k-p])
d[j] = (1 - alpha) * d[j-1] + alpha * d[j]
}
}
return d[p]
}
}
public static func makeUniform<Number: FloatingPoint>(controlPoints c: [Number], degree p: Int) -> (Number) -> Number {
return makeBSpline(knots: uniformKnot(c.count), controlPoints: c, degree: p)
}
public static func pad<Number: FloatingPoint>(_ t: [Number], degree p: Int) -> [Number] {
assert(t.count > 0, "`t` must be nonempty")
let first = Array(repeating: t.first!, count: p)
let last = Array(repeating: t.last!, count: p)
return first + t + last
}
public static func uniformKnot<Number: FloatingPoint>(_ n: Int) -> [Number] {
return (0..<n).map({ Number($0)/Number(n-1) })
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment