Merge pull request #102 from SDWebImage/hack_exif_webimage_rendering_aspect_ratio
Try to solve the SwiftUI bug of rendering EXIF UIImage in WebImage, as well as vector images
This commit is contained in:
commit
10ab2a2415
|
@ -39,13 +39,9 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||
// Dynamic check to support vector format for both WebImage/AnimatedImage
|
||||
SDWebImageManager.shared.optionsProcessor = SDWebImageOptionsProcessor { url, options, context in
|
||||
var options = options
|
||||
var context = context
|
||||
if let _ = context?[.animatedImageClass] as? SDAnimatedImage.Type {
|
||||
// AnimatedImage supports vector rendering, should not force decode
|
||||
options.insert(.avoidDecodeImage)
|
||||
} else {
|
||||
// WebImage supports bitmap rendering only
|
||||
context?[.imageThumbnailPixelSize] = CGSize.zero
|
||||
}
|
||||
return SDWebImageOptionsResult(options: options, context: context)
|
||||
}
|
||||
|
|
|
@ -47,13 +47,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||
// Dynamic check to support vector format for both WebImage/AnimatedImage
|
||||
SDWebImageManager.shared.optionsProcessor = SDWebImageOptionsProcessor { url, options, context in
|
||||
var options = options
|
||||
var context = context
|
||||
if let _ = context?[.animatedImageClass] as? SDAnimatedImage.Type {
|
||||
// AnimatedImage supports vector rendering, should not force decode
|
||||
options.insert(.avoidDecodeImage)
|
||||
} else {
|
||||
// WebImage supports bitmap rendering only
|
||||
context?[.imageThumbnailPixelSize] = CGSize.zero
|
||||
}
|
||||
return SDWebImageOptionsResult(options: options, context: context)
|
||||
}
|
||||
|
|
|
@ -20,13 +20,6 @@ class ExtensionDelegate: NSObject, WKExtensionDelegate {
|
|||
SDImageCodersManager.shared.addCoder(SDImageWebPCoder.shared)
|
||||
SDImageCodersManager.shared.addCoder(SDImageSVGCoder.shared)
|
||||
SDImageCodersManager.shared.addCoder(SDImagePDFCoder.shared)
|
||||
// Dynamic check to support vector format for WebImage
|
||||
SDWebImageManager.shared.optionsProcessor = SDWebImageOptionsProcessor { url, options, context in
|
||||
var context = context
|
||||
// WebImage supports bitmap rendering only
|
||||
context?[.imageThumbnailPixelSize] = CGSize.zero
|
||||
return SDWebImageOptionsResult(options: options, context: context)
|
||||
}
|
||||
}
|
||||
|
||||
func applicationDidBecomeActive() {
|
||||
|
|
|
@ -26,13 +26,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||
// Dynamic check to support vector format for both WebImage/AnimatedImage
|
||||
SDWebImageManager.shared.optionsProcessor = SDWebImageOptionsProcessor { url, options, context in
|
||||
var options = options
|
||||
var context = context
|
||||
if let _ = context?[.animatedImageClass] as? SDAnimatedImage.Type {
|
||||
// AnimatedImage supports vector rendering, should not force decode
|
||||
options.insert(.avoidDecodeImage)
|
||||
} else {
|
||||
// WebImage supports bitmap rendering only
|
||||
context?[.imageThumbnailPixelSize] = CGSize.zero
|
||||
}
|
||||
return SDWebImageOptionsResult(options: options, context: context)
|
||||
}
|
||||
|
|
|
@ -81,6 +81,7 @@ struct ContentView: View {
|
|||
"https://nr-platform.s3.amazonaws.com/uploads/platform/published_extension/branding_icon/275/AmazonS3.png",
|
||||
"https://raw.githubusercontent.com/ibireme/YYImage/master/Demo/YYImageDemo/mew_baseline.jpg",
|
||||
"https://via.placeholder.com/200x200.jpg",
|
||||
"https://raw.githubusercontent.com/recurser/exif-orientation-examples/master/Landscape_5.jpg",
|
||||
"https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/w3c.svg",
|
||||
"https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/wikimedia.svg",
|
||||
"https://raw.githubusercontent.com/icons8/flat-color-icons/master/pdf/stack_of_photos.pdf",
|
||||
|
|
|
@ -75,6 +75,10 @@
|
|||
32C43E3322FD5DF400BE87F5 /* SDWebImageSwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32C43E3122FD5DE100BE87F5 /* SDWebImageSwiftUI.swift */; };
|
||||
32C43E3422FD5DF400BE87F5 /* SDWebImageSwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32C43E3122FD5DE100BE87F5 /* SDWebImageSwiftUI.swift */; };
|
||||
32C43E3522FD5DF400BE87F5 /* SDWebImageSwiftUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32C43E3122FD5DE100BE87F5 /* SDWebImageSwiftUI.swift */; };
|
||||
32D26A022446B546005905DA /* Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32D26A012446B546005905DA /* Image.swift */; };
|
||||
32D26A032446B546005905DA /* Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32D26A012446B546005905DA /* Image.swift */; };
|
||||
32D26A042446B546005905DA /* Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32D26A012446B546005905DA /* Image.swift */; };
|
||||
32D26A052446B546005905DA /* Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32D26A012446B546005905DA /* Image.swift */; };
|
||||
32ED4826242A13030053338E /* ImageManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32ED4825242A13030053338E /* ImageManagerTests.swift */; };
|
||||
32ED4827242A13030053338E /* ImageManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32ED4825242A13030053338E /* ImageManagerTests.swift */; };
|
||||
32ED4828242A13030053338E /* ImageManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32ED4825242A13030053338E /* ImageManagerTests.swift */; };
|
||||
|
@ -133,6 +137,7 @@
|
|||
32C43E2922FD586200BE87F5 /* SDWebImage.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SDWebImage.framework; path = Carthage/Build/tvOS/SDWebImage.framework; sourceTree = "<group>"; };
|
||||
32C43E2D22FD586E00BE87F5 /* SDWebImage.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SDWebImage.framework; path = Carthage/Build/watchOS/SDWebImage.framework; sourceTree = "<group>"; };
|
||||
32C43E3122FD5DE100BE87F5 /* SDWebImageSwiftUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SDWebImageSwiftUI.swift; sourceTree = "<group>"; };
|
||||
32D26A012446B546005905DA /* Image.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Image.swift; sourceTree = "<group>"; };
|
||||
32ED4825242A13030053338E /* ImageManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageManagerTests.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
|
@ -278,6 +283,7 @@
|
|||
32C43DDF22FD54C600BE87F5 /* AnimatedImage.swift */,
|
||||
32C43E3122FD5DE100BE87F5 /* SDWebImageSwiftUI.swift */,
|
||||
326E480923431C0F00C633E9 /* ImageViewWrapper.swift */,
|
||||
32D26A012446B546005905DA /* Image.swift */,
|
||||
);
|
||||
path = Classes;
|
||||
sourceTree = "<group>";
|
||||
|
@ -698,6 +704,7 @@
|
|||
326B8487236335110011BDFB /* ActivityIndicator.swift in Sources */,
|
||||
32C43E1622FD583700BE87F5 /* ImageManager.swift in Sources */,
|
||||
32C43E1822FD583700BE87F5 /* AnimatedImage.swift in Sources */,
|
||||
32D26A022446B546005905DA /* Image.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -714,6 +721,7 @@
|
|||
326B8488236335110011BDFB /* ActivityIndicator.swift in Sources */,
|
||||
32C43E1922FD583700BE87F5 /* ImageManager.swift in Sources */,
|
||||
32C43E1B22FD583700BE87F5 /* AnimatedImage.swift in Sources */,
|
||||
32D26A032446B546005905DA /* Image.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -730,6 +738,7 @@
|
|||
326B8489236335110011BDFB /* ActivityIndicator.swift in Sources */,
|
||||
32C43E1C22FD583800BE87F5 /* ImageManager.swift in Sources */,
|
||||
32C43E1E22FD583800BE87F5 /* AnimatedImage.swift in Sources */,
|
||||
32D26A042446B546005905DA /* Image.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -746,6 +755,7 @@
|
|||
326B848A236335110011BDFB /* ActivityIndicator.swift in Sources */,
|
||||
32C43E1F22FD583800BE87F5 /* ImageManager.swift in Sources */,
|
||||
32C43E2122FD583800BE87F5 /* AnimatedImage.swift in Sources */,
|
||||
32D26A052446B546005905DA /* Image.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -1369,7 +1379,7 @@
|
|||
repositoryURL = "https://github.com/nalexn/ViewInspector.git";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 0.3.5;
|
||||
minimumVersion = 0.3.11;
|
||||
};
|
||||
};
|
||||
/* End XCRemoteSwiftPackageReference section */
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
"repositoryURL": "https://github.com/nalexn/ViewInspector.git",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "ec943ed718cd293b95f17a2b81e8917d6ed70752",
|
||||
"version": "0.3.8"
|
||||
"revision": "7d55eb940242512aad2bf28db354d09d5de43893",
|
||||
"version": "0.3.11"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* This file is part of the SDWebImage package.
|
||||
* (c) DreamPiggy <lizhuoli1126@126.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
|
||||
extension Image {
|
||||
@inlinable init(platformImage: PlatformImage) {
|
||||
#if os(macOS)
|
||||
self.init(nsImage: platformImage)
|
||||
#else
|
||||
self.init(uiImage: platformImage)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
|
||||
extension PlatformImage {
|
||||
static var empty = PlatformImage()
|
||||
}
|
||||
|
||||
#if os(iOS) || os(tvOS) || os(watchOS)
|
||||
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
|
||||
extension PlatformImage.Orientation {
|
||||
@inlinable var toSwiftUI: Image.Orientation {
|
||||
switch self {
|
||||
case .up:
|
||||
return .up
|
||||
case .upMirrored:
|
||||
return .upMirrored
|
||||
case .down:
|
||||
return .down
|
||||
case .downMirrored:
|
||||
return .downMirrored
|
||||
case .left:
|
||||
return .left
|
||||
case .leftMirrored:
|
||||
return .leftMirrored
|
||||
case .right:
|
||||
return .right
|
||||
case .rightMirrored:
|
||||
return .rightMirrored
|
||||
@unknown default:
|
||||
return .up
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Image.Orientation {
|
||||
@inlinable var toPlatform: PlatformImage.Orientation {
|
||||
switch self {
|
||||
case .up:
|
||||
return .up
|
||||
case .upMirrored:
|
||||
return .upMirrored
|
||||
case .down:
|
||||
return .down
|
||||
case .downMirrored:
|
||||
return .downMirrored
|
||||
case .left:
|
||||
return .left
|
||||
case .leftMirrored:
|
||||
return .leftMirrored
|
||||
case .right:
|
||||
return .right
|
||||
case .rightMirrored:
|
||||
return .rightMirrored
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
|
@ -18,19 +18,6 @@ public typealias PlatformImage = NSImage
|
|||
public typealias PlatformImage = UIImage
|
||||
#endif
|
||||
|
||||
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
|
||||
extension Image {
|
||||
init(platformImage: PlatformImage) {
|
||||
#if os(macOS)
|
||||
self.init(nsImage: platformImage)
|
||||
#else
|
||||
self.init(uiImage: platformImage)
|
||||
#endif
|
||||
}
|
||||
|
||||
static var empty = Image(platformImage: PlatformImage())
|
||||
}
|
||||
|
||||
#if os(macOS)
|
||||
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
|
||||
public typealias PlatformView = NSView
|
||||
|
|
|
@ -63,13 +63,13 @@ public struct WebImage : View {
|
|||
// this prefetch the memory cache of image, to immediately render it on screen
|
||||
// this solve the case when `onAppear` not been called, for example, some transaction indeterminate state, SwiftUI :)
|
||||
if imageManager.isFirstPrefetch {
|
||||
self.imageManager.prefetch()
|
||||
imageManager.prefetch()
|
||||
}
|
||||
return Group {
|
||||
if imageManager.image != nil {
|
||||
if isAnimating && !self.imageManager.isIncremental {
|
||||
if isAnimating && !imageManager.isIncremental {
|
||||
if currentFrame != nil {
|
||||
configure(image: Image(platformImage: currentFrame!))
|
||||
configure(image: currentFrame!)
|
||||
.onAppear {
|
||||
self.imagePlayer?.startPlaying()
|
||||
}
|
||||
|
@ -84,16 +84,16 @@ public struct WebImage : View {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
configure(image: Image(platformImage: imageManager.image!))
|
||||
configure(image: imageManager.image!)
|
||||
.onReceive(imageManager.$image) { image in
|
||||
self.setupPlayer(image: image)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if currentFrame != nil {
|
||||
configure(image: Image(platformImage: currentFrame!))
|
||||
configure(image: currentFrame!)
|
||||
} else {
|
||||
configure(image: Image(platformImage: imageManager.image!))
|
||||
configure(image: imageManager.image!)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -122,10 +122,52 @@ public struct WebImage : View {
|
|||
}
|
||||
}
|
||||
|
||||
func configure(image: Image) -> some View {
|
||||
/// Configure the platform image into the SwiftUI rendering image
|
||||
func configure(image: PlatformImage) -> some View {
|
||||
// Actual rendering SwiftUI image
|
||||
let result: Image
|
||||
// NSImage works well with SwiftUI, include Vector and EXIF images.
|
||||
#if os(macOS)
|
||||
result = Image(nsImage: image)
|
||||
#else
|
||||
// Fix the SwiftUI.Image rendering issue, like when use EXIF UIImage, the `.aspectRatio` does not works. SwiftUI's Bug :)
|
||||
// See issue #101
|
||||
var cgImage: CGImage?
|
||||
// Case 1: Vector Image, draw bitmap image
|
||||
if image.sd_isVector {
|
||||
// ensure CGImage is nil
|
||||
if image.cgImage == nil {
|
||||
// draw vector into bitmap with the screen scale (behavior like AppKit)
|
||||
#if os(iOS) || os(tvOS)
|
||||
let scale = UIScreen.main.scale
|
||||
#else
|
||||
let scale = WKInterfaceDevice.current().screenScale
|
||||
#endif
|
||||
UIGraphicsBeginImageContextWithOptions(image.size, false, scale)
|
||||
image.draw(at: .zero)
|
||||
cgImage = UIGraphicsGetImageFromCurrentImageContext()?.cgImage
|
||||
UIGraphicsEndImageContext()
|
||||
} else {
|
||||
cgImage = image.cgImage
|
||||
}
|
||||
}
|
||||
// Case 2: Image with EXIF orientation (only EXIF 5-8 contains bug)
|
||||
else if [.left, .leftMirrored, .right, .rightMirrored].contains(image.imageOrientation) {
|
||||
cgImage = image.cgImage
|
||||
}
|
||||
// If we have CGImage, use CGImage based API, else use UIImage based API
|
||||
if let cgImage = cgImage {
|
||||
let scale = image.scale
|
||||
let orientation = image.imageOrientation.toSwiftUI
|
||||
result = Image(decorative: cgImage, scale: scale, orientation: orientation)
|
||||
} else {
|
||||
result = Image(uiImage: image)
|
||||
}
|
||||
#endif
|
||||
|
||||
// Should not use `EmptyView`, which does not respect to the container's frame modifier
|
||||
// Using a empty image instead for better compatible
|
||||
configurations.reduce(image) { (previous, configuration) in
|
||||
return configurations.reduce(result) { (previous, configuration) in
|
||||
configuration(previous)
|
||||
}
|
||||
}
|
||||
|
@ -136,12 +178,12 @@ public struct WebImage : View {
|
|||
if let placeholder = placeholder {
|
||||
// If use `.delayPlaceholder`, the placeholder is applied after loading failed, hide during loading :)
|
||||
if imageManager.options.contains(.delayPlaceholder) && imageManager.isLoading {
|
||||
return AnyView(configure(image: Image.empty))
|
||||
return AnyView(configure(image: .empty))
|
||||
} else {
|
||||
return placeholder
|
||||
}
|
||||
} else {
|
||||
return AnyView(configure(image: Image.empty))
|
||||
return AnyView(configure(image: .empty))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -260,7 +302,9 @@ extension WebImage {
|
|||
/// - Parameter image: A Image view that describes the placeholder.
|
||||
public func placeholder(_ image: Image) -> WebImage {
|
||||
return placeholder {
|
||||
configure(image: image)
|
||||
configurations.reduce(image) { (previous, configuration) in
|
||||
configuration(previous)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -133,4 +133,23 @@ class WebImageTests: XCTestCase {
|
|||
ViewHosting.expel()
|
||||
}
|
||||
|
||||
func testWebImageEXIFImage() throws {
|
||||
let expectation = self.expectation(description: "WebImage EXIF image url")
|
||||
// EXIF 5, Left Mirrored
|
||||
let imageUrl = URL(string: "https://raw.githubusercontent.com/recurser/exif-orientation-examples/master/Landscape_5.jpg")
|
||||
let imageView = WebImage(url: imageUrl)
|
||||
let introspectView = imageView.onSuccess { image, cacheType in
|
||||
let displayImage = try? imageView.inspect().group().image(0).cgImage()
|
||||
let orientation = try! imageView.inspect().group().image(0).orientation()
|
||||
XCTAssertNotNil(displayImage)
|
||||
XCTAssertEqual(orientation, .leftMirrored)
|
||||
expectation.fulfill()
|
||||
}.onFailure { error in
|
||||
XCTFail(error.localizedDescription)
|
||||
}
|
||||
_ = try introspectView.inspect()
|
||||
ViewHosting.host(view: introspectView)
|
||||
self.waitForExpectations(timeout: 5, handler: nil)
|
||||
ViewHosting.expel()
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue