@@ -8,12 +8,14 @@ import Foundation
88import gd
99
1010public class Image {
11+ // MARK: Private properties
12+ private var internalImage : gdImagePtr
13+
14+ // MARK: - Public properties
1115 public enum FlipMode {
1216 case horizontal, vertical, both
1317 }
1418
15- private var internalImage : gdImagePtr
16-
1719 public var size : Size {
1820 Size ( width: internalImage. pointee. sx, height: internalImage. pointee. sy)
1921 }
@@ -25,6 +27,7 @@ public class Image {
2527 }
2628 }
2729
30+ // MARK: - Init
2831 public init ? ( width: Int , height: Int ) {
2932 internalImage = gdImageCreateTrueColor ( Int32 ( width) , Int32 ( height) )
3033 }
@@ -39,8 +42,8 @@ public class Image {
3942
4043 let ext = url. lastPathComponent. lowercased ( )
4144 let img : gdImagePtr ? = ext. hasSuffix ( " jpg " ) || ext. hasSuffix ( " jpeg " )
42- ? gdImageCreateFromJpeg ( file)
43- : ext. hasSuffix ( " png " ) ? gdImageCreateFromPng ( file) : nil
45+ ? gdImageCreateFromJpeg ( file)
46+ : ext. hasSuffix ( " png " ) ? gdImageCreateFromPng ( file) : nil
4447
4548 guard let loadedImage = img else { return nil }
4649 self . init ( gdImage: loadedImage)
@@ -50,55 +53,62 @@ public class Image {
5053 try self . init ( gdImage: format. imagePtr ( of: data) )
5154 }
5255
53- // MARK: - Transform Operations
56+ // MARK: - Deinit
57+ deinit {
58+ gdImageDestroy ( internalImage)
59+ }
60+ }
5461
55- public func cloned( ) -> Image ? {
62+ // MARK: - Transform Operations
63+ public extension Image {
64+ func cloned( ) -> Image ? {
5665 gdImageClone ( internalImage) . map { Image ( gdImage: $0) }
5766 }
5867
59- public func resizedTo( width: Int , height: Int , smooth: Bool = true ) -> Image ? {
68+ func resizedTo( width: Int , height: Int , smooth: Bool = true ) -> Image ? {
6069 setInterpolation ( smooth: smooth, from: size, to: Size ( width: width, height: height) )
6170 return gdImageScale ( internalImage, UInt32 ( width) , UInt32 ( height) ) . map { Image ( gdImage: $0) }
6271 }
6372
64- public func resizedTo( width: Int , smooth: Bool = true ) -> Image ? {
73+ func resizedTo( width: Int , smooth: Bool = true ) -> Image ? {
6574 let ratio = Double ( width) / Double( size. width)
6675 let newHeight = Int32 ( Double ( size. height) * ratio)
6776 return resizedTo ( width: width, height: Int ( newHeight) , smooth: smooth)
6877 }
6978
70- public func resizedTo( height: Int , smooth: Bool = true ) -> Image ? {
79+ func resizedTo( height: Int , smooth: Bool = true ) -> Image ? {
7180 let ratio = Double ( height) / Double( size. height)
7281 let newWidth = Int32 ( Double ( size. width) * ratio)
7382 return resizedTo ( width: Int ( newWidth) , height: height, smooth: smooth)
7483 }
7584
76- public func cropped( to rect: Rectangle ) -> Image ? {
85+ func cropped( to rect: Rectangle ) -> Image ? {
7786 var r = gdRect ( x: Int32 ( rect. point. x) , y: Int32 ( rect. point. y) ,
7887 width: Int32 ( rect. size. width) , height: Int32 ( rect. size. height) )
7988 return gdImageCrop ( internalImage, & r) . map { Image ( gdImage: $0) }
8089 }
8190
82- public func rotated( _ angle: Angle ) -> Image ? {
91+ func rotated( _ angle: Angle ) -> Image ? {
8392 gdImageRotateInterpolated ( internalImage, Float ( angle. degrees) , 0 ) . map { Image ( gdImage: $0) }
8493 }
8594
86- public func flipped( _ mode: FlipMode ) -> Image ? {
95+ func flipped( _ mode: FlipMode ) -> Image ? {
8796 guard let copy = gdImageClone ( internalImage) else { return nil }
8897 applyFlip ( to: copy, mode: mode)
8998 return Image ( gdImage: copy)
9099 }
91100
92- public func flip( _ mode: FlipMode ) {
101+ func flip( _ mode: FlipMode ) {
93102 applyFlip ( to: internalImage, mode: mode)
94103 }
104+ }
95105
96- // MARK: - Drawing Operations
97-
106+ // MARK: - Drawing Operations
107+ public extension Image {
98108 @MainActor
99- public func renderText( _ text: String , from: Point , fontList: [ String ] ,
100- color: Color , size: Double , angle: Angle = . zero)
101- -> ( upperLeft: Point , upperRight: Point , lowerRight: Point , lowerLeft: Point ) {
109+ func renderText( _ text: String , from: Point , fontList: [ String ] ,
110+ color: Color , size: Double , angle: Angle = . zero)
111+ -> ( upperLeft: Point , upperRight: Point , lowerRight: Point , lowerLeft: Point ) {
102112 guard !text. isEmpty, !fontList. isEmpty,
103113 var textCChar = text. cString ( using: . utf8) ,
104114 var fonts = fontList. joined ( separator: " ; " ) . cString ( using: . utf8) else {
@@ -110,106 +120,132 @@ public class Image {
110120
111121 var box : [ Int32 ] = . init( repeating: 0 , count: 8 )
112122 gdImageStringFT ( internalImage, & box, c, & fonts, size, - angle. radians,
113- Int32 ( from. x) , Int32 ( from. y) , & textCChar)
123+ Int32 ( from. x) , Int32 ( from. y) , & textCChar)
114124
115125 return ( Point ( x: box [ 6 ] , y: box [ 7 ] ) , Point ( x: box [ 4 ] , y: box [ 5 ] ) ,
116126 Point ( x: box [ 2 ] , y: box [ 3 ] ) , Point ( x: box [ 0 ] , y: box [ 1 ] ) )
117127 }
118128
119- public func fill( from: Point , color: Color ) {
129+ func fill( from: Point , color: Color ) {
120130 let c = allocateColor ( color)
121131 defer { gdImageColorDeallocate ( internalImage, c) }
122132 gdImageFill ( internalImage, Int32 ( from. x) , Int32 ( from. y) , c)
123133 }
124134
125- public func drawLine( from: Point , to: Point , color: Color ) {
135+ func drawLine( from: Point , to: Point , color: Color ) {
126136 let c = allocateColor ( color)
127137 defer { gdImageColorDeallocate ( internalImage, c) }
128138 gdImageLine ( internalImage, Int32 ( from. x) , Int32 ( from. y) , Int32 ( to. x) , Int32 ( to. y) , c)
129139 }
130140
131141 @MainActor
132- public func drawImage( _ image: Image , at topLeft: Point = . zero) {
142+ func drawImage( _ image: Image , at topLeft: Point = . zero) {
133143 gdImageCopy ( internalImage, image. internalImage,
134- Int32 ( topLeft. x) , Int32 ( topLeft. y) , 0 , 0 ,
135- Int32 ( size. width - topLeft. x) , Int32 ( size. height - topLeft. y) )
144+ Int32 ( topLeft. x) , Int32 ( topLeft. y) , 0 , 0 ,
145+ Int32 ( size. width - topLeft. x) , Int32 ( size. height - topLeft. y) )
136146 }
137147
138- public func set( pixel: Point , to color: Color ) {
148+ func set( pixel: Point , to color: Color ) {
139149 let c = allocateColor ( color)
140150 defer { gdImageColorDeallocate ( internalImage, c) }
141151 gdImageSetPixel ( internalImage, Int32 ( pixel. x) , Int32 ( pixel. y) , c)
142152 }
143153
144- public func get( pixel: Point ) -> Color {
154+ func get( pixel: Point ) -> Color {
145155 let c = gdImageGetTrueColorPixel ( internalImage, Int32 ( pixel. x) , Int32 ( pixel. y) )
146156 return Color ( red: Double ( ( c >> 16 ) & 0xFF ) / 255 ,
147- green: Double ( ( c >> 8 ) & 0xFF ) / 255 ,
148- blue: Double ( c & 0xFF ) / 255 ,
149- alpha: 1 - Double( ( c >> 24 ) & 0xFF ) / 127 )
157+ green: Double ( ( c >> 8 ) & 0xFF ) / 255 ,
158+ blue: Double ( c & 0xFF ) / 255 ,
159+ alpha: 1 - Double( ( c >> 24 ) & 0xFF ) / 127 )
150160 }
151161
152- public func strokeEllipse( center: Point , size: Size , color: Color ) {
162+ func strokeEllipse( center: Point , size: Size , color: Color ) {
153163 let c = allocateColor ( color)
154164 defer { gdImageColorDeallocate ( internalImage, c) }
155- gdImageEllipse ( internalImage, Int32 ( center. x) , Int32 ( center. y) ,
156- Int32 ( size. width) , Int32 ( size. height) , c)
165+ gdImageEllipse (
166+ internalImage,
167+ Int32 ( center. x) ,
168+ Int32 ( center. y) ,
169+ Int32 ( size. width) ,
170+ Int32 ( size. height) ,
171+ c
172+ )
157173 }
158174
159- public func fillEllipse( center: Point , size: Size , color: Color ) {
175+ func fillEllipse( center: Point , size: Size , color: Color ) {
160176 let c = allocateColor ( color)
161177 defer { gdImageColorDeallocate ( internalImage, c) }
162- gdImageFilledEllipse ( internalImage, Int32 ( center. x) , Int32 ( center. y) ,
163- Int32 ( size. width) , Int32 ( size. height) , c)
178+ gdImageFilledEllipse (
179+ internalImage,
180+ Int32 ( center. x) ,
181+ Int32 ( center. y) ,
182+ Int32 ( size. width) ,
183+ Int32 ( size. height) ,
184+ c
185+ )
164186 }
165187
166- public func strokeRectangle( topLeft: Point , bottomRight: Point , color: Color ) {
188+ func strokeRectangle( topLeft: Point , bottomRight: Point , color: Color ) {
167189 let c = allocateColor ( color)
168190 defer { gdImageColorDeallocate ( internalImage, c) }
169- gdImageRectangle ( internalImage, Int32 ( topLeft. x) , Int32 ( topLeft. y) ,
170- Int32 ( bottomRight. x) , Int32 ( bottomRight. y) , c)
191+ gdImageRectangle (
192+ internalImage,
193+ Int32 ( topLeft. x) ,
194+ Int32 ( topLeft. y) ,
195+ Int32 ( bottomRight. x) ,
196+ Int32 ( bottomRight. y) ,
197+ c
198+ )
171199 }
172200
173- public func fillRectangle( topLeft: Point , bottomRight: Point , color: Color ) {
201+ func fillRectangle( topLeft: Point , bottomRight: Point , color: Color ) {
174202 let c = allocateColor ( color)
175203 defer { gdImageColorDeallocate ( internalImage, c) }
176- gdImageFilledRectangle ( internalImage, Int32 ( topLeft. x) , Int32 ( topLeft. y) ,
177- Int32 ( bottomRight. x) , Int32 ( bottomRight. y) , c)
204+ gdImageFilledRectangle (
205+ internalImage,
206+ Int32 ( topLeft. x) ,
207+ Int32 ( topLeft. y) ,
208+ Int32 ( bottomRight. x) ,
209+ Int32 ( bottomRight. y) ,
210+ c
211+ )
178212 }
213+ }
179214
180- // MARK: - Effects
181-
182- public func pixelate( blockSize: Int ) {
215+ // MARK: - Effects
216+ public extension Image {
217+ func pixelate( blockSize: Int ) {
183218 gdImagePixelate ( internalImage, Int32 ( blockSize) , GD_PIXELATE_AVERAGE . rawValue)
184219 }
185220
186- public func blur( radius: Int ) {
221+ func blur( radius: Int ) {
187222 if let result = gdImageCopyGaussianBlurred ( internalImage, Int32 ( radius) , - 1 ) {
188223 gdImageDestroy ( internalImage)
189224 internalImage = result
190225 }
191226 }
192227
193- public func colorize( using color: Color ) {
228+ func colorize( using color: Color ) {
194229 let c = colorComponents ( color)
195230 gdImageColor ( internalImage, c. 0 , c. 1 , c. 2 , c. 3 )
196231 }
197232
198- public func desaturate( ) {
233+ func desaturate( ) {
199234 gdImageGrayScale ( internalImage)
200235 }
201236
202- public func reduceColors( max numberOfColors: Int , dither: Bool = true ) throws {
237+ func reduceColors( max numberOfColors: Int , dither: Bool = true ) throws {
203238 guard numberOfColors > 1 else {
204239 throw Error . invalidMaxColors ( reason: " Indexed images must have at least 2 colors " )
205240 }
206241 gdImageTrueColorToPalette ( internalImage, dither ? 1 : 0 , Int32 ( numberOfColors) )
207242 }
243+ }
208244
209- // MARK: - Export
210-
245+ // MARK: - Export
246+ public extension Image {
211247 @discardableResult
212- public func write( to url: URL , quality: Int = 100 , allowOverwrite: Bool = false ) -> Bool {
248+ func write( to url: URL , quality: Int = 100 , allowOverwrite: Bool = false ) -> Bool {
213249 let ext = url. pathExtension. lowercased ( )
214250 guard ext == " png " || ext == " jpeg " || ext == " jpg " else { return false }
215251 guard allowOverwrite || !FileManager. default. fileExists ( atPath: url. path) else { return false }
@@ -226,23 +262,24 @@ public class Image {
226262 return FileManager . default. fileExists ( atPath: url. path)
227263 }
228264
229- public func export( as format: ExportableFormat = . png) throws -> Data {
265+ func export( as format: ExportableFormat = . png) throws -> Data {
230266 try format. data ( of: internalImage)
231267 }
268+ }
232269
233- // MARK: - Private Helpers
234-
235- private func allocateColor( _ color: Color ) -> Int32 {
270+ // MARK: - Private Helpers
271+ private extension Image {
272+ func allocateColor( _ color: Color ) -> Int32 {
236273 let c = colorComponents ( color)
237274 return gdImageColorAllocateAlpha ( internalImage, c. 0 , c. 1 , c. 2 , c. 3 )
238275 }
239276
240- private func colorComponents( _ color: Color ) -> ( Int32 , Int32 , Int32 , Int32 ) {
277+ func colorComponents( _ color: Color ) -> ( Int32 , Int32 , Int32 , Int32 ) {
241278 ( Int32 ( color. redComponent * 255 ) , Int32 ( color. greenComponent * 255 ) ,
242279 Int32 ( color. blueComponent * 255 ) , 127 - Int32( color. alphaComponent * 127 ) )
243280 }
244281
245- private func setInterpolation( smooth: Bool , from: Size , to: Size ) {
282+ func setInterpolation( smooth: Bool , from: Size , to: Size ) {
246283 guard smooth else {
247284 gdImageSetInterpolationMethod ( internalImage, GD_NEAREST_NEIGHBOUR)
248285 return
@@ -251,15 +288,11 @@ public class Image {
251288 gdImageSetInterpolationMethod ( internalImage, method)
252289 }
253290
254- private func applyFlip( to image: gdImagePtr , mode: FlipMode ) {
291+ func applyFlip( to image: gdImagePtr , mode: FlipMode ) {
255292 switch mode {
256293 case . horizontal: gdImageFlipHorizontal ( image)
257294 case . vertical: gdImageFlipVertical ( image)
258295 case . both: gdImageFlipBoth ( image)
259296 }
260297 }
261-
262- deinit {
263- gdImageDestroy ( internalImage)
264- }
265298}
0 commit comments