stakbeko
stakbeko

Reputation: 31

Change RGB Color to HSV swift

Have a nice day. I'm doing RGB LED color changing application. I'm doing the color selection with HSB. These colors have hue, saturation, brightness and alpha values. I save these values ​​in the database. I am reading this data with arduino. But how do I find out which color these color values ​​belong to? So how do I find the equivalent of the color I chose through arduino?

import Foundation
import UIKit

// The view to edit HSB color components.
public class EFHSBView: UIView, EFColorView, UITextFieldDelegate {

    let EFColorSampleViewHeight: CGFloat = 30.0
    let EFViewMargin: CGFloat = 20.0
    let EFColorWheelDimension: CGFloat = 200.0

    private let colorWheel: EFColorWheelView = EFColorWheelView()
    let brightnessView: EFColorComponentView = EFColorComponentView()
    private let colorSample: UIView = UIView()

    private var colorComponents: HSB = HSB(1, 1, 1, 1)
    private var layoutConstraints: [NSLayoutConstraint] = []

    weak public var delegate: EFColorViewDelegate?

    public var isTouched: Bool {
        if self.colorWheel.isTouched {
            return true
        }

        if self.brightnessView.isTouched {
            return true
        }

        return false
    }

    public var color: UIColor {
        get {
            return UIColor(
                hue: colorComponents.hue,
                saturation: colorComponents.saturation,
                brightness: colorComponents.brightness,
                alpha: colorComponents.alpha
            )
        }
        set {

            colorComponents = EFRGB2HSB(rgb: EFRGBColorComponents(color: newValue))
            self.reloadData()
        }
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        self.ef_baseInit()
    }

    required public init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        self.ef_baseInit()
    }

    func reloadData() {
        colorSample.backgroundColor = self.color
        colorSample.accessibilityValue = EFHexStringFromColor(color: self.color)
        self.ef_reloadViewsWithColorComponents(colorComponents: colorComponents)
        self.colorWheel.display(self.colorWheel.layer)
    }

    override public func updateConstraints() {
        self.ef_updateConstraints()
        super.updateConstraints()
    }

    // MARK:- Private methods
    private func ef_baseInit() {
        self.accessibilityLabel = "hsb_view"

        colorSample.accessibilityLabel = "color_sample"
        colorSample.layer.borderColor = UIColor.black.cgColor
        colorSample.layer.borderWidth = 0.5
        colorSample.translatesAutoresizingMaskIntoConstraints = false
        self.addSubview(colorSample)

        colorWheel.translatesAutoresizingMaskIntoConstraints = false
        self.addSubview(colorWheel)

        brightnessView.title = NSLocalizedString("Brightness", comment: "")
        brightnessView.maximumValue = EFHSBColorComponentMaxValue
        brightnessView.format = "%.2f"
        brightnessView.translatesAutoresizingMaskIntoConstraints = false
        brightnessView.setColors(colors: [UIColor.black, UIColor.white])
        self.addSubview(brightnessView)

        colorWheel.addTarget(
            self, action: #selector(ef_colorDidChangeValue(sender:)), for: UIControl.Event.valueChanged
        )
        brightnessView.addTarget(
            self, action: #selector(ef_brightnessDidChangeValue(sender:)), for: UIControl.Event.valueChanged
        )

        self.setNeedsUpdateConstraints()
    }

    private func ef_updateConstraints() {
        // remove all constraints first
        if !layoutConstraints.isEmpty {
            self.removeConstraints(layoutConstraints)
        }

        layoutConstraints = UIUserInterfaceSizeClass.compact == self.traitCollection.verticalSizeClass
            ? self.ef_constraintsForCompactVerticalSizeClass()
            : self.ef_constraintsForRegularVerticalSizeClass()

        self.addConstraints(layoutConstraints)
    }

    private func ef_constraintsForRegularVerticalSizeClass() -> [NSLayoutConstraint] {
        let metrics = [
            "margin" : EFViewMargin,
            "height" : EFColorSampleViewHeight,
            "color_wheel_dimension" : EFColorWheelDimension
        ]
        let views = [
            "colorSample" : colorSample,
            "colorWheel" : colorWheel,
            "brightnessView" : brightnessView
        ]

        var layoutConstraints: [NSLayoutConstraint] = []
        let visualFormats = [
            "H:|-margin-[colorSample]-margin-|",
            "H:|-margin-[colorWheel(>=color_wheel_dimension)]-margin-|",
            "H:|-margin-[brightnessView]-margin-|",
            "V:|-margin-[colorSample(height)]-margin-[colorWheel]-margin-[brightnessView]-(>=margin@250)-|"
        ]
        for visualFormat in visualFormats {
            layoutConstraints.append(
                contentsOf: NSLayoutConstraint.constraints(
                    withVisualFormat: visualFormat,
                    options: NSLayoutConstraint.FormatOptions(rawValue: 0),
                    metrics: metrics,
                    views: views
                )
            )
        }
        layoutConstraints.append(
            NSLayoutConstraint(
                item: colorWheel,
                attribute: NSLayoutConstraint.Attribute.width,
                relatedBy: NSLayoutConstraint.Relation.equal,
                toItem: colorWheel,
                attribute: NSLayoutConstraint.Attribute.height,
                multiplier: 1,
                constant: 0)
        )
        return layoutConstraints
    }

    private func ef_constraintsForCompactVerticalSizeClass() -> [NSLayoutConstraint] {
        let metrics = [
            "margin" : EFViewMargin,
            "height" : EFColorSampleViewHeight,
            "color_wheel_dimension" : EFColorWheelDimension
        ]
        let views = [
            "colorSample" : colorSample,
            "colorWheel" : colorWheel,
            "brightnessView" : brightnessView
        ]

        var layoutConstraints: [NSLayoutConstraint] = []
        let visualFormats = [
            "H:|-margin-[colorSample]-margin-|",
            "H:|-margin-[colorWheel(>=color_wheel_dimension)]-margin-[brightnessView]-(margin@500)-|",
            "V:|-margin-[colorSample(height)]-margin-[colorWheel]-(margin@500)-|"
        ]
        for visualFormat in visualFormats {
            layoutConstraints.append(
                contentsOf: NSLayoutConstraint.constraints(
                    withVisualFormat: visualFormat,
                    options: NSLayoutConstraint.FormatOptions(rawValue: 0),
                    metrics: metrics,
                    views: views
                )
            )
        }
        layoutConstraints.append(
            NSLayoutConstraint(
                item: colorWheel,
                attribute: NSLayoutConstraint.Attribute.width,
                relatedBy: NSLayoutConstraint.Relation.equal,
                toItem: colorWheel,
                attribute: NSLayoutConstraint.Attribute.height,
                multiplier: 1,
                constant: 0)
        )
        layoutConstraints.append(
            NSLayoutConstraint(
                item: brightnessView,
                attribute: NSLayoutConstraint.Attribute.centerY,
                relatedBy: NSLayoutConstraint.Relation.equal,
                toItem: self,
                attribute: NSLayoutConstraint.Attribute.centerY,
                multiplier: 1,
                constant: 0)
        )
        return layoutConstraints
    }

    private func ef_reloadViewsWithColorComponents(colorComponents: HSB) {
        colorWheel.hue = colorComponents.hue
        colorWheel.saturation = colorComponents.saturation
        colorWheel.brightness = colorComponents.brightness
        self.ef_updateSlidersWithColorComponents(colorComponents: colorComponents)
    }

    private func ef_updateSlidersWithColorComponents(colorComponents: HSB) {
        brightnessView.value = colorComponents.brightness
    }

    @objc private func ef_colorDidChangeValue(sender: EFColorWheelView) {
        colorComponents.hue = sender.hue
        colorComponents.saturation = sender.saturation
        self.delegate?.colorView(self, didChangeColor: self.color)
        self.reloadData()
    }

    @objc private func ef_brightnessDidChangeValue(sender: EFColorComponentView) {
        colorComponents.brightness = sender.value
        self.colorWheel.brightness = sender.value
        self.delegate?.colorView(self, didChangeColor: self.color)
        self.reloadData()
    }
}

