Skip to content

Instantly share code, notes, and snippets.

@qwzybug
Created June 26, 2016 09:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save qwzybug/4c728071fd8350a466a5bef5a4db4993 to your computer and use it in GitHub Desktop.
Save qwzybug/4c728071fd8350a466a5bef5a4db4993 to your computer and use it in GitHub Desktop.
//: Playground - noun: a place where people can play
import Cocoa
import CoreGraphics
extension CGColor {
static func white(_ value: CGFloat = 1.0, alpha: CGFloat = 1.0) -> CGColor {
return CGColor(red: value, green: value, blue: value, alpha: 1.0)
}
}
class GrayscaleBitmap {
let width: Int
let height: Int
let ctx: CGContext
var pixels: UnsafeMutablePointer<UInt8>
init?(width: Int, height: Int, backgroundColor: CGColor = CGColor.white()) {
guard let ctx = CGContext.grayscaleBitmapContext(width: width, height: height), data = ctx.data else {
return nil
}
self.ctx = ctx
self.pixels = unsafeBitCast(data, to: UnsafeMutablePointer<UInt8>.self)
self.width = width
self.height = height
ctx.setFillColor(backgroundColor)
ctx.fill(bounds)
}
var bounds: CGRect {
return CGRect(x: 0, y: 0, width: width, height: height)
}
var image: CGImage? {
return ctx.makeImage()
}
subscript(x: Int, y: Int) -> UInt8 {
get { return pixels[y * width + x] }
set { pixels[y * width + x] = newValue }
}
}
extension CGContext {
static func grayscaleBitmapContext(width: Int, height: Int) -> CGContext? {
let bytesPerPixel = 1
let bytesPerRow = bytesPerPixel * width
let bitsPerComponent = 8
let colorSpace = CGColorSpaceCreateDeviceGray()
guard let ctx = CGContext(data: nil, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: 0) else {
return nil
}
ctx.scale(x: 1, y: -1)
ctx.translate(x: 0, y: -CGFloat(height))
return ctx
}
}
extension CGImage {
static func radialGradient(radius: Int, from startColor: CGColor, to endColor: CGColor) -> CGImage? {
guard let ctx = CGContext.grayscaleBitmapContext(width: radius * 2, height: radius * 2),
gradient = CGGradient(colorsSpace: CGColorSpaceCreateDeviceGray(), colors: [startColor, endColor], locations: [0, 1])
else {
return nil
}
ctx.drawRadialGradient(gradient, startCenter: CGPoint(x: radius, y: radius), startRadius: 0.5, endCenter: CGPoint(x: radius, y: radius), endRadius: CGFloat(radius), options: [.drawsAfterEndLocation, .drawsBeforeStartLocation])
return ctx.makeImage()
}
func show() -> NSImage {
return NSImage(cgImage: self, size: NSSize(width: CGFloat(self.width), height: CGFloat(self.height)))
}
}
func iterateLine(p1: CGPoint, p2: CGPoint, precision: CGFloat = 0.05, apply: (CGPoint) -> ()) {
let dx = p2.x - p1.x
let dy = p2.y - p1.y
var t: CGFloat = 0.0
while t <= 1.0001 {
apply(CGPoint(x: p1.x + dx * t, y: p1.y + dy * t))
t += precision
}
}
typealias CubicBezier = (start: CGPoint, cp1: CGPoint, cp2: CGPoint, end: CGPoint)
// http://stackoverflow.com/questions/4058979/find-a-point-a-given-distance-along-a-simple-cubic-bezier-curve-on-an-iphone
func bezierInterpolate(t: CGFloat, a: CGFloat, b: CGFloat, c: CGFloat, d: CGFloat) -> CGFloat {
let t2 = t * t;
let t3 = t2 * t;
return a + (-a * 3 + t * (3 * a - a * t)) * t
+ (3 * b + t * (-6 * b + b * 3 * t)) * t
+ (c * 3 - c * 3 * t) * t2
+ d * t3;
}
func iterateBezier(curve: CubicBezier, precision: CGFloat = 0.05, apply: (CGPoint) -> ()) {
var t: CGFloat = 0.0
while t <= 1.0001 {
let x = bezierInterpolate(t: t, a: curve.start.x, b: curve.cp1.x, c: curve.cp2.x, d: curve.end.x)
let y = bezierInterpolate(t: t, a: curve.start.y, b: curve.cp1.y, c: curve.cp2.y, d: curve.end.y)
apply(CGPoint(x: x, y: y))
t += precision
}
}
// http://stackoverflow.com/questions/1074395/quadratic-bezier-interpolation
func quadraticInterpolate(t: CGFloat, a: CGFloat, b: CGFloat, c: CGFloat) -> CGFloat {
return a * (1 - t) * (1 - t)
+ b * 2 * (1 - t) * t
+ c * t * t
}
typealias Quadratic = (start: CGPoint, cp: CGPoint, end: CGPoint)
func iterateQuad(curve: Quadratic, precision: CGFloat = 0.05, apply: (CGPoint) -> ()) {
var t: CGFloat = 0.0
while t <= 1.0001 {
let x = quadraticInterpolate(t: t, a: curve.start.x, b: curve.cp.x, c: curve.end.x)
let y = quadraticInterpolate(t: t, a: curve.start.y, b: curve.cp.y, c: curve.end.y)
apply(CGPoint(x: x, y: y))
t += precision
}
}
extension CGContext {
func draw(at point: CGPoint, image: CGImage) {
draw(in: CGRect(x: point.x - CGFloat(image.width) / 2, y: point.y - CGFloat(image.height) / 2, width: CGFloat(image.width), height: CGFloat(image.height)), image: image)
}
}
struct DrawPathContext {
let brush: CGImage
let context: CGContext
let resolution: CGFloat
var position: CGPoint
var subStart: CGPoint
}
extension CGPath {
func draw(in context: CGContext, brush: CGImage, resolution: CGFloat) -> () {
var ctx = DrawPathContext(brush: brush, context: context, resolution: resolution, position: .zero, subStart: .zero)
apply(info: &ctx) { userInfo, elemPtr in
let ptr = unsafeBitCast(userInfo!, to: UnsafeMutablePointer<DrawPathContext>.self)
var ctx = ptr.pointee
let elem = elemPtr.pointee
switch elem.type {
case .moveToPoint:
ctx.subStart = elem.points.pointee
ctx.position = elem.points.pointee
case .addLineToPoint:
let p1 = ctx.position
let p2 = elem.points.pointee
iterateLine(p1: p1, p2: p2, precision: ctx.resolution) { point in
ctx.context.draw(at: point, image: ctx.brush)
}
ctx.position = p2
case .addQuadCurveToPoint:
let cp = elem.points.pointee
let end = elem.points.advanced(by: 1).pointee
iterateQuad(curve: (start: ctx.position, cp: cp, end: end), precision: ctx.resolution) { point in
ctx.context.draw(at: point, image: ctx.brush)
}
ctx.position = end
case .addCurveToPoint:
let cp1 = elem.points.pointee
let cp2 = elem.points.advanced(by: 1).pointee
let end = elem.points.advanced(by: 2).pointee
iterateBezier(curve: (start: ctx.position, cp1: cp1, cp2: cp2, end: end), precision: ctx.resolution) { point in
ctx.context.draw(at: point, image: ctx.brush)
}
ctx.position = end
case .closeSubpath:
let p1 = ctx.position
let p2 = ctx.subStart
iterateLine(p1: p1, p2: p2, precision: ctx.resolution) { point in
ctx.context.draw(at: point, image: ctx.brush)
}
ctx.position = p2
}
ptr.assignFrom(&ctx, count: 1)
}
}
}
let path = CGMutablePath()
path.addEllipseIn(nil, rect: CGRect(x: 0, y: 0, width: 100, height: 100))
path.moveTo(nil, x: 120, y: 100)
path.addLineTo(nil, x: 180, y: 0)
path.addLineTo(nil, x: 240, y: 100)
path.closeSubpath()
path.addRect(nil, rect: CGRect(x: 260, y: 0, width: 100, height: 100))
let box = path.boundingBox
let radius = 24
guard let inside = GrayscaleBitmap(width: Int(box.width) + 2 * radius, height: Int(box.height) + 2 * radius, backgroundColor: CGColor.white(0.0)),
outside = GrayscaleBitmap(width: Int(box.width) + 2 * radius, height: Int(box.height) + 2 * radius, backgroundColor: CGColor.white(1.0)),
inGradient = CGImage.radialGradient(radius: radius, from: CGColor.white(0.5), to: CGColor.white(0.0)),
outGradient = CGImage.radialGradient(radius: radius, from: CGColor.white(0.5), to: CGColor.white(1.0)),
texture = GrayscaleBitmap(width: Int(box.width) + 2 * radius, height: Int(box.height) + 2 * radius),
mask = GrayscaleBitmap(width: Int(box.width) + 2 * radius, height: Int(box.height) + 2 * radius, backgroundColor: CGColor.white(0.0))
else {
NSLog("Ack!")
exit(-1)
}
inside.ctx.translate(x: CGFloat(radius), y: CGFloat(radius))
outside.ctx.translate(x: CGFloat(radius), y: CGFloat(radius))
mask.ctx.translate(x: CGFloat(radius), y: CGFloat(radius))
inside.ctx.setBlendMode(.lighten)
outside.ctx.setBlendMode(.darken)
path.draw(in: inside.ctx, brush: inGradient, resolution: 0.01)
path.draw(in: outside.ctx, brush: outGradient, resolution: 0.01)
inside.image!.show()
outside.image!.show()
mask.ctx.setFillColor(CGColor.white(1.0))
mask.ctx.addPath(path)
mask.ctx.fillPath()
mask.image!.show()
guard let inside = inside.ctx.makeImage(), outside = outside.ctx.makeImage(), mask = mask.ctx.makeImage(), masked = inside.masking(mask) else {
NSLog("Ack")
exit(-1)
}
texture.ctx.draw(in: texture.bounds, image: outside)
texture.ctx.draw(in: texture.bounds, image: masked)
texture.image!.show()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment