@@ -49,34 +49,56 @@ class Subtitle: @unchecked Sendable {
4949
5050 // Converts the RGBA data to a CGImage
5151 func createImage( _ invert: Bool ) -> CGImage ? {
52- // Convert the image data to RGBA format using the palette
53- let rgbaData = imageDataToRGBA ( )
52+ var rgbaData = imageDataToRGBA ( )
5453
54+ var minX = imageWidth!, maxX = 0 , minY = imageHeight!, maxY = 0
55+ for y in 0 ..< imageHeight! {
56+ for x in 0 ..< imageWidth! {
57+ let pixelIndex = ( y * imageWidth! + x) * 4
58+ let alpha = rgbaData [ pixelIndex + 3 ]
59+ if alpha > 0 {
60+ minX = min ( minX, x)
61+ maxX = max ( maxX, x)
62+ minY = min ( minY, y)
63+ maxY = max ( maxY, y)
64+ if !invert {
65+ rgbaData [ pixelIndex] = 255 - rgbaData[ pixelIndex]
66+ rgbaData [ pixelIndex + 1 ] = 255 - rgbaData[ pixelIndex + 1 ]
67+ rgbaData [ pixelIndex + 2 ] = 255 - rgbaData[ pixelIndex + 2 ]
68+ }
69+ } else {
70+ // Set transparent pixels to white
71+ rgbaData [ pixelIndex] = 255
72+ rgbaData [ pixelIndex + 1 ] = 255
73+ rgbaData [ pixelIndex + 2 ] = 255
74+ rgbaData [ pixelIndex + 3 ] = 255
75+ }
76+ }
77+ }
78+
79+ guard let provider = CGDataProvider ( data: rgbaData as CFData ) else { return nil }
5580 let bitmapInfo = CGBitmapInfo . byteOrder32Big
5681 . union ( CGBitmapInfo ( rawValue: CGImageAlphaInfo . premultipliedLast. rawValue) )
5782 let colorSpace = CGColorSpaceCreateDeviceRGB ( )
5883
59- guard let provider = CGDataProvider ( data: rgbaData as CFData ) else {
60- return nil
61- }
62-
63- let image = CGImage ( width: imageWidth!,
64- height: imageHeight!,
65- bitsPerComponent: 8 ,
66- bitsPerPixel: 32 ,
67- bytesPerRow: imageWidth! * 4 , // 4 bytes per pixel (RGBA)
68- space: colorSpace,
69- bitmapInfo: bitmapInfo,
70- provider: provider,
71- decode: nil ,
72- shouldInterpolate: false ,
73- intent: . defaultIntent)
84+ let image = CGImage (
85+ width: imageWidth!,
86+ height: imageHeight!,
87+ bitsPerComponent: 8 ,
88+ bitsPerPixel: 32 ,
89+ bytesPerRow: imageWidth! * 4 ,
90+ space: colorSpace,
91+ bitmapInfo: bitmapInfo,
92+ provider: provider,
93+ decode: nil ,
94+ shouldInterpolate: false ,
95+ intent: . defaultIntent)
7496
75- guard var croppedImage = cropImageToVisibleArea ( image!) else { return nil }
76- if invert {
77- croppedImage = invertColors ( of: croppedImage) ?? croppedImage
97+ if minX == imageWidth! || maxX == 0 || minY == imageHeight! || maxY == 0 {
98+ return nil
7899 }
79- return changeTransparency ( for: croppedImage, to: CGColor . white)
100+ let croppedRect = CGRect ( x: minX, y: minY, width: maxX - minX + 1 , height: maxY - minY + 1 )
101+ return image? . cropping ( to: croppedRect)
80102 }
81103
82104 // MARK: - Methods
@@ -108,138 +130,4 @@ class Subtitle: @unchecked Sendable {
108130
109131 return rgbaData
110132 }
111-
112- /// Crops the image to the visible (non-transparent) area and adds a buffer around it.
113- /// - Parameter image: The original CGImage to crop.
114- /// - Returns: A cropped CGImage, or nil if the image is fully transparent.
115- private func cropImageToVisibleArea( _ image: CGImage ) -> CGImage ? {
116- let buffer = 10 // Buffer size around the non-transparent area
117- let width = image. width
118- let height = image. height
119- let newWidth = width + buffer * 2
120- let newHeight = height + buffer * 2
121-
122- // Create a new image context with extended dimensions
123- guard let context = CGContext ( data: nil ,
124- width: newWidth,
125- height: newHeight,
126- bitsPerComponent: image. bitsPerComponent,
127- bytesPerRow: 0 ,
128- space: image. colorSpace ?? CGColorSpaceCreateDeviceRGB ( ) ,
129- bitmapInfo: image. bitmapInfo. rawValue) else {
130- return nil
131- }
132-
133- // Clear the context (set a transparent background)
134- context. clear ( CGRect ( x: 0 , y: 0 , width: newWidth, height: newHeight) )
135-
136- // Draw the original image onto the new context at the center
137- context. draw ( image, in: CGRect ( x: buffer, y: buffer, width: width, height: height) )
138-
139- guard let extendedImage = context. makeImage ( ) ,
140- let dataProvider = extendedImage. dataProvider,
141- let data = dataProvider. data,
142- let pixelData = CFDataGetBytePtr ( data) else {
143- return nil
144- }
145-
146- let bytesPerPixel = extendedImage. bitsPerPixel / 8
147- let bytesPerRow = extendedImage. bytesPerRow
148-
149- // Initialize variables to track the non-transparent bounds
150- var minX = newWidth, maxX = 0 , minY = newHeight, maxY = 0
151-
152- // Scan the image to find the non-transparent pixels
153- for y in 0 ..< newHeight {
154- for x in 0 ..< newWidth {
155- let pixelIndex = y * bytesPerRow + x * bytesPerPixel
156- let alpha = pixelData [ pixelIndex + 3 ] // Assuming RGBA format
157-
158- if alpha > 0 { // Non-transparent pixel found
159- minX = min ( minX, x)
160- maxX = max ( maxX, x)
161- minY = min ( minY, y)
162- maxY = max ( maxY, y)
163- }
164- }
165- }
166-
167- // If the image is fully transparent, return nil
168- if minX == newWidth || maxX == 0 || minY == newHeight || maxY == 0 {
169- return nil
170- }
171-
172- // Apply buffer to the bounding box, ensuring it's within image bounds
173- minX = max ( 0 , minX - buffer)
174- maxX = min ( newWidth - 1 , maxX + buffer)
175- minY = max ( 0 , minY - buffer)
176- maxY = min ( newHeight - 1 , maxY + buffer)
177-
178- // Crop the image to the visible (non-transparent) area with the buffer
179- let croppedRect = CGRect ( x: minX, y: minY, width: maxX - minX + 1 , height: maxY - minY + 1 )
180- return extendedImage. cropping ( to: croppedRect)
181- }
182-
183- private func invertColors( of image: CGImage ) -> CGImage ? {
184- let colorSpace = CGColorSpaceCreateDeviceRGB ( )
185-
186- // Create a context with the same dimensions as the image
187- guard let context = CGContext (
188- data: nil ,
189- width: image. width,
190- height: image. height,
191- bitsPerComponent: 8 ,
192- bytesPerRow: image. width * 4 ,
193- space: colorSpace,
194- bitmapInfo: CGImageAlphaInfo . premultipliedLast. rawValue) else {
195- return nil
196- }
197-
198- // Draw the image into the context
199- context. draw ( image, in: CGRect ( x: 0 , y: 0 , width: image. width, height: image. height) )
200-
201- // Get the pixel data from the context
202- guard let pixelBuffer = context. data else {
203- return nil
204- }
205-
206- let pixelData = pixelBuffer. bindMemory ( to: UInt8 . self, capacity: image. width * image. height * 4 )
207-
208- // Iterate through the pixel data and invert the colors
209- for y in 0 ..< image. height {
210- for x in 0 ..< image. width {
211- let pixelIndex = ( y * image. width + x) * 4
212- pixelData [ pixelIndex] = 255 - pixelData[ pixelIndex] // Red
213- pixelData [ pixelIndex + 1 ] = 255 - pixelData[ pixelIndex + 1 ] // Green
214- pixelData [ pixelIndex + 2 ] = 255 - pixelData[ pixelIndex + 2 ] // Blue
215- }
216- }
217-
218- // Create a new CGImage from the modified pixel data
219- return context. makeImage ( )
220- }
221-
222- func changeTransparency( for image: CGImage , to color: CGColor ) -> CGImage ? {
223- let colorSpace = CGColorSpaceCreateDeviceRGB ( )
224-
225- guard let context = CGContext (
226- data: nil ,
227- width: image. width,
228- height: image. height,
229- bitsPerComponent: image. bitsPerComponent,
230- bytesPerRow: image. bytesPerRow,
231- space: colorSpace,
232- bitmapInfo: CGImageAlphaInfo . premultipliedLast. rawValue) else {
233- return nil
234- }
235-
236- // Fill the context with the specified color
237- context. setFillColor ( color)
238- context. fill ( CGRect ( x: 0 , y: 0 , width: image. width, height: image. height) )
239-
240- // Draw the image on top of the colored background
241- context. draw ( image, in: CGRect ( x: 0 , y: 0 , width: image. width, height: image. height) )
242-
243- return context. makeImage ( )
244- }
245133}
0 commit comments