@@ -70,156 +70,86 @@ extension CGError {
7070// MARK: - CGImage
7171
7272extension 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
8673
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
9875
9976 /// Computes and returns the average color of the image.
10077 ///
10178 /// - 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
12099 }
121100
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 )
127103 }
128104
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 )
131108
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+ }
135112
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) )
139115
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 )
145118
146119 for column in 0 ..< width {
147120 for row in 0 ..< height {
148- let pixel = data [ ( row * width) + column]
121+ let pixel = pixelData [ ( row * width) + column]
149122
150123 // Check alpha before computing other components.
151- let alphaComponent = computeComponentValue ( . alpha , for : pixel)
124+ let alphaComponent = computeComponent ( shift : 24 , pixel : pixel)
152125
153126 guard alphaComponent >= alphaThreshold else {
154- pixelCount -= 1 // Don't include this pixel.
127+ includedPixelCount -= 1 // Don't include this pixel.
155128 continue
156129 }
157130
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
167136 }
168137 }
169138
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 )
175142
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
201148 )
202149 }
203150
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
221152
222- extension CGImage {
223153 /// A context for handling transparency data in an image.
224154 private final class TransparencyContext {
225155 private let image : CGImage
0 commit comments