Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions Example/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,19 @@ export OPENSWIFTUI_OPENATTRIBUTESHIMS_COMPUTE_USE_BINARY=1

## Generate Project

The recommended setup path is the local setup script:

```shell
./setup.sh
```

The script trusts and installs the tools declared by `Example/mise.toml`, then runs Tuist through `mise exec` so the pinned Tuist version is used.

To run the steps manually:

```shell
mise trust mise.toml
mise install
mise exec -- tuist install
mise exec -- tuist generate --no-open
```
Expand Down
2 changes: 1 addition & 1 deletion Example/Shared/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ import SwiftUI

struct ContentView: View {
var body: some View {
AsyncRenderExample()
ImageConversionsExample()
}
}
11 changes: 11 additions & 0 deletions Example/setup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/usr/bin/env bash

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"

cd "$SCRIPT_DIR"
mise trust "$SCRIPT_DIR/mise.toml"
mise install
mise exec -- tuist install
mise exec -- tuist generate --no-open
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,18 @@ extension DisplayList.ViewUpdater {
}
}

struct ContentsCenter: AsyncLayer.Property {
static let keyPath = "contentsCenter"

static func boxValue(_ value: CGRect) -> NSObject {
#if canImport(QuartzCore)
NSValue(cgRect: value)
#else
_openSwiftUIPlatformUnimplementedFailure()
#endif
}
}

struct AffineTransformLayer: AsyncLayer.Property {
static let keyPath = "transform"

Expand Down Expand Up @@ -166,6 +178,14 @@ extension DisplayList.ViewUpdater {
}
}

struct ContentsScale: AsyncLayer.Property {
static let keyPath = "contentsScale"

static func boxValue(_ value: CGFloat) -> NSObject {
NSNumber(value: Double(value))
}
}

