Histogram of UIImage - calculating data of Lookup Table - swift

Code
This is my code for creating Lookup Table from UIImage - in order to use it as data for image's histogram. But I'm not quite happy with the performance time of it.
extension UIImage {
func getImageLookupTable() -> [UIColor: [Int]] {
let startTime = CFAbsoluteTimeGetCurrent()
guard let cgImage = self.cgImage else { return [:] }
guard let imageData = cgImage.dataProvider?.data as Data? else { return [:] }
let size = cgImage.width * cgImage.height
var LUT: [UIColor: [Int]] = [:]
//Grayscale, single channel image
if cgImage.bitsPerPixel == 8 {
let buffer = UnsafeMutableBufferPointer<UInt8>.allocate(capacity: size)
_ = imageData.copyBytes(to: buffer)
var gray_channel: [Int] = Array(repeating: Int(0), count: 256)
for pixel in buffer {
var grayVal : UInt32 = 0
if cgImage.byteOrderInfo == .orderDefault || cgImage.byteOrderInfo == .order16Little {
grayVal = UInt32((pixel) & 255)
gray_channel[Int(grayVal)]+=1
}
}
LUT[.black] = gray_channel
print(gray_channel)
}
//RGBA or BGRA, 4 channels image
if cgImage.bitsPerPixel == 32 {
let buffer = UnsafeMutableBufferPointer<UInt32>.allocate(capacity: size)
_ = imageData.copyBytes(to: buffer)
var red_channel:[Int] = Array(repeating: Int(0), count: 256)
var green_channel:[Int] = Array(repeating: Int(0), count: 256)
var blue_channel:[Int] = Array(repeating: Int(0), count: 256)
for pixel in buffer {
var r : UInt32 = 0
var g : UInt32 = 0
var b : UInt32 = 0
if cgImage.byteOrderInfo == .orderDefault || cgImage.byteOrderInfo == .order32Big {
r = pixel & 255
red_channel[Int(r)]+=1
g = (pixel >> 8) & 255
blue_channel[Int(g)]+=1
b = (pixel >> 16) & 255
green_channel[Int(b)]+=1
}
else if cgImage.byteOrderInfo == .order32Little {
r = (pixel >> 16) & 255
g = (pixel >> 8) & 255
b = pixel & 255
blue_channel[Int(b)]+=1
green_channel[Int(g)]+=1
red_channel[Int(r)]+=1
}
}
LUT[.red] = red_channel
LUT[.blue] = blue_channel
LUT[.green] = green_channel
}
let timeElapsed = CFAbsoluteTimeGetCurrent() - startTime
print("Time elapsed for \(self.size): \(timeElapsed) s.")
return LUT
}
}
Problem
It takes around 10.15s for image (RGBA) with size: 3024 x 4032, which is definitely to much.
How can I improve it, what would you recommend ?

Related

CIImage with alpha from sourceFrame of a video

