Tony Adams
Tony Adams

Reputation: 701

Swift: Operate on a Given Property

I have a struct which represents a bitmap image in RGBA form. That struct has properties for each of the color channels, red, blue and green.

I'm trying to build an image filter which adjusts a particular color level. This version works fine, but clearly the pixel.COLOR property is hard coded.

func boostColor(inputColor: String) -> UIImage {
    let avgColor = "avg" + inputColor
   // let color = inputColor.lowercaseString // plan to use this to set the property
    for y in 0..<self.inputRGBA.height {
        for x in 0..<self.inputRGBA.width {
            let index = y *  self.inputRGBA.width + x
            var pixel = self.inputRGBA.pixels[index]
            // see how far this pixel's chosen color varies from the average
            let colorDiff = Int(pixel.red) - Int(self.pixelColorAverages[avgColor]!)
            // if less than average red,
            if(colorDiff>0){
                pixel.red = UInt8( max(0,min(255,Int(self.pixelColorAverages[avgColor]!) + colorDiff*100 ) ) )
                // write the adjusted pixel back to the image object
                self.inputRGBA.pixels[index] = pixel
            }
        }
    }
    // write the filtered RGBA image to a UIImage
    return self.inputRGBA.toUIImage()!
}

This loop's function takes in a string value of either "red", "blue" or "green". What I would like to do is replace instances of

pixel.red

with

pixel.[ONE OF THE INPUT COLORS]

Thanks! (This is an academic playground project. I realize I'm probably re-inventing the wheel by building a color filter. It isn't the color filter itself that I'm interested in, but how to solve this property problem in Swift.) Here is the struct:

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)
    }
}

}

Upvotes: 3

Views: 164

Answers (3)

Wallace Campos
Wallace Campos

Reputation: 1301

If you want to pass the color name as string, why don't you define a subscript for your structure? It's awesome and very flexible.

Classes, structures, and enumerations can define subscripts, which are shortcuts for accessing the member elements of a collection, list, or sequence. You use subscripts to set and retrieve values by index without needing separate methods for setting and retrieval.

struct Pixel {
  // ...
  subscript(color: String) -> UInt8? {
    get {
      switch color {
      case "red":
        return red
      case "green":
        return green
      case "blue":
        return blue
      case "alpha":
        return alpha
      default:
        return nil
      }
    }
    set {
      if newValue != nil {
        switch color {
        case "red":
          red = newValue!
        case "green":
          green = newValue!
        case "blue":
          blue = newValue!
        case "alpha":
          alpha = newValue!
        default:
          break
        }
      }
    }
  }
}

var pixel = Pixel(value: 0)
pixel["red"] = 128

Note that it will fail silently in case a wrong color is set like in pixel["yellow"] = 155. To avoid situations like that you could implement a enum and use it as key.

enum Color {
  case Red
  case Green
  case Blue
  case Alpha
}

extension Pixel {
  subscript(color: Color) -> UInt8 {
    get {
      switch color {
      case .Red:
        return red
      case .Green:
        return green
      case .Blue:
        return blue
      case .Alpha:
        return alpha
      }
    }
    set {
      switch color {
      case .Red:
        red = newValue
      case .Green:
        green = newValue
      case .Blue:
        blue = newValue
      case .Alpha:
        alpha = newValue
      }
    }
  }
}

var pixel = Pixel(value: 0)
pixel[.Red] = 128

Upvotes: 0

R Menke
R Menke

Reputation: 8391

Implementing Key Value Coding is possible with reflecting but it is very un-Swift. In your case it is also just a bad idea since there are much more powerful and useful alternatives.

I would go for an OptionSetType since there are only a fixed number of possibilities (RGBA). An enum would be fine too but an OptionSetType has the extra benefit of being a "set". So you can choose to alter RGB and not A in a very convenient way.

interesting read on OptionSetType


This is an enum to store the rawValues for the OptionSetType. Can be used on it's own.

public enum RGBAColor : Int, CustomStringConvertible {
    case Red = 1, Green = 2, Blue = 4, Alpha = 8

