Swift 中的亮度饱和度圆形垫
序言
我正在尝试使用 SwiftUI 创建一个圆形亮度饱和度垫,既用于指示(从给定颜色,显示光标位置)又用于使用(通过移动光标,获取结果颜色)。
我为我糟糕的英语道歉。
演示代码
首先,我使用两个 Circle 元素绘制圆形,并填充 LinearGradient 元素和亮度平淡模式。
public var body: some View {
ZStack(alignment: .top) {
//MARK: - Inner Color circle
Group {
Circle()
.fill(
LinearGradient(
colors: [self.minSaturatedColor, self.maxSaturatedColor],
startPoint: .leading,
endPoint: .trailing
)
)
Circle()
.fill(
LinearGradient(
colors: [.white, .black],
startPoint: .top,
endPoint: .bottom
)
)
.blendMode(.luminosity)
}
.frame(width: self.diameter, height: self.diameter, alignment: .center)
.gesture(
DragGesture(minimumDistance: 0, coordinateSpace: .local)
.onChanged(self.updateCursorPosition)
.onEnded({ _ in
self.startIndication = nil
self.changed(self.finalColor)
})
)
//MARK: - Indicator
Circle()
.stroke(Color.black, lineWidth: 2)
.foregroundColor(Color.clear)
.frame(width: self.cursorDiameter, height: self.cursorDiameter, alignment: .center)
.position(cursorPosition)
}
.frame(width: self.diameter, height: self.diameter, alignment: .center)
}
理论
我不确定这是正确的方法,但看起来是这样,因为它产生了以下具有(可能)所有色调的圆圈:
问题 1
我用来确定光标位置(从给定颜色)的简化代码是:
public var cursorPosition: CGPoint {
let hsb = self.finalColor.hsba
let x: CGFloat = hsb.saturation * self.diameter
let y: CGFloat = hsb.brightness * self.diameter
// Adjust the Apple reversed ordinate
return CGPoint(x: x, y: self.diameter - y)
}
不幸的是,这似乎不正确,因为通过给出某种颜色(即#bd4d22
) 它使指针放置在错误的位置。
问题 2
还有另一个问题:组件应该允许移动光标,从而使用正确的亮度和饱和度更新 finalColor
State 属性。
private func updateCursorPosition(_ value: DragGesture.Value) -> Void {
let hsb = self.baseColor.hsba
let saturation = value.location.x / self.diameter
let brightness = (0 - value.location.y + self.diameter) / self.diameter
self.finalColor = Color(
hue: hsb.hue,
saturation: saturation,
brightness: brightness
)
}
但结果并不是预期的,因为即使光标跟随该点(意味着计算值是正确的),所得的颜色也不是在指针后面呈现的颜色!
示例 pt. 1
在这里,正如您所看到的,完全饱和的颜色(理论上应该是圆周的 2π)是不正确的。
示例 pt. 2
问题
- 谁能解释一下我哪里失败了?
- 如果光标被拖出圆圈,任何人都可以帮助我阻止
updateCursorPosition
结果颜色值吗? - 我是否以错误的方式做其他事情?
谢谢大家的耐心等待
Preamble
I'm trying to create a rounded Brightness Saturation pad using SwiftUI both for indication (from a given color, show the cursor position) and use (by moving the cursor, obtain the resulting color).
I apologize for my bad English ????.
Presentation code
Firstly I draw the Circle using two Circle elements, filled with a LinearGradient element and the luminosity bland mode.
public var body: some View {
ZStack(alignment: .top) {
//MARK: - Inner Color circle
Group {
Circle()
.fill(
LinearGradient(
colors: [self.minSaturatedColor, self.maxSaturatedColor],
startPoint: .leading,
endPoint: .trailing
)
)
Circle()
.fill(
LinearGradient(
colors: [.white, .black],
startPoint: .top,
endPoint: .bottom
)
)
.blendMode(.luminosity)
}
.frame(width: self.diameter, height: self.diameter, alignment: .center)
.gesture(
DragGesture(minimumDistance: 0, coordinateSpace: .local)
.onChanged(self.updateCursorPosition)
.onEnded({ _ in
self.startIndication = nil
self.changed(self.finalColor)
})
)
//MARK: - Indicator
Circle()
.stroke(Color.black, lineWidth: 2)
.foregroundColor(Color.clear)
.frame(width: self.cursorDiameter, height: self.cursorDiameter, alignment: .center)
.position(cursorPosition)
}
.frame(width: self.diameter, height: self.diameter, alignment: .center)
}
Theory
Here's the theory above the mixtures of the color saturation with the brightness.
I'm not sure that this is the correct approach, but it seems so because it produces the following circle having (probably) all the shades:
Problem 1
The simplified code that I used to determine the Cursor position (from a given color) is:
public var cursorPosition: CGPoint {
let hsb = self.finalColor.hsba
let x: CGFloat = hsb.saturation * self.diameter
let y: CGFloat = hsb.brightness * self.diameter
// Adjust the Apple reversed ordinate
return CGPoint(x: x, y: self.diameter - y)
}
Unfortunately, this it seems not correct because by giving a certain color (I.e. #bd4d22
) it makes the pointer placed in a wrong position.
Problem 2
There's also another problem: the component should allow to move the cursor and so update the finalColor
State property with the correct amount of brightness and saturation.
private func updateCursorPosition(_ value: DragGesture.Value) -> Void {
let hsb = self.baseColor.hsba
let saturation = value.location.x / self.diameter
let brightness = (0 - value.location.y + self.diameter) / self.diameter
self.finalColor = Color(
hue: hsb.hue,
saturation: saturation,
brightness: brightness
)
}
yet the result is not what expected because, event tough the cursor follows the point (meaning that the computed value is correct), the resulting color is not what rendered behind the pointer!
Example pt. 1
Here, as you can see, the full saturated color (that should theoretically be 2π of the circumference) is not correct.
Example pt. 2
Here, as you can see, there's an issue by going outside of the circle.
Question
- Can anyone explain where am I failing?
- Can anyone help me blocking the
updateCursorPosition
resulting color value, if the cursor is dragged out of the circle? - Am I doing something else in the wrong way?
Thank you for the patience ????????!
Ps. To accomplish this, I take clue form what done by Procreate and what asked here Circular saturation-brightness gradient for color wheel
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
好吧,这花了一段时间,因为这不是一回事。我最终将您的代码拆开并更简单地重建它,并从颜色视图中重构了指示器。为了修复该指标,我从这个答案中获得了灵感。给它一个碰撞。
至于色轮本身,我将其简化为一个
Circle()
并叠加了第二个Circle()
。由于我不是设计师,所以直接确定颜色需要花费更多时间,因此我不得不不太直观地处理颜色。在处理这个问题时,我有两个认识。为了处理最大和最小饱和度,唯一可以使用的变量是色调。亮度和饱和度是固定的,饱和度为 0 或 1,亮度为 1,否则基色渐变关闭。
这导致我必须处理亮度梯度。颜色已经处于最大亮度,因此从白色到黑色的渐变渲染再次错误。我的另一个认识是亮度渐变应该从透明到黑色。这样就解决了颜色问题。代码如下并注释。
色轮:
指示器:
此外,作为旁注,您拥有的大多数变量都是常量,应该是
let
而不是vars
。结构内部的任何内容都应该是私有的。@State
变量始终是私有的。Okay, this took a while as it wasn't one thing. I ended up pulling your code apart and rebuilding it a little more simply, as well as refactoring the indicator from the color view. To fix the indicator, I took inspiration from this answer. Give it a bump.
As to the color wheel itself, I simplified it into a
Circle()
with an overlay of the secondCircle()
. Getting the colors straight took a lot more time as I am not a designer, so I had to work through dealing with the colors less intuitively. I had a two realizations while dealing with this.To handle the maximum and minimum saturations, the only variable that could be used was the hue. The brightness and saturation were fixed, either 0 or 1 for saturation and 1 for brightness, otherwise the base color gradient was off.
That caused me to have to deal with the brightness gradient. The color was already at maximum brightness, so putting a gradient from white to black rendered incorrectly, yet again. Another realization I had was that the brightness gradient should go from clear to black. That resolved the color issues. The code is below and commented.
The Color Wheel:
The Indicator:
Also, as a side note, most of the variables you had were constants and should be
lets
and notvars
. Anything internal to the struct should be private.@State
variables are always private.