I am trying to get CIImage with alpha, from a video that has an alpha.
The video is coded in HEVC with Alpha.
When I am doing this:
if let sourcePixel = request.sourceFrame(byTrackID: 3) {
let image = CIImage(cvPixelBuffer: sourcePixel
}
The alpha in the image is 1 (opaque).
If I test a pixel value from the relevant plane of sourcePixel, I got the correct alpha according to the value of the video.
if let sourcePixel = request.sourceFrame(byTrackID: 3) {
let results = CVPixelBufferLockBaseAddress(sourcePixel, .readOnly)
if let baseAddress = CVPixelBufferGetBaseAddressOfPlane(sourcePixel, 0) {
//Get width and height of buffer
let bytesPerRow = CVPixelBufferGetBytesPerRow(sourcePixel)
let buffer = baseAddress.assumingMemoryBound(to: UInt8.self)
var r = buffer[0]
var g = buffer[1]
var b = buffer[2]
var a = buffer[3]
print("value: ,(\(r),\(g),\(b),\(a))")
}
}
I tried to get the bitmap of the sourcePixel, and crate the CIImage from the bitmap.
But I didn't manage. I got image that looks like all the values are zero
if let sourcePixel = request.sourceFrame(byTrackID: layerInstruction.trackID) {
print (sourcePixel)
let results = CVPixelBufferLockBaseAddress(sourcePixel, .readOnly)
if let baseAddress = CVPixelBufferGetBaseAddressOfPlane(sourcePixel, 0) {
let width = CVPixelBufferGetWidth(sourcePixel)
let height = CVPixelBufferGetHeight(sourcePixel)
let bitmapData = Data(bytesNoCopy: baseAddress, count: width * height * 4, deallocator: .none)
let ciImage = CIImage(bitmapData: bitmapData, bytesPerRow: width * 4, size: CGSize(width: width, height: height), format: .ABGR8, colorSpace: CGColorSpaceCreateDeviceRGB())
}
}
Does using the bitmap is the right approach? if yes what am I doing wrong? If not what should I use?
And another related question, If I print the sourceFrame I got
<CVPixelBuffer 0x282458fa0 width=1080 height=1920 pixelFormat=420f iosurface=0x281778830 planes=2 poolName=22133:decode_1>.
<Plane 0 width=1080 height=1920 bytesPerRow=1088>.
<Plane 1 width=540 height=960 bytesPerRow=1088>
<attributes={
PixelFormatDescription = {
BitsPerComponent = 8;
ComponentRange = FullRange;
ContainsAlpha = 0;
ContainsGrayscale = 0;
ContainsRGB = 0;
ContainsYCbCr = 1;
FillExtendedPixelsCallback = {length = 24, bytes = 0x00000000000000001060248b010000000000000000000000};
IOSurfaceCoreAnimationCompatibility = 1;
IOSurfaceCoreAnimationCompatibilityHTPCOK = 1;
IOSurfaceOpenGLESFBOCompatibility = 1;
IOSurfaceOpenGLESTextureCompatibility = 1;
OpenGLESCompatibility = 1;
PixelFormat = 875704422;
Planes = (
{
BitsPerBlock = 8;
BlackBlock = {length = 1, bytes = 0x00};
},
{
BitsPerBlock = 16;
BlackBlock = {length = 2, bytes = 0x8080};
HorizontalSubsampling = 2;
VerticalSubsampling = 2;
}
);
};
} propagatedAttachments={
AlphaChannelMode = StraightAlpha;
CVFieldCount = 1;
CVImageBufferChromaLocationBottomField = Left;
CVImageBufferChromaLocationTopField = Left;
CVImageBufferColorPrimaries = "ITU_R_709_2";
CVImageBufferTransferFunction = "ITU_R_709_2";
CVImageBufferYCbCrMatrix = "ITU_R_709_2";
CVPixelAspectRatio = {
HorizontalSpacing = 1;
VerticalSpacing = 1;
};
} nonPropagatedAttachments={.
Why does the video have two planes?
why does it say that it contains no alpha?

SKTexture: Error loading image resource: "Tile_-4412407809"

I'm trying to find out what is causing this Tile_xxxxx name to be some sort of long random number in this code.
SKTexture: Error loading image resource: "Tile_-4412407809"
The code related to it is in this tutorial: https://www.raywenderlich.com/54-how-to-make-a-game-like-candy-crush-with-spritekit-and-swift-part-2
The Tile name is set in the swift file GameScene.swift line 144.
let name = String(format: "Tile_%ld", value)
and it's probably best if I put the code from the file in here. Something is setting "value" to something it can't use. But what? Where is it getting hashValue from
var value = topLeft.hashValue
import SpriteKit
import GameplayKit
class GameScene: SKScene {
// Sound FX
let swapSound = SKAction.playSoundFileNamed("Chomp.wav", waitForCompletion: false)
let invalidSwapSound = SKAction.playSoundFileNamed("Error.wav", waitForCompletion: false)
let matchSound = SKAction.playSoundFileNamed("Ka-Ching.wav", waitForCompletion: false)
let fallingCookieSound = SKAction.playSoundFileNamed("Scrape.wav", waitForCompletion: false)
let addCookieSound = SKAction.playSoundFileNamed("Drip.wav", waitForCompletion: false)
var level: Level!
let tilesLayer = SKNode()
let cropLayer = SKCropNode()
let maskLayer = SKNode()
let tileWidth: CGFloat = 32.0
let tileHeight: CGFloat = 36.0
let gameLayer = SKNode()
let cookiesLayer = SKNode()
var swipeHandler: ((Swap) -> Void)?
private var swipeFromColumn: Int?
private var swipeFromRow: Int?
private var selectionSprite = SKSpriteNode()
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder) is not used in this app")
}
override init(size: CGSize) {
super.init(size: size)
anchorPoint = CGPoint(x: 0.5, y: 0.5)
// This is the Witches background image
let background = SKSpriteNode(imageNamed: "Background")
background.size = size
addChild(background)
addChild(gameLayer)
gameLayer.isHidden = true
let layerPosition = CGPoint(
x: -tileWidth * CGFloat(numColumns) / 2,
y: -tileHeight * CGFloat(numRows) / 2)
tilesLayer.position = layerPosition
maskLayer.position = layerPosition
cropLayer.maskNode = maskLayer
gameLayer.addChild(tilesLayer)
gameLayer.addChild(cropLayer)
cookiesLayer.position = layerPosition
cropLayer.addChild(cookiesLayer)
let _ = SKLabelNode(fontNamed: "GillSans-BoldItalic")
}
func addSprites(for cookies: Set<Cookie>) {
for cookie in cookies {
let sprite = SKSpriteNode(imageNamed: cookie.cookieType.spriteName)
sprite.size = CGSize(width: tileWidth, height: tileHeight)
sprite.position = pointFor(column: cookie.column, row: cookie.row)
cookiesLayer.addChild(sprite)
cookie.sprite = sprite
// Give each cookie sprite a small, random delay. Then fade them in.
sprite.alpha = 0
sprite.xScale = 0.5
sprite.yScale = 0.5
sprite.run(
SKAction.sequence([
SKAction.wait(forDuration: 0.25, withRange: 0.5),
SKAction.group([
SKAction.fadeIn(withDuration: 0.25),
SKAction.scale(to: 1.0, duration: 0.25)
])
]))
}
}
func addTiles() {
for row in 0..<numRows {
for column in 0..<numColumns {
if level.tileAt(column: column, row: row) != nil {
let tileNode = SKSpriteNode(imageNamed: "MaskTile")
tileNode.size = CGSize(width: tileWidth, height: tileHeight)
tileNode.position = pointFor(column: column, row: row)
maskLayer.addChild(tileNode)
}
}
}
for row in 0...numRows {
for column in 0...numColumns {
let topLeft = (column > 0) && (row < numRows)
&& level.tileAt(column: column - 1, row: row) != nil
let bottomLeft = (column > 0) && (row > 0)
&& level.tileAt(column: column - 1, row: row - 1) != nil
let topRight = (column < numColumns) && (row < numRows)
&& level.tileAt(column: column, row: row) != nil
let bottomRight = (column < numColumns) && (row > 0)
&& level.tileAt(column: column, row: row - 1) != nil
var value = topLeft.hashValue
value = value | topRight.hashValue << 1
value = value | bottomLeft.hashValue << 2
value = value | bottomRight.hashValue << 3
// Values 0 (no tiles), 6 and 9 (two opposite tiles) are not drawn.
if value != 0 && value != 6 && value != 9 {
let name = String(format: "Tile_%ld", value)
//print(name)
let tileNode = SKSpriteNode(imageNamed: name)
tileNode.size = CGSize(width: tileWidth, height: tileHeight)
var point = pointFor(column: column, row: row)
point.x -= tileWidth / 2
point.y -= tileHeight / 2
tileNode.position = point
tilesLayer.addChild(tileNode)
}
}
}
}
Fixed.
Change
var value = topLeft.hashable
value = value | topRight.hashable << 1
value = value | bottomLeft.hashable << 2
value = value | bottomRight.hashable << 3
to
var value = (topLeft ? 1 : 0)
value = value | (topRight ? 1 : 0) << 1
value = value | (bottomLeft ? 1 : 0) << 2
value = value | (bottomRight ? 1 : 0) << 3

Execution was interrupted in Swift when creating Grayscale filter to RGB Image

I am very new to Swift. I am trying to create a Grayscale filter using the mean method.
This is the RGBAImage.swift file I am using:
import UIKit
public struct Pixel {
public var value: UInt32
public var red: UInt8 {
get {
return UInt8(value & 0xFF)
}
set {
value = UInt32(newValue) | (value & 0xFFFFFF00)
}
}
public var green: UInt8 {
get {
return UInt8((value >> 8) & 0xFF)
}
set {
value = (UInt32(newValue) << 8) | (value & 0xFFFF00FF)
}
}
public var blue: UInt8 {
get {
return UInt8((value >> 16) & 0xFF)
}
set {
value = (UInt32(newValue) << 16) | (value & 0xFF00FFFF)
}
}
public var alpha: UInt8 {
get {
return UInt8((value >> 24) & 0xFF)
}
set {
value = (UInt32(newValue) << 24) | (value & 0x00FFFFFF)
}
}
}
public struct RGBAImage {
public var pixels: UnsafeMutableBufferPointer<Pixel>
public var width: Int
public var height: Int
public init?(image: UIImage) {
guard let cgImage = image.CGImage else { return nil }
// Redraw image for correct pixel format
let colorSpace = CGColorSpaceCreateDeviceRGB()
var bitmapInfo: UInt32 = CGBitmapInfo.ByteOrder32Big.rawValue
bitmapInfo |= CGImageAlphaInfo.PremultipliedLast.rawValue & CGBitmapInfo.AlphaInfoMask.rawValue
width = Int(image.size.width)
height = Int(image.size.height)
let bytesPerRow = width * 4
let imageData = UnsafeMutablePointer<Pixel>.alloc(width * height)
guard let imageContext = CGBitmapContextCreate(imageData, width, height, 8, bytesPerRow, colorSpace, bitmapInfo) else { return nil }
CGContextDrawImage(imageContext, CGRect(origin: CGPointZero, size: image.size), cgImage)
pixels = UnsafeMutableBufferPointer<Pixel>(start: imageData, count: width * height)
}
public func toUIImage() -> UIImage? {
let colorSpace = CGColorSpaceCreateDeviceRGB()
var bitmapInfo: UInt32 = CGBitmapInfo.ByteOrder32Big.rawValue
bitmapInfo |= CGImageAlphaInfo.PremultipliedLast.rawValue & CGBitmapInfo.AlphaInfoMask.rawValue
let bytesPerRow = width * 4
let imageContext = CGBitmapContextCreateWithData(pixels.baseAddress, width, height, 8, bytesPerRow, colorSpace, bitmapInfo, nil, nil)
guard let cgImage = CGBitmapContextCreateImage(imageContext) else {return nil}
let image = UIImage(CGImage: cgImage)
return image
}
}
And this is my code:
import UIKit
let image = UIImage(named: "sample")!
let myRGBA = RGBAImage(image: image)!
struct grayscale_mean_method {
var result: UIImage {
for y in 0..<myRGBA.height {
for x in 0..<myRGBA.width {
let index = y * myRGBA.width + x
var pixel = myRGBA.pixels[index]
let intensity = (pixel.blue + pixel.green + pixel.red) / 3
let gray = UInt8(max(0,min(255, intensity)))
pixel.red = gray
pixel.green = gray
pixel.blue = gray
myRGBA.pixels[index] = pixel
}
}
return myRGBA.toUIImage()!
}
}
var newImage = grayscale_mean_method.init().result
But I am getting an error when assigning
let intensity = (pixel.blue + pixel.green + pixel.red) / 3
Execution was interrupted, reason = EXC_BAD_INSTRUCTION (code =EXC_l386_INVOP, subcode=0x0)
I searched but didn't find a solution. Any idea what am I doing wrong?
In
let intensity = (pixel.blue + pixel.green + pixel.red) / 3
all operands of the addition have the type UInt8, and the addition
overflows (with a runtime exception) if the result does not fit
into the range of UInt8. To solve the problem, convert all values
to Int before computing the average:
let intensity = (Int(pixel.blue) + Int(pixel.green) + Int(pixel.red)) / 3

Procedural Level Generation With Cellular Automaton In Swift

Is there a easy way to create a procedural level with a cellular automaton in swift/SpriteKit(library?)? I want to create a 'cave' with 11 fields in the height and 22 width. These should be randomly created and every field without a wall should be reached.
I just found a documentation using Objective-C, which I am not familiar with. I spend quite some time trying to understand the code and follow the example without success.
PS: If there is an easier way I appreciate some algorithms
I made a Playground where you can experiment
//: Playground - noun: a place where people can play
import UIKit
import SpriteKit
import XCPlayground
class Cave {
var cellmap:[[Bool]]
let chanceToStartAlive = 35
let deathLimit = 3
let birthLimit = 4
var xCell = 40 // number of cell in x axes
var yCell = 20 // number of cell in y axes
var wCell = 20 // cell width
var hCell = 20 // cell height
init(){
cellmap = Array(count:yCell, repeatedValue:
Array(count:xCell, repeatedValue:false))
cellmap = self.initialiseMap(xCell, yIndex:yCell)
}
func initialiseMap(xIndex:Int, yIndex:Int) -> [[Bool]]{
var map:[[Bool]] = Array(count:yIndex, repeatedValue:
Array(count:xIndex, repeatedValue:false))
for y in 0...(yIndex - 1) {
for x in 0...(xIndex - 1) {
let diceRoll = Int(arc4random_uniform(100))
if diceRoll < chanceToStartAlive {
map[y][x] = true
} else {
map[y][x] = false
}
}
}
return map
}
func addSprite(scene:SKScene){
for (indexY, row) in cellmap.enumerate(){
for (indexX, isWall) in row.enumerate(){
if isWall {
let wall = SKSpriteNode(color: UIColor.redColor(), size: CGSize(width: wCell, height: hCell))
wall.position = CGPoint(x: (indexX * wCell) + (wCell / 2) , y: (indexY * hCell) + (hCell / 2) )
scene.addChild(wall)
}
}
}
}
func countAliveNeighbours(x:Int, y:Int) -> Int{
var count = 0
var neighbour_x = 0
var neighbour_y = 0
for i in -1...1 {
for j in -1...1 {
neighbour_x = x + j
neighbour_y = y + i
if(i == 0 && j == 0){
} else if(neighbour_x < 0 || neighbour_y < 0 || neighbour_y >= cellmap.count || neighbour_x >= cellmap[0].count){
count = count + 1
} else if(cellmap[neighbour_y][neighbour_x]){
count = count + 1
}
}
}
return count
}
func applyRules(){
var newMap:[[Bool]] = Array(count:yCell, repeatedValue:
Array(count:xCell, repeatedValue:false))
for y in 0...(cellmap.count - 1) {
for x in 0...(cellmap[0].count - 1) {
let nbs = countAliveNeighbours( x, y: y);
if(cellmap[y][x]){
if(nbs < deathLimit){
newMap[y][x] = false;
}
else{
newMap[y][x] = true;
}
} else{
if(nbs > birthLimit){
newMap[y][x] = true;
}
else{
newMap[y][x] = false;
}
}
}
}
cellmap = newMap
}
}
let view:SKView = SKView(frame: CGRectMake(0, 0, 1024, 768))
XCPShowView("Live View", view: view)
let scene:SKScene = SKScene(size: CGSizeMake(1024, 768))
scene.scaleMode = SKSceneScaleMode.AspectFit
let aCave = Cave()
aCave.applyRules()
aCave.applyRules()
aCave.addSprite(scene)
view.presentScene(scene)
Updated the playground code for Xcode 8 and Swift 3. I swapped the X and Y cell count since you will likely see the view in a "portrait" orientation.
Remember to open the Assistant Editor to view the results. It also takes a little while to execute, so give it a couple of minutes to run the algorithm.
//: Playground - noun: a place where people can play
import UIKit
import SpriteKit
import XCPlayground
import PlaygroundSupport
class Cave {
var cellmap:[[Bool]]
let chanceToStartAlive = 35
let deathLimit = 3
let birthLimit = 4
var xCell = 20 // number of cell in x axes
var yCell = 40 // number of cell in y axes
var wCell = 20 // cell width
var hCell = 20 // cell height
init(){
cellmap = Array(repeating:
Array(repeating:false, count:xCell), count:yCell)
cellmap = self.initialiseMap(xIndex: xCell, yIndex:yCell)
}
func initialiseMap(xIndex:Int, yIndex:Int) -> [[Bool]]{
var map:[[Bool]] = Array(repeating:
Array(repeating:false, count:xIndex), count:yIndex)
for y in 0...(yIndex - 1) {
for x in 0...(xIndex - 1) {
let diceRoll = Int(arc4random_uniform(100))
if diceRoll < chanceToStartAlive {
map[y][x] = true
} else {
map[y][x] = false
}
}
}
return map
}
func addSprite(scene:SKScene){
for (indexY, row) in cellmap.enumerated(){
for (indexX, isWall) in row.enumerated(){
if isWall {
let wall = SKSpriteNode(color: UIColor.red, size: CGSize(width: wCell, height: hCell))
wall.position = CGPoint(x: (indexX * wCell) + (wCell / 2) , y: (indexY * hCell) + (hCell / 2) )
scene.addChild(wall)
}
}
}
}
func countAliveNeighbours(x:Int, y:Int) -> Int{
var count = 0
var neighbour_x = 0
var neighbour_y = 0
for i in -1...1 {
for j in -1...1 {
neighbour_x = x + j
neighbour_y = y + i
if(i == 0 && j == 0){
} else if(neighbour_x < 0 || neighbour_y < 0 || neighbour_y >= cellmap.count || neighbour_x >= cellmap[0].count){
count = count + 1
} else if(cellmap[neighbour_y][neighbour_x]){
count = count + 1
}
}
}
return count
}
func applyRules(){
var newMap:[[Bool]] = Array(repeating:
Array(repeating:false, count:xCell), count:yCell)
for y in 0...(cellmap.count - 1) {
for x in 0...(cellmap[0].count - 1) {
let nbs = countAliveNeighbours( x: x, y: y);
if(cellmap[y][x]){
if(nbs < deathLimit){
newMap[y][x] = false;
}
else{
newMap[y][x] = true;
}
} else{
if(nbs > birthLimit){
newMap[y][x] = true;
}
else{
newMap[y][x] = false;
}
}
}
}
cellmap = newMap
}
}
let view:SKView = SKView(frame: CGRect(x: 0, y: 0, width: 768, height: 1024))
let scene:SKScene = SKScene(size: CGSize(width: 768, height: 1024))
scene.scaleMode = SKSceneScaleMode.aspectFit
PlaygroundPage.current.needsIndefiniteExecution = true
PlaygroundPage.current.liveView = view
let aCave = Cave()
aCave.applyRules()
aCave.applyRules()
aCave.addSprite(scene: scene)
view.presentScene(scene)

Accessing one class from another: Instance Member cannot be used on type

I'm getting into Swift by using the Xcode Playground, and I'm following an online course regarding simple image filtering.
So far, here's my code:
import UIKit
let image = UIImage(named: "sample")!
let rgbaImage = RGBAImage(image: image)
class ImageInfo {
let rgbaImage = RGBAImage(image: image)
func getAvgCols() -> [String:Int] {
var totalRed = 0
var totalGreen = 0
var totalBlue = 0
var avgCol = [String:Int]()
for y in 0..<rgbaImage!.height{
for x in 0..<rgbaImage!.width{
let index = y * rgbaImage!.width + x
let pixel = rgbaImage!.pixels[index]
totalRed += Int(pixel.red)
totalGreen += Int(pixel.green)
totalBlue += Int(pixel.blue)
}
}
let pixelCount = rgbaImage!.width * rgbaImage!.height
avgCol["Red"] = (totalRed / pixelCount)
avgCol["Green"] = (totalGreen / pixelCount)
avgCol["Blue"] = (totalBlue / pixelCount)
return avgCol
}
}
let imageInfo = ImageInfo()
var avgRed = imageInfo.getAvgCols()["Red"]!
var avgGreen = imageInfo.getAvgCols()["Green"]!
var avgBlue = imageInfo.getAvgCols()["Blue"]!
var modifier = 20
// IMAGE EDITING LOOP
for y in 0..<rgbaImage!.height{
for x in 0..<rgbaImage!.width{
let index = y * rgbaImage!.width + x
var pixel = rgbaImage!.pixels[index]
let redDelta = Int(pixel.red) - avgRed
let greenDelta = Int(pixel.green) - avgGreen
let blueDelta = Int(pixel.blue) - avgBlue
let redModifier = Double(modifier) * 0.49
let greenModifier = Double(modifier) * 0.25
let blueModifier = Double(modifier) * 0.07
pixel.red = UInt8(max(min(255, avgRed + Int(redModifier) * redDelta),0))
pixel.green = UInt8(max(min(255, avgGreen + Int(greenModifier) * greenDelta),0))
pixel.blue = UInt8(max(min(255, avgBlue + Int(blueModifier) * blueDelta),0))
rgbaImage!.pixels[index] = pixel
}
}
let newImage = rgbaImage!.toUIImage()!
So far, that works well. The last bit of code modifies the image on a per-pixel basis, and the end result is a poor-man's attempt at some sepia colour.
The problem, however, is when I stick the entire image editing loop in another class, like so:
class ImageEdit {
let imageInfo = ImageInfo()
var avgRed = imageInfo.getAvgCols()["Red"]!
var avgGreen = imageInfo.getAvgCols()["Green"]!
var avgBlue = imageInfo.getAvgCols()["Blue"]!
func applyFilter(filter: String, modifier: Int) -> UIImage{
for y in 0..<rgbaImage!.height{
for x in 0..<rgbaImage!.width{
let index = y * rgbaImage!.width + x
var pixel = rgbaImage!.pixels[index]
let redDelta = Int(pixel.red) - avgRed
let greenDelta = Int(pixel.green) - avgGreen
let blueDelta = Int(pixel.blue) - avgBlue
let redModifier = Double(modifier) * 0.49
let greenModifier = Double(modifier) * 0.25
let blueModifier = Double(modifier) * 0.07
pixel.red = UInt8(max(min(255, avgRed + Int(redModifier) * redDelta),0))
pixel.green = UInt8(max(min(255, avgGreen + Int(greenModifier) * greenDelta),0))
pixel.blue = UInt8(max(min(255, avgBlue + Int(blueModifier) * blueDelta),0))
rgbaImage!.pixels[index] = pixel
}
}
let newImage = rgbaImage!.toUIImage()!
return newImage
}
}
NOTE: Since I am using the play ground, both classes are in the same file. For clarification just take my previous code, and replace everything below class ImageInfo with class ImageEdit
I would need access to avgRed, avgGreen, and avgBlue, but all I'm getting is
instance member 'imageInfo' cannot be used on type 'ImageEdit'
and since I'm a noob at Swift, I'm not exactly sure how to fix it.
Did I miss anything?
EDIT: Here's RGBAImage. I did not write this code, it is free to download to anyone who takes the online course.
import UIKit
public struct Pixel {
public var value: UInt32
public var red: UInt8 {
get {
return UInt8(value & 0xFF)
}
set {
value = UInt32(newValue) | (value & 0xFFFFFF00)
}
}
public var green: UInt8 {
get {
return UInt8((value >> 8) & 0xFF)
}
set {
value = (UInt32(newValue) << 8) | (value & 0xFFFF00FF)
}
}
public var blue: UInt8 {
get {
return UInt8((value >> 16) & 0xFF)
}
set {
value = (UInt32(newValue) << 16) | (value & 0xFF00FFFF)
}
}
public var alpha: UInt8 {
get {
return UInt8((value >> 24) & 0xFF)
}
set {
value = (UInt32(newValue) << 24) | (value & 0x00FFFFFF)
}
}
}
public struct RGBAImage {
public var pixels: UnsafeMutableBufferPointer<Pixel>
public var width: Int
public var height: Int
public init?(image: UIImage) {
guard let cgImage = image.CGImage else { return nil }
// Redraw image for correct pixel format
let colorSpace = CGColorSpaceCreateDeviceRGB()
var bitmapInfo: UInt32 = CGBitmapInfo.ByteOrder32Big.rawValue
bitmapInfo |= CGImageAlphaInfo.PremultipliedLast.rawValue & CGBitmapInfo.AlphaInfoMask.rawValue
width = Int(image.size.width)
height = Int(image.size.height)
let bytesPerRow = width * 4
let imageData = UnsafeMutablePointer<Pixel>.alloc(width * height)
guard let imageContext = CGBitmapContextCreate(imageData, width, height, 8, bytesPerRow, colorSpace, bitmapInfo) else { return nil }
CGContextDrawImage(imageContext, CGRect(origin: CGPointZero, size: image.size), cgImage)
pixels = UnsafeMutableBufferPointer<Pixel>(start: imageData, count: width * height)
}
public func toUIImage() -> UIImage? {
let colorSpace = CGColorSpaceCreateDeviceRGB()
var bitmapInfo: UInt32 = CGBitmapInfo.ByteOrder32Big.rawValue
bitmapInfo |= CGImageAlphaInfo.PremultipliedLast.rawValue & CGBitmapInfo.AlphaInfoMask.rawValue
let bytesPerRow = width * 4
let imageContext = CGBitmapContextCreateWithData(pixels.baseAddress, width, height, 8, bytesPerRow, colorSpace, bitmapInfo, nil, nil)
guard let cgImage = CGBitmapContextCreateImage(imageContext) else {return nil}
let image = UIImage(CGImage: cgImage)
return image
}
}
The problem is that you are calling the attribute imageInfo inside the ImageEdit before initialising the class.
It is only when the initialisation is called that everything becomes something. So avgRed can't get values from a function of imageInfo because it has not been constructed yet.
Doing this inside a function (init() is basically a function) is just find. Doing this inside a class doesn't work.
This does not work :
class A {
let attribute : Int = 0
init() {
}
}
class B {
let attributeA = A()
let attributeB = attributeA.attribute // this is your problem
}
This does :
For all the attributes that rely on other attributes to be constructed, you use a late init. All the attributes that rely on external input, failable functions,... you declare as an optional.
Late init : var / let attrName : Type
Optional : var / let attrName : Type ? / !
class B {
let attributeA = A()
let attributeB : Int
var attributeC : Int?
init () {
attributeB = attributeA.attribute
attributeC = somethingOptional()
}
func somethingOptional() -> Int? {
return 0 // not really optional, but just illustration
}
}
So for you it would be something like this :
class ImageEdit {
let imageInfo = ImageInfo()
let dict : [String:Int]
init () {
dict = imageInfo.getAvgCols()
}
}