Skip to content

Commit 74f2332

Browse files
committed
Improves memory management with GDImage wrapper
Introduces a `GDImage` wrapper around `gdImagePtr` for automatic memory management, ensuring that images are properly destroyed when no longer needed. This change prevents potential memory leaks by managing the lifecycle of GD images using RAII principles. The `Image` class is updated to use `GDImage` internally, simplifying resource handling and improving overall stability.
1 parent bdebe96 commit 74f2332

2 files changed

Lines changed: 118 additions & 88 deletions

File tree

Sources/SwiftLibgd/Format.swift

Lines changed: 31 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ import gd
1212
public enum ImportableFormat {
1313
case bmp, gif, jpg, png, tiff, tga, wbmp, webp, avif, any
1414

15-
public func imagePtr(of data: Data) throws -> gdImagePtr {
15+
/// Returns a managed GD image that will automatically be destroyed when deallocated
16+
public func image(of data: Data) throws -> GDImage {
1617
if case .any = self {
1718
return try tryAllFormats(data: data)
1819
}
@@ -32,16 +33,16 @@ public enum ImportableFormat {
3233
case .any: gdImageCreateFromPngPtr // Never reached
3334
}
3435

35-
guard let image = creator(size, ptr) else {
36+
guard let imagePtr = creator(size, ptr) else {
3637
throw Error.invalidFormat
3738
}
38-
return image
39+
return GDImage(imagePtr)
3940
}
4041

41-
private func tryAllFormats(data: Data) throws -> gdImagePtr {
42+
private func tryAllFormats(data: Data) throws -> GDImage {
4243
let formats: [ImportableFormat] = [.jpg, .png, .gif, .webp, .tiff, .bmp, .wbmp]
4344
for format in formats {
44-
if let image = try? format.imagePtr(of: data) {
45+
if let image = try? format.image(of: data) {
4546
return image
4647
}
4748
}
@@ -61,45 +62,49 @@ public enum ExportableFormat {
6162
case webp
6263
case avif
6364

64-
public func data(of imagePtr: gdImagePtr) throws -> Data {
65+
public func data(of gdImage: GDImage) throws -> Data {
6566
var size: Int32 = 0
6667

6768
let bytes: UnsafeMutableRawPointer? = switch self {
6869
case .bmp(let compress):
69-
gdImageBmpPtr(imagePtr, &size, compress ? 1 : 0)
70+
gdImageBmpPtr(gdImage.ptr, &size, compress ? 1 : 0)
7071
case .gif:
71-
gdImageGifPtr(imagePtr, &size)
72+
gdImageGifPtr(gdImage.ptr, &size)
7273
case .jpg(let quality):
73-
gdImageJpegPtr(imagePtr, &size, quality)
74+
gdImageJpegPtr(gdImage.ptr, &size, quality)
7475
case .png:
75-
gdImagePngPtr(imagePtr, &size)
76+
gdImagePngPtr(gdImage.ptr, &size)
7677
case .tiff:
77-
gdImageTiffPtr(imagePtr, &size)
78+
gdImageTiffPtr(gdImage.ptr, &size)
7879
case .wbmp(let index):
79-
gdImageWBMPPtr(imagePtr, &size, index)
80+
gdImageWBMPPtr(gdImage.ptr, &size, index)
8081
case .webp:
81-
gdImageWebpPtr(imagePtr, &size)
82+
gdImageWebpPtr(gdImage.ptr, &size)
8283
case .avif:
83-
gdImageAvifPtr(imagePtr, &size)
84+
gdImageAvifPtr(gdImage.ptr, &size)
8485
}
8586

8687
guard let bytes = bytes else {
8788
throw Error.invalidFormat
8889
}
8990

90-
// Use custom deallocator for formats that need gdFree
91-
if case .bmp = self {
92-
return Data(bytesNoCopy: bytes, count: Int(size),
93-
deallocator: .custom({ ptr, _ in gdFree(ptr) }))
94-
} else if case .jpg = self {
95-
return Data(bytesNoCopy: bytes, count: Int(size),
96-
deallocator: .custom({ ptr, _ in gdFree(ptr) }))
97-
} else if case .wbmp = self {
98-
return Data(bytesNoCopy: bytes, count: Int(size),
99-
deallocator: .custom({ ptr, _ in gdFree(ptr) }))
100-
}
91+
return Data(bytesNoCopy: bytes, count: Int(size), deallocator: .custom { ptr, _ in
92+
gdFree(ptr)
93+
})
94+
}
95+
}
96+
97+
// MARK: - Memory-Safe GD Image Wrapper
98+
/// Wraps `gdImagePtr` and automatically destroys the image on deinit
99+
public final class GDImage {
100+
public var ptr: gdImagePtr
101+
102+
public init(_ ptr: gdImagePtr) {
103+
self.ptr = ptr
104+
}
101105

102-
return Data(bytes: bytes, count: Int(size))
106+
deinit {
107+
gdImageDestroy(ptr)
103108
}
104109
}
105110

0 commit comments

Comments
 (0)