From 24f5e10eff36f6a515a2f3896ce4189c5bee4c39 Mon Sep 17 00:00:00 2001 From: Yuta Koshizawa Date: Tue, 21 Apr 2020 02:17:12 +0900 Subject: [PATCH 1/4] Update .travis.yml for Swift 5.2 and Xcode 11.4 --- .travis.yml | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/.travis.yml b/.travis.yml index 3f8bfe9..96a7b24 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,6 +26,19 @@ matrix: - xcodebuild test -scheme SwiftImage-iOS -configuration Release -sdk iphonesimulator -destination "platform=iOS Simulator,name=iPhone 11 Pro" | xcpretty -c - xcodebuild test -scheme SwiftImage-tvOS -configuration Release -sdk appletvsimulator -destination "platform=tvOS Simulator,name=Apple TV 4K" | xcpretty -c - xcodebuild build -scheme SwiftImage-watchOS -configuration Release -sdk watchsimulator -destination "platform=watchOS Simulator,name=Apple Watch Series 5 - 44mm" | xcpretty -c + - os: osx + language: objective-c + osx_image: xcode11.4 + script: + - set -o pipefail + - xcodebuild test -scheme SwiftImage-macOS -configuration Debug | xcpretty -c + - xcodebuild test -scheme SwiftImage-iOS -configuration Debug -sdk iphonesimulator -destination "platform=iOS Simulator,name=iPhone 11 Pro" | xcpretty -c + - xcodebuild test -scheme SwiftImage-tvOS -configuration Debug -sdk appletvsimulator -destination "platform=tvOS Simulator,name=Apple TV 4K" | xcpretty -c + - xcodebuild build -scheme SwiftImage-watchOS -configuration Debug -sdk watchsimulator -destination "platform=watchOS Simulator,name=Apple Watch Series 5 - 44mm" | xcpretty -c + - xcodebuild test -scheme SwiftImage-macOS -configuration Release | xcpretty -c + - xcodebuild test -scheme SwiftImage-iOS -configuration Release -sdk iphonesimulator -destination "platform=iOS Simulator,name=iPhone 11 Pro" | xcpretty -c + - xcodebuild test -scheme SwiftImage-tvOS -configuration Release -sdk appletvsimulator -destination "platform=tvOS Simulator,name=Apple TV 4K" | xcpretty -c + - xcodebuild build -scheme SwiftImage-watchOS -configuration Release -sdk watchsimulator -destination "platform=watchOS Simulator,name=Apple Watch Series 5 - 44mm" | xcpretty -c - os: osx language: generic osx_image: xcode10.2 @@ -44,6 +57,15 @@ matrix: - swift test - swift build -c release - swift test -c release + - os: osx + language: generic + osx_image: xcode11.4 + script: + - swift --version + - swift build + - swift test + - swift build -c release + - swift test -c release - os: linux language: generic sudo: required @@ -68,3 +90,15 @@ matrix: - swift test - swift build -c release - swift test -c release + - os: linux + language: generic + sudo: required + env: SWIFT_VERSION=5.2 + install: + - eval "$(curl -sL https://swiftenv.fuller.li/install.sh)" + script: + - swift --version + - swift build + - swift test + - swift build -c release + - swift test -c release From 29a8ab76e169338a685e6f4a1ba0020fd9d9bfbf Mon Sep 17 00:00:00 2001 From: Yuta Koshizawa Date: Wed, 22 Apr 2020 13:32:19 +0900 Subject: [PATCH 2/4] Fix a test case for Swit 5.2 Optimizations in Swift 5.2 broke the test case because it changed when to release unused variables. --- Tests/SwiftImageTests/HigherOrderFunctionsTests.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Tests/SwiftImageTests/HigherOrderFunctionsTests.swift b/Tests/SwiftImageTests/HigherOrderFunctionsTests.swift index dccf013..7dee62d 100644 --- a/Tests/SwiftImageTests/HigherOrderFunctionsTests.swift +++ b/Tests/SwiftImageTests/HigherOrderFunctionsTests.swift @@ -141,6 +141,8 @@ class HigherOrderFunctionsTests : XCTestCase { flags |= 0b1000 } XCTAssertEqual(flags, 0b111) + + XCTAssertEqual(image.count, 1) // to prevent `image` being released } } From c45a8a3b603266acf3c6d1e08d50e9537e694642 Mon Sep 17 00:00:00 2001 From: Yuta Koshizawa Date: Sun, 26 Apr 2020 20:47:44 +0900 Subject: [PATCH 3/4] Fix unsafe pointer operations --- Sources/SwiftImage/CoreGraphics.swift | 193 ++++++++++++++------------ 1 file changed, 103 insertions(+), 90 deletions(-) diff --git a/Sources/SwiftImage/CoreGraphics.swift b/Sources/SwiftImage/CoreGraphics.swift index 4b6f122..4c980f1 100644 --- a/Sources/SwiftImage/CoreGraphics.swift +++ b/Sources/SwiftImage/CoreGraphics.swift @@ -417,10 +417,10 @@ extension Image: _CGImageDirectlyConvertible where Pixel: _CGDirectPixel { public var cgImage: CGImage { let length = count * MemoryLayout.size - let provider: CGDataProvider = CGDataProvider(data: Data( - bytes: UnsafeMutableRawPointer(mutating: pixels), - count: length - ) as CFData)! + let data: Data = pixels.withUnsafeBytes { pixelsPointer in + Data(bytes: pixelsPointer.baseAddress!, count: length) + } + let provider: CGDataProvider = CGDataProvider(data: data as CFData)! return CGImage( width: width, @@ -443,27 +443,29 @@ extension Image: _CGImageDirectlyConvertible where Pixel: _CGDirectPixel { let length = count * MemoryLayout.size var image = self - let provider: CGDataProvider = CGDataProvider(data: Data( - bytesNoCopy: &image.pixels, - count: length, - deallocator: .none - ) as CFData)! - - let cgImage = CGImage( - width: width, - height: height, - bitsPerComponent: MemoryLayout.size * 8, - bitsPerPixel: MemoryLayout.size * 8, - bytesPerRow: MemoryLayout.size * width, - space: Pixel._ez_cgColorSpace, - bitmapInfo: Pixel._ez_cgBitmapInfo, - provider: provider, - decode: nil, - shouldInterpolate: false, - intent: CGColorRenderingIntent.defaultIntent - )! - - return try body(cgImage) + return try image.pixels.withUnsafeMutableBufferPointer { pixelsPointer in + let provider: CGDataProvider = CGDataProvider(data: Data( + bytesNoCopy: pixelsPointer.baseAddress!, + count: length, + deallocator: .none + ) as CFData)! + + let cgImage = CGImage( + width: width, + height: height, + bitsPerComponent: MemoryLayout.size * 8, + bitsPerPixel: MemoryLayout.size * 8, + bytesPerRow: MemoryLayout.size * width, + space: Pixel._ez_cgColorSpace, + bitmapInfo: Pixel._ez_cgBitmapInfo, + provider: provider, + decode: nil, + shouldInterpolate: false, + intent: CGColorRenderingIntent.defaultIntent + )! + + return try body(cgImage) + } } @inlinable @@ -474,24 +476,26 @@ extension Image: _CGImageDirectlyConvertible where Pixel: _CGDirectPixel { precondition(width >= 0) precondition(height >= 0) - let context = CGContext( - data: &pixels, - width: width, - height: height, - bitsPerComponent: MemoryLayout.size * 8, - bytesPerRow: MemoryLayout.size * width, - space: Pixel._ez_cgColorSpace, - bitmapInfo: Pixel._ez_cgBitmapInfo.rawValue - )! - switch coordinates { - case .original: - break - case .natural: - context.scaleBy(x: 1, y: -1) - context.translateBy(x: 0.5, y: 0.5 - CGFloat(height)) + try self.pixels.withUnsafeMutableBytes { pixelsPointer in + let context = CGContext( + data: pixelsPointer.baseAddress!, + width: width, + height: height, + bitsPerComponent: MemoryLayout.size * 8, + bytesPerRow: MemoryLayout.size * width, + space: Pixel._ez_cgColorSpace, + bitmapInfo: Pixel._ez_cgBitmapInfo.rawValue + )! + switch coordinates { + case .original: + break + case .natural: + context.scaleBy(x: 1, y: -1) + context.translateBy(x: 0.5, y: 0.5 - CGFloat(height)) + } + + try body(context) } - - try body(context) } } @@ -517,14 +521,19 @@ extension ImageSlice: _CGImageDirectlyConvertible where Pixel: _CGDirectPixel { var data: Data if offset + pixelCount <= imageCount { - let bytes: UnsafeMutablePointer = UnsafeMutablePointer(mutating: image.pixels) + (yRange.lowerBound * image.width + xRange.lowerBound) - data = Data(bytes: bytes, count: length) + data = image.pixels.withUnsafeBufferPointer { pixelsPointer in + let bytes: UnsafePointer = pixelsPointer.baseAddress! + (yRange.lowerBound * image.width + xRange.lowerBound) + return Data(bytes: bytes, count: length) + } } else { - let bytes: UnsafeMutablePointer = UnsafeMutablePointer(mutating: image.pixels) + (yRange.lowerBound * image.width + xRange.lowerBound) - let pointer: UnsafeMutablePointer = UnsafeMutableRawPointer(bytes).bindMemory(to: UInt8.self, capacity: length) - data = Data(capacity: pixelCount * MemoryLayout.size) - data.append(pointer, count: (imageCount - offset) * MemoryLayout.size) - data.append(pointer, count: (offset + pixelCount - imageCount) * MemoryLayout.size) + data = image.pixels.withUnsafeBufferPointer { pixelsPointer in + let bytes: UnsafePointer = pixelsPointer.baseAddress! + (yRange.lowerBound * image.width + xRange.lowerBound) + let pointer: UnsafePointer = UnsafeRawPointer(bytes).bindMemory(to: UInt8.self, capacity: length) + var data = Data(capacity: pixelCount * MemoryLayout.size) + data.append(pointer, count: (imageCount - offset) * MemoryLayout.size) + data.append(pointer, count: (offset + pixelCount - imageCount) * MemoryLayout.size) + return data + } } let provider: CGDataProvider = CGDataProvider(data: data as CFData)! @@ -549,29 +558,30 @@ extension ImageSlice: _CGImageDirectlyConvertible where Pixel: _CGDirectPixel { let length = image.width * self.height * MemoryLayout.size var slice = self - let bytes: UnsafeMutablePointer = &slice.image.pixels + (slice.yRange.lowerBound * slice.image.width + slice.xRange.lowerBound) - let provider: CGDataProvider = CGDataProvider(data: Data( - bytesNoCopy: bytes, - count: length, - deallocator: .none - ) as CFData)! - - let cgImage = CGImage( - width: width, - height: height, - bitsPerComponent: MemoryLayout.size * 8, - bitsPerPixel: MemoryLayout.size * 8, - bytesPerRow: MemoryLayout.size * image.width, - space: Pixel._ez_cgColorSpace, - bitmapInfo: Pixel._ez_cgBitmapInfo, - provider: provider, - decode: nil, - shouldInterpolate: false, - intent: CGColorRenderingIntent.defaultIntent - )! - - return try body(cgImage) - + return try slice.image.pixels.withUnsafeMutableBufferPointer { pixelsPointer in + let bytes: UnsafeMutablePointer = pixelsPointer.baseAddress! + (slice.yRange.lowerBound * slice.image.width + slice.xRange.lowerBound) + let provider: CGDataProvider = CGDataProvider(data: Data( + bytesNoCopy: bytes, + count: length, + deallocator: .none + ) as CFData)! + + let cgImage = CGImage( + width: width, + height: height, + bitsPerComponent: MemoryLayout.size * 8, + bitsPerPixel: MemoryLayout.size * 8, + bytesPerRow: MemoryLayout.size * image.width, + space: Pixel._ez_cgColorSpace, + bitmapInfo: Pixel._ez_cgBitmapInfo, + provider: provider, + decode: nil, + shouldInterpolate: false, + intent: CGColorRenderingIntent.defaultIntent + )! + + return try body(cgImage) + } } @inlinable @@ -582,25 +592,28 @@ extension ImageSlice: _CGImageDirectlyConvertible where Pixel: _CGDirectPixel { precondition(width >= 0) precondition(height >= 0) - let data: UnsafeMutablePointer = &self.image.pixels + (yRange.lowerBound * self.image.width + xRange.lowerBound) - let context = CGContext( - data: data, - width: width, - height: height, - bitsPerComponent: MemoryLayout.size * 8, - bytesPerRow: MemoryLayout.size * self.image.width, - space: Pixel._ez_cgColorSpace, - bitmapInfo: Pixel._ez_cgBitmapInfo.rawValue - )! - switch coordinates { - case .original: - break - case .natural: - context.scaleBy(x: 1, y: -1) - context.translateBy(x: 0.5 - CGFloat(xRange.lowerBound), y: 0.5 - CGFloat(yRange.lowerBound + height)) + try self.image.pixels.withUnsafeMutableBufferPointer { pixelsPointer in + let data: UnsafeMutablePointer = pixelsPointer.baseAddress! + (yRange.lowerBound * self.image.width + xRange.lowerBound) + + let context = CGContext( + data: data, + width: width, + height: height, + bitsPerComponent: MemoryLayout.size * 8, + bytesPerRow: MemoryLayout.size * self.image.width, + space: Pixel._ez_cgColorSpace, + bitmapInfo: Pixel._ez_cgBitmapInfo.rawValue + )! + switch coordinates { + case .original: + break + case .natural: + context.scaleBy(x: 1, y: -1) + context.translateBy(x: 0.5 - CGFloat(xRange.lowerBound), y: 0.5 - CGFloat(yRange.lowerBound + height)) + } + + try body(context) } - - try body(context) } } #endif From 3345065ac509c88f9dc64d07303c29fd6ea9179f Mon Sep 17 00:00:00 2001 From: Yuta Koshizawa Date: Sun, 26 Apr 2020 21:27:02 +0900 Subject: [PATCH 4/4] Make it available for Swift 5.0 Modifications in last commit made it unavailable for Swift 5.0. --- Sources/SwiftImage/CoreGraphics.swift | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Sources/SwiftImage/CoreGraphics.swift b/Sources/SwiftImage/CoreGraphics.swift index 4c980f1..be166bf 100644 --- a/Sources/SwiftImage/CoreGraphics.swift +++ b/Sources/SwiftImage/CoreGraphics.swift @@ -556,10 +556,14 @@ extension ImageSlice: _CGImageDirectlyConvertible where Pixel: _CGDirectPixel { @inlinable public func withCGImage(_ body: (CGImage) throws -> R) rethrows -> R { let length = image.width * self.height * MemoryLayout.size + let width = self.width + let xRange = self.xRange + let yRange = self.yRange + let imageWidth = self.image.width var slice = self return try slice.image.pixels.withUnsafeMutableBufferPointer { pixelsPointer in - let bytes: UnsafeMutablePointer = pixelsPointer.baseAddress! + (slice.yRange.lowerBound * slice.image.width + slice.xRange.lowerBound) + let bytes: UnsafeMutablePointer = pixelsPointer.baseAddress! + (yRange.lowerBound * imageWidth + xRange.lowerBound) let provider: CGDataProvider = CGDataProvider(data: Data( bytesNoCopy: bytes, count: length, @@ -588,19 +592,22 @@ extension ImageSlice: _CGImageDirectlyConvertible where Pixel: _CGDirectPixel { public mutating func withCGContext(coordinates: CGContextCoordinates = .natural, _ body: (CGContext) throws -> Void) rethrows { let width = self.width let height = self.height + let xRange = self.xRange + let yRange = self.yRange + let imageWidth = self.image.width precondition(width >= 0) precondition(height >= 0) try self.image.pixels.withUnsafeMutableBufferPointer { pixelsPointer in - let data: UnsafeMutablePointer = pixelsPointer.baseAddress! + (yRange.lowerBound * self.image.width + xRange.lowerBound) + let data: UnsafeMutablePointer = pixelsPointer.baseAddress! + (yRange.lowerBound * imageWidth + xRange.lowerBound) let context = CGContext( data: data, width: width, height: height, bitsPerComponent: MemoryLayout.size * 8, - bytesPerRow: MemoryLayout.size * self.image.width, + bytesPerRow: MemoryLayout.size * imageWidth, space: Pixel._ez_cgColorSpace, bitmapInfo: Pixel._ez_cgBitmapInfo.rawValue )!