Skip to content

Commit 4f05aa8

Browse files
committed
Refactors and improves Image class structure
Improves code organization by using extensions for logical groupings of image operations. Adds deinitializer to properly release allocated memory, preventing memory leaks.
1 parent a52b7ef commit 4f05aa8

1 file changed

Lines changed: 95 additions & 62 deletions

File tree

Sources/SwiftLibgd/Image.swift

Lines changed: 95 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@ import Foundation
88
import gd
99

1010
public 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

Comments
 (0)