Wallpaper pattern tiling in landscape

This commit is contained in:
Ilya Laktyushin 2023-02-28 22:26:01 +04:00
parent 1d5837a4f8
commit 8733c482c7
20 changed files with 108 additions and 68 deletions

View File

@ -125,7 +125,7 @@ public enum DeviceMetrics: CaseIterable, Equatable {
}
}
var screenSize: CGSize {
public var screenSize: CGSize {
switch self {
case .iPhone4:
return CGSize(width: 320.0, height: 480.0)

View File

@ -1070,7 +1070,7 @@ final class MediaPickerSelectedListNode: ASDisplayNode, UIScrollViewDelegate, UI
self.wallpaperBackgroundNode.update(wallpaper: wallpaper)
self.wallpaperBackgroundNode.updateBubbleTheme(bubbleTheme: theme, bubbleCorners: bubbleCorners)
transition.updateFrame(node: self.wallpaperBackgroundNode, frame: CGRect(origin: CGPoint(x: inset, y: 0.0), size: CGSize(width: size.width - inset * 2.0, height: size.height)))
self.wallpaperBackgroundNode.updateLayout(size: CGSize(width: size.width - inset * 2.0, height: size.height), transition: transition)
self.wallpaperBackgroundNode.updateLayout(size: CGSize(width: size.width - inset * 2.0, height: size.height), tile: false, transition: transition)
self.updateItems(transition: itemsTransition)

View File

@ -2469,7 +2469,7 @@ public func svgIconImageFile(account: Account, fileReference: FileMediaReference
} else {
renderSize = CGSize(width: 90.0, height: 90.0)
}
fullSizeImage = renderPreparedImage(data, renderSize, .clear, UIScreenScale)
fullSizeImage = renderPreparedImage(data, renderSize, .clear, UIScreenScale, false)
if let image = fullSizeImage {
fittedSize = image.size.aspectFitted(arguments.boundingSize)
}

View File

@ -285,7 +285,7 @@ private final class BubbleSettingsControllerNode: ASDisplayNode, UIScrollViewDel
bottomInset = 37.0
self.chatBackgroundNode.frame = chatFrame
self.chatBackgroundNode.updateLayout(size: chatFrame.size, transition: transition)
self.chatBackgroundNode.updateLayout(size: chatFrame.size, tile: false, transition: transition)
self.messagesContainerNode.frame = chatFrame
transition.updateFrame(node: self.toolbarNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - toolbarHeight), size: CGSize(width: layout.size.width, height: toolbarHeight + layout.intrinsicInsets.bottom)))

View File

@ -268,7 +268,7 @@ class ForwardPrivacyChatPreviewItemNode: ListViewItemNode {
backgroundNode.frame = backgroundFrame.insetBy(dx: 0.0, dy: -100.0)
backgroundNode.update(wallpaper: item.wallpaper)
backgroundNode.updateBubbleTheme(bubbleTheme: item.theme, bubbleCorners: item.chatBubbleCorners)
backgroundNode.updateLayout(size: backgroundNode.bounds.size, transition: .immediate)
backgroundNode.updateLayout(size: backgroundNode.bounds.size, tile: false, transition: .immediate)
}
strongSelf.maskNode.frame = backgroundFrame.insetBy(dx: params.leftInset, dy: 0.0)

View File

@ -393,7 +393,7 @@ class ReactionChatPreviewItemNode: ListViewItemNode {
backgroundNode.frame = backgroundFrame.insetBy(dx: 0.0, dy: -100.0)
backgroundNode.update(wallpaper: item.wallpaper)
backgroundNode.updateBubbleTheme(bubbleTheme: item.theme, bubbleCorners: item.chatBubbleCorners)
backgroundNode.updateLayout(size: backgroundNode.bounds.size, transition: .immediate)
backgroundNode.updateLayout(size: backgroundNode.bounds.size, tile: false, transition: .immediate)
}
strongSelf.maskNode.frame = backgroundFrame.insetBy(dx: params.leftInset, dy: 0.0)

View File

@ -564,7 +564,7 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView
}
self.chatBackgroundNode.frame = chatFrame
self.chatBackgroundNode.updateLayout(size: chatFrame.size, transition: transition)
self.chatBackgroundNode.updateLayout(size: chatFrame.size, tile: false, transition: transition)
self.messagesContainerNode.frame = chatFrame
transition.updateFrame(node: self.toolbarNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - toolbarHeight), size: CGSize(width: layout.size.width, height: toolbarHeight + layout.intrinsicInsets.bottom)))

