Try to solve the SwiftUI bug of rendering EXIF UIImage in WebImage. Now we use the Image(decorative:) API to grab the CGImage and orientation instead. For vector image, draw vector image into bitmap for rescue

This commit is contained in:
DreamPiggy 2020-04-15 12:31:57 +08:00
parent 14a3cce8e5
commit 0a41337ed0
9 changed files with 138 additions and 43 deletions

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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() {

View File

@ -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)
}

View File

@ -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://dv6mh24acw2xi.cloudfront.net/public/moments/gabbr/tmpImg9143171451882065582.jpeg",
"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",

View File

@ -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;
};

View File

@ -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

View File

@ -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

View File

@ -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,47 @@ 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 {
var image = image
// Actual rendering SwiftUI image
let result: Image
// NSImage works well with SwiftUI, include Animated and Vector Image
#if os(macOS)
result = Image(nsImage: image)
#else
// Fix the SwiftUI.Image rendering issue when use UIImage based, the `.aspectRatio` does not works. SwiftUI's Bug :)
// See issue #101
// Case 1: UIAnimatedImage, grab poster image
if image.sd_isAnimated {
// check images property
if let images = image.images, images.count > 0 {
image = images[0]
}
}
// Case 2: Vector Image, draw bitmap image
else if image.sd_isVector {
// ensure CGImage is nil
if image.cgImage == nil {
// draw vector into bitmap with the screen scale (behavior like AppKit)
UIGraphicsBeginImageContextWithOptions(image.size, false, UIScreen.main.scale)
image.draw(at: .zero)
image = UIGraphicsGetImageFromCurrentImageContext() ?? .empty
UIGraphicsEndImageContext()
}
}
// If we have CGImage, use CGImage based API, else use UIImage based API
if let cgImage = image.cgImage {
let orientation = image.imageOrientation.toSwiftUI
result = Image(decorative: cgImage, scale: image.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 +173,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 +297,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)
}
}
}