Upvotes: 0

Views: 1900

Answers (1)

AterLux
AterLux

Reputation: 4654

Hue-Saturation-Value model is a widely used method to encode colors.

Hue is a cyclical parameter, that's why it is encoded in degrees (360 degrees = full cycle). red, green and blue components of color periodically changed along HUE axis, being at their lower value for 120 degrees, then rising to upper value within 60 degrees, remains 120 derees at upper value and then decreasing down again for next 60 degrees. The cycle repeats after 360 degrees.

HSV model

Red Green and Blue components are shifted relative to each other by 120 degrees, thus making possible to encode any proportion between them.

On the drawing above you can see, for each H value, one of RGB components is at upper value, another at lower value, and third is changing in between.

The Value component encodes the upper value of RGB. I.e. 0% value equals to black color (whatever Hue and Saturation values are), while 100% - the brightest color

The Saturation component of HSV model encodes the difference between upper and lower values of R G B components, relative to the V value. I.e. if Value is 50% and Saturation is 30% then lower value will be 35% and upper value will be 50%.

Is Saturation is zero, then color is gray, whatever Hue value is.

Thus, the code to convert HSV to RGB may be as follows:

float h = ..., s = ..., v = ...; // input values

float r, g, b;

float d = v * s; // difference between upper and lower value
float l = v - d; // calculating the lower value

// code below assumes 0 <= h < 360. Otherwise wrap the value before
if (h < 60) { // 0..60
  r = v;
  g = l + (h / 60.0) * d;
  b = l;
} else if (h < 120) {  // 60..120
  r = u - ((h - 60) / 60.0) * d;
  g = v;
  b = l;
} else if (h < 180) {  // 120..180
  r = l;
  g = v;
  b = l + ((h - 120) / 60.0) * d;
} else if (h < 240) {  // 180..240
  r = l;
  g = u - ((h - 180) / 60.0) * d;
  b = v;
} else if (h < 300) {  // 240..300
  r = l + ((h - 240) / 60.0) * d;
  g = l;
  b = v;
} else { // 300..360
  r = v;
  g = l;
  b = u - ((h - 300) / 60.0) * d;
}

Read more in Wikipedia

Note, when lighting up RGB LEDs with MCU, you may get not the same color as you see on the display. That's because displays are using Gamma Correction, i.e. 50% does not mean 50% of luminescence (usually it is closer to 25%). Thus, you may need to apply the same gamma correction to the obtained RGB values before passing them to LED PWM.

Upvotes: 1

Related Questions