View File

@ -1198,7 +1198,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
transition.updateFrame(node: self.backgroundContainerNode, frame: CGRect(origin: CGPoint(), size: backgroundSize))
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: layout.size))
self.backgroundNode.updateLayout(size: self.backgroundNode.bounds.size, transition: transition)
self.backgroundNode.updateLayout(size: self.backgroundNode.bounds.size, tile: false, transition: transition)
transition.updatePosition(node: self.backgroundWrapperNode, position: CGPoint(x: backgroundSize.width / 2.0, y: backgroundSize.height / 2.0))

View File

@ -712,11 +712,11 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
self.messagesContainerNode.frame = self.chatContainerNode.bounds
self.instantChatBackgroundNode.frame = self.chatContainerNode.bounds
self.instantChatBackgroundNode.updateLayout(size: self.instantChatBackgroundNode.bounds.size, transition: .immediate)
self.instantChatBackgroundNode.updateLayout(size: self.instantChatBackgroundNode.bounds.size, tile: false, transition: .immediate)
self.remoteChatBackgroundNode.frame = self.chatContainerNode.bounds
self.blurredNode.frame = self.chatContainerNode.bounds
self.wallpaperNode.frame = self.chatContainerNode.bounds
self.wallpaperNode.updateLayout(size: self.wallpaperNode.bounds.size, transition: .immediate)
self.wallpaperNode.updateLayout(size: self.wallpaperNode.bounds.size, tile: false, transition: .immediate)
transition.updateFrame(node: self.toolbarNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - toolbarHeight), size: CGSize(width: layout.size.width, height: toolbarHeight)))
self.toolbarNode.updateLayout(size: CGSize(width: layout.size.width, height: 49.0), layout: layout, transition: transition)

View File

@ -267,7 +267,7 @@ class ThemeSettingsChatPreviewItemNode: ListViewItemNode {
let backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
if let backgroundNode = strongSelf.backgroundNode {
backgroundNode.frame = backgroundFrame.insetBy(dx: 0.0, dy: -100.0)
backgroundNode.updateLayout(size: backgroundNode.bounds.size, transition: .immediate)
backgroundNode.updateLayout(size: backgroundNode.bounds.size, tile: false, transition: .immediate)
}
strongSelf.maskNode.frame = backgroundFrame.insetBy(dx: params.leftInset, dy: 0.0)
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))

View File