    public var description : String { // if you still want to have a String value for each color
        var shift = 0
        while (rawValue >> shift != 1) { shift++ }
        return ["Red","Green","Blue","Alpha"][shift]
    }
}

This is the OptionSetType. I create snippets of these kind of structures and copy / paste / modify them when needed. No need to reinvent the wheel when it will always be the same pattern.

public struct RGBAColors : OptionSetType, CustomStringConvertible {

    public  let rawValue : Int
    public  init(rawValue:Int) { self.rawValue = rawValue}
    private init(_ color:RGBAColor) { self.rawValue = color.rawValue }

    static let Red  = RGBAColors(RGBAColor.Red)
    static let Green  = RGBAColors(RGBAColor.Green)
    static let Blue = RGBAColors(RGBAColor.Blue)
    static let Alpha = RGBAColors(RGBAColor.Alpha)

    public var description : String {
        var result = ""
        var shift = 0

        while let currentcolor = RGBAColor(rawValue: 1 << shift++){
            if self.contains(RGBAColors(currentcolor)){
                result += (result.characters.count == 0) ? "\(currentcolor)" : ",\(currentcolor)"
            }
        }

        return "[\(result)]"
    }
}

Now you can add a function to your Pixel struct that will return the color value based on an enum case. You can also choose to pass the OptionSetType directly to this function.

public struct Pixel {

    ...

    public func getValueForColor(color:RGBAColor) -> UInt8 {
        switch color {
        case .Red : return self.red
        case .Green : return self.green
        case .Blue : return self.blue
        case .Alpha : return self.alpha
        }
    }
}

This is just a control flow function. You can now do different things based on which color that needs to be altered. (or apply the same to all) This is also the only reason to choose for an OptionSetType, if you don't need this, just go for an enum. Since the OptionSetType builds on the enum you can always add it later.

func someFuncWithOptions(colors:RGBAColors) {

    if colors.contains(.Red) {
        someFunc(.Red)
    }
    if colors.contains(.Blue) {
        someFunc(.Blue)
    }
    if colors.contains(.Green) {
        someFunc(.Green)
    }
    if colors.contains(.Alpha) {
        someFunc(.Alpha)
    }
}

This will be your actual function

func someFunc(color:RGBAColor) {
    // do something with pixel.getValueForColor(color)
    print(color)
}

This will be exposed to other parts of your code. So the actual usage of all this will be really simpel. Just input .SomeColor.

The best thing is that the compiler will warn you if you change all this to CMYK for example. Key Value Coding will never generate warnings, it will just crash.

Because it is a "set" and not per definition one color, you pass a collection of options. [.option1,.option2,...] or you pass nothing []

someFuncWithOptions([.Red]) // red
someFuncWithOptions([.Green,.Red]) // red green

You can also add convenient collections to the OptionSetType. This one will be a set of all RGB colors but without the Alpha channel.

static let RGB : RGBAColors = [.Red,.Green,.Blue]

someFuncWithOptions([.RGB]) // red blue green

Upvotes: 1

adpalumbo
adpalumbo

Reputation: 3031

One approach would be to add a KVC-like syntax to Swift using Swift's built-in reflection capabilities. You can learn how to do that here, although it's serious overkill for this problem:

http://blog.shiftybit.net/2015/07/kvc-in-swift/

A more narrow and suitable solution would just be to write a function for your struct that takes a String argument, switches over it, and returns the desired value. You'd also want to write another function for your struct that takes a String and a value, and sets the value on your struct.

Examples:

struct Pixel
{
  // ... rest of struct definition ...

  func getColor(color:String) -> UInt8
  {
     switch color
     {
         case "red":
           return self.red
         case "green":
           return self.green
         case "blue":
           return self.blue
         case "alpha":
           return self.alpha
     }  
  }
  func setColor(color:String, value:UInt8)
  {
     switch color
     {
         case "red":
           self.red = value
         case "green":
           self.green = value
         case "blue":
           self.blue = value
         case "alpha":
           self.alpha = value
     }  
   }
}

If you wanted, you could take it a step further and use an enum instead of a String for improved clarity and type safety.

Upvotes: 1

Related Questions