Merge pull request #657 from mpretty-cyro/fix/use-yyimageview-only-when-needed

Only use YYImage for Gif/WebP images
This commit is contained in:
Morgan Pretty 2022-08-16 14:01:07 +10:00 committed by GitHub
commit b53ad0c5ed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 156 additions and 53 deletions

View File

@ -240,7 +240,7 @@ public final class FullConversationCell: UITableViewCell {
profile: cellViewModel.profile, profile: cellViewModel.profile,
additionalProfile: cellViewModel.additionalProfile, additionalProfile: cellViewModel.additionalProfile,
threadVariant: cellViewModel.threadVariant, threadVariant: cellViewModel.threadVariant,
openGroupProfilePicture: cellViewModel.openGroupProfilePictureData.map { UIImage(data: $0) }, openGroupProfilePictureData: cellViewModel.openGroupProfilePictureData,
useFallbackPicture: (cellViewModel.threadVariant == .openGroup && cellViewModel.openGroupProfilePictureData == nil) useFallbackPicture: (cellViewModel.threadVariant == .openGroup && cellViewModel.openGroupProfilePictureData == nil)
) )
@ -280,7 +280,7 @@ public final class FullConversationCell: UITableViewCell {
profile: cellViewModel.profile, profile: cellViewModel.profile,
additionalProfile: cellViewModel.additionalProfile, additionalProfile: cellViewModel.additionalProfile,
threadVariant: cellViewModel.threadVariant, threadVariant: cellViewModel.threadVariant,
openGroupProfilePicture: cellViewModel.openGroupProfilePictureData.map { UIImage(data: $0) }, openGroupProfilePictureData: cellViewModel.openGroupProfilePictureData,
useFallbackPicture: (cellViewModel.threadVariant == .openGroup && cellViewModel.openGroupProfilePictureData == nil) useFallbackPicture: (cellViewModel.threadVariant == .openGroup && cellViewModel.openGroupProfilePictureData == nil)
) )
@ -341,7 +341,7 @@ public final class FullConversationCell: UITableViewCell {
profile: cellViewModel.profile, profile: cellViewModel.profile,
additionalProfile: cellViewModel.additionalProfile, additionalProfile: cellViewModel.additionalProfile,
threadVariant: cellViewModel.threadVariant, threadVariant: cellViewModel.threadVariant,
openGroupProfilePicture: cellViewModel.openGroupProfilePictureData.map { UIImage(data: $0) }, openGroupProfilePictureData: cellViewModel.openGroupProfilePictureData,
useFallbackPicture: ( useFallbackPicture: (
cellViewModel.threadVariant == .openGroup && cellViewModel.threadVariant == .openGroup &&
cellViewModel.openGroupProfilePictureData == nil cellViewModel.openGroupProfilePictureData == nil

View File

@ -94,7 +94,7 @@ final class SimplifiedConversationCell: UITableViewCell {
profile: cellViewModel.profile, profile: cellViewModel.profile,
additionalProfile: cellViewModel.additionalProfile, additionalProfile: cellViewModel.additionalProfile,
threadVariant: cellViewModel.threadVariant, threadVariant: cellViewModel.threadVariant,
openGroupProfilePicture: cellViewModel.openGroupProfilePictureData.map { UIImage(data: $0) }, openGroupProfilePictureData: cellViewModel.openGroupProfilePictureData,
useFallbackPicture: (cellViewModel.threadVariant == .openGroup && cellViewModel.openGroupProfilePictureData == nil), useFallbackPicture: (cellViewModel.threadVariant == .openGroup && cellViewModel.openGroupProfilePictureData == nil),
showMultiAvatarForClosedGroup: true showMultiAvatarForClosedGroup: true
) )

View File

@ -34,6 +34,8 @@ public extension Data {
case (0x42, 0x4d): return .bmp case (0x42, 0x4d): return .bmp
case (0x4D, 0x4D): return .tiff // Motorola byte order TIFF case (0x4D, 0x4D): return .tiff // Motorola byte order TIFF
case (0x49, 0x49): return .tiff // Intel byte order TIFF case (0x49, 0x49): return .tiff // Intel byte order TIFF
case (0x52, 0x49): return .webp // First two letters of WebP
default: return .unknown default: return .unknown
} }
} }
@ -113,6 +115,9 @@ public extension Data {
mimeType == OWSMimeTypeImageBmp1 || mimeType == OWSMimeTypeImageBmp1 ||
mimeType == OWSMimeTypeImageBmp2 mimeType == OWSMimeTypeImageBmp2
) )
case .webp:
return (mimeType == nil || mimeType == OWSMimeTypeImageWebp)
} }
} }