@ -1169,7 +1169,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
if self.cropNode.supernode == nil {
self.imageNode.frame = self.wrapperNode.bounds
self.nativeNode.frame = self.wrapperNode.bounds
self.nativeNode.updateLayout(size: self.nativeNode.bounds.size, transition: .immediate)
self.nativeNode.updateLayout(size: self.nativeNode.bounds.size, tile: false, transition: .immediate)
self.blurredNode.frame = self.imageNode.frame
} else {
self.cropNode.frame = self.wrapperNode.bounds

View File

@ -1625,7 +1625,7 @@ final class MessageStoryRenderer {
let size = layout.size
self.containerNode.frame = CGRect(origin: CGPoint(), size: layout.size)
self.instantChatBackgroundNode.frame = CGRect(origin: CGPoint(), size: layout.size)
self.instantChatBackgroundNode.updateLayout(size: size, transition: .immediate)
self.instantChatBackgroundNode.updateLayout(size: size, tile: false, transition: .immediate)
self.messagesContainerNode.frame = CGRect(origin: CGPoint(), size: layout.size)
let addressLayout = self.addressNode.updateLayout(size)

View File

@ -5,7 +5,7 @@
#import <UIKit/UIKit.h>
NSData * _Nullable prepareSvgImage(NSData * _Nonnull data);
UIImage * _Nullable renderPreparedImage(NSData * _Nonnull data, CGSize size, UIColor * _Nonnull backgroundColor, CGFloat scale);
UIImage * _Nullable renderPreparedImage(NSData * _Nonnull data, CGSize size, UIColor * _Nonnull backgroundColor, CGFloat scale, bool fit);
UIImage * _Nullable drawSvgImage(NSData * _Nonnull data, CGSize size, UIColor * _Nullable backgroundColor, UIColor * _Nullable foregroundColor, bool opaque);

View File

@ -9,6 +9,11 @@ CGSize aspectFillSize(CGSize size, CGSize bounds) {
return CGSizeMake(floor(size.width * scale), floor(size.height * scale));
}
CGSize aspectFitSize(CGSize size, CGSize bounds) {
CGFloat scale = MIN(bounds.width / MAX(1.0, size.width), bounds.height / MAX(1.0, size.height));
return CGSizeMake(floor(size.width * scale), floor(size.height * scale));
}
@interface SvgXMLParsingDelegate : NSObject <NSXMLParserDelegate> {
NSString *_elementName;
NSString *_currentStyleString;
@ -358,7 +363,7 @@ UIImage * _Nullable drawSvgImage(NSData * _Nonnull data, CGSize size, UIColor *b
@end
UIImage * _Nullable renderPreparedImage(NSData * _Nonnull data, CGSize size, UIColor *backgroundColor, CGFloat scale) {
UIImage * _Nullable renderPreparedImage(NSData * _Nonnull data, CGSize size, UIColor *backgroundColor, CGFloat scale, bool fit) {
NSDate *startTime = [NSDate date];
UIColor *foregroundColor = [UIColor whiteColor];
@ -383,6 +388,15 @@ UIImage * _Nullable renderPreparedImage(NSData * _Nonnull data, CGSize size, UIC
bool isTransparent = [backgroundColor isEqual:[UIColor clearColor]];
CGSize svgSize = CGSizeMake(width, height);
CGSize drawingSize;
if (fit) {
drawingSize = aspectFitSize(svgSize, size);
size = drawingSize;
} else {
drawingSize = aspectFillSize(svgSize, size);
}
UIGraphicsBeginImageContextWithOptions(size, !isTransparent, scale);
CGContextRef context = UIGraphicsGetCurrentContext();
if (isTransparent) {
@ -392,8 +406,7 @@ UIImage * _Nullable renderPreparedImage(NSData * _Nonnull data, CGSize size, UIC
CGContextFillRect(context, CGRectMake(0.0f, 0.0f, size.width, size.height));
}
CGSize svgSize = CGSizeMake(width, height);
CGSize drawingSize = aspectFillSize(svgSize, size);
CGFloat renderScale = MAX(size.width / MAX(1.0, svgSize.width), size.height / MAX(1.0, svgSize.height));

View File

@ -1468,7 +1468,12 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate {
}
transition.updateFrame(node: self.backgroundNode, frame: contentBounds)
self.backgroundNode.updateLayout(size: contentBounds.size, transition: transition)
var shouldTile = false
if case .regular = layout.metrics.widthClass, layout.size.height == layout.deviceMetrics.screenSize.width {
shouldTile = true
}
self.backgroundNode.updateLayout(size: contentBounds.size, tile: shouldTile, transition: transition)
transition.updateBounds(node: self.historyNodeContainer, bounds: contentBounds)
transition.updatePosition(node: self.historyNodeContainer, position: contentBounds.center)

View File

@ -1852,7 +1852,7 @@ private class QrContentNode: ASDisplayNode, ContentNode {
transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(), size: size))
transition.updateFrame(node: self.wallpaperBackgroundNode, frame: CGRect(origin: CGPoint(), size: size))
self.wallpaperBackgroundNode.updateLayout(size: size, transition: transition)
self.wallpaperBackgroundNode.updateLayout(size: size, tile: false, transition: transition)
let textLength = self.codeTextNode.attributedText?.string.count ?? 0
@ -2195,7 +2195,7 @@ private class MessageContentNode: ASDisplayNode, ContentNode {
transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(), size: size))
transition.updateFrame(node: self.wallpaperBackgroundNode, frame: CGRect(origin: CGPoint(), size: size))
self.wallpaperBackgroundNode.updateLayout(size: size, transition: transition)
self.wallpaperBackgroundNode.updateLayout(size: size, tile: false, transition: transition)
let inset: CGFloat = 24.0
let contentInset: CGFloat = 16.0

View File

@ -666,7 +666,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
let cleanInsets = layout.insets(options: [])
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: layout.size))
self.backgroundNode.updateLayout(size: self.backgroundNode.bounds.size, transition: transition)
self.backgroundNode.updateLayout(size: self.backgroundNode.bounds.size, tile: false, transition: transition)
let intrinsicPanelHeight: CGFloat = 47.0
let panelHeight = intrinsicPanelHeight + cleanInsets.bottom

View File

@ -138,7 +138,7 @@ final class MetalWallpaperBackgroundNode: ASDisplayNode, WallpaperBackgroundNode
}
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
func updateLayout(size: CGSize, tile: Bool, transition: ContainedViewLayoutTransition) {
if self.metalLayer.drawableSize != size {
self.metalLayer.drawableSize = size

View File

@ -60,7 +60,7 @@ public protocol WallpaperBackgroundNode: ASDisplayNode {
func update(wallpaper: TelegramWallpaper)
func _internalUpdateIsSettingUpWallpaper()
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition)
func updateLayout(size: CGSize, tile: Bool, transition: ContainedViewLayoutTransition)
func updateIsLooping(_ isLooping: Bool)
func animateEvent(transition: ContainedViewLayoutTransition, extendAnimation: Bool)
func updateBubbleTheme(bubbleTheme: PresentationTheme, bubbleCorners: PresentationChatBubbleCorners)
@ -599,7 +599,7 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode
private let bakedBackgroundView: UIImageView
private var validLayout: CGSize?
private var validLayout: (CGSize, Bool)?
private var wallpaper: TelegramWallpaper?
private var isSettingUpWallpaper: Bool = false
@ -884,8 +884,8 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode
}
}
if let size = self.validLayout {
self.updateLayout(size: size, transition: .immediate)
if let (size, tile) = self.validLayout {
self.updateLayout(size: size, tile: tile, transition: .immediate)
self.updateBubbles()
if scheduleLoopingEvent {
@ -953,7 +953,7 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode
}
}
private func loadPatternForSizeIfNeeded(size: CGSize, transition: ContainedViewLayoutTransition) {
private func loadPatternForSizeIfNeeded(size: CGSize, tile: Bool, transition: ContainedViewLayoutTransition) {
guard let wallpaper = self.wallpaper else {
return
}
@ -1024,8 +1024,8 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode
strongSelf.validPatternImage = ValidPatternImage(wallpaper: wallpaper, invertPattern: invertPattern, generate: generator)
strongSelf.validPatternGeneratedImage = nil
if let size = strongSelf.validLayout {
strongSelf.loadPatternForSizeIfNeeded(size: size, transition: .immediate)
if let (size, tile) = strongSelf.validLayout {
strongSelf.loadPatternForSizeIfNeeded(size: size, tile: tile, transition: .immediate)
} else {
strongSelf._isReady.set(true)
}
@ -1067,7 +1067,7 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode
self.patternImageLayer.suspendCompositionUpdates = false
self.patternImageLayer.updateCompositionIfNeeded()
} else {
let patternArguments = TransformImageArguments(corners: ImageCorners(), imageSize: size, boundingSize: size, intrinsicInsets: UIEdgeInsets(), custom: PatternWallpaperArguments(colors: [patternBackgroundColor], rotation: nil, customPatternColor: patternColor, preview: false), scale: min(2.0, UIScreenScale))
let patternArguments = TransformImageArguments(corners: ImageCorners(), imageSize: size, boundingSize: size, intrinsicInsets: UIEdgeInsets(), custom: PatternWallpaperArguments(colors: [patternBackgroundColor], rotation: nil, customPatternColor: patternColor, preview: false, tile: tile), scale: min(2.0, UIScreenScale))
if self.useSharedAnimationPhase || self.patternImageLayer.contents == nil {
if let drawingContext = validPatternImage.generate(patternArguments) {
if let image = drawingContext.generateImage() {
@ -1121,9 +1121,9 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode
transition.updateFrame(layer: self.patternImageLayer, frame: CGRect(origin: CGPoint(), size: size))
}
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
func updateLayout(size: CGSize, tile: Bool, transition: ContainedViewLayoutTransition) {
let isFirstLayout = self.validLayout == nil
self.validLayout = size
self.validLayout = (size, tile)
if let blurredBackgroundPortalSourceView = self.blurredBackgroundPortalSourceView {
transition.updateFrame(view: blurredBackgroundPortalSourceView, frame: CGRect(origin: CGPoint(), size: size))
@ -1151,7 +1151,7 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode
outgoingBubbleGradientBackgroundNode.updateLayout(size: size, transition: transition, extendAnimation: false, backwards: false, completion: {})
}
self.loadPatternForSizeIfNeeded(size: size, transition: transition)
self.loadPatternForSizeIfNeeded(size: size, tile: tile, transition: transition)
/*for (animationNode, relativePosition) in self.inlineAnimationNodes {
let sizeNorm = CGSize(width: 1440, height: 2960)
@ -1201,7 +1201,7 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode
if bubbleTheme.chat.message.outgoing.bubble.withoutWallpaper.fill.count >= 3 && bubbleTheme.chat.animateMessageColors {
if self.outgoingBubbleGradientBackgroundNode == nil {
let outgoingBubbleGradientBackgroundNode = GradientBackgroundNode(adjustSaturation: false)
if let size = self.validLayout {
if let (size, _) = self.validLayout {
outgoingBubbleGradientBackgroundNode.frame = CGRect(origin: CGPoint(), size: size)
outgoingBubbleGradientBackgroundNode.updateLayout(size: size, transition: .immediate, extendAnimation: false, backwards: false, completion: {})
}
@ -2021,7 +2021,7 @@ final class WallpaperBackgroundNodeMergedImpl: ASDisplayNode, WallpaperBackgroun
self.isSettingUpWallpaper = true
}
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
func updateLayout(size: CGSize, tile: Bool, transition: ContainedViewLayoutTransition) {
self.validLayout = size
self.staticView.frame = CGRect(origin: CGPoint(), size: size)

View File

@ -341,13 +341,15 @@ public struct PatternWallpaperArguments: TransformImageCustomArguments {
let preview: Bool
let customPatternColor: UIColor?
let bakePatternAlpha: CGFloat
let tile: Bool
public init(colors: [UIColor], rotation: Int32?, customPatternColor: UIColor? = nil, preview: Bool = false, bakePatternAlpha: CGFloat = 1.0) {
public init(colors: [UIColor], rotation: Int32?, customPatternColor: UIColor? = nil, preview: Bool = false, bakePatternAlpha: CGFloat = 1.0, tile: Bool = false) {
self.colors = colors
self.rotation = rotation
self.customPatternColor = customPatternColor
self.preview = preview
self.bakePatternAlpha = bakePatternAlpha
self.tile = tile
}
public func serialized() -> NSArray {
@ -359,6 +361,7 @@ public struct PatternWallpaperArguments: TransformImageCustomArguments {
}
array.add(NSNumber(value: self.preview))
array.add(NSNumber(value: Double(self.bakePatternAlpha)))
array.add(NSNumber(value: self.tile))
return array
}
}
@ -539,12 +542,13 @@ private func patternWallpaperImageInternal(fullSizeData: Data?, fullSizeComplete
c.restoreGState()
}
let tile = customArguments.tile
let overlayImage = generateImage(arguments.drawingRect.size, rotatedContext: { size, c in
c.clear(CGRect(origin: CGPoint(), size: size))
var image: UIImage?
if let fullSizeData = fullSizeData {
if mode == .screen {
image = renderPreparedImage(fullSizeData, CGSize(width: size.width * context.scale, height: size.height * context.scale), .black, 1.0)
image = renderPreparedImage(fullSizeData, CGSize(width: size.width * context.scale, height: size.height * context.scale), .black, 1.0, tile)
} else {
image = UIImage(data: fullSizeData)
}
@ -566,39 +570,57 @@ private func patternWallpaperImageInternal(fullSizeData: Data?, fullSizeComplete
if abs(fittedSize.height - arguments.boundingSize.height).isLessThanOrEqualTo(CGFloat(1.0)) {
fittedSize.height = arguments.boundingSize.height
}
fittedSize = fittedSize.aspectFilled(arguments.drawingRect.size)
let fittedRect = CGRect(origin: CGPoint(x: drawingRect.origin.x + (drawingRect.size.width - fittedSize.width) / 2.0, y: drawingRect.origin.y + (drawingRect.size.height - fittedSize.height) / 2.0), size: fittedSize)
c.interpolationQuality = customArguments.preview ? .low : .medium
c.clip(to: fittedRect, mask: image.cgImage!)
if let customPatternColor = customArguments.customPatternColor {
c.setFillColor(customPatternColor.cgColor)
c.fill(CGRect(origin: CGPoint(), size: arguments.drawingRect.size))
} else if colors.count >= 3 && customArguments.customPatternColor == nil {
c.setFillColor(UIColor(white: 0.0, alpha: 0.5).cgColor)
c.fill(CGRect(origin: CGPoint(), size: arguments.drawingRect.size))
} else if colors.count == 1 {
c.setFillColor(customArguments.customPatternColor?.cgColor ?? patternColor(for: color, intensity: intensity, prominent: prominent).cgColor)
c.fill(CGRect(origin: CGPoint(), size: arguments.drawingRect.size))
if tile {
fittedSize = fittedSize.aspectFitted(arguments.drawingRect.size)
} else {
let gradientColors = colors.map { patternColor(for: $0, intensity: intensity, prominent: prominent).cgColor } as CFArray
let delta: CGFloat = 1.0 / (CGFloat(colors.count) - 1.0)
var locations: [CGFloat] = []
for i in 0 ..< colors.count {
locations.append(delta * CGFloat(i))
}
let colorSpace = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)!
c.translateBy(x: arguments.drawingSize.width / 2.0, y: arguments.drawingSize.height / 2.0)
c.rotate(by: CGFloat(customArguments.rotation ?? 0) * CGFloat.pi / -180.0)
c.translateBy(x: -arguments.drawingSize.width / 2.0, y: -arguments.drawingSize.height / 2.0)
c.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: arguments.drawingSize.height), options: [.drawsBeforeStartLocation, .drawsAfterEndLocation])
fittedSize = fittedSize.aspectFilled(arguments.drawingRect.size)
}
c.interpolationQuality = customArguments.preview ? .low : .medium
let drawTile: (CGRect) -> Void = { fittedRect in
c.saveGState()
c.clip(to: fittedRect, mask: image.cgImage!)
if let customPatternColor = customArguments.customPatternColor {
c.setFillColor(customPatternColor.cgColor)
c.fill(CGRect(origin: CGPoint(), size: arguments.drawingRect.size))
} else if colors.count >= 3 && customArguments.customPatternColor == nil {
c.setFillColor(UIColor(white: 0.0, alpha: 0.5).cgColor)
c.fill(CGRect(origin: CGPoint(), size: arguments.drawingRect.size))
} else if colors.count == 1 {
c.setFillColor(customArguments.customPatternColor?.cgColor ?? patternColor(for: color, intensity: intensity, prominent: prominent).cgColor)
c.fill(CGRect(origin: CGPoint(), size: arguments.drawingRect.size))
} else {
let gradientColors = colors.map { patternColor(for: $0, intensity: intensity, prominent: prominent).cgColor } as CFArray
let delta: CGFloat = 1.0 / (CGFloat(colors.count) - 1.0)
var locations: [CGFloat] = []
for i in 0 ..< colors.count {
locations.append(delta * CGFloat(i))
}
let colorSpace = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)!
c.translateBy(x: arguments.drawingSize.width / 2.0, y: arguments.drawingSize.height / 2.0)
c.rotate(by: CGFloat(customArguments.rotation ?? 0) * CGFloat.pi / -180.0)
c.translateBy(x: -arguments.drawingSize.width / 2.0, y: -arguments.drawingSize.height / 2.0)
c.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: arguments.drawingSize.height), options: [.drawsBeforeStartLocation, .drawsAfterEndLocation])
}
c.restoreGState()
}
if tile {
var fittedRect = CGRect(origin: CGPoint(x: drawingRect.origin.x, y: drawingRect.origin.y + (drawingRect.size.height - fittedSize.height) / 2.0), size: fittedSize)
drawTile(fittedRect)
fittedRect = fittedRect.offsetBy(dx: fittedSize.width, dy: 0.0)
drawTile(fittedRect)
} else {
let fittedRect = CGRect(origin: CGPoint(x: drawingRect.origin.x + (drawingRect.size.width - fittedSize.width) / 2.0, y: drawingRect.origin.y + (drawingRect.size.height - fittedSize.height) / 2.0), size: fittedSize)
drawTile(fittedRect)
}
}
})
if let customPatternColor = customArguments.customPatternColor, customPatternColor.alpha < 1.0 {