struct ContentsMultiplyColor: AsyncLayer.Property {
static let keyPath = "contentsMultiplyColor"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -963,7 +963,62 @@ extension DisplayList.ViewUpdater.Platform {
size = bounds.size
}

func updateState(
@inline(__always)
func makeInheritedViewInfo(
item: DisplayList.Item,
size: CGSize,
state: UnsafePointer<DisplayList.ViewUpdater.Model.State>
) -> DisplayList.ViewUpdater.ViewInfo {
var viewInfo = DisplayList.ViewUpdater.ViewInfo(
platform: self,
kind: .inherited
)
updateState(
&viewInfo,
item: item,
size: size,
state: state
)
return viewInfo
}

@inline(__always)
func updateInheritedViewInfo(
_ viewInfo: inout DisplayList.ViewUpdater.ViewInfo,
item: DisplayList.Item,
size: CGSize,
state: UnsafePointer<DisplayList.ViewUpdater.Model.State>
) {
updateState(
&viewInfo,
item: item,
size: size,
state: state
)
}

@inline(__always)
func updateInheritedLayerAsync(
layer: inout DisplayList.ViewUpdater.AsyncLayer,
oldItem: DisplayList.Item,
oldSize: CGSize,
oldState: UnsafePointer<DisplayList.ViewUpdater.Model.State>,
newItem: DisplayList.Item,
newSize: CGSize,
newState: UnsafePointer<DisplayList.ViewUpdater.Model.State>
) -> Bool {
updateStateAsync(
layer: &layer,
oldItem: oldItem,
oldSize: oldSize,
oldState: oldState,
newItem: newItem,
newSize: newSize,
newState: newState
)
}

private func updateState(
_ viewInfo: inout DisplayList.ViewUpdater.ViewInfo,
item: DisplayList.Item,
size: CGSize,
Expand Down Expand Up @@ -1036,7 +1091,7 @@ extension DisplayList.ViewUpdater.Platform {
#endif
}

func updateStateAsync(
private func updateStateAsync(
layer: inout DisplayList.ViewUpdater.AsyncLayer,
oldItem: DisplayList.Item,
oldSize: CGSize,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -293,18 +293,13 @@ extension DisplayList {
tag: .inherited,
in: container.id
) { [platform] _, item, state in
// TODO: Optimize the call here in Platform
var info = ViewInfo(platform: platform, kind: .inherited)
platform.updateState(
&info,
platform.makeInheritedViewInfo(
item: item,
size: item.size,
state: state
)
return info
} updateView: { [platform] info, _, item, state in
// TODO: Optimize the call here in Platform
platform.updateState(
platform.updateInheritedViewInfo(
&info,
item: item,
size: item.size,
Expand Down Expand Up @@ -399,7 +394,7 @@ extension DisplayList {
newState: newStatePtr,
tag: .inherited
) { layer, _, oldItem, oldState, newItem, newState in
platform.updateStateAsync(
platform.updateInheritedLayerAsync(
layer: &layer,
oldItem: oldItem,
oldSize: oldItem.size,
Expand Down
58 changes: 56 additions & 2 deletions Sources/OpenSwiftUICore/View/Image/GraphicsImage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ package struct ResolvedVectorGlyph: Equatable {
}
}

// MARK: - GraphicsImage + Extension [WIP]
// MARK: - GraphicsImage + Extension

extension GraphicsImage {
package var bitmapOrientation: Image.Orientation {
Expand All @@ -291,6 +291,60 @@ extension GraphicsImage {
}

package func render(at targetSize: CGSize, prefersMask: Bool = false) -> CGImage? {
_openSwiftUIUnimplementedFailure()
guard targetSize.width > 0, targetSize.height > 0 else {
return nil
}
switch contents {
case let .cgImage(cgImage):
return cgImage
case let .vectorGlyph(vectorGlyph):
#if OPENSWIFTUI_LINK_COREUI
let sizeAndScale = renderedSize(at: targetSize).map { ($0, scale) }
guard let glyph = vectorGlyph.glyph else {
return nil
}
return glyph.image(
at: sizeAndScale,
value: vectorGlyph.value
)
#else
return nil
#endif
case let .vectorLayer(vectorLayer):
let imageSize: CGSize
if let renderedSize = renderedSize(at: targetSize) {
imageSize = renderedSize
} else if scale != 0 {
imageSize = unrotatedPixelSize * (1.0 / scale)
} else {
imageSize = .zero
}
return vectorLayer.image(
size: imageSize,
imageScale: scale,
prefersMask: prefersMask
)
default:
return nil
}
}

private func renderedSize(at targetSize: CGSize) -> CGSize? {
if let resizingInfo {
guard resizingInfo.capInsets.isEmpty, resizingInfo.mode == .stretch else {
return nil
}
}
let renderedSize = targetSize.apply(orientation)
let nativeSize: CGSize
if scale != 0 {
nativeSize = unrotatedPixelSize * (1.0 / scale)
} else {
nativeSize = .zero
}
guard renderedSize != nativeSize else {
return nil
}
return renderedSize
}
}
81 changes: 78 additions & 3 deletions Sources/OpenSwiftUICore/View/Image/ImageLayer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// OpenSwiftUICore
//
// Audited for 6.5.4
// Status: Complete - Blocked by AsyncUpdate
// Status: Complete
// ID: 854C382F3D9A82BFCF900A549E57F233 (SwiftUICore)

#if canImport(Darwin)
Expand Down Expand Up @@ -139,8 +139,83 @@ final package class ImageLayer: CALayer {
newImage: GraphicsImage,
newSize: CGSize
) -> Bool {
_openSwiftUIUnimplementedWarning()
return false
guard (oldImage.maskColor == nil) == (newImage.maskColor == nil),
oldImage.interpolation == newImage.interpolation,
oldImage.isAntialiased == newImage.isAntialiased
else {
return false
}
switch (oldImage.contents, newImage.contents) {
case (.cgImage, .cgImage):
return updateAsyncLayerProperties(
layer: &layer,
oldImage: oldImage,
oldSize: oldSize,
newImage: newImage,
newSize: newSize
)
case (.ioSurface, .ioSurface):
return updateAsyncLayerProperties(
layer: &layer,
oldImage: oldImage,
oldSize: oldSize,
newImage: newImage,
newSize: newSize
)
case let (.vectorGlyph(oldContents), .vectorGlyph(newContents)) where oldContents == newContents:
Copy link
Copy Markdown

@augmentcode augmentcode Bot May 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ImageLayer.updateAsync treats .vectorGlyph as compatible even when GraphicsImage.scale changes, but the underlying layer.contents may have been rasterized at the old scale (via GraphicsImage.render/CUINamedVectorGlyph.rasterizeImage). Consider making scale changes force a full update here (return false) unless the contents is guaranteed to be scale-independent.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

return updateAsyncLayerProperties(
layer: &layer,
oldImage: oldImage,
oldSize: oldSize,
newImage: newImage,
newSize: newSize
)
case let (.color(oldColor), .color(newColor)):
layer.update(
DisplayList.ViewUpdater.BackgroundColor.self,
from: oldColor,
to: newColor
)
return true
default:
return false
}
}

@inline(__always)
private static func updateAsyncLayerProperties(
layer: inout DisplayList.ViewUpdater.AsyncLayer,
oldImage: GraphicsImage,
oldSize: CGSize,
newImage: GraphicsImage,
newSize: CGSize
) -> Bool {
guard oldSize == newSize else {
return false
}
let (oldCenter, oldTiled) = oldImage.layerStretchInPixels(size: oldSize)
let (newCenter, newTiled) = newImage.layerStretchInPixels(size: newSize)
guard oldTiled == newTiled else {
return false
}
layer.update(
DisplayList.ViewUpdater.ContentsCenter.self,
from: oldCenter,
to: newCenter
)
layer.update(
DisplayList.ViewUpdater.ContentsScale.self,
from: oldImage.scale,
to: newImage.scale
)
if let newMaskColor = newImage.maskColor {
layer.update(
DisplayList.ViewUpdater.ContentsMultiplyColor.self,
from: oldImage.maskColor,
to: newMaskColor
)
}
return true
}
}

Expand Down
25 changes: 25 additions & 0 deletions Sources/OpenSwiftUICore/View/Image/VectorImageLayer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,31 @@ extension VectorImageLayer: ProtobufMessage {
}
}

#if OPENSWIFTUI_LINK_COREUI
private let valueLock = NSLock()

extension CUINamedVectorGlyph {
package func image(at sizeAndScale: (CGSize, CGFloat)?, value: Float?) -> CGImage? {
valueLock.lock()
defer {
valueLock.unlock()
}
let oldMinValue = variableMinValue
let oldMaxValue = variableMaxValue
defer {
setVariableMinValue(oldMinValue)
setVariableMaxValue(oldMaxValue)
}
setVariableMinValue(value == nil ? .infinity : .zero)
setVariableMaxValue(value.map { CGFloat($0) } ?? .infinity)
guard let (size, scale) = sizeAndScale else {
return image
}
return rasterizeImage(usingScaleFactor: scale, forTargetSize: size)?.takeRetainedValue()
}
}
#endif

// MARK: - VectorImageContents

@_spi(ForOpenSwiftUIOnly)
Expand Down
Loading