View File

@ -9,4 +9,5 @@ public enum ImageFormat {
case tiff case tiff
case jpeg case jpeg
case bmp case bmp
case webp
} }

View File

@ -328,7 +328,7 @@ typedef struct {
// Intel byte order TIFF // Intel byte order TIFF
return ImageFormat_Tiff; return ImageFormat_Tiff;
} else if (byte0 == 0x52 && byte1 == 0x49) { } else if (byte0 == 0x52 && byte1 == 0x49) {
// First two letters of RIFF tag. // First two letters of WebP tag.
return ImageFormat_Webp; return ImageFormat_Webp;
} }

View File

@ -19,8 +19,61 @@ public final class ProfilePictureView: UIView {
// MARK: - Components // MARK: - Components
private lazy var imageView = getImageView() private lazy var imageContainerView: UIView = {
private lazy var additionalImageView = getImageView() let result: UIView = UIView()
result.translatesAutoresizingMaskIntoConstraints = false
result.clipsToBounds = true
result.backgroundColor = Colors.unimportant
return result
}()
private lazy var imageView: UIImageView = {
let result: UIImageView = UIImageView()
result.translatesAutoresizingMaskIntoConstraints = false
result.contentMode = .scaleAspectFill
result.isHidden = true
return result
}()
private lazy var animatedImageView: YYAnimatedImageView = {
let result: YYAnimatedImageView = YYAnimatedImageView()
result.translatesAutoresizingMaskIntoConstraints = false
result.contentMode = .scaleAspectFill
result.isHidden = true
return result
}()
private lazy var additionalImageContainerView: UIView = {
let result: UIView = UIView()
result.translatesAutoresizingMaskIntoConstraints = false
result.clipsToBounds = true
result.backgroundColor = Colors.unimportant
result.layer.cornerRadius = (Values.smallProfilePictureSize / 2)
result.isHidden = true
return result
}()
private lazy var additionalImageView: UIImageView = {
let result: UIImageView = UIImageView()
result.translatesAutoresizingMaskIntoConstraints = false
result.contentMode = .scaleAspectFill
result.isHidden = true
return result
}()
private lazy var additionalAnimatedImageView: YYAnimatedImageView = {
let result: YYAnimatedImageView = YYAnimatedImageView()
result.translatesAutoresizingMaskIntoConstraints = false
result.contentMode = .scaleAspectFill
result.isHidden = true
return result
}()
// MARK: - Lifecycle // MARK: - Lifecycle
@ -35,27 +88,33 @@ public final class ProfilePictureView: UIView {
} }
private func setUpViewHierarchy() { private func setUpViewHierarchy() {
// Set up image view
addSubview(imageView)
imageView.pin(.leading, to: .leading, of: self)
imageView.pin(.top, to: .top, of: self)
let imageViewSize = CGFloat(Values.mediumProfilePictureSize) let imageViewSize = CGFloat(Values.mediumProfilePictureSize)
imageViewWidthConstraint = imageView.set(.width, to: imageViewSize)
imageViewHeightConstraint = imageView.set(.height, to: imageViewSize)
// Set up additional image view
addSubview(additionalImageView)
additionalImageView.pin(.trailing, to: .trailing, of: self)
additionalImageView.pin(.bottom, to: .bottom, of: self)
let additionalImageViewSize = CGFloat(Values.smallProfilePictureSize) let additionalImageViewSize = CGFloat(Values.smallProfilePictureSize)
additionalImageViewWidthConstraint = additionalImageView.set(.width, to: additionalImageViewSize)
additionalImageViewHeightConstraint = additionalImageView.set(.height, to: additionalImageViewSize) addSubview(imageContainerView)
additionalImageView.layer.cornerRadius = additionalImageViewSize / 2 addSubview(additionalImageContainerView)
imageContainerView.pin(.leading, to: .leading, of: self)
imageContainerView.pin(.top, to: .top, of: self)
imageViewWidthConstraint = imageContainerView.set(.width, to: imageViewSize)
imageViewHeightConstraint = imageContainerView.set(.height, to: imageViewSize)
additionalImageContainerView.pin(.trailing, to: .trailing, of: self)
additionalImageContainerView.pin(.bottom, to: .bottom, of: self)
additionalImageViewWidthConstraint = additionalImageContainerView.set(.width, to: additionalImageViewSize)
additionalImageViewHeightConstraint = additionalImageContainerView.set(.height, to: additionalImageViewSize)
imageContainerView.addSubview(imageView)
imageContainerView.addSubview(animatedImageView)
additionalImageContainerView.addSubview(additionalImageView)
additionalImageContainerView.addSubview(additionalAnimatedImageView)
imageView.pin(to: imageContainerView)
animatedImageView.pin(to: imageContainerView)
additionalImageView.pin(to: additionalImageContainerView)
additionalAnimatedImageView.pin(to: additionalImageContainerView)
} }
// FIXME: Remove this once we refactor the ConversationVC to Swift (use the HomeViewModel approach) // FIXME: Remove this once we refactor the OWSConversationSettingsViewController to Swift (use the HomeViewModel approach)
@objc(updateForThreadId:) @objc(updateForThreadId:)
public func update(forThreadId threadId: String?) { public func update(forThreadId threadId: String?) {
guard guard
@ -74,7 +133,7 @@ public final class ProfilePictureView: UIView {
profile: viewModel.profile, profile: viewModel.profile,
additionalProfile: viewModel.additionalProfile, additionalProfile: viewModel.additionalProfile,
threadVariant: viewModel.threadVariant, threadVariant: viewModel.threadVariant,
openGroupProfilePicture: viewModel.openGroupProfilePictureData.map { UIImage(data: $0) }, openGroupProfilePictureData: viewModel.openGroupProfilePictureData,
useFallbackPicture: ( useFallbackPicture: (
viewModel.threadVariant == .openGroup && viewModel.threadVariant == .openGroup &&
viewModel.openGroupProfilePictureData == nil viewModel.openGroupProfilePictureData == nil
@ -88,7 +147,7 @@ public final class ProfilePictureView: UIView {
profile: Profile? = nil, profile: Profile? = nil,
additionalProfile: Profile? = nil, additionalProfile: Profile? = nil,
threadVariant: SessionThread.Variant, threadVariant: SessionThread.Variant,
openGroupProfilePicture: UIImage? = nil, openGroupProfilePictureData: Data? = nil,
useFallbackPicture: Bool = false, useFallbackPicture: Bool = false,
showMultiAvatarForClosedGroup: Bool = false showMultiAvatarForClosedGroup: Bool = false
) { ) {
@ -101,20 +160,38 @@ public final class ProfilePictureView: UIView {
} }
imageView.contentMode = .center imageView.contentMode = .center
imageView.backgroundColor = UIColor(rgbHex: 0x353535) imageView.isHidden = false
imageView.layer.cornerRadius = (self.size / 2) animatedImageView.isHidden = true
imageContainerView.backgroundColor = UIColor(rgbHex: 0x353535)
imageContainerView.layer.cornerRadius = (self.size / 2)
imageViewWidthConstraint.constant = self.size imageViewWidthConstraint.constant = self.size
imageViewHeightConstraint.constant = self.size imageViewHeightConstraint.constant = self.size
additionalImageView.isHidden = true additionalImageContainerView.isHidden = true
animatedImageView.image = nil
additionalImageView.image = nil additionalImageView.image = nil
additionalImageView.layer.cornerRadius = (self.size / 2) additionalAnimatedImageView.image = nil
additionalImageView.isHidden = true
additionalAnimatedImageView.isHidden = true
return return
} }
guard !publicKey.isEmpty || openGroupProfilePicture != nil else { return } guard !publicKey.isEmpty || openGroupProfilePictureData != nil else { return }
func getProfilePicture(of size: CGFloat, for publicKey: String, profile: Profile?) -> (image: UIImage, isTappable: Bool) { func getProfilePicture(of size: CGFloat, for publicKey: String, profile: Profile?) -> (image: UIImage?, animatedImage: YYImage?, isTappable: Bool) {
if let profile: Profile = profile, let profileData: Data = ProfileManager.profileAvatar(profile: profile), let image: YYImage = YYImage(data: profileData) { if let profile: Profile = profile, let profileData: Data = ProfileManager.profileAvatar(profile: profile) {
return (image, true) let format: ImageFormat = profileData.guessedImageFormat
let image: UIImage? = (format == .gif || format == .webp ?
nil :
UIImage(data: profileData)
)
let animatedImage: YYImage? = (format != .gif && format != .webp ?
nil :
YYImage(data: profileData)
)
if image != nil || animatedImage != nil {
return (image, animatedImage, true)
}
} }
return ( return (
@ -124,6 +201,7 @@ public final class ProfilePictureView: UIView {
.defaulting(to: publicKey), .defaulting(to: publicKey),
size: size size: size
), ),
nil,
false false
) )
} }
@ -147,56 +225,75 @@ public final class ProfilePictureView: UIView {
imageViewHeightConstraint.constant = targetSize imageViewHeightConstraint.constant = targetSize
additionalImageViewWidthConstraint.constant = targetSize additionalImageViewWidthConstraint.constant = targetSize
additionalImageViewHeightConstraint.constant = targetSize additionalImageViewHeightConstraint.constant = targetSize
additionalImageView.isHidden = false additionalImageContainerView.isHidden = false
if let additionalProfile: Profile = additionalProfile { if let additionalProfile: Profile = additionalProfile {
additionalImageView.image = getProfilePicture( let (image, animatedImage, _): (UIImage?, YYImage?, Bool) = getProfilePicture(
of: targetSize, of: targetSize,
for: additionalProfile.id, for: additionalProfile.id,
profile: additionalProfile profile: additionalProfile
).image )
// Set the images and show the appropriate imageView (non-animated should be
// visible if there is no image)
additionalImageView.image = image
additionalAnimatedImageView.image = animatedImage
additionalImageView.isHidden = (animatedImage != nil)
additionalAnimatedImageView.isHidden = (animatedImage == nil)
} }
default: default:
targetSize = self.size targetSize = self.size
imageViewWidthConstraint.constant = targetSize imageViewWidthConstraint.constant = targetSize
imageViewHeightConstraint.constant = targetSize imageViewHeightConstraint.constant = targetSize
additionalImageView.isHidden = true additionalImageContainerView.isHidden = true
additionalImageView.image = nil additionalImageView.image = nil
additionalImageView.isHidden = true
additionalAnimatedImageView.image = nil
additionalAnimatedImageView.isHidden = true
} }
// Set the image // Set the image
if let openGroupProfilePicture: UIImage = openGroupProfilePicture { if let openGroupProfilePictureData: Data = openGroupProfilePictureData {
imageView.image = openGroupProfilePicture let format: ImageFormat = openGroupProfilePictureData.guessedImageFormat
let image: UIImage? = (format == .gif || format == .webp ?
nil :
UIImage(data: openGroupProfilePictureData)
)
let animatedImage: YYImage? = (format != .gif && format != .webp ?
nil :
YYImage(data: openGroupProfilePictureData)
)
imageView.image = image
animatedImageView.image = animatedImage
imageView.isHidden = (animatedImage != nil)
animatedImageView.isHidden = (animatedImage == nil)
hasTappableProfilePicture = true hasTappableProfilePicture = true
} }
else { else {
let (image, isTappable): (UIImage, Bool) = getProfilePicture( let (image, animatedImage, isTappable): (UIImage?, YYImage?, Bool) = getProfilePicture(
of: targetSize, of: targetSize,
for: publicKey, for: publicKey,
profile: profile profile: profile
) )
imageView.image = image imageView.image = image
animatedImageView.image = animatedImage
imageView.isHidden = (animatedImage != nil)
animatedImageView.isHidden = (animatedImage == nil)
hasTappableProfilePicture = isTappable hasTappableProfilePicture = isTappable
} }
imageView.contentMode = .scaleAspectFill imageView.contentMode = .scaleAspectFill
imageView.backgroundColor = Colors.unimportant animatedImageView.contentMode = .scaleAspectFill
imageView.layer.cornerRadius = (targetSize / 2) imageContainerView.backgroundColor = Colors.unimportant
additionalImageView.layer.cornerRadius = (targetSize / 2) imageContainerView.layer.cornerRadius = (targetSize / 2)
additionalImageContainerView.layer.cornerRadius = (targetSize / 2)
} }
// MARK: - Convenience // MARK: - Convenience
private func getImageView() -> YYAnimatedImageView {
let result = YYAnimatedImageView()
result.layer.masksToBounds = true
result.backgroundColor = Colors.unimportant
result.contentMode = .scaleAspectFill
return result
}
@objc public func getProfilePicture() -> UIImage? { @objc public func getProfilePicture() -> UIImage? {
return (hasTappableProfilePicture ? imageView.image : nil) return (hasTappableProfilePicture ? imageView.image : nil)
} }