@@ -70,156 +70,86 @@ extension CGError {
70
70
// MARK: - CGImage
71
71
72
72
extension CGImage {
73
- /// Constants that determine the resolution of a color averaging algorithm.
74
- enum ColorAverageResolution {
75
- /// Low resolution, reducing accuracy, but increasing performance.
76
- case low
77
- /// Medium resolution, with nominal accuracy and performance.
78
- case medium
79
- /// High resolution, increasing accuracy, but reducing performance.
80
- case high
81
- }
82
-
83
- /// Options that affect the output of a color averaging algorithm.
84
- struct ColorAverageOptions : OptionSet {
85
- let rawValue : Int
86
73
87
- /// The alpha component of the result is ignored and replaced with a value of `1`.
88
- static let ignoreAlpha = ColorAverageOptions ( rawValue: 1 << 0 )
89
- }
90
-
91
- /// A color component in the ARGB color space.
92
- private enum ARGBComponent : UInt32 {
93
- case alpha = 0x18
94
- case red = 0x10
95
- case green = 0x08
96
- case blue = 0x00
97
- }
74
+ // MARK: Average Color
98
75
99
76
/// Computes and returns the average color of the image.
100
77
///
101
78
/// - Parameters:
102
- /// - resolution: The resolution of the algorithm.
103
- /// - options: Options that further specify how the average should be computed.
104
- /// - alphaThreshold: An alpha value below which pixels should be ignored. Pixels
105
- /// whose alpha component is less than this value are not used in the computation.
106
- func averageColor(
107
- resolution: ColorAverageResolution = . medium,
108
- options: ColorAverageOptions = [ ] ,
109
- alphaThreshold: CGFloat = 0.5
110
- ) -> CGColor ? {
111
- // Resize the image based on the resolution. Smaller images remove more pixels,
112
- // decreasing accuracy, but increasing performance.
113
- let size = switch resolution {
114
- case . low:
115
- CGSize ( width: min ( width, 10 ) , height: min ( height, 10 ) )
116
- case . medium:
117
- CGSize ( width: min ( width, 50 ) , height: min ( height, 50 ) )
118
- case . high:
119
- CGSize ( width: min ( width, 100 ) , height: min ( height, 100 ) )
79
+ /// - alphaThreshold: An alpha value below which pixels should be ignored. Pixels with
80
+ /// an alpha component greater than or equal to this value contribute to the average.
81
+ /// - makeOpaque: A Boolean value that indicates whether the resulting color should be
82
+ /// made opaque, regardless of the alpha content of the image.
83
+ func averageColor( alphaThreshold: CGFloat = 0.5 , makeOpaque: Bool = false ) -> CGColor ? {
84
+ func createPixelData( width: Int , height: Int ) -> [ UInt32 ] ? {
85
+ var data = [ UInt32] ( repeating: 0 , count: width * height)
86
+ guard let context = CGContext (
87
+ data: & data,
88
+ width: width,
89
+ height: height,
90
+ bitsPerComponent: 8 ,
91
+ bytesPerRow: width * 4 ,
92
+ space: CGColorSpaceCreateDeviceRGB ( ) ,
93
+ bitmapInfo: CGImageByteOrderInfo . order32Little. rawValue | CGImageAlphaInfo . premultipliedFirst. rawValue
94
+ ) else {
95
+ return nil
96
+ }
97
+ context. draw ( self , in: CGRect ( x: 0 , y: 0 , width: width, height: height) )
98
+ return data
120
99
}
121
100
122
- guard
123
- let context = createContext ( size: size) ,
124
- let data = createImageData ( context: context)
125
- else {
126
- return nil
101
+ func computeComponent( shift: UInt32 , pixel: UInt32 ) -> Int {
102
+ return Int ( ( pixel >> shift) & 255 )
127
103
}
128
104
129
- let width = Int ( size. width)
130
- let height = Int ( size. height)
105
+ // Resize the image for better performance.
106
+ let width = min ( width, 10 )
107
+ let height = min ( height, 10 )
131
108
132
- // Convert the alpha threshold to an integer, multiplied by 255. Pixels with
133
- // an alpha component below this value are excluded from the average.
134
- let alphaThreshold = Int ( alphaThreshold * 255 )
109
+ guard let pixelData = createPixelData ( width : width , height : height ) else {
110
+ return nil
111
+ }
135
112
136
- // Start with a full pixel count. If any pixels are skipped, the count is
137
- // decremented accordingly.
138
- var pixelCount = width * height
113
+ // Convert the alpha threshold to a valid component for comparison.
114
+ let alphaThreshold = Int ( ( alphaThreshold. clamped ( to: 0 ... 1 ) * 255 ) . rounded ( . toNearestOrAwayFromZero) )
139
115
140
- // Start with the totals zeroed out.
141
- var totalRed = 0
142
- var totalGreen = 0
143
- var totalBlue = 0
144
- var totalAlpha = 0
116
+ var includedPixelCount = width * height
117
+ var totals = ( red: 0 , green: 0 , blue: 0 , alpha: 0 )
145
118
146
119
for column in 0 ..< width {
147
120
for row in 0 ..< height {
148
- let pixel = data [ ( row * width) + column]
121
+ let pixel = pixelData [ ( row * width) + column]
149
122
150
123
// Check alpha before computing other components.
151
- let alphaComponent = computeComponentValue ( . alpha , for : pixel)
124
+ let alphaComponent = computeComponent ( shift : 24 , pixel : pixel)
152
125
153
126
guard alphaComponent >= alphaThreshold else {
154
- pixelCount -= 1 // Don't include this pixel.
127
+ includedPixelCount -= 1 // Don't include this pixel.
155
128
continue
156
129
}
157
130
158
- let redComponent = computeComponentValue ( . red, for: pixel)
159
- let greenComponent = computeComponentValue ( . green, for: pixel)
160
- let blueComponent = computeComponentValue ( . blue, for: pixel)
161
-
162
- // Sum the red, green, blue, and alpha components.
163
- totalRed += redComponent
164
- totalGreen += greenComponent
165
- totalBlue += blueComponent
166
- totalAlpha += alphaComponent
131
+ // Add the components to the totals.
132
+ totals. red += computeComponent ( shift: 16 , pixel: pixel)
133
+ totals. green += computeComponent ( shift: 8 , pixel: pixel)
134
+ totals. blue += computeComponent ( shift: 0 , pixel: pixel)
135
+ totals. alpha += alphaComponent
167
136
}
168
137
}
169
138
170
- // Compute the averages of the summed components.
171
- let averageRed = CGFloat ( totalRed) / CGFloat( pixelCount)
172
- let averageGreen = CGFloat ( totalGreen) / CGFloat( pixelCount)
173
- let averageBlue = CGFloat ( totalBlue) / CGFloat( pixelCount)
174
- let averageAlpha = CGFloat ( totalAlpha) / CGFloat( pixelCount)
139
+ // Multiply the included pixel count by 255 to convert the components
140
+ // to their corresponding floating point values.
141
+ let adjustedPixelCount = CGFloat ( includedPixelCount * 255 )
175
142
176
- // Divide each component by 255 to convert to floating point.
177
- let red = averageRed / 255
178
- let green = averageGreen / 255
179
- let blue = averageBlue / 255
180
- let alpha = options. contains ( . ignoreAlpha) ? 1 : averageAlpha / 255
181
-
182
- return CGColor ( red: red, green: green, blue: blue, alpha: alpha)
183
- }
184
-
185
- /// Creates a bitmap context for resizing the image to the given size.
186
- private func createContext( size: CGSize ) -> CGContext ? {
187
- let width = Int ( size. width)
188
- let height = Int ( size. height)
189
- let bytesPerRow = width * 4
190
- let colorSpace = CGColorSpaceCreateDeviceRGB ( )
191
- let byteOrder = CGImageByteOrderInfo . order32Little. rawValue
192
- let alphaInfo = CGImageAlphaInfo . premultipliedFirst. rawValue
193
- return CGContext (
194
- data: nil ,
195
- width: width,
196
- height: height,
197
- bitsPerComponent: 8 ,
198
- bytesPerRow: bytesPerRow,
199
- space: colorSpace,
200
- bitmapInfo: byteOrder | alphaInfo
143
+ return CGColor (
144
+ red: CGFloat ( totals. red) / adjustedPixelCount,
145
+ green: CGFloat ( totals. green) / adjustedPixelCount,
146
+ blue: CGFloat ( totals. blue) / adjustedPixelCount,
147
+ alpha: makeOpaque ? 1 : CGFloat ( totals. alpha) / adjustedPixelCount
201
148
)
202
149
}
203
150
204
- /// Draws the image into the given context and returns the raw data.
205
- private func createImageData( context: CGContext ) -> UnsafeMutablePointer < UInt32 > ? {
206
- let rect = CGRect ( x: 0 , y: 0 , width: context. width, height: context. height)
207
- context. draw ( self , in: rect)
208
- guard let rawData = context. data else {
209
- return nil
210
- }
211
- return rawData. bindMemory ( to: UInt32 . self, capacity: context. width * context. height)
212
- }
213
-
214
- /// Computes the value of a color component for the given pixel value.
215
- private func computeComponentValue( _ component: ARGBComponent , for pixel: UInt32 ) -> Int {
216
- return Int ( ( pixel >> component. rawValue) & 255 )
217
- }
218
- }
219
-
220
- // MARK: - CGImage
151
+ // MARK: Trim Transparent Pixels
221
152
222
- extension CGImage {
223
153
/// A context for handling transparency data in an image.
224
154
private final class TransparencyContext {
225
155
private let image : CGImage
0 commit comments