From f19f22b200e9c712691e2313c34e516927b7a24c Mon Sep 17 00:00:00 2001 From: Saniul Ahmed Date: Sun, 15 Mar 2015 00:24:13 -0700 Subject: [PATCH 1/9] beginnings of image pixel data access --- Prototope/Color.swift | 27 +++++++ Prototope/Image.swift | 104 ++++++++++++++++++++++++++ PrototopeTestApp/ViewController.swift | 39 +++++++++- 3 files changed, 167 insertions(+), 3 deletions(-) diff --git a/Prototope/Color.swift b/Prototope/Color.swift index 4a4ea6a..9097df8 100644 --- a/Prototope/Color.swift +++ b/Prototope/Color.swift @@ -53,6 +53,33 @@ public struct Color { init(_ uiColor: UIColor) { self.uiColor = uiColor } + + public func getRGBAValues() -> (red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) { + let colorSpace = CGColorGetColorSpace(self.uiColor.CGColor); + let colorSpaceModel = CGColorSpaceGetModel(colorSpace); + + var r = 0 as CGFloat + var g = 0 as CGFloat + var b = 0 as CGFloat + var a = 0 as CGFloat + + switch colorSpaceModel.value { + + case kCGColorSpaceModelMonochrome.value: + var w = 0 as CGFloat + self.uiColor.getWhite(&w, alpha: &a) + r = w + g = w + b = w + case kCGColorSpaceModelRGB.value: + self.uiColor.getRed(&r, green: &g, blue: &b, alpha: &a) + + default: + fatalError("unsupported color space for color: \(self)") + } + + return (r, g, b, a) + } public static var black: Color { return Color(UIColor.blackColor()) } public static var darkGray: Color { return Color(UIColor.darkGrayColor()) } diff --git a/Prototope/Image.swift b/Prototope/Image.swift index 68733a5..9f5ccca 100644 --- a/Prototope/Image.swift +++ b/Prototope/Image.swift @@ -34,4 +34,108 @@ public struct Image { init(_ image: UIImage) { uiImage = image } + + public func toPixels() -> PixelBitmap { + return PixelBitmap(image: self) + } } + +public struct PixelBitmap : MutableCollectionType { + typealias Index = Int + + init(image: Image) { + width = Int(image.size.width) + height = Int(image.size.height) + + data = UnsafeMutablePointer.alloc(width*height) + + let colorSpace: CGColorSpace = CGColorSpaceCreateDeviceRGB() + let bitmapInfo = CGBitmapInfo(CGImageAlphaInfo.PremultipliedLast.rawValue) + let bytesPerRow = width * 4 + + let context = CGBitmapContextCreate(data, width, height, 8, bytesPerRow, colorSpace, bitmapInfo) + + CGContextDrawImage(context, CGRect(origin: CGPointZero, size: image.uiImage.size), image.uiImage.CGImage) + } + + var data: UnsafeMutablePointer + let width: Int + let height: Int + + public let startIndex: Int = 0 + + public var endIndex: Int { return height * width } + + public subscript (position: Int) -> Pixel { + get { return data[position] } + set { data[position] = newValue } + } + + public func pixelAt(row: Int, column: Int) -> Pixel { + let idx = row * width + column + return self[idx] + } + + public func generate() -> IndexingGenerator { + return IndexingGenerator(self) + } + + public func toImage() -> Image { + let colorSpace: CGColorSpace = CGColorSpaceCreateDeviceRGB() + let bitmapInfo = CGBitmapInfo(CGImageAlphaInfo.PremultipliedLast.rawValue) + let context = CGBitmapContextCreate(data, width, height, 8, width * 4, colorSpace, bitmapInfo) + let cgImage = CGBitmapContextCreateImage(context) + + return Image(UIImage(CGImage: cgImage)!) + } + + public func transform(transform: Pixel -> Pixel) -> PixelBitmap { + var newBitmap = self + for idx in startIndex.. Pixel) -> PixelBitmap { + var newBitmap = self + for idx in startIndex.. Pixel) -> PixelBitmap { + var newBitmap = self + for idx in startIndex.. Layer { let redLayer = Layer(parent: Layer.root, name: name) - redLayer.image = Image(name: "paint") + + let redImage = Image(name: "paint") + let pixels = redImage.toPixels() + + let width = redImage.size.width + + var modified = pixels + + // White-out every pixel in even rows and columns + modified = modified.transform { row, column, pixel in + var newPixel = pixel + newPixel.red = (row % 2 == 0) || (column % 2 == 0) ? 255 : newPixel.red + newPixel.green = (row % 2 == 0) || (column % 2 == 0) ? 255 : newPixel.green + newPixel.blue = (row % 2 == 0) || (column % 2 == 0) ? 255 : newPixel.blue + return newPixel + } + + // Make every tenth pixel blue + modified = modified.transform { idx, pixel in + var newPixel = pixel + newPixel.blue = idx % 10 == 0 ? 255 : pixel.blue + return newPixel + } + + // Invert all pixels + modified = modified.transform { (pixel: Pixel) -> Pixel in + var newPixel = pixel + newPixel.red = 255 - pixel.red + newPixel.green = 255 - pixel.green + newPixel.blue = 255 - pixel.blue + return newPixel + } + + redLayer.image = modified.toImage() tunable(50, name: "x") { value in redLayer.frame.origin = Point(x: value, y: y) } redLayer.backgroundColor = Color.red redLayer.cornerRadius = 10 From f7911f32325991ea54b27433f3e0e32ae63c266a Mon Sep 17 00:00:00 2001 From: Saniul Ahmed Date: Sun, 15 Mar 2015 18:33:57 -0700 Subject: [PATCH 2/9] added a PixelDataDestroyer to properly clean up the underlying PixelBitmap data buffer --- Prototope/Image.swift | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/Prototope/Image.swift b/Prototope/Image.swift index 9f5ccca..cdc5391 100644 --- a/Prototope/Image.swift +++ b/Prototope/Image.swift @@ -43,11 +43,22 @@ public struct Image { public struct PixelBitmap : MutableCollectionType { typealias Index = Int + var data: UnsafeMutablePointer + let width: Int + let height: Int + + public let startIndex: Int = 0 + + public var endIndex: Int { return height * width } + + private let dataDestroyer: PixelDataDestroyer + init(image: Image) { width = Int(image.size.width) height = Int(image.size.height) data = UnsafeMutablePointer.alloc(width*height) + dataDestroyer = PixelDataDestroyer(data: data, width: width, height: height) let colorSpace: CGColorSpace = CGColorSpaceCreateDeviceRGB() let bitmapInfo = CGBitmapInfo(CGImageAlphaInfo.PremultipliedLast.rawValue) @@ -57,14 +68,6 @@ public struct PixelBitmap : MutableCollectionType { CGContextDrawImage(context, CGRect(origin: CGPointZero, size: image.uiImage.size), image.uiImage.CGImage) } - - var data: UnsafeMutablePointer - let width: Int - let height: Int - - public let startIndex: Int = 0 - - public var endIndex: Int { return height * width } public subscript (position: Int) -> Pixel { get { return data[position] } @@ -117,6 +120,22 @@ public struct PixelBitmap : MutableCollectionType { return newBitmap } + + private class PixelDataDestroyer { + let pixelDataToCleanUp: UnsafeMutablePointer + let width: Int + let height: Int + + init(data: UnsafeMutablePointer, width: Int, height: Int) { + self.pixelDataToCleanUp = data + self.width = width + self.height = height + } + + deinit { + pixelDataToCleanUp.dealloc(width*height) + } + } } From af7c2a7c441e702e613b40008579905ff7a0e8e1 Mon Sep 17 00:00:00 2001 From: Saniul Ahmed Date: Mon, 23 Mar 2015 18:58:29 -0700 Subject: [PATCH 3/9] incorporated PR feedback --- Prototope/Color.swift | 10 ++- Prototope/Image.swift | 99 ++++++++++++++++++++------- PrototopeTestApp/ViewController.swift | 22 +++--- 3 files changed, 93 insertions(+), 38 deletions(-) diff --git a/Prototope/Color.swift b/Prototope/Color.swift index 9097df8..3e3adfb 100644 --- a/Prototope/Color.swift +++ b/Prototope/Color.swift @@ -21,6 +21,11 @@ public struct Color { public init(red: Double, green: Double, blue: Double, alpha: Double = 1.0) { uiColor = UIColor(red: CGFloat(red), green: CGFloat(green), blue: CGFloat(blue), alpha: CGFloat(alpha)) } + + /** Constructs a color from RGB and alpha values. Arguments range from 0.0 to 1.0. */ + public init(red: Float, green: Float, blue: Float, alpha: Float = 1.0) { + uiColor = UIColor(red: CGFloat(red), green: CGFloat(green), blue: CGFloat(blue), alpha: CGFloat(alpha)) + } /** Constructs a grayscale color. Arguments range from 0.0 to 1.0. */ public init(white: Double, alpha: Double = 1.0) { @@ -54,7 +59,8 @@ public struct Color { self.uiColor = uiColor } - public func getRGBAValues() -> (red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) { + /** Returns the R, G, B and A values of the Color. */ + public func getRGBAValues() -> (red: Float, green: Float, blue: Float, alpha: Float) { let colorSpace = CGColorGetColorSpace(self.uiColor.CGColor); let colorSpaceModel = CGColorSpaceGetModel(colorSpace); @@ -78,7 +84,7 @@ public struct Color { fatalError("unsupported color space for color: \(self)") } - return (r, g, b, a) + return (Float(r), Float(g), Float(b), Float(a)) } public static var black: Color { return Color(UIColor.blackColor()) } diff --git a/Prototope/Image.swift b/Prototope/Image.swift index cdc5391..b595204 100644 --- a/Prototope/Image.swift +++ b/Prototope/Image.swift @@ -40,24 +40,31 @@ public struct Image { } } +/** A representation of an image bitmap that allows pixel data access */ public struct PixelBitmap : MutableCollectionType { typealias Index = Int - var data: UnsafeMutablePointer - let width: Int - let height: Int + public var pixelWidth: Int { return width } + public var pixelHeight: Int { return height } - public let startIndex: Int = 0 + private let width: Int + private let height: Int + public let startIndex: Int = 0 public var endIndex: Int { return height * width } + private let scale: Double + + private var data: UnsafeMutablePointer + private let dataDestroyer: PixelDataDestroyer init(image: Image) { - width = Int(image.size.width) - height = Int(image.size.height) + scale = Double(image.uiImage.scale) + width = Int(image.size.width * scale) + height = Int(image.size.height * scale) - data = UnsafeMutablePointer.alloc(width*height) + data = UnsafeMutablePointer.alloc(width * height) dataDestroyer = PixelDataDestroyer(data: data, width: width, height: height) let colorSpace: CGColorSpace = CGColorSpaceCreateDeviceRGB() @@ -66,7 +73,8 @@ public struct PixelBitmap : MutableCollectionType { let context = CGBitmapContextCreate(data, width, height, 8, bytesPerRow, colorSpace, bitmapInfo) - CGContextDrawImage(context, CGRect(origin: CGPointZero, size: image.uiImage.size), image.uiImage.CGImage) + let cgImageSize = CGSize(width: CGFloat(width), height: CGFloat(height)) + CGContextDrawImage(context, CGRect(origin: CGPointZero, size: cgImageSize), image.uiImage.CGImage) } public subscript (position: Int) -> Pixel { @@ -74,25 +82,40 @@ public struct PixelBitmap : MutableCollectionType { set { data[position] = newValue } } - public func pixelAt(row: Int, column: Int) -> Pixel { - let idx = row * width + column - return self[idx] + public subscript (#row: Int, #column: Int) -> Pixel { + get { + let idx = indexOf(row: row, column: column) + return self[idx] + } + set { + let idx = indexOf(row: row, column: column) + self[idx] = newValue + } + } + + /** Returns the pixel data at a given row and column. */ + public func pixelAt(#row: Int, column: Int) -> Pixel { + return self[row: row, column: column] } public func generate() -> IndexingGenerator { return IndexingGenerator(self) } + /** Returns the Image representation of the PixelBitmap. */ public func toImage() -> Image { let colorSpace: CGColorSpace = CGColorSpaceCreateDeviceRGB() let bitmapInfo = CGBitmapInfo(CGImageAlphaInfo.PremultipliedLast.rawValue) let context = CGBitmapContextCreate(data, width, height, 8, width * 4, colorSpace, bitmapInfo) let cgImage = CGBitmapContextCreateImage(context) - return Image(UIImage(CGImage: cgImage)!) + let image = UIImage(CGImage: cgImage, scale: CGFloat(scale), orientation: .Up)! + + return Image(image) } - public func transform(transform: Pixel -> Pixel) -> PixelBitmap { + /** Constructs a new PixelBitmap by applying the transform on each pixel the original. */ + public func map(transform: Pixel -> Pixel) -> PixelBitmap { var newBitmap = self for idx in startIndex.. Pixel) -> PixelBitmap { + + /** Constructs a new PixelBitmap by applying the transform on each pixel the original. */ + public func map(transform: (position: Int, Pixel) -> Pixel) -> PixelBitmap { var newBitmap = self for idx in startIndex.. Pixel) -> PixelBitmap { + /** Constructs a new PixelBitmap by applying the transform on each pixel the original. */ + public func map(transform: (row: Int, column: Int, Pixel) -> Pixel) -> PixelBitmap { var newBitmap = self for idx in startIndex.. Int { + return row * width + column + } + private class PixelDataDestroyer { let pixelDataToCleanUp: UnsafeMutablePointer let width: Int @@ -141,19 +170,39 @@ public struct PixelBitmap : MutableCollectionType { /** A representation of a single pixel in an RGBA bitmap image. */ public struct Pixel { - public var red: UInt8 - public var green: UInt8 - public var blue: UInt8 - public var alpha: UInt8 + private var redRaw: UInt8 + private var greenRaw: UInt8 + private var blueRaw: UInt8 + private var alphaRaw: UInt8 + + public var red: Float { + get { return Float(redRaw/255) } + set { self.redRaw = UInt8(newValue*255) } + } + + public var green: Float { + get { return Float(greenRaw/255) } + set { self.greenRaw = UInt8(newValue*255) } + } + + public var blue: Float { + get { return Float(blueRaw/255) } + set { self.blueRaw = UInt8(newValue*255) } + } + + public var alpha: Float { + get { return Float(alphaRaw/255) } + set { self.alphaRaw = UInt8(newValue*255) } + } public var color: Color { - get { return Color(red: Double(red/255), green: Double(green/255), blue: Double(blue/255), alpha: Double(alpha/255)) } + get { return Color(red: red, green: green, blue: blue, alpha: alpha) } set { let (r, g, b, a) = newValue.getRGBAValues() - self.red = UInt8(r*255) - self.green = UInt8(g*255) - self.blue = UInt8(b*255) - self.alpha = UInt8(a*255) + self.red = Float(r) + self.green = Float(g) + self.blue = Float(b) + self.alpha = Float(a) } } } diff --git a/PrototopeTestApp/ViewController.swift b/PrototopeTestApp/ViewController.swift index 7d26aad..3cad3de 100644 --- a/PrototopeTestApp/ViewController.swift +++ b/PrototopeTestApp/ViewController.swift @@ -43,27 +43,27 @@ class ViewController: UIViewController { var modified = pixels // White-out every pixel in even rows and columns - modified = modified.transform { row, column, pixel in + modified = modified.map { row, column, pixel in var newPixel = pixel - newPixel.red = (row % 2 == 0) || (column % 2 == 0) ? 255 : newPixel.red - newPixel.green = (row % 2 == 0) || (column % 2 == 0) ? 255 : newPixel.green - newPixel.blue = (row % 2 == 0) || (column % 2 == 0) ? 255 : newPixel.blue + newPixel.red = (row % 2 == 0) || (column % 2 == 0) ? 1.0 : newPixel.red + newPixel.green = (row % 2 == 0) || (column % 2 == 0) ? 1.0 : newPixel.green + newPixel.blue = (row % 2 == 0) || (column % 2 == 0) ? 1.0 : newPixel.blue return newPixel } - // Make every tenth pixel blue - modified = modified.transform { idx, pixel in + // Make every 7th pixel blue + modified = modified.map { idx, pixel in var newPixel = pixel - newPixel.blue = idx % 10 == 0 ? 255 : pixel.blue + newPixel.blue = idx % 7 == 0 ? 1.0 : pixel.blue return newPixel } // Invert all pixels - modified = modified.transform { (pixel: Pixel) -> Pixel in + modified = modified.map { (pixel: Pixel) -> Pixel in var newPixel = pixel - newPixel.red = 255 - pixel.red - newPixel.green = 255 - pixel.green - newPixel.blue = 255 - pixel.blue + newPixel.red = 1.0 - pixel.red + newPixel.green = 1.0 - pixel.green + newPixel.blue = 1.0 - pixel.blue return newPixel } From 47f0183d1ecb35a8a941b7434ec8b628f967e4b2 Mon Sep 17 00:00:00 2001 From: Saniul Ahmed Date: Tue, 24 Mar 2015 14:21:11 -0700 Subject: [PATCH 4/9] some progress on the PixelBitmap JS bridge --- PrototopeJSBridge/Context.swift | 2 + PrototopeJSBridge/ImageBridge.swift | 124 ++++++++++++++++++++++++++++ 2 files changed, 126 insertions(+) diff --git a/PrototopeJSBridge/Context.swift b/PrototopeJSBridge/Context.swift index 2bd7791..b0370dc 100644 --- a/PrototopeJSBridge/Context.swift +++ b/PrototopeJSBridge/Context.swift @@ -82,5 +82,7 @@ public class Context { TextAlignmentBridge.addToContext(context) CameraLayerBridge.addToContext(context) CameraPositionBridge.addToContext(context) + PixelBitmapBridge.addToContext(context) + PixelBridge.addToContext(context) } } \ No newline at end of file diff --git a/PrototopeJSBridge/ImageBridge.swift b/PrototopeJSBridge/ImageBridge.swift index 7172958..996576e 100644 --- a/PrototopeJSBridge/ImageBridge.swift +++ b/PrototopeJSBridge/ImageBridge.swift @@ -12,6 +12,8 @@ import JavaScriptCore @objc public protocol ImageJSExport: JSExport { init?(args: NSDictionary) + + func toPixels() -> PixelBitmapJSExport } @objc public class ImageBridge: NSObject, ImageJSExport, BridgeType { @@ -35,4 +37,126 @@ import JavaScriptCore self.image = image super.init() } + + public func toPixels() -> PixelBitmapJSExport { + let pixelBitmap = image.toPixels() + return PixelBitmapBridge(pixelBitmap) + } } + +@objc public protocol PixelBitmapJSExport: JSExport { + var pixelWidth: Int { get } + var pixelHeight: Int { get } + + func toImage() -> ImageJSExport + + func pixelAt(args: NSDictionary) -> PixelJSExport + + func map(args: NSDictionary) +} + +@objc public class PixelBitmapBridge: NSObject, PixelBitmapJSExport, BridgeType { + var pixelBitmap: PixelBitmap! + + public class func addToContext(context: JSContext) { + context.setObject(self, forKeyedSubscript: "PixelBitmap") + } + + init(_ pixelBitmap: PixelBitmap) { + self.pixelBitmap = pixelBitmap + super.init() + } + + public var pixelWidth: Int { + return pixelBitmap.pixelWidth + } + + public var pixelHeight: Int { + return pixelBitmap.pixelHeight + } + + public func toImage() -> ImageJSExport { + let image = pixelBitmap.toImage() + return ImageBridge(image) + } + + public func pixelAt(args: NSDictionary) -> PixelJSExport { + let row = (args["row"] as! Int?) ?? 0 + let column = (args["column"] as! Int?) ?? 0 + let pixel = self.pixelBitmap.pixelAt(row: row, column: column) + return PixelBridge(pixel) + } + + public func map(args: NSDictionary) { + let transformValue = (args["transform"] as! JSValue) + // TODO: Implement this + } +} + +@objc public protocol PixelJSExport: JSExport { + var red: Float { get set } + var green: Float { get set } + var blue: Float { get set } + var alpha: Float { get set } + + var color: ColorJSExport { get set } +} + +@objc public class PixelBridge: NSObject, PixelJSExport, BridgeType { + var pixel: Pixel! + + public class func addToContext(context: JSContext) { + context.setObject(self, forKeyedSubscript: "Pixel") + } + + init(_ pixel: Pixel) { + self.pixel = pixel + super.init() + } + + public var red: Float { + get { + return pixel.red + } + set { + pixel.red = newValue + } + } + + public var green: Float { + get { + return pixel.green + } + set { + pixel.green = newValue + } + } + + public var blue: Float { + get { + return pixel.blue + } + set { + pixel.blue = newValue + } + } + + public var alpha: Float { + get { + return pixel.alpha + } + set { + pixel.alpha = newValue + } + } + + public var color: ColorJSExport { + get { + return ColorBridge(pixel.color) + } + set { + pixel.color = (newValue as! ColorBridge).color + } + } +} + From d641ae5f2e1e98d290055f34cb81075b4735d001 Mon Sep 17 00:00:00 2001 From: Saniul Ahmed Date: Tue, 24 Mar 2015 19:28:33 -0700 Subject: [PATCH 5/9] some changes + JS bridge --- Prototope/Image.swift | 53 ++++++++++++++---- PrototopeJSBridge/ImageBridge.swift | 86 +++++++++++++++++++++++++---- 2 files changed, 116 insertions(+), 23 deletions(-) diff --git a/Prototope/Image.swift b/Prototope/Image.swift index b595204..417f734 100644 --- a/Prototope/Image.swift +++ b/Prototope/Image.swift @@ -78,8 +78,26 @@ public struct PixelBitmap : MutableCollectionType { } public subscript (position: Int) -> Pixel { - get { return data[position] } - set { data[position] = newValue } + get { + return data[position] + } + set { + data[position] = newValue + } + } + + /** Returns the pixel data at a given position. */ + public func pixelAt(#position: Int) -> Pixel? { + if position < height * width { + return self[position] + } else { + return nil + } + } + + /** Sets the pixel data at a given position. */ + public mutating func setPixelAt(#position: Int, value: Pixel) { + self[position] = value } public subscript (#row: Int, #column: Int) -> Pixel { @@ -94,8 +112,17 @@ public struct PixelBitmap : MutableCollectionType { } /** Returns the pixel data at a given row and column. */ - public func pixelAt(#row: Int, column: Int) -> Pixel { - return self[row: row, column: column] + public func pixelAt(#row: Int, column: Int) -> Pixel? { + if row < pixelHeight && column < pixelWidth { + return self[row: row, column: column] + } else { + return nil + } + } + + /** Sets the pixel data at a given row and column. */ + public mutating func setPixelAt(#row: Int, column: Int, value: Pixel) { + self[row: row, column: column] = value } public func generate() -> IndexingGenerator { @@ -176,23 +203,25 @@ public struct Pixel { private var alphaRaw: UInt8 public var red: Float { - get { return Float(redRaw/255) } - set { self.redRaw = UInt8(newValue*255) } + get { return Float(redRaw)/255 } + set { self.redRaw = UInt8(min(1, max(0, newValue))*255) } } public var green: Float { - get { return Float(greenRaw/255) } - set { self.greenRaw = UInt8(newValue*255) } + get { return Float(greenRaw)/255 } + set { + self.greenRaw = UInt8(min(1, max(0, newValue))*255) + } } public var blue: Float { - get { return Float(blueRaw/255) } - set { self.blueRaw = UInt8(newValue*255) } + get { return Float(blueRaw)/255 } + set { self.blueRaw = UInt8(min(1, max(0, newValue))*255) } } public var alpha: Float { - get { return Float(alphaRaw/255) } - set { self.alphaRaw = UInt8(newValue*255) } + get { return Float(alphaRaw)/255 } + set { self.alphaRaw = UInt8(min(1, max(0, newValue))*255) } } public var color: Color { diff --git a/PrototopeJSBridge/ImageBridge.swift b/PrototopeJSBridge/ImageBridge.swift index 996576e..d43cc8e 100644 --- a/PrototopeJSBridge/ImageBridge.swift +++ b/PrototopeJSBridge/ImageBridge.swift @@ -50,9 +50,10 @@ import JavaScriptCore func toImage() -> ImageJSExport - func pixelAt(args: NSDictionary) -> PixelJSExport + func pixelAt(args: NSDictionary) -> PixelJSExport? + func setPixelAt(args: NSDictionary) - func map(args: NSDictionary) + func map(args: JSValue) -> PixelBitmapJSExport } @objc public class PixelBitmapBridge: NSObject, PixelBitmapJSExport, BridgeType { @@ -80,16 +81,79 @@ import JavaScriptCore return ImageBridge(image) } - public func pixelAt(args: NSDictionary) -> PixelJSExport { - let row = (args["row"] as! Int?) ?? 0 - let column = (args["column"] as! Int?) ?? 0 - let pixel = self.pixelBitmap.pixelAt(row: row, column: column) - return PixelBridge(pixel) + public func pixelAt(args: NSDictionary) -> PixelJSExport? { + var pixel: Pixel? + + if let position = (args["position"] as! Int?) { + pixel = self.pixelBitmap.pixelAt(position: position) + } else if let row = (args["row"] as! Int?), let column = (args["column"] as! Int?) { + pixel = self.pixelBitmap.pixelAt(row: row, column: column) + } else { + Environment.currentEnvironment?.exceptionHandler("Invalid parameters passed into Pixel.pixelAt") + } + + if let pixel = pixel { + return PixelBridge(pixel) + } else { + return nil + } } - public func map(args: NSDictionary) { - let transformValue = (args["transform"] as! JSValue) - // TODO: Implement this + public func setPixelAt(args: NSDictionary) { + if let position = (args["position"] as! Int?), + let value = (args["value"] as! PixelBridge?) { + self.pixelBitmap.setPixelAt(position: position, value: value.pixel) + } else if let row = (args["row"] as! Int?), + let column = (args["column"] as! Int?), + let value = (args["value"] as! PixelBridge?) { + self.pixelBitmap.setPixelAt(row: row, column: column, value: value.pixel) + } else { + Environment.currentEnvironment?.exceptionHandler("Invalid parameters passed into Pixel.setPixelAt") + } + + } + + public func map(args: JSValue) -> PixelBitmapJSExport { + let transformValue = args.objectForKeyedSubscript("transform") + let numberOfArguments = transformValue.objectForKeyedSubscript("length").toInt32() + + let result: PixelBitmap + + func safeTryCallingTransformFunction(transformValue: JSValue, withArgs args: [AnyObject], fallback fallbackBridgedPixel: PixelBridge) -> PixelBridge! { + if let bridgedOutput = (transformValue.callWithArguments(args).toObject() as? PixelBridge) { + return bridgedOutput + } else { + Environment.currentEnvironment?.exceptionHandler("PixelBridge's map transform function didn't return a transformed pixel") + // Crashing here for now + return nil + } + } + + switch numberOfArguments { + case 1: + result = pixelBitmap.map { (input: Pixel) -> Pixel in + let bridgedInput = PixelBridge(input) + let bridgedOutput = safeTryCallingTransformFunction(transformValue, withArgs: [bridgedInput], fallback: bridgedInput) + return bridgedOutput.pixel + } + case 2: + result = pixelBitmap.map { (position:Int, input: Pixel) -> Pixel in + let bridgedInput = PixelBridge(input) + let bridgedOutput = safeTryCallingTransformFunction(transformValue, withArgs: [position, bridgedInput], fallback: bridgedInput) + return bridgedOutput.pixel + } + case 3: + result = pixelBitmap.map { (row: Int, column: Int, input: Pixel) -> Pixel in + let bridgedInput = PixelBridge(input) + let bridgedOutput = safeTryCallingTransformFunction(transformValue, withArgs: [row, column, bridgedInput], fallback: bridgedInput) + return bridgedOutput.pixel + } + default: + Environment.currentEnvironment?.exceptionHandler("PixelBitmap's map called with an invalid transform parameter") + result = self.pixelBitmap + } + + return PixelBitmapBridge(result) } } @@ -155,7 +219,7 @@ import JavaScriptCore return ColorBridge(pixel.color) } set { - pixel.color = (newValue as! ColorBridge).color + pixel.color = (newValue as JSExport as! ColorBridge).color } } } From 7a9e4032ae2ba5b85c09e11aee099dcff72d5f00 Mon Sep 17 00:00:00 2001 From: Saniul Ahmed Date: Tue, 24 Mar 2015 19:56:05 -0700 Subject: [PATCH 6/9] Style --- Prototope/Image.swift | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Prototope/Image.swift b/Prototope/Image.swift index 417f734..17e719b 100644 --- a/Prototope/Image.swift +++ b/Prototope/Image.swift @@ -209,9 +209,7 @@ public struct Pixel { public var green: Float { get { return Float(greenRaw)/255 } - set { - self.greenRaw = UInt8(min(1, max(0, newValue))*255) - } + set { self.greenRaw = UInt8(min(1, max(0, newValue))*255) } } public var blue: Float { From 964c4592bb4934e4cc99215568f2b3c83c8b0c33 Mon Sep 17 00:00:00 2001 From: Saniul Ahmed Date: Tue, 24 Mar 2015 19:56:14 -0700 Subject: [PATCH 7/9] Pixel Data example scene --- Examples/Pixel Data/main.js | 24 ++++++++++++++++++++++++ Examples/Pixel Data/pusheen.png | Bin 0 -> 37029 bytes 2 files changed, 24 insertions(+) create mode 100644 Examples/Pixel Data/main.js create mode 100644 Examples/Pixel Data/pusheen.png diff --git a/Examples/Pixel Data/main.js b/Examples/Pixel Data/main.js new file mode 100644 index 0000000..208453f --- /dev/null +++ b/Examples/Pixel Data/main.js @@ -0,0 +1,24 @@ +var layer = new Layer() +var pusheen = new Image({name:"pusheen"}) + +var pixels = pusheen.toPixels() +var start = new Date() + +var t = function(pixel) { +// var color = pixel.color +// pixel.red = 1.0 - pixel.red +// pixel.green = 1.0 - pixel.green +// pixel.blue = 1.0 - pixel.blue + return pixel +} +pixels = pixels.map({ transform: t}) + + + +var end = new Date() +console.log("Done! Took:"+ (end-start)); +pusheen = pixels.toImage() + +layer.image = pusheen + +layer.position = new Point({ x: Layer.root.width * 0.5, y: Layer.root.height * 0.5 }) \ No newline at end of file diff --git a/Examples/Pixel Data/pusheen.png b/Examples/Pixel Data/pusheen.png new file mode 100644 index 0000000000000000000000000000000000000000..1350dbf1598bd6712e7aad5c3ecc5d377909d04f GIT binary patch literal 37029 zcmeEt_dnHt__yuY=OB*AIJRUbvN^~;%CV9?vdXCJk$G$x$IMJPk`*FSW_D#Odxf%2 zvbkSA_xFB0?*HKa<$jd%c;h(l>vdhv>-l_M*EZId1#AmAj>LaG?~68`>7{>joIXy;;k`}W$;wY*{7JDgC*Q02*ho@#aJ=~zk$ zMyj&OgQr|uE+>!Yrkl+*uWwXT{<~qDejEN=Ia9(RNw;=Fa?5Xg|76bKJ2|@;=Pf=S zo{2T?>8K@{#HA;9-qG}Uvh}PdKV(e=7e=_gNwa|qrD{d);L9@l(a0Yj;6g5KOvA$E zg=_Tg9pL*tZ}a^B7x@1%_>a;Y^^PX{f}oqDZ-^ zBKps>>GYRA`5GpVl@ga)WHAj3Ekf$Bh??&_j=ml@>kCzo5nd*u4_bqKgF}g95%EwB zVc#u^N2kLayC2Nj9M;>NoX1@!by;iz)oP4vPG0(~C9juyoMvs??>)Nb$>xOTn~uU~ zo968P+TL1zF864$sfXQh<@=iRD49yc-IC~C1|RE2=M(RF!=Ac>&zkcmvJLu_5bmO~ zUeSx~#-=T+w(g=PJj4sS8YC~OjD#1uXr-nJX!lBFz8lfcJ$7RD9FH{lI`$)JNhXK? zn{=+yMxor}g*Bx^Cc0Ta$qrXC7~wGa5=FrzbuA;~uZ=%8a~+8*qdqMe+y?T*OMY$% zSdrZR#`BG(f#wCJ-9Q1X4t%K)7rN(#2&>0xRO+*JsPue{JP6tA|NZn6sPwucn0tqx zAR20eES4T<3&LM4fnP?ZRuL_TRq(Q5wu_o22QsZ$?Rq}@ zaYE4W1jpHKZlD+_df~f&MgHFh@?Tq26k%+*K36V;y;&DR>7(?s57K`Bpumw$HQ z_R-_$gXzChRsWOg1qA}4PVO4nJn=hc1WSHzk}zN0q#o8wh@5^YeSa7dLI?{7cajJ0 zL`OLLK-lVd{)6A&k0-ybnY6>haeH@(1aN&2SU99iNR%JZX&ASGYG>JQ!qAu zK}HzYsnFkld)jUyyH-TEC^Ph$e>y6>2-3dSMRizleQr?AXYjR;1x0v4JXbr0C$TpL z4pVWy*M$!Xvw%Pf;&~A~p1STbi!BEaKCk4Tkq0UfFhXZ@8I6=VW4O|K>Aa(Hs+`^-usYu%{dqoj>#N+`2n=DG;bj7XJ81fl@!()G`rN&d z{5e{OS^Q#(A8y{7VgaWoku|!!OPSb*(zYV!zb?;pcyIM}sMsR^+_Bupv#umVfyl!q zwlWEA=pqlIUxHS~iwtC=^n=zj9?uAZU=ht?%ca7pMvsFVZ9FbgUzp=saxr33D2E=w zfSncBklt4>OXoq@Ku@e2Z@kzjKAo$&;AlRf^vxsJ&LN1!B)_Kcyly+u`fuq=>agwi z}NA>DaLVuH5g8TJ^@(a)Wag7)0MeX$V^ ziL{N`XR$yp-Ehhs{mg@TnPJ3Sze7=uZsi0Y|>)iV;>~QS7J&$3~=T9`}(Tc~2VTIt_jMpCs z=N+)Mo$fZ8*8laR?2-`WQpNI&;%O|CV86S@p2*L1h>qxE*bz`gbn)186TC;QI| zUHo2HyK7gBEhgLPFW+#=OWF-Aa=Zq!xkgGtD!&B6W7hOUyzRKRt$IF-Um#5z1F?&N zguCK?zw5~hT#%cnSxRg5l|0U9Ys%(0U$Pn*yWa`#FvV%tS*{h>87wa2ML6{sQe-rc zzPw>5+!OKb^`65jj^I@Pr1SHy) zQ1|r<5LaxnDK3#H;)gF(NX@hOnM%)%5JvPrHH0u zvT2|yzPywW&a@BBzB@x%r>muN9lm69!-RcQBvIp|nf{+t0@tUlzPAlF$Ry&s>D}$F zKihV1TlL;nq8qgJIwC5FA1j-f49X|=`|&|hIa3sRla;f*;;wRm>Dy}i&uqcx9goGT zy-2%MDbz0m-aVg9w${4srFeK$wcyA8n5-k*_5JFir0?OD-1;>Wl}ZWiP=VMl8QQr) z-=Zfy8(YgvqMU69xUJ5e;@5~N zFVQvdGB2;z3*`sTW&I5KQTg|;2}pGc)w-#IkoJb$fL-)oy-BQNYZPY3yi5q8T4S-$ z)WfmJtaQxeS*&j=0K!BMTQo*ir0*1p2ED8kZ3(*-zI}1Fb~rm1l=pTP(Jfif@@YC= z#zp+>uhJTZVYmQYRp>&vP;Y3&#msbPiAm|hKi7ePV{z>jN*pLeu0+J5%u2|`A0L_h zVUbIk06NV5^3Apjnvc&0$px`^-@M_t2;qd9ajy-zx#8gGYdS%`kch+>IAsYv-A_-+ zTMrJ1<98um0D%k1pF`?K*hf6r z))FqO<>3?_e>54O@Axg(sq6`eU$@Js>+gVH;?bor)BqS<{S#75j z2R{Rek5mbljtap2+IT!o7d-jhlkxV9lz~sr5nmv{3E{px5x)!BpSSRi{yk_E+LBXs z)WuN9$8)HQ!|}WRy@%t`DiISpNCC!vd9f3|OUkoIPWW-V7#DxD8rQw+l;22ND8PzFqsSiK%z34yT4z`?RhGglIBsrq1?>=lvF@Bd zVz;S5mXGQ|qn6`%oBfXko<6;|wN+w@1EmRDjguK-RezUI`L*qQ=4Rlb3I?M? zjl+t{v`h>*)+~=R-0J@PlVW{}1aI+5yBLoa!=uB-?VaWD14CRItef8G2B^KQqJDJG?_=~D>FIIl zMPG-{aFLM+F_A#=cd5cL%=?Z0x#jo+i=l4pYw=2r@aY}SvClWD>E7D-THg$>$Plk~ z;q7oWmXv=VyYTtuXfzp!fmF`ZMdNTD{X3~lX4!?ty^KbLq>O3IG1b_weAd($Sl}Fg zMnA(co>AK&HvLiaN%yyrMw*}{zOPE2a@~(V5HIoR+y0DjU*DRGvMq1FZm8^$JA?tC zly&jfF7Pz$o>{9KMVB^3nzTa%?z`J~R`9f(%yC+s^C->@g3~TEavbb)IQ<=9<+QSR z1b&Al4`(u;c)d%fgGoq?&jY`kIjK$@0igyKzPHsXupn}Vmub*y?2r^hw-A32NMI-b zorfqwDIHgW5|*s?C%rhDsRHfIma;?wv{hsojmdicdxem*GuFwcRcD9yriASPZ6>b3 z*14?P8Vd*IrS~({fp$n{EMn(tO*2+_bF@P3-PN0*oU?iCS;}g$;+$mhq2gqAB;sV^ zWZXm$1Q7xgDkKV3X3;T3qtq*HE4$3&>RHBB9aU~XIN3>>LT#InKHI*>-3IpU|Kuc` z*Jh@IGX&k*Mi?8i-Z9Lyp2-Yt@`93Ct;qkFviT9O*W8m1jbH|RY&7frnSQQMuO`Sg zhQ@5p_NcqU=**M9#itW=AGJoyQm->AU)FQ6F~5lbJ}cy0Fj`M3a!`d{Gw^&*X{q7j zT=e+3zo11K2^upvuA$atZr$t_ssw+U2pXK}+nq^bpWQj7bLp`8HLrrc+hLemM|485 zfo#I?`G~<6=KdDr{RI`{D$~B&wY55c#!RBVi4JkAbzZ%yWX6>R((-pk-=y*tP--ZX z#FKYR`sPJn1_c(5sTMSIHOzV8z~Y>pQeu4rqiniU{^X!;@Kx{c>P`0Fx1WZ*{@tpS z>hmM@oFdgDcS0K!&zF%T#ZUaAGU?OvSNBSY1VpRNG_rYf_Y!qv<}vG1F6cBGolcZM zdeio}l{bE02>~HG<@^tWsA-I*{5EDS@ChMyJr53e`Xz2(vS5TN|0UeDV_1VvAAH9EGdpDi_MzW+36Z_25D+*k?0=v7Z+i0V0ci zJsOqMa&a$Y@W>=EY`>iLtxZ=-q;Q^6bfkBl{kNZ`X3ZWV6<85R4{Op&jZ`T@LU|7P zxp_bKyfyA_PQ)AD(7jboB3zld8}g0icPr4t)bB)UGxAzWKe#a@s?}FsEy=Kz^Wz?Bp6U*yK zm^ce1-+CWuTOI+}OwqfmdJS_=>G*V~V}x}GY=jrmAupkX=y4q+$~Eg`lpEk11)dAn zXPRK1F!TD)uo<&O%-RW$772tkSEwoQx%tz~&8jn@xHScx8Y4$xSfOFXy{+{%s=3z0 z@)M5kMN)6y;H?p1-+vTLig=Q^HKqQ(k~sKPQ}xmK8YX$VK&zc_f~6z49h5|U%8R0) zte(eNm5tw?;*jufV*Z!60b4za(d-@PnruCfMrb*$1QulMF8PY@@j=^CDsxLZz^5Zy zLL-*+5*A$F`t}jQy#u9qjn=;IBHS{S`qh0QrIT3`v%{YwY5fjq;z;E}0MebD?XQcI zMah=i0Wk!f(xo0kr9RZO+@U+`V356k6~dYX`m@bYYSIp#BQRVl5#r^i?Dt%`?wfV# z%)#rBcAmVDEiE$R8>M3pZ6w9m51)TNAeNs~>*R#=K^%$=rx{Uc(&%wRlrq6e5)`UT zViOVil`nJ$QDTQEh^=SwWQ1vAVG3tKzZ(wD*?j*}uss=|0G|{3<|Qp%0^eazh0>>| zb0^ZRXAYLy4@YsoYv#B>UtJN<#=dqlqU_R+1gW&THg`b=YOP(B(8#Y2RWHR#lNhw= z$L75u`|j#fqx1yr&u}$U0kKV!i{9UJ76yf;sz_f7nEC!yts;`=^W=ni+uXL31&l^F zaIt7A95@3*Bf=6rWo0N7YNt%ii4iY@lMup?BuD~6Ml`7~E>@r*)`iU2a1d4hrX-n% zV%$6)9`7WU7mZPgSAmWj-Y#^BS&Asj;v_m_;H#c^uIY8S`*`J3v4Q>KKeR^*RK z$^pMcF2#R&_gF0JVr|PYI0+#g2C*nf(f473Bebu~s)*XV`4EgcSJ<0f`P$AyCKg%3 zx7SQHr&=fy{L`R#qFGEY&TeAy&`^NM5N<|? zYout@v~YGwS$&z_SK%%`aWm{!6G51DIW66Hw^7V}niLtn{=1`L9EZzcY?x3`EU-6& zm|{#>I*f$<$9An8`LzIg%{<@RBeaUK7MVR$&EP<<9F~fn^In_}h7>fmk-(MBbRzug z`$mcBR^ojwNXJ2;??$0^0y&f*?{}LyJTCs+x+k0glYWc?OKk%4fbmoQ}(hsv&L>Kq4qGfGNZlHuVORQP2bhu9Lz5^ZMi zH`0r_vu_EmikCkgrUogtijCS z(S_d)Blb~IWX_|mQn>I-0^nhNrK7^6#(za3YW$5x9f?~M#61j7hcwkBSh*5I5UVF? zKFFs<^dM*kxF|2!y!*`9j1%6KujWT#p6;$)5>VytSX(aY?~dQs`jPRhwbzTa<&Awn}!t$aJESBx@f#I z>-#%0H)P14r#|XZn7ymPU25m7K4lR-o@yI;hIt!4&01~(&q*Rd}}x1|$NITEVK zPjHfs+kMwzglOJG?(F?EPAH$jR1UYkFKbVpKW)HoABbonB|_#t?PXMyPB)kjC)$pL zAGE3?sYRgxL2G+HMguG6JPhWXjc8{Tu@Vs9&gyx8bj%_C-b*k~90Q?**ik_2oi$SU z-f$Ph?o!Jt5!fg&LK*Lj#0&TPHsx|y%d4PO9x`Y^!ipftW+kYN)lYT?^FVgmB2#!U zJ5l%0Ku1+-yvz^Ut{4b878IIsfGYb@)MJHpdcRY<-hcp(ty+U@`jE8cxwOuz^nBKn zVSVdIl(r9D~S8_B8VlgV6 zp^%WYP`!KE2~5tEOMGq}pL<>o>^i+1ey%zE>A+j&K064TwX3nnQt|ff7flB88YTv$ zXa^%~i78q?WWq{pKeXfFctt2dAFF`?T4*5XpI|V)KcC^&=3yQHGSJ6hoMzu_E_1T4z7OiB)q5!oKn) zn}I}e!KWShmAp@u|N*qed2AYCUwi96*2u27!%E3@t!bz9co zQc5D4plEgEWM&FaiII@UUi|vs{eMt?3g8QL`wnCWF%J*D)KsK~kz7%}Ov`NE$@f~* za6~y>@=dZDLk1L*8cqyX_Txo_nd?-Hy&9`d3+Q9>Sl-PIoF!$GUNUMABy{Qr;*05b zRLF^l)zP5?qqd*?u-zNS;_jhgA#Ahdq>H#cqWgffgj4B@$%#Ru?X6qAy$bpf2|jJ$k6d? zHkB)@_npKapW`EJzVydHJX28RM!uUdl4XqecSR7G%{>Mf$j$!z{GM>1neIJxJa7!; zl?qB5^Uq^a;f;U({dJdou{G<-sC@-+Ib^k2R6UCsws5=%7fPgBN&*REJNS7)kR*RB zV*^JZ+}tXSJ84rJRu0#D7LUks%(^8I81aE-{G+@a$s{pqGf_}Zh%g*)+|^h6Dk;-0 z-;X(^6P17R%Tk9pAzm_%gMs8PEHX}Y6_HIn zahydOgn%qes|WNI2x<|>wvN(~GLxy%c8kQ7>n2`H17=9iuBt-u>SLc{v}&Be7+) zASZzLcx7yg22IL_(Dw!Ot3jb`UW_=@?DuF0MVy>2Y(AltD!Tw$qjYUdx5#sjmcav9NIQ z_CG!|{UgJJTpUqv46)!k{j)FGFcs7Qg{~FuaAYZSxMm}KSxhMT zVQ#&9kD8w{#bpR{=MUG3^J-~x<*zL>B-&YoD0#A7%HeoO-kRsh%)!p6r1zI-t}5*- zNH{M7vx|aB#b~jHcXnLA<3c?7o{%Uk8ne?!0=u(_nfA~%dCWXw5JJB8!2gQepO3eO za#Am2QDx~(I&s#o^lik-Q5an^<|1+SuF-or#tCo=LWg@)-m=(A(_TGECs>#G$L%`B zO5->MpSc9PMEP9d{_61*I<-8jmWca~gM9rG{}YGp@&J!zo9N`%AP$)M6Gfk&Pw5O4 z&)ALvznFq6;5zs$M)Gx?UI>S0%KT|zpyG0`Q{K`)x$W+Qz<6;tYD$1zul0V!71PN@ z@MxjX$^E3DhsMD>;V9lXR-TUQG0`T;H>17@tHme52}uC6br6OD z<3nnLCH2{KjJL=+r&G(C#9k!*|33W!hd8F4Z+HXi-c#AMX6=F&&P49MnV^=l?@2)Q z(;HSi=X7&vviKB>u4r1vnt{6i@AT`sECG^~bx63fa|2ajL(^%ue_-r7um)?8@u0~C+6d7)=+ z;d=fdLDyZ(Zs32;Rmk#vnUz1xFZjgA&82Umc_Bm_31n@pN77_uYC4lOx z*d@+4ul)S@aZ~!|&s<(jY#|b8sIq(~{*&XYeXgR<4m@M&_J+jJ_%&`@f|Zp{;Hrzs zPuiX~F!-*ma4Z4#joBub$x#9=Sj;(lr>TEv>7UhE*z7ql9m|CKmFF)R?T76Eu|fZY z!AJ~HwE&Dc0T{z00|{X^Z)9S7nbq{OcbWL&H~IDHH87ycyboI2mR|Ja7nllgBI2)f z!Aex0rzVD^hU&=upIrc|rQ?%3DG`QwYcPHaNvA=_&-gfjWjx#tex*x1k6%~m_~Ek| zKg~s%Zoj7>?|=GH0U%T8)Iw%bO+mj)fpnr(v{t%oMBjP3a4TaN$j5E(gP;TauO)+I zIy`VmiaE)|*~?PuU68KWWNOh;G^<9urUkST6*`Ig<7F063sH^B0U+LF(Z3YM8~j-f2GyFs*3IU!U4VP#Oi2{Y{)F=c6rhxzAyA89oYoJ^`UZY%+fT(_S@x2Zw?m;*q`Ke> zJK?nU)3f)qMi|i!Z#W^Q3SwU#9Q^G0?$FP|1=tDbx5NiC8O{NJRIm1(?&br%whAH_G9z=J_QUh<*A>`VWH1IbFl~i z6~_uc(9iAUR5fWD$P3W5NfP|Pg|i=*z~bmvz230H7Z)@+?0Rb*c(lS`Y9=M%d$xx1 zK5T{IQXE+j?E>J2zN~nSveK*$-M>VUJ0oe@O#_LkKGq*77{0h^U>z`!p^AHGc{}iJ z!sfMU0UW-Vm41`?BgN<2+=v!q=WKd@ikWmu@;_~&9}K2wTqQ39a%g9C#g6(phsQ+g z0sf0!(mjD?7))9UT{7iXyZx?Jc5AZO5RDbi>pOT`5B-HHiFVE*hZ zyU>aR@W~uNEUxbcaoABiXWLc+gh2u*uO0`qrrE?I()o4*tiD3zyvBHE=DNmR|%o?XK$#@jM#qjr8kw8F&59;5grEvqoY|+=k*| zfk2susyg0q&IW@KFpG&1C!0Hf!BD()fL?v0u%qS%E_)XtDG78M356l4yCpH=5LK*$ zy>onhEJjTAO4z3!ct`eg!d210Jarwcgp70Vhe4E)vj@)H~<2W(uDO8a_5$cQN(tEqf;1BLGMHm=R3nz?wyiw#P z_z!Zy{o!>^-(5xp()!?pXiHV(oyCX^k2tl`;rTw%z=W4Rb2;D#F!OHafGVoIJyXhG zAh$X~q~{?JQ9x&VvUx7bZZCkzU6EPUu45q%1IY;kZOyF`Ndo%}rxPa|_O&K~qImo7 ze&^v!G=G;(Q!7>Sb4(AcbjT!r`OXMaLBn9kh))MFyN6Oa)O02+SFgS{uCl9iFe*e9 ze5g=0@L^u@q6mkU3TD+qLYf2N5>Dr;Tkkg?We+kP1?DNC>&A+Vggpje(RFzJRB(Sa zj1I`m>(+ruO$G{mz%&{zYBETr!jq_kUe2{a3aSy)Jj-fk<~Uw7k^S|H!9$u3+#1(L zy^W&ZXJ>i;Ik#s2zOubAnb@+wf4yf*OvE28@_^}K4~q^G7)5JG@Zn3n^9q=JH=S=@ zmD)sabf}Hu!bhXX%V}u7m(dzlnShCcy|Z>xMMe;ZyGs78GA7>G@<1VG83w=a^bXYek8 z=FJ!`JZyVyCp`2Pn3T+Qmx?N+^0=f>se#d6v84lO|9j*3IRmW2q;SWSc6k^CK{kxv zCE_rk&ZOOx4^U(j-hn2Q?C8V(+BFT_urbI!Nde7m-#j zi22g+?fmdW#TcGPg}763M7udofHj|>NzC5kAtpNH0uCesPb?yEuIC_#gm8hE1jSgP zjdgtGtgXmV+pjNIGp88vZDdA|t*1<=N?pXtx^sK`!%Ml~Ace8#XvSDzPG>N{a2-JS zBQk=2>*V^ZqvKJGDju`G;0QVC=AK>$PPQHPhIuFiSf_n63d?co?bVn2L)9q0fwlFG zKc%K`lSQ!t=^rSl)PdIqjDWyGj5zzt=6~Bt9S3cD>dpSzkamH3tW=Eb(KY6pIhZ6c zFG2qE0^&7IGK}9jWy{+x9RFT6!D3pUi|%Kt@#@9DF>lB${5&S*qQ+y0#J(=zM8Jow z<3Gd0;dD^sp&9}tNw>RP7G9(&_U&wNij1vydOvAdq`FSV-tXKd;Rg-58pSc7>Z`E? zedai(-3YIm&ISg4F^7oTsLbCq*0+oeiABp!~2d!jL7yA zo!8;VXpUtFo)<{fF0V-2lDMrr#b*DW5hWYBZoQw0=k5(9A&WdSM;T0CJ)BtiW<}!)QGUyu)V7k&h%iYBeU6leaa?7A;z3W+Nm4v zrJ|heKut|s6WB#LPE9RgQTc+0ViVG2p={EGeEe9Hp@GHVvB9mpHE>zFj&9FA^QFXlc zpj-pY;s`Ji9e-omw|F$Xd-~JKtj3;j?D@@CS`irFX?NqryQ@_&U?bqke$o<9rC_qC z1y+r25yQA3fkCi78^*H*Ys%{+NGV>EuegDg_$Ki1d(F{3d;lZ2NF_D{!mUyT(}+x~h^sp@xK) z;L1QxeKYvwdzx_lB=sB8C6#2n0QLjCZN}aWTl@Q{$v2NM!f@O?MQULgKimIYbEv?u zYSFF9=*p5GiOrW-b*e1xedB)C zc|&8W1B=Uz967*+K>s3vsUXD=@nFJkh&D>6(R04Xo6)~Ssz^8EN7Xp9!+}h=ePqjm zvTTQFG2Y`ylvxVXY1+eI-1&$iVua=<0JKy3#(glfIFga8^3x@`pIJGb#z2_>4QxK? z{x7v@Jo+%=uDg*>(+oXFi@wVx;zf=iOMH(sfko}sgrrsx>ES*Yu>JS=ZRzdzV4yd5 zuuIIr%^thwUvB;eJ?i+MAMZk_f`p0#yuC*!lU8h?m{X17(Xu76@h%0uFC_l5_fZp> zq63vUg&F-eUIa89HFHS3cf8qkHw$1_ip=ZVY?qV!`Pktz;wB6B|JDZb%cSMMtSNf1 z`<$;b9rvt>e;ke@L5y{;nXRu`Mb*dF3l3qg3p0Z0@{+~H zS=Bd}Q9(u>3=CySOqd39x#B8$$%v@I3}2i~DT5nA#ki*qHX?z4{ck@V-1czi zA0K4ACOz14(095#I{>gu@+Ty1N9*-(Oq(%xo8k%j;=tloVws8EtY6o~A~7f)TWVK9 zP8rsihpeohQt4)Tlma)r^89}hYFvU>H!)2469fY^cf2?T77qu57bpw}ByOYh2_1Ay zF=lDlpE+7rDblf(OkfDB6%Sojef05M^K`3$RWQAevjqXOiNFCC*nhW{2be~Ans1X! zQEyOhpBQef7gv<#zs*zZn`uoGnn~-s(jNGNGr#9@!2=91%u@sq{dbWhYx@ka*b)(} zaBhE9$ngWF;e4GMi*ABg0gO$L#(L#y4lbU~C(OAPLI#AZ#u{kD$(h?-$iwF%jme=|l;U%~skImZY~{P^SB%X1;X6T>M8FR|+yN(s za08b5AiHx}=gLsiVTao-KW6iU+nDN_7>F%(SbnsF28Q1Mew|Oq4=kwqIEcnOCKJaEb)BdbhBBQs{h&hwb%WAf13Eeti=jENTa(4 zN{ufO5ic(DR;O_CP8(r3i`4eYsg5L6;dW$Yo&r?`G(UngHd|cwOSHrVO zicfP2v6?Ba|>qNix6q6@o z3J(Q#Qsylcks*pdh5?FF{zXcZrJEO`AoGHJyZME#NVL0L0U2YwI2cB;sbd^o^sYEx zmC5|<`gSmRVY9aT>w=UN9cOC4%@0}^Mmzd=ad>LMrTkUomc%@Vh|j{eSf><)(j)`ZmZTihM;(HihY-?e^}mk^}!u#)tQ7FLyzg_o&%!=hjcK_J1mLO+ms8;YFd4R&Dh9;(PW7s{@(sr)DFt8hGh-pxM!QOqdfWI1{ zMG#fot(a$vRLC)ui))E{FM{c#TwL3OXN01P`;exrwW>|LUYi1D z^YqIqm2j?HPS5s-aF)j(bx{?zF<;fuu>PVNKY|>$4^&G&KZ$>MQf%BrM$9^bHwMpZ zxuCTS+~8_2h%HMVWS;aY4Vv`NXcg!CoEb6u8fRZ?wlOA@%HEVQk>?lh6>yyrQXud^ zVv1*~7{sr5cDeC>#n2vQJAuL;{X|5E*Q+$O&& zxAUr+Ce-LU=U+-v@G4ND5?V&y98qA7#TWwU)v^W4+=9IOT`#UPW=Yx!`BNH?Vdwa|1)Gbi}0)_d1e^`_DI+91U^V35%pMrOz zmZ+4vu$lPUvf}%Fwuxe6LC+uQ%#tDhs88LP(i{N5x>mNq$gg<|yHPE@EGg6Sc)+24#M2E2`TT+1i}C(;E#1CL`fPDo*wXv=!DK*m8{RB(Mf)EP_e!%3v14%Vt42WFfTM?*=_%D?my zHw`PFcx03>K7qr*D|F;-TX`TV;9qL)yD!K;HV$;Qde5j zVBaP3TaH_Au$3%JKm8)l>suIyg=Y4HnZTQ(@<-4kRTxS!0fQ3EfQ!2j(;gi+dIsh5 zB(7hJ-E_iqU}Pm(#uxu#TKfNTrr>X_gvn=nOR8boQ+ z_x*g^=Q5>_cozkT7>(A8LJ72QIS3M_9Z(=BAz{{2(utc(PPO)wq<~5M-E1S%BPRPM zWerUjBo>sCT6FTpm_~p#9eQpAsCV4r2}_ZT?IKOcVr51iuZaLrp9C-rP$FY8XKTz} zledQK1xX`Bn?W~qer%>|TE&e`zRu`;RSXkY^MVdV3iD3|6eF8Rwb7+1T)X_`T}{@K zai(dka8a>4#h)8-mp@egR6N})#!HhGgx<8`Zk37FBHWJND4j>^P7%b8o<6leV;#&_ z!-x=NTGiL6e$=jOp((*K!zMzzX> zwCL@AfGB6!N#`hlF|gHb)RoHXAu<^1wev8i_2LV51Xl zhD|VSKWnt?$0sSMifjYi0u4L0x`>gK6s?}1G6uGmzz%nfFzdu{vZ&P8N%_!K6*aSf z)|xkxj@-xy0crqItvcxSltE@^GpYRhRqvC#@TClHM-TaON-qj*E?-l6#?eW7=9$wmjcNFp$~%UN zUrjw1y=p$Q13y_Fqc$Fv+;u6n6l556?KDKDz@-)xC{3A6i4dGj7zkt=Rs+0E5@<7K z2Zjjy^Byb(K8Y)a^x$QGZI;#y`Lo`wFj9^H^k$h-eb)a-me1lsQ@O@HdIeoI2Cp5z z0~Y+Bcts)%8EXR>VXVUk<>VxX zd*&OPf4fibDiseOR}A;`eWuh-aT>j|UVD6m3e8P2W(Jm zhNpbepwDFbwSV#?{k3;e;DGDt5RmS5k@2;O#l%clk!8>ODwctHIYN7Lky36efK%!0 zEWtNR*h*^5iyVNQ=GM`VD+4bTucU@9g*i}RArIokJH>e@>}y3f-zX(Mpp_46rB%WP zpG{bU3PJNu$Dloi6EeKlhtG5VVQJ;*gjwD0SG%$PXn^&r0KdBIxUmH0v2dM8`{U@}Pl_U`s%L%4(4U;7jiSez)w9&onm zx$#be^G*Ecy+2v*iNzH9C46>kTFQGP=V-Sm_6bh9aBnJ6jlSoyH|d5fU9RmE1Pa00 z{nMjXpI;u8v!$Jw4S24gT1X;GUb3|H^EDGIRYnq-xBY8?)AMMxTV6bs&k1&B4l_?5 z-6%3`2w!vZ{q>ghXo8plR2{}txC8?20krTF*r_KbXY-cO5#;>)wC;6lZ;4Y#bScMs z=7~rPt~mD}PYhRxk;+#V1&WL*?$EDvY>3Q3FMty!`Y+Xo|835o6xHr8$gA0^MD)L2Ao(2c#Wu)kuveo&704Z z4j*;{+F$RIWl#(p0TA|gy`ti~-AeZ!ZGXZmjKX#4M?!W`do+3g`J)A>tKFmN7&x_T zf9fWlhev&#t&JF0$JbmVr;6i*IJ14tC;oV>rx8CL>R%a)*>OgaDCI201Uz1=33$Yt z)6azfaTL17+x@8N#!2uB5=?&Rjng-e)8wY#@At)JDY@QmQhh$GQ*YsTrq}3rQI&@= zWzu3xHCHg=O6^5epqrL9=ES}GHtq51pT6oL1)iqCB?1{Q0?rhut(+7-LL3bOQmt~I zNmVJF$m&uXpj5!6e3A~3J zdD?LLRW(NS-%mSt-D(GijKFHKsXI9ODun|^hR25tScir8>;5$p z%?nK+hrx*^`9bj~k6WG57BO?s%RbW|e zKJ)&kbPOIl;!3o;TM9pz82Y-UL$pb!HAY|DXkqzGsQfp zI-v@*cIMGZ%}8)GLytJ0)-M{$No>NBjIQ4wQp=!K3T^i|@lf7NYKfUN#YUPkVIdfx z)YT2+wZybkQ+OVxKqH@q`M(sSzEcDRJBeWqW}vp+%`gtT?9X`;%O^H|zuzu&Ydm=l zjt!S$-r7m;)kKsu7r>e{8$^|*%Ty!$AM2)-IbA-uReP34{@p~~=2KN$2))n6zIpcX z8nmAbt*EVX1q!xAQQ)aaengrMZ;S5pe5-#x4bu*fXNyCt+b;vpH1x77>ElO^s5+-Rq8H==t z5Pyv-jDeu&W_$A6c`{KAxCxMo1fjfqUTPQl*XVz(|7RCKe)n<9>EB|w-i|y+f5Uo_ z#+{U8f%u2NCI*Jc*?Qma{$LobfZLf`HY`Ck#etRzc-^)u(WDA9TGtChiKI_j<9C1i zjP_z6TJ&`mjBY==cNHvs_qf>GPMCyN=-;l{%F!Z-DEY)AY_9$AoU}cene@!>sCJ8% zp(H45EEB@IFF8q&sJ!$qi5HLM1ose%;hY5jK`=Y)gp8xWyt|YFNR^l7CCH{x{c^yu zbeKlQYcmtayNtB2k<<&t#HgqcrV12yC)#aa+|CPBWAJ25Ha|1EGB*s3AZ&c#Ctmkj zmoG>3YwCYuzqqn-rKN)J>tvNu(6@T#{EjQkxq5Hsm;6LWA(?Mp|ACt^rNnqo|7po%PlYCd>?Xfents?#Q_-X<@3aFH%}(^^V4&cjX`xL z56V&}J@D=nE@-e+KLW8f@?>M4z-P(s(^&d$5UvhoYS)`<=!Nkz<>fmh6n20mOZ_)( zy}l%nZ~~akKGdzJz4jW33feC}jPd~^X_^RN;a{~5Z+)(H>!e2tH)rx7sEKeT^w->V z9-6%i?ZU5pZMG*Apf+=T(48>%+_ufJy+D7Xu2|qmmHVDm!1E7p&lI%imt;g{NVue7 zD|cT?4mD+}C0aUiE=-cmx$pVuxn~b)|ZytIzhaB~L z0{Lsj*UkCNt7V+@pT>l!9P)H4zV!U*B(7eR23;s8-?IH4&Ja@!QA!0A z9cw7&awjEIAXb2p)etST@=SiBr@_6m_LrcR?Z*CXz`jou=xi%q{zK^7j`D<7t1@=d zpStAKIEVZnd_gB$!TpIFu*)(=b>nm`E!T88P9YzhpIrBf$Ztk6T0O~{r&oR zIK}2oMV-q@zKlrXCKq;xoz#xLNUSpx4mHjeO5C)Om45v3FZHmZzY^72iyB)bC(xC( zy~|W@nuZ=q&)N8-$SfZ9Y&*>ktdF{0i5C|xdEzObU&yT?5nQTZ_PBTunENt`P?&KP z4iWlCM&@mjgV__q+vT81r^u{c+@J&&gTvGlEnOmDoQi7mB4N|M;6<;Ql*~nlz$zCj&y2v7L*vVTzwmXU3%Vhq)1frYRBj*-?^^js&#jTL5Y{$3d{r$9JcpZ~ z(R1!ewU*~E)ll-Dg~MoFFh=dCs$^un^yg=inLJ?5g*8+r>Gv$Zk*o4&rt#A=sA5@h z{Qwt#ZeL@Fr{HxpV8cL%}-uJ;G@NS=kdrYX{Q3$habO??S$3-{S$D z9W_wrp(`?`wlS|suOLcD`KXf?dRLMK zmoCw05zK{m)aT9_4&QqmCN92`2#vw(z89+*M6r?1EP*S>)92%rUQfg3A>5`TdkrDxEl#*`v z_V~Q-cle$CXTL`t=so-1d#$zCx~^-jyD=)@^PRRcuI?U5C{?a2sOGD9NF-PFlwxM1 zpM4s<&H?sF0lQ@B+d8K^yESL#rTNrdTMvIT?)CZ_Cropv!SY!p_k^1@tPbcdtE;d!q530 zkeR&V7lr?@-D?vc(%|rlfk^T#xM@+KEV=TTl;#ded89d|zGY>;=%RdD#Lb%OcA zJktl-H1Vp1Iy>4oKp$8xLfX7&P8aA3UdY}aP_Roq%l9Z5@*%3SlU;vOfxw_5`{j+9+7==f z|90y6)uzYEm4FYwM~@zWmZiqIk-|U!fuAZg57StglT{17`|m_b9-mzP3>f(2nGIa? z;QQPYtNktaWpFD4;q@sDq)lSQ^j}y!X65z+vC#)iHsoer281siBj0l6vs-yZERn;q z72UQoQAjNLy4)v)d0tFw#$pMhOSS)<*Yj@*9xlUN;RUoy_0qF?$+;%TwaeJ&GqXp< zCLi)SOZB+NH4wYLIbb(Gq?-0!Oh4$ea^c*GDu{=N*7yfd%2Mu+@_Nu)*=QK2wJd=zz^Ly?!XV)8Ay^xHM#- z4G=i&`YVsNUy(%qs(H-eD8)L~*ec`j%Q&&*1iI(~lhQ!Uu`nhKQJ+o*Q($VyFP5)R z_-X{GueI-jrAQ>+FdF+xr67SPmW^focHC_#IJUaVx=TqL2@I(*U9{cy9^AQ06hid9 z658cn!jq~qI)vkec{7_?kb`#w9~Gw~`Lo3A>?E|yP%f7=*8YSo92S4QKHzlt#1c;6fF_QY7Q z0qWR%idWHkgvVoVFIpaDG2=@+;7%YxrJa2$mV=QYhOW{pKzbdvJ5 z#=bq42pZ2TKnhsD_2lQ1KE>6*AuJ&TVJ?J|5Hhn6Xz|$WpvRDW;=X6O+DpS*d@bVX zrDH{jtUzyYGJ;!W$Gy|~xYAQNWeZESPBiurIe2K}zlZ*}wtU8%j;*K0N*20gT5~Lr z2^`vtTTbh{*53lYYIWo)aVM+#kpm?N=ic7C-ugAir@zEWQjlZ1K5FSLR~wxEPNQxp z{evRbh=#w)^NYhTujJc`?wK1Lyh8)bt9IJWwAHu$DSmKZ7b`v>ca)Ov?hGz>rFMNrZ?4(P8gS)~hTjBTt5;|K z05R$mI=Byv4dWhLha!tWDcI>IwI~V&dng^Vaf(4IR`!St4z^(!I>5E;X_*iyZO6^Y z>%>n=_U&k0+4WbQJHu-u)E8++!=3xSddUt@`ASGT`dRw3Z_$CVu6JF?^#BuethOBeWQEVAi<75jr)|2@~AD!#|50p`dE_k7Y! zK;HqhCwnkQD$%I*2IuUHaY4h)%3DqJzCzkGfaTRG{a#E))~ueQyxka4%$W5EoHfV8 zuvZQJzh&C~<;yKAb8V(Wjsc#8kc6RCEesK4rq$~%MIwHrr(|D$H~AF-8CH@5I`RgP zYPEm29rL;>d}_nz7QZWq!Z(*-l}p}>rskKsQ$_C`*>R&va`s#_sMu0Lq^}J^*y!1mQZ;GGQ^5skP3NFZa)d&m`Qx-`4lU za+NdXts!b7(!oZ&Q6R;OTEd@k7F&N=Tz!=0jQfcYME+0BcD-y7EDmj}I&)t8i0TH( z0Te{9Y?&@Y^!kqv0L(p~fP7Dj36Bk`-0JJf6;H)*)^_eUQ{8UBvf1@nu>eCFDt$VTEKbD2s;PmpYDCGz5NAISm9nedQ-5XW)MEFV~en=qSPU zP0*1$=UvVn`+RLZ`=qlfm6pP_8)KL{_?da4eeK#s*$%s@-E#!WJ%2=77F7^m^H?&i zc&9DAXB8U_(**>AfHw=qb|gnQVp%{Ge9^$=Iem|4b?{g1ect?M+wF(GjS;dwFVlHE zE(F(1#9pfl_eOdH7&0rMM?gLEf;ML8CIsL{8qnyK2d+PlHa`w*6VrXqVZwhD4JuWW z7fE6M8tOhvcN|*wpl?_^oF%mi4at>54nwZ@HwLEn|5_LzKzw$gggm1fUh?VP?2Z;O z$@{7WNBn+LmNacSV>|`^jmjdf1vzGQiu$!NHz>-lSsYs~?lt(5DgLJVzG1niINbCC zo6f$9K1UrZZzhvZsPDX?*!eMqYl7>_67b(#UpLoqEfwB$U z@=o`t5y+0i0L%XSBMD#D02T+gG1M`?7;f5&8rWgV1>}ISktTL;2r*4Dx4bfOK zGU)_a-uZSS3?V9F8oE_KRXZeztxfZ$tHU7R1}}3(gPs~xci@J6Iu!yPz}Bv`ta1~QQ`N<9i&zYd2Y+6!6!|d!wpY= zMx=h(v%T_~aWit?KDII7&oOVqW@IZ-kxkzGI%!kjFx#5WILu z7&COS_rr&#WjE!51MnFEmz@>$D}2=Cp$L#+pnXGVgVD?)o~-F|EX)F&VcCnK?6DL* z_8+kf?W^uWC%KK{KW=C-9Ov7__$zY9aJzDn> zrxPRooo4eVyo_sy)a=efp)yORq(9Zz*W$k*wF`XH&~h=%O+Ag4;n8pevx;m8sTvR$ z5l(vs-=4C%phB_1wWIN`imLnfU*q5wBjHKhZ z8Pzo8QwM)(TTbfJ>1kW&GXPvfi8(W7ChbG*6mcTWB5TixB}g-VSp4) zQPlqsB${;?o;l?Zhx6MWv!km+8N;NlwIVL_>GGIF>sev{1P}}ipEEwD#05nkIQhL0 zb>5LShWaRF_A7dsL`8t%KU#!zE%rHHLdc@d9UEfPb>};3nN0^R45-lky(;;TnZrI3 z1Jbr@r|&dcosp=eG*%UG+^crgqx$5Cd%Y_Q1OZFGZ?VS@tpcy$KoqtVb}Vqb9kyLF z3i^5uqR*|C({B$z+Ih+O>C-Rk8dqH)z-b2xrS2yZ+lqkt2l>qt1b{hKzfU$?W~fC3 zi^p#4nSoB>vhS6!zSUPCUCGb&jR!&#REM}pdBV^5!#*L>&#VF^a#(p$mCeABVPoHID+#fk0~Wd4 zfO7r^q)98k?ZeA-Q23wKByV`@-A6h!{V`u1$qLIE+BE~*vw?7}J&?jkG81lun02LT z)IML@I$BQV+Nty$l^4touMX1FZYR%U2opNq8S#EeV)?B_WNfZFO8o?GfcbYk0Ig!K3`7KSVTuywg!A|?TE!x??ZEr??`Dyv~LZ10oT z)2^VjNlwkMTp&XPT9ATIJyr9k%=0H#r|%zQtfjo=ueH(XMvkuCAPdx$C|LaV5RT}< z$%EfAHk@W!IR7jfk#3Z&LOS2s=`kC!a|+mo-?$G64!v&=RO|a(#!S0U`T<#*em*CZ z1jl!NEi~0+AszGp9U8#~%oPE8z9mnt17@tq&0yaf!3iPNd6ud-<-c<&y5S)b4_-hV zlUJT?k+yz$J9&`~Zgr{gmx!=v{nX$#f4m0y1W`gv6hIWI3Y%K~dCBKA=@tNg__>xI zpu)kpT3W#>y6 z?T3j|}S=|QPqzE;~v*O@po1FV1H)Ij!>{~_K(0dol&j^4g1!^(62TRH%} z)vusXW@d8LMF`%_NtqDd_W4VuS}54Mmgh@&6GnvlVt+2b)rKl%B@kaKf=y5L4v^2u zG#uc8!a_!x2%L`v_X>%>>!x(G;BOV$vpZVIbP#cRW;Xjw!WrXK%%cS`*9{t6H#1mO zK+byK^AgBeSN53z(i2RJ!?|eXw6EbA^YZxxR3VHQd2H<7Z6udj<`I0bB z>LEdIb(LL|CMy+G-v0BZA)llZpaoluN zx~@s_|718R(`N(c)dN?`LK1-UUbvQFvU_--nHA*wIPow(}hI+S8yC z?cuN}QDQk_(>do`6AaQ>OvERmk&F7mDac(gH&b9`%hs~S;EJNkPA}vv$o4z6Iu5L> z8Ve{fAiZStAKYRuCepKBO*mG-1@Q+A2Y(eu8# z9sJ9QC)vGkCZ25j{W9ZD2ExzLM6~&|FiO$>v+IlfVrFcRIkRF%X#Ysjo7(!-d3xno z&YH;w#<2Z0z~l$}i({nwV-%$9=%;@#h-af!P>E?kl926exC3u_hOr*+#2uf(?0sHR z=_|^fKzoB`W!#5&LJOUs9dxRa84O_|5sg)N!|MA+y=QIZDaZcz2O|p&3}A;CfXR^k z!%#j;fKtf7H|bk-eaFdLk5!Jg^ZS>RG(p%w00+h-Q-c^zg72{e`AoHwQL2Lqd9eOw zz`>X3IZq-0m4G{7JX3PAu<%pNIiq)y>ZeEy=gASVL?g3Qv&P%R?<7lMM!B2A`3o?0 zEJJs-<{AP4CpZZbYne)F( zm=(DS1>jVs<%7GhnIc0<#gplKK$6Pcsa&q9Rh9sNrIs(w_W-qUEOUS#MO6pt=Nu&j zXi1~5oejI)^IjQ(>zfh1^Wf$1JqFJcXQ0bYhq05C4Ese@t{B5+@TyKY^Xye3+(dq& zYIs|+6X+Z|))X=Ce%K}vHxO`05!o#Xm9OK(=RDXLVdn77e60{hG4T?3jGkk)J)H$H zsmu}U&R|B&&N(d3yJa|x?)}KoOf*L$&t(rn4%P9 zfd%(RO48k`IUE01U)-XnUGJv=Vc>fZ#Nrw@o39s&=C3otim;__9@6535wFEBjpXK) zrgP#y!Wg2ZK@gj8oQzaNT~X`5pJgJx`>{v%XbPze1{A1}OGjWvZg3$semd{R4EpRF zyswX);stnav*wM$4~2uz3Y7MPqbjU6S7)k|s+0a2P+%->W9Myq$VU0Qs)qJ!BA{piyLCsytoyGP}4mD>d)3k#2OSFEOG!@uV9&%#3D81@4rE zU!m!jSU2y(IO#@4<`1ekc!BoO&kqu1A8j?N^p~4}0!s~+AHf>x8(U}M70Wde$CjhA zRIlBtj4O{^2olEkuJCcAy0f?7F9>y#O`D$NGKV=hq*tbR`REQRGDiDAs zaGLsE2_*K&^mFXAsIZbVlRIl8GRVm~_@9q9!{|IV`_iwQ({Bs!^qCXlU+dBVQI zE;L&lS@iFZXwBvmYV|j(XAFsNb(!GADm`Q+#wPCM&q#&185gz41V1$m2!7Nh2S2e@ z<$d~mgGU5;ZZDy8;HQ_|tp`@CFR9Y#Pv+RNhwZ3Nt2rBVvx|ZNSS;rEFU%8ugo=rK z%!iI$`{TSUZz7AKgj}V<)k?sKpHD<<&PNI7{7XYmRmSy#!;-wFg$zBWV;Ig!S@E$u z_}lr33mOy)w53nhE!bNHZ_d<$NSFiWS&W$tJ)HVr__QmC2}tB_iDcwz_3chad!(H$ zdbXZe;?-I#DOp@VszQt=STc8~3+6v(`7R`Xe9=Uktn@J?GB=R_dS*K7VTnl2#*5bF`;P3cdS|!m;!%?ZGwe+nD`#7%ds-%^6-UOyyuPH#4A;QO?tUhtzl|hgOwNMXt4+jTQF#Q@%3LyOTvqZ~nhpfKLACRjL@J z)8(4H%~j0oc5o<9UML$vrDJs%h5IrTgfn)AD4lg<=~F%ni2qV05t!t#XJptxqT zvmLOq_qF1|BlUqTq*7r@{p9k65T3lC!Hac2u!Ju`j&1DOk93M?b^6wR&#gUopR#w{ zYXEc5%xIKcqLhrK-=2!`se9j)1KYl%4O*oja&~lzU(K^g%JdT7N<_sKwj%#CK+DIdwt1*G*Ved$pWGkZ+j$rR2pAvE@Hh z5XvZcK5s3>LN5ycwduQZVP&*?5}Si&99QXE9y6IM5jF&OmdCAoUyZ8VqBy$=bx5ja z!oN{?%&ai&nn~Y>e8Bl{7hQ}~9a~RR2>6=eRW!fqc~QA$vigH$mK(b@pH8L~aZRLX z&%ot29e9&723HHL1JP`kl0{v8gtsxaR+)Fe;6#bwc~%pG-LWSN_EOOKXQ$Dt{snxj z$6bXm(C-KHW)x3~soPZN&BXc)+-16jHO2Bs)9!q-8hO$ng|qfta&OHY5iM4KWK`^P+sE znu(bC2Ih(WmU@m)=Qy4&2-M^&F_>*T?Pat)u?=>z>vo4GopwVVU`q3^IY!NbHTrxE z!1Mh{M{T0T_E&e;`{)b~#(UvEInAeG%rG+S4xldvvtSzDhwpTvUzT@2mOA@paoss> znYm@4jY`py1Ff1Syyr{Lp|PP`qTt!sRq)75yd3p&61w;iByj2zsjejz>2!gZUL_OF zb?Qp`)ExFe9nR%1NTBV1-!i)Dx-}kWe0Hy|k^G8qB8z>K$-r+&TA& zZZ4Kmp5MO0Df{q=Av7x-<0}+o`MbCk(E-t^+4mHMsFdL+%s^I6exmrwB-@+}{p{To zu!$+Jipy!|IO&hZ_8L$0FcFwzyCI@%SS(f_7KWTjIt|6H(u?#DzHWc<@`Li+02E3S zW+ucjQ0)Ze9f@wp6k&hNl>Mlh7>hD86#LZob$hrCO#b}Sr1b`GQ`K0u-?aI8IoyJP zmW%1V4dxi*=KVlR-U_aH6-|o0?kY85mk#29&Q}n#ZZEzl6mY!JEZ9I#wS6|No5W7* z5&16s4HEyP8(O@9JyjGV(b$Q54gn>Z>=D6yQ*~`VxMgN~qv+HI4>&HMR z7BphjtN_c-O{lbylk%bWrLi`46B*Fak_|}?1!iUWGq}#VyE5;;bGlDO*rm&Lc3V1h zeDYapR;nVLA9JHV?qb_P-gW8yGRkbKmBh>e#;ENg0w~d z*Mdgj;7~M+{K9YddEJHMVj?o7bHTfp)O&yFC;6=>*1)FA8ps=bm;qcb#~dZ}zJ(XDcE?YcW@6`e5~_tkjsFbTUS#2>pH1ipF;} zlvsSl=!rYVNT_>a%}+{|pA(2C;3Hq}ScsAMkxIlK3eSXPrD8n3LD7Ps_3A>Gp<#)( zSzQVf7l^SX`Fd5TO*Go%IBKdXKuA>g{bR;^+WCLuhU~h_x|Z+{Z;4m`0^*PgB7#HL zSA2<{q3tE^Ytt_Gd;LMVkE+%mY(^$**6uLB-^5r{MVn|gk)|l5?H8GILDEgR8|VY+ zWg6k%^(+l`w3vN)hh}|)Xue*(FX=VUgVLX>RWzuI?~ScD;x`w1+jwk=p1_xN?swaBkJC9L@f%w#5V(Ls)|4AGIZ~?F1iGy*50g+IN@* zF_sbK=ocMRH=X_8{??EdIZw1_!?KQfcHxAG!q;KUq4L?r=M4SsO$X35B<8>!syyFB zXi5zZXv(MyrNBc9L6+nGvb>kgBNjR2CSv~IiXQH>gwc;iIa&{~W}~qS;o68jvADNB zjIyax$A!PV{tM+~WFW{B6e#blz9Z;L_FuBF}x$^$Dd`NOOFvWNJeJ*of{hP(j?jd;4Hwp&_il#j` z=%>!(Eo>lRSeO*IJEljU-&(l9u_^M$sf%-gK)+t{*$-mntXEVD!_Bxj;QaYhvtH3G zDbK384Nz*VG!wtHue^IsA|6pf*L@s?vFB8M$`o2Asu?&(5dK>ZEBv@*LW*fhKC$VqDWjB+7Q>zt`&1ovnU&PAZ)&T=uCr zejju6R8Y8uGrRODgfA{2$x{*wnzT}V_t}RZ*=jrYdd2DNW3hmHrBxlz4&LbuBaiC^ z3P{0uzC|UAO^J0&@%L>4Vti4U4F(9*`f3)l-|ipk#h%5a(lF39>OQ}tCGN2k`d_9r!!@cO zG;Mm7DNtvsQQI1e+W4QC;rhdr#gI6s1@Frmj`z!MHp6qgxQS|sUyfZ=GX_U^j$7Bra+N}Qd&CkCj-G_zN6Y6Rz(9A zg9#w4R0O~+=b(zg;v z)@1L14Kh^)&31GgDUReFXM8sob#3K>293 zWs(DU2R2ebuFq=Bu70I<(`DTm#Rf**n9=l9kVU|E1rMWJw_aEU>~d9k1{LD|p(^_Q z)6e6|eP#)GPP7s@{}MG#sUT<4IgO|ku8jUQsb_VD!CDSdkGddwHI5 z(lhJxl9OKKjM}3pq|vR`2Yl05b>)q2*MA# zZ;pSty2za`%rVAo3QbYp-G0d@u>IN)6s!Z)Tw_`)lh&eV@1w*vyJ`&Nu|EGZ?xPAK z&8635Pqk;cL%;U%Q8`%O*gGs^L$o zOJ#a&tL|VaQ{YLP(SZAGH&HL5813oN&kCBTt?qGvA~lIib*qCo{H-fqZ}uf=G$T83 zl>4juSr6Xv>1NYux|LCKUTgLNgZgBUj1w#Umg>U{C_+KlKH*A1nn{rLNIBjAmy%jW z7Da|inu{4Zbt>4^y>$HdsL-aRz5V*j9BS=Z!3dNV6#zwk)g0&bR-e{Nycv3jF$HHr z?)qI(z>BZu8S)l8e8$eNqCu{J#^Z8#6s)~ zuJ}US>fb{29&?Edw}{`RjevfxFtZDeUCTR;>i0%YMm@;f3Q$OGkf}{riEeg15Ix!q zczz)YeAs9!$ScbWPAZ*!L{<8SGA6`#xIm+6TAYm*I!04FZR-)u$nmV>)vvRC&4tgI<#|O2a!7v%6B9o65hmH% z_?c{1#DF=&@NROh){A8vhSp1Nutl+;hSkE=|H!G)`?k^f%a#R*4>U5M0AFce27_lh zC`jam+MNqP$oOVA=Vk59_)16)0>P68K0MC`?OIgH_POw9j|+%~b1Ul7av!?gjg?r_ z!OH@_BtyYtCZ^T&TslBH{}!W$Pa-;lTQ9lVoLMs;opGh`*gE;4c>FOYrS(mvi_w1C z6d&%;8gx282&IjxNy2YX52LbwVs_G3Y~ZoPBXIR#lstt(gp{$Tuk8AmYaz|(O3lF3;U;rQCGcLkE}cv5zjnTmI^C5X$fKXr^z^H1 zhgzaDK-#u4ns67BG?!CUd zv_=Njrc>u6-HCT(-eag+idGp-dhv)i)dGCdre?Oh<;%Mb2yv%=dMS5=qi;oe}j(00MQ*sNI@eWb}wDz7(f3zmUvlgIO!hd*7KfcB1`ad2gVWi=utM`lD?f z9*{xC{Gqb%TayH(k_8)EO6W!pjm#OzT-FKku4N>W>l2aS0i!oL2WXy&&d~&hHFzOb zbGpAEbz;}!qc^UXhfXRDO4X=w8S!`BaeU|bADoug&ff3L3l>1+hexo5sbmF=NVQkpAkLgNRPBI5RM6vmsYMuhvCGDy+ur}%L*qd zVOF&Q1xXzNDDoYOBxPbf1tXY}BHBAnZM_*>IjxPw$Oo$V{iT6a>ACHcJgUU%rl zVNdw)?;mdU@Ueikz4(b!=X{#35YYq0#8dneMaDZ*YhRb9ECJMG^#`*sAO#enweCnu zXa>0#s60cIpdI-)4_!A}_}1cRLMbAiUkHM|FWxgr*^k>8SteL2u|?cOV~$8YM~9|R9{jK)Jy!)cXH5g9u=h>?r3_Q)Ry)A+q8aoZaiP$RF=o(2SsW8vN;6`?BNx~=d^EDjVv@~I5xzY51NY2N!O*Z})tqvXRsBy$-Dun4`#BKY&s zAR&pv&7qqj(LFIe{QhEK$1Kpy*C%76lG1zb&uUe$<#kW9l-&k|4`fzdf0WnaHQ3>G z@;|WyQSgCGS`UkdiE@<|6M4i}y(%r)?$zdiS|mEKC=CY|xx^&Qt8V@h|Zkgfrm&lDcs(%mZIM%25cfHYk4ESF{06pqn{R zCtnpf{c%w%#-HF_9kuqDO&hQf6S(poQ*iw=fwMFTJpE>TX~hzs!qG}h1mD+c4En1B zT^6)`Vr*egB6=o-e(?;{=O~ceyrW@Q_MWMzRVdpfoFS0P1-|60mn?|rLyoy_&qYa*hz5bGZ|5BIJlJ(tHHu9~ z{hmlAY)d29^c^@idd3<{B=iH?7S*j=$^6kM$>1G!xND9wNl)zAq4uaGgZh2{YPe~D4@ zCrmXm*GUg<9a&8jGin2zAA#f|14+?@{Y=$$ysqt-gB$3e!NbI&0Y(JdqS7e;a#aG$ ziPMh_)yAc4#Mxhj|;*~M}>QxX37P)_>rYT@{@aQKh3OOTm2M_D;kb%S0=_ZIcw+p;b zDV0G4I5`8uG#!A(Q!EqaYQ0a4y>}`(d$g@NnSGux;1$vuhw;}vg?})G#D)t)$ObbCv&@E3g z5b+fhy;Ht3^dM}?3J_a}Ey9h2h`lzm$v~{wVAVwg>aatXs~KwPmO3((#!3xIqgO!) zApA4kyS?^J7jJP264R}P=!XMLE3XkrcSPq3efhqLIsHr=b1K|9C`D+3`Cd)j{!fcz z0FzABYUrYwqo^$JI(67)dCuLT8BzlWoP>Tg)W9L|XEk878i5VO`?jo>4-VhYnMr{M zlJop{AC#)X0suhmQc6m4#JI2cB|QcK3rb}sj|%2SLglxDyeH<(?}yEcr3i2Run^({ zXDv&JUukn?G|cFI!?9H|A;n%NSBMZ&YyeKxm>G9(nb( zieooCsvKl0viuow;MuR&7@Kn1a-`GvVGvDL?b}#+pnw7pI8W`4>c~|vs!>4lA5__M zoy`sZiJ!9dca!Cqf913o#j&WLg2M^EvWHEyRtT_9`qP;Y7B>=P_e@GxU9bM23FoWR&zaokN@Lvh z8YCoJqL|7u{Q_H4#`RX7DJyMM0UE|8PlF4~$XRL6>vj1SI{Dm?q-O`1T=HQ4sSAz& zS%NyPNNman3Y2{?cML9D*MOzXY%P7$wht!;q?c#btct&d3shntQ)NU)F4cD}I?wrU z-ORr0C(`X3Ya4w~12V%~S;L;?IGGI2#*HuEeN}`~?oPM7`f- z?yP4Jh7Ze1wE~Tyt3hmzTScIwt_4t1sRyOJK+7%umETF=+6kA zp(`sf4q?MR5YjKK2=zg|F8EtKJ75v3NW-73*V(B+TQ9i?9@40I_|d44-rCfK`fcNO z#O^#B_ytugEJ3#@n%%kwUKiV$5j}Q`5xD4n53cwDD}Z!j^8ecT4Cs(WcF76%PzzVDM0x`?qz8DszZ#y;ENH9#(|UToC9 z$I)?X%w!GlGtaNGrdi2V;4oQRRop)>$Ming4E^p@{_3}bBXA`GSL09dwi=xM>H2%x zcYh^SuTK5Bp)gui7b;H^2I9a^&mizQhtS8f-rw?v(3JaP7v@;turru|D-epsJka=8R2uG|W3qJ`Awfl&bqh zz5foa`)}8n^#ExAsj_L?R&xE*c#F<%G=U!OxPahhNV2gI)!NeWocTp3OX*vY zR8PUzNQqo#jR^c!(_DYE><>+@E2Pw>1812I8aN1ub_yi=ns<~un(-kh5PmwJ-W)u$ zV=v91NqB?{n)x9B_F^LLLsm~c4gr~*`r5YCKfq2#39C2a7aXc@KAMy-L{6o9bK#sa zi5x-8g&&iIj0of=#8>0gDETI9o0|(Ok_%+SEeS*6gPP@U4*v6L5_1F42g}DsoZQX_ zL^Gu1l!{uoz0N4O5kwA-mAA+l#ibg|PrOTXv!neMS%%I{z)Xx@IC_DUJze7br4E8i z_C{d>Q_3%|yL%?>4Cf@jPWG={_d@`fAmi0f`*Ja zQ-eZNZ}fV7%%<(=Y}tiD1|*wbybM}TU$IZ0DS|Ij;YSf%YvSg?LqPuV{55_G8xs{Y zh%E?A0bZe1075+lRr0i${zjqj zWmNABxTD|&l&=`T1qTL(o^*p|NK?Yd#h%2jB?8~JKvNhL>KjK0S?rr_G$Eln7-6>6nE@)&pL2n|AjftCyDe#*tgp3xS4YaQ3n8_PYH2K@eE zYs09>$Y{Are)U-Rf*i}l)0`3`Dxt+i1?e>4YH349Su^cs)61ofS!gvEsfoZgv8Ep! zurwbmD|C^ogs{WE^1pL83mcbkeZFpxT0PSm{#=g%>`~5Efk*MHkx!|Dzki1lC_F+G zLeylFi<<2+H0ov}T1dq*`0)YBUa05%lhccD08|$v96N2Af-69X7R414!j`n7om2#j zJw*#lTk{XxW1(ER=DOC@E`$>V1@ec`A5Ud&x10lz*^FjS3R>?FUCJjlv>!JYzH>{I zEHl~o*R2q2WA}N0<&lChc^yo-3FHy=mfvi9eme79U;SlygU|-*O@Tr|0yRKKi;WZy zWcyUToW}yy3O_Mco*XRU*CBP!?qt2x(fcIY&tN=WyW3K1ciT^ z6;2;fd94k)V!|>Lj|G9Mr+yTGSH43fnS90WpN+`B0Kg^rEh>J%3ykZ$E_iJ5;kGb9 z6v$}1DY)jm4i$Um0w}aa!t5nD`}gGGeO?A{#(6|-))_&9uR+)VLwyC*@$Pm~gyiY3 z52Ba}JxMDrpaxF?Wk;f2Gr~H$AB4bNB2Ffu6W23|i^CI~^vmwzE($;=i;Y;E&mLI; z8`kafDdE=WCRbsX*%##l>ZL0Edt_z}`H^eLbs!&U+?-DJ6%MX`uP^!KK*SWe=_dk` zkCd$&k=mGlGL6g${#~tX3XWZnjG0#B?@x;3{XY-l5+A*j)gQ)}`&EG6bUAUhYaVw8 z_qVJKM6<}#u^*o8`|AmqVNq?@4-zckrh*sc#rhtJJObRQAoB3#27Y}Gn#Hrc^hu@P zUXgr3QzHKfYKpVZYg{TWzpPbbmQ0~y;^z(@rj=IwVWO+?<71b(Gr`z|n1wJ6pgVxPshujP1I#%kK>ot8)VR9daLolQAu*_}T~(D`W)USAk$JNa z2;`F(-^q7FkX$PgA#9Xj^fJiUF#@sMb$`_7JL8nKL3)MQM5$G20o=c01t@_iqB!(b zImrMJ>0c$gPfp7v6Zf^%(F2#hyHTjO!;EXZ%1(c zpg2R}|8Df2m~+y_a2d5_A6{!WfGop5`A6tJW>?a8S1ENKa%o-VEFx>{z(C(l3qF=eNZ3z~CgfBr%@x9zlY0p7 zgn)Y%q4@CKYi@8=CSim(P6Z^R`u7lpTq6UObL@uSj{j0rv5>sp8GD3cd!z2ZDqwQ& z9GK2MvM52aymji`YB8-8wT|i}wh%TwEG!OlGEG)6!m#jt{D|&YBhRhdQo$+qGYiN+ za>;PSLl^52^Y1a&tj2ZmfFMc$JD*?^?TM!CxUn*+jT9hL1^-h$Bu53pWL$^h|62u% zDgu+ctL+*O)(3=*-2sqi`Dep(f5kfO?3*VpxQ&WlP#67fJ#NjCF{iAh_*`NHa;3IN zknqh3obUFZIP2A^zOD|DWlk+Fa-%zv`9t}{(8c(hTND_g4SO^GaPagG5kuqYs~6OR zfc5312yyib(~#I{7qB*ow4XV_#5{sd$ZcTA;2RWL;PbuOT8doSRJtG+mX~3|_X}cT zBK8#m|K`Rx1h9Y#dUBl*@HZrZ96asdpc6EikypV16e|`dyfu+~9Y6NylZ{GMeDPp4 z&_gq{`QS3PN`-m~DzfCokBl}N`*@RzX28)I?Z*Zwf5(z?lx^M#F)lPwDnk10dKYH7 z_ylY(X!6SesF02Z9Krhc7V4%$`alY2?b~DnR^Ji}(oeqIKpiN_Kcq$qE`22p; zfmBGYK5mk!f^BYKWeBKPWWs@(tHbY_b{Jqo#f^%QNYpQCLZ+|&Tene95jDSND$+%K zs2;NhA;ki9ip*pi=a%Z*jCwxzUJ?{kV}kpO$?HQshWdpU&pxW|8aEiFd=mK?oqdZz z5oAH5{IZoLWyb9fNau&5>)0uxda?2VOf+`y)#C3gr|lKJAZ-!T;X~>yIfMo|S>E7r zlqgy1Wq4MFeo$aK<6{c|4&z7OwBx|$L;~;*is+o*$!-rb zbbY7OEZ~g2M!b!~IjT<#Yz+=RgCj2;9tP@1AgzMR>zW%_Zj61jsUZw#JGY&f^;C=Q zX9w3onT9uzjfn3;?C7#kYAkZvdkw_)Pj}j>-Zp{?)o^v>G7N)*&+u6Q50jta@uz0b(5+`rv`5#8gEXJK1g0po_={`P8T0(vPq8goPWqSn~m zg6-#khK^u+BXsA*qsx3)Z?+3Tc(y0i{YE=K`cAyXdy)1Ki0!#cS{r88YiZ@lyaQQE*D!}Sd!=K1s@F_hH|$B2?jh4!wF~T zEv3FDg8a`5BLBP;+ey3hy6j%FnHCS_X6qo2#P0SbK>%eMpa`JyLbM7nx#U8an!ad7 zcC5rsM|Yi+Dioo?mIgEv`)0rr>IKf=n?-_Zch%9Vw;bxUAE-&!HMp97p( zX7drCqn$S8Li}U(p%E|oJW9D~!%#>tnkEh(Xp`H++l)N;W{Z38-xaao~lqQT&ZQ8HvKd_K@# zLhlWAuXL9p2ZKB-6o8$eYPO}xNB?`ZR7_@6^2~^T5v%cFDlYlUAnE*4kOk>#&F?YW zQQ-(S(AO2fij4-6i?MwBm%~ll(zRQ)9LbwoPqQt$x&y4v=yW3*A!=Pf1X84@aFHLE z$^+rH8!MDh2sa+Idh6by?~ejOzzV=RPv81MHRnX$=(&L^GYq|_3lA}`YR^CQa-Z^} z9&T7=cvjIUo(p5v3zBCZK?X=ha_lsv3YW2du9i*CQ3Gnyz*Cp%O$i%_wUIkf?N()V z`tAfJ@AZDwpy!4?`o+DUAVZNu*s20t39v-J#d97`Y_vzLnSLH_IV#rSVuA@=&s5xX zS#xXtI~6>5^@`n}8O+cGV^ANxGk@Rmd3E#KG2}?xlOOSrM<9g{Tn$4|UDIBk0GV** z9a=dW+;`~3soG$jB0wpE?)U^dzgYf2<0G*^ijlS~#1D^L9l!k*bzV{b8#}Gyp(*wh zpcf7ElDPAH)lUAg_D${!KR325l8;_iRi6Qpvi@?9C{5IXc=`-RMxBLzHldJME*E_G6n0V^-+Ff7`z zsmHh$-ArIt6>rc0O-#>$B(FD#v(0fX}U*v1WE`69C2JLMK=d5S&=Z#kHn14oEmbJHuH$9gjzh6>S$a2^G8rdN@idn z$8>pd(QQtmAtkepR$$>DvK#v7|Nk%lUu}X6RtNnipJF$P{|5M@ NqWBnIF8@5}{{XRu`Zxdp literal 0 HcmV?d00001 From ea36d2d3670051271214a8b3a4a287d4170c62e4 Mon Sep 17 00:00:00 2001 From: Saniul Ahmed Date: Thu, 2 Apr 2015 19:40:50 -0700 Subject: [PATCH 8/9] checkpoint --- Examples/Pixel Data/main.js | 56 ++++++++++++++----- .../xcschemes/Protoscope.xcscheme | 2 +- Prototope/Image.swift | 24 ++++++-- PrototopeJSBridge/ImageBridge.swift | 43 +++++++++++++- 4 files changed, 103 insertions(+), 22 deletions(-) diff --git a/Examples/Pixel Data/main.js b/Examples/Pixel Data/main.js index 208453f..514bda3 100644 --- a/Examples/Pixel Data/main.js +++ b/Examples/Pixel Data/main.js @@ -1,24 +1,50 @@ var layer = new Layer() var pusheen = new Image({name:"pusheen"}) -var pixels = pusheen.toPixels() -var start = new Date() - -var t = function(pixel) { -// var color = pixel.color -// pixel.red = 1.0 - pixel.red -// pixel.green = 1.0 - pixel.green -// pixel.blue = 1.0 - pixel.blue - return pixel -} -pixels = pixels.map({ transform: t}) +var dimension = (400*400); +var start = new Date() +pusheen.loadPixels() +var end = new Date() +console.log("Load done! Took:"+ (end-start)); +start = new Date() +for (var i=0; i < dimension; i++) { + var pixel = pusheen.pixels[i] + pixel[0] = 255 - pixel[0] + pixel[1] = 255 - pixel[1] + pixel[2] = 255 - pixel[2] + pusheen.pixels[i] = pixel +} +end = new Date() +console.log("Modifications done! Took:"+ (end-start)); -var end = new Date() -console.log("Done! Took:"+ (end-start)); -pusheen = pixels.toImage() +start = new Date() +pusheen.updatePixels() +end = new Date() +console.log("Update done! Took:"+ (end-start)); layer.image = pusheen +layer.position = new Point({ x: Layer.root.width * 0.5, y: Layer.root.height * 0.5 }) -layer.position = new Point({ x: Layer.root.width * 0.5, y: Layer.root.height * 0.5 }) \ No newline at end of file +//var pixels = pusheen.toPixels() +//var start = new Date() +// +//var t = function(pixel) { +//// var color = pixel.color +//// pixel.red = 1.0 - pixel.red +//// pixel.green = 1.0 - pixel.green +//// pixel.blue = 1.0 - pixel.blue +// return pixel +//} +//pixels = pixels.map({ transform: t}) +// +// +// +//var end = new Date() +//console.log("Done! Took:"+ (end-start)); +//pusheen = pixels.toImage() +// +//layer.image = pusheen +// +//layer.position = new Point({ x: Layer.root.width * 0.5, y: Layer.root.height * 0.5 }) \ No newline at end of file diff --git a/Prototope.xcodeproj/xcshareddata/xcschemes/Protoscope.xcscheme b/Prototope.xcodeproj/xcshareddata/xcschemes/Protoscope.xcscheme index eea9e6b..258d5fc 100644 --- a/Prototope.xcodeproj/xcshareddata/xcschemes/Protoscope.xcscheme +++ b/Prototope.xcodeproj/xcshareddata/xcschemes/Protoscope.xcscheme @@ -68,7 +68,7 @@ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" launchStyle = "0" useCustomWorkingDirectory = "NO" - buildConfiguration = "Debug" + buildConfiguration = "Release" ignoresPersistentStateOnLaunch = "NO" debugDocumentVersioning = "YES" allowLocationSimulation = "YES"> diff --git a/Prototope/Image.swift b/Prototope/Image.swift index 304c37a..56f7f2a 100644 --- a/Prototope/Image.swift +++ b/Prototope/Image.swift @@ -55,7 +55,7 @@ public struct PixelBitmap : MutableCollectionType { private let scale: Double - private var data: UnsafeMutablePointer + public var data: UnsafeMutablePointer private let dataDestroyer: PixelDataDestroyer @@ -197,10 +197,24 @@ public struct PixelBitmap : MutableCollectionType { /** A representation of a single pixel in an RGBA bitmap image. */ public struct Pixel { - private var redRaw: UInt8 - private var greenRaw: UInt8 - private var blueRaw: UInt8 - private var alphaRaw: UInt8 + public var redRaw: UInt8 + public var greenRaw: UInt8 + public var blueRaw: UInt8 + public var alphaRaw: UInt8 + + public init(_ redRaw: Int, _ greenRaw: Int, _ blueRaw: Int, _ alphaRaw: Int) { + self.redRaw = UInt8(redRaw) + self.greenRaw = UInt8(greenRaw) + self.blueRaw = UInt8(blueRaw) + self.alphaRaw = UInt8(alphaRaw) + } + + public init(_ red: Float, _ green: Float, _ blue: Float, _ alpha: Float) { + self.redRaw = UInt8(min(1, max(0, red))*255) + self.greenRaw = UInt8(min(1, max(0, green))*255) + self.blueRaw = UInt8(min(1, max(0, blue))*255) + self.alphaRaw = UInt8(min(1, max(0, alpha))*255) + } public var red: Float { get { return Float(redRaw)/255 } diff --git a/PrototopeJSBridge/ImageBridge.swift b/PrototopeJSBridge/ImageBridge.swift index d43cc8e..df7f97e 100644 --- a/PrototopeJSBridge/ImageBridge.swift +++ b/PrototopeJSBridge/ImageBridge.swift @@ -13,12 +13,19 @@ import JavaScriptCore @objc public protocol ImageJSExport: JSExport { init?(args: NSDictionary) +// var pixels: JSValue? { get set } + func toPixels() -> PixelBitmapJSExport + func loadPixels() + func updatePixels() } @objc public class ImageBridge: NSObject, ImageJSExport, BridgeType { var image: Image! - + +// public var pixels: JSValue? + private var loadedBitmap: PixelBitmap? + public class func addToContext(context: JSContext) { context.setObject(self, forKeyedSubscript: "Image") } @@ -42,6 +49,40 @@ import JavaScriptCore let pixelBitmap = image.toPixels() return PixelBitmapBridge(pixelBitmap) } + + public func loadPixels() { + loadPixelsArr() + } + + public func loadPixelsArr() { + let pixelBitmap = image.toPixels() + loadedBitmap = pixelBitmap + + var array = [[Int]]() + array.reserveCapacity(pixelBitmap.endIndex) + + for pixel in pixelBitmap { + array.append([Int(pixel.redRaw), Int(pixel.greenRaw), Int(pixel.blueRaw)]) + } + + println("started") + let before = CACurrentMediaTime() + let jsPixels = JSValue(object: array, inContext: JSContext.currentContext()) + JSContext.currentThis().setObject(jsPixels, forKeyedSubscript: "pixels") + let after = CACurrentMediaTime() + println("took \(after-before)s") + + } + + public func updatePixels() { + let bridgedArray = JSContext.currentThis().objectForKeyedSubscript("pixels")!.toArray()! + var idx = 0 + for pixelArray in bridgedArray { + loadedBitmap![idx] = Pixel(pixelArray[0] as! Int, pixelArray[1] as! Int, pixelArray[2] as! Int, Int(loadedBitmap![idx].alphaRaw)) + idx++ + } + self.image = loadedBitmap?.toImage() + } } @objc public protocol PixelBitmapJSExport: JSExport { From 355256c51f38bd7f90b6d2a6e598429d4a4a82a6 Mon Sep 17 00:00:00 2001 From: Saniul Ahmed Date: Thu, 2 Apr 2015 21:16:13 -0700 Subject: [PATCH 9/9] trying to speed this up --- Examples/Pixel Data/main.js | 62 ++++++++++++++--------------- PrototopeJSBridge/ImageBridge.swift | 25 ++++++++++++ 2 files changed, 55 insertions(+), 32 deletions(-) diff --git a/Examples/Pixel Data/main.js b/Examples/Pixel Data/main.js index 514bda3..b253da9 100644 --- a/Examples/Pixel Data/main.js +++ b/Examples/Pixel Data/main.js @@ -1,50 +1,48 @@ var layer = new Layer() var pusheen = new Image({name:"pusheen"}) +var start +var end + var dimension = (400*400); -var start = new Date() -pusheen.loadPixels() -var end = new Date() +//var start = new Date() +//pusheen.loadPixels() +//var end = new Date() +//console.log("Loading pixel array done! Took:"+ (end-start)); +//console.log(pusheen.pixels.length); + +start = new Date() +var pixels = pusheen.toPixels() +end = new Date() console.log("Load done! Took:"+ (end-start)); start = new Date() for (var i=0; i < dimension; i++) { - var pixel = pusheen.pixels[i] - pixel[0] = 255 - pixel[0] - pixel[1] = 255 - pixel[1] - pixel[2] = 255 - pixel[2] - pusheen.pixels[i] = pixel +// var color = pusheen.pixels[i] + var color = pixels.colorAt(i) + + pixels.setColorAt(i,255-color[0],255-color[1],255-color[2]) } -end = new Date() +end = new Date() console.log("Modifications done! Took:"+ (end-start)); start = new Date() -pusheen.updatePixels() -end = new Date() +pusheen = pixels.toImage() +end = new Date() console.log("Update done! Took:"+ (end-start)); layer.image = pusheen + layer.position = new Point({ x: Layer.root.width * 0.5, y: Layer.root.height * 0.5 }) -//var pixels = pusheen.toPixels() -//var start = new Date() -// -//var t = function(pixel) { -//// var color = pixel.color -//// pixel.red = 1.0 - pixel.red -//// pixel.green = 1.0 - pixel.green -//// pixel.blue = 1.0 - pixel.blue -// return pixel -//} -//pixels = pixels.map({ transform: t}) -// -// -// -//var end = new Date() -//console.log("Done! Took:"+ (end-start)); -//pusheen = pixels.toImage() -// -//layer.image = pusheen -// -//layer.position = new Point({ x: Layer.root.width * 0.5, y: Layer.root.height * 0.5 }) \ No newline at end of file + +//////////// + +//this.pixels = []; +//for (var rgb in this.pixelNumbers) { +//var red = (rgb >> 16) & 0xFF; +//var green = (rgb >> 8) & 0xFF; +//var blue = rgb & 0xFF; +//this.pixels.push([red,green,blue]); +//} \ No newline at end of file diff --git a/PrototopeJSBridge/ImageBridge.swift b/PrototopeJSBridge/ImageBridge.swift index df7f97e..5060bb5 100644 --- a/PrototopeJSBridge/ImageBridge.swift +++ b/PrototopeJSBridge/ImageBridge.swift @@ -95,6 +95,11 @@ import JavaScriptCore func setPixelAt(args: NSDictionary) func map(args: JSValue) -> PixelBitmapJSExport + + func colorAt(position: Int) -> JSValue + func colorAt(row: Int, _ column: Int) -> JSValue + func setColorAt(position: Int, _ red: Int, _ green: Int, _ blue: Int) + func setColorAt(row: Int, _ column: Int, _ red: Int, _ green: Int, _ blue: Int) } @objc public class PixelBitmapBridge: NSObject, PixelBitmapJSExport, BridgeType { @@ -121,6 +126,26 @@ import JavaScriptCore let image = pixelBitmap.toImage() return ImageBridge(image) } + + public func setColorAt(position: Int, _ red: Int, _ green: Int, _ blue: Int) { + let pixel = Pixel(red, green, blue, Int(self.pixelBitmap[position].alphaRaw)) + self.pixelBitmap.setPixelAt(position: position, value: pixel) + } + + public func setColorAt(row: Int, _ column: Int, _ red: Int, _ green: Int, _ blue: Int) { + let pixel = Pixel(red, green, blue, Int(self.pixelBitmap[row: row, column: column].alphaRaw)) + self.pixelBitmap.setPixelAt(row: row, column: column, value: pixel) + } + + public func colorAt(position: Int) -> JSValue { + let pixel = self.pixelBitmap.pixelAt(position: position)! + return JSValue(object: [Int(pixel.redRaw), Int(pixel.greenRaw), Int(pixel.blueRaw)], inContext: JSContext.currentContext()) + } + + public func colorAt(row: Int, _ column: Int) -> JSValue { + let pixel = self.pixelBitmap.pixelAt(row: row, column: row)! + return JSValue(object: [Int(pixel.redRaw), Int(pixel.greenRaw), Int(pixel.blueRaw)], inContext: JSContext.currentContext()) + } public func pixelAt(args: NSDictionary) -> PixelJSExport? { var pixel: Pixel?