ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
Core Image、Metal、SwiftUIでのEDRコンテンツの表示
Core ImageベースのマルチプラットフォームSwiftUI Appから、レンダリングのサポートにExtended Dynamic Range(EDR)を追加する方法をご覧ください。ここでは、ViewRepresentableを使用して、CIImagesをMTKViewに表示するベストプラクティスを紹介します。また、EDRレンダリングを有効化する簡単な手順や、EDRをサポートする内蔵CIFilter(150個以上)の例も紹介します。
リソース
関連ビデオ
Tech Talks
WWDC22
-
ダウンロード
ようこそDavid Haywardです Core Imageチームの ソフトウェアエンジニアです 拡張ダイナミックレンジコンテンツを Core Imageで表示する方法をご説明します 4つのパートに分けてお話ししていきます まず 当社プラットフォームにおける EDRの重要な用語についてご紹介します 次は 新しいCore Imageの サンプルプロジェクトでの EDRのサポートの追加方法を説明します 最後にCIFiltersを使用してEDRコンテンツを 生成する画像を作成する方法を紹介します
では 重要な用語についてご説明します SDRとは RGBの黒を0白を1として 正規化して表現する従来の方法です これに対し EDRは通常の範囲を超えて RGBの色を表現するために 推奨されている方法です
SDRと同様 0が黒1がSDRの白と 同じ明るさを表します しかし EDRでは1より大きな値を使用して さらに明るい画素を 表現することができます
ただし1より大きな値も許容されますが ヘッドルームを超える値は クリップされることに注意してください
ヘッドルームは ディスプレイの最大Nitsを SDRホワイトのNitsで割ったものです
ヘッドルームの値はディスプレイの種類や 明るさの変化 周囲の状況で 変わることがあります
「iOSのEDRについて詳しく見る」で より深くご理解いただけると思います EDRコンテンツにはいくつかのソースがあります TIFFやOpenEXRなどのファイルフォーマットは EDR用の浮動小数点値を格納することができます
AVFoundationでHDRビデオ からフレームを取得します
Metal APIは EDR環境を テキスチャにレンダリングします ProRAWのDNGファイルはEDRを格納できます
「ProRAW画像の撮影と処理」もご覧ください
次のパートではSwiftUI Appで Core ImageをMetalと併用する方法を説明します 後でEDR対応を追加する方法についても 紹介します
SwiftUI のマルチプラットフォームAppで Core Image と Metal Kit View を組み合わせる 新しいサンプルコードを最近リリースしました ぜひダウンロードしてコードをご覧ください この場を借りて 見た目と機能についてご紹介します
サンプルはMetalビューに表示される アニメーション化されたCIImageを描画します 最適な機能実現のために MTKViewを使用しています Appが必要とするコンテンツのプロキシとして アニメのチェッカーボードCIImageを表示します
SwiftUIを採用しているため macOS iOS iPadOSの各プラットフォームで 共通のコードベースを使用することができます
これはいくつかの短いソースファイルから 構築されています クラスがどのように相互作用するか説明します
このAppには3つの重要なパーツがあります まず 最も重要なのは「MetalView」です MTKViewクラスをラップする SwiftUIと互換性があるView実装を提供します
MTKViewクラスはmacOSのNSViewと 他のプラットフォームの UIViewがベースとなっているため MetalView実装はViewRepresentableを使って SwiftUIとMTKViewクラスの間をブリッジします
ただしMTKViewが直接レンダリングをしません 代わりにデリゲートを使って作業を行います
レンダラクラスがMTKViewのデリゲートです Metalコマンドキューや Core Imageコンテキスト等の グラフィックス状態オブジェクトの 初期化を担当します
必要なdraw()メソッドも実装しています
しかし レンダラがどのような画像を描画するか 決定するわけではありません imageProviderブロックを使い 描画するCIImageを取得します
ContentViewクラスが レンダリングされるCIImageを 提供するコードブロックを実装しています
MetalViewは デリゲートを 呼び出して描画します Rendererのdraw()メソッドは ContentViewを呼び出して 描画する画像を提供します
3つのクラスのコードについて もう少し詳しく説明します まず MetalViewクラスです makeView()のコードから MTKViewを作成するためにmakeView()を呼んで デリゲートをレンダラステート オブジェクトに設定します NSView や UIView をラップした SwiftUI のビューを 実装するための標準的なアプローチです
次にpreferredFramesPerSecondです ビューのレンダリング頻度を指定します このプロパティはビューの描画を 駆動するものを決定するため重要です この仕組みについて説明します
コードはview.preferredFramesPerSecondを 目的のフレームレートに設定します
この設定によりMTKViewは ビュー自体が描画イベントの タイミングを駆動するように構成されます
レンダリングデリゲートが 定期的にdraw()を実行し 現在時刻のCIImageを作成するように要求します
アニメーションが一時停止するまで 繰り返されます
また 画像編集Appの場合 ビューを描画するタイミングを操作するために ユーザーがコントロールを 操作するのが最適です
enableSetNeedsDisplayをtrueに設定すると タイミングを駆動できるように MTKViewが構成されます コントロール移動時は updateView()を呼び出し
ビューのデリゲートが draw()を1回呼び出します
各描画はコンテンツプロバイダーに 現在の制御状態のCIImageを 作成するように要求します
このアプローチはビデオフレームの到着が 描画イベントを駆動する場合にも適しています
MetalViewクラスについては以上です レンダラデリゲートで最も重要なコードは draw()メソッドです
周期的なフレームレートで呼びだされます draw()メソッドが呼び出されると ディスプレイの解像度を反映するコンテンツの スケールファクターを決定する必要があります CIImagesはポイントではなく ピクセルで測定するからです ビューが別のディスプレイに移動されると プロパティが変更される可能性があるため draw()メソッドを呼び出すたびに 行うことが重要です
次にmtlTextureProviderを使って CIRenderDestinationを作成します
コンテンツプロバイダーを 呼び出して 現在の時間と スケールファクターに使うCIImageを作成します この画像は ビューの可視領域で センタリングされ 不透明な背景の上にブレンド そしてCIImageをビューデスティネーションに レンダリングを開始します
ContentViewクラスで最も重要なコードは init()メソッドです
コンテンツビューの本体を 作成するためのものです レンダラクラスと MetalViewクラスの接続が確立されます
はじめに画像プロバイダーブロックで レンダラオブジェクトを作成します
そのブロックは 要求された時間とスケールの CIImageを返す役割を果たします
最後にContentViewの本体を レンダラを使用するMetalViewに設定します
これでCore Imageを使用して レンダリングできる シンプルなSwiftUI Appができました 次にEDRヘッドルームを使う レンダリングに対応させます
EDR対応にするのはとても簡単です 1番はEDR用のビューの初期化 2番は毎回のレンダリング前に ヘッドルームを計算 3番はヘッドルームを使用する CIImage を構築します 実際のコードをご覧ください MetalView クラスに1つ追加事項があります ビューを作成するとき必要なレイヤーに ExtendedDynamicRangeContent を通知し そのpixelFormatが .rgba16Floatであり 色空間が拡張されてリニアであると ビューに通知する必要があります
次に レンダラクラスのdraw()メソッドに いくつかの変更が必要です
draw()メソッドでは ビューの現在の画面を取得し 画面に現在のEDRヘッドルームを要求する コードを追加する必要があります
ヘッドルームは画像プロバイダブロックに パラメーターとして渡されます draw()メソッドを呼び出すたびに行います ヘッドルームは周囲の状況や ディスプレイの輝度の変化に応じて 変化する動的な特性があります
3つ目の変更点はContentViewクラスの プロバイダブロックです
ここでヘッドルーム引数を ブロック宣言に追加します CIFiltersでヘッドルームを使って EDRディスプレイで見栄えする CIImageを返します このAppにEDRサポートを追加する 3つの簡単なステップをまとめます EDRのビューを初期化 すべてのレンダリングの 前にヘッドルームを決定 ヘッドルームを指定してCIImageを構築します これが後半のトピックです EDRに対応したのでCIFiltersを使って CIImagesを作り EDRコンテンツを表示します
コアイメージに搭載された 150種類以上のフィルターが EDRに対応しています つまり これらのフィルタは EDRコンテンツを含む画像を生成するか EDRコンテンツを含む画像を 処理することができます CIcolorcontrolsと CIExposureAdjustフィルターを使って EDRカラーの画像の 輝度 色相 彩度 コントラストを変更できます グラデーションフィルターなど使って EDRのカラーパラメーターを指定し 画像を生成できます
3つの新しいフィルターも EDR画像に対応しています CIAreaLogarithmicHistogramは 任意の範囲の輝度値に対する ヒストグラムを生成することができます
CIColorCubeフィルターは EDR入力画像対応するため 今年更新されたフィルターの1つです
Core Imageの作業色空間は 固定しておらず 線形なので 0~1の範囲外のRGB値も許容されるため 内蔵フィルターがすべて機能します フィルターがEDR対応しているか確認できます
フィルタのインスタンスを 作成し フィルターの属性に そのカテゴリーを尋ねてから配列に kCICategoryHighDynamic Rangeがあるか確認します その他 追加した新機能はCIFilter変数の XcodeQuickLookデバッグサポートです 各入力パラメーターのカテゴリーと要件を含む 各Filterクラスのドキュメントを表示します
これらのEDRフィルターがあれば コンテンツに適用できる効果は 無限に広がります 今日説明する例では サンプルAppの市松模様に 明るい鏡面反射を伴う 波紋効果を追加してみます
この効果を作成するには rippleTransitionフィルターの インスタンスが必要です
入力画像とターゲット画像を チェッカー画像に設定します
次に 波紋の中心時間と遷移時間を制御する フィルター入力を設定します
そして波紋に鏡面反射ハイライトを 発生させるグラデーションを shadingImageに設定します
最後に設定したすべての フィルター入力を指定して フィルターにoutputImageを要求します
また 波紋効果の鏡面反射ハイライトを 作成するために使用する shadingImageの作成方法について説明します ビットマップデータから作成もできますが CIImageを生成してパフォーマンスを向上します
linearGradientフィルタの インスタンスを作成します 2つのポイントと2つのCIColorを指定して
グラデーションを作成します 鏡面反射光を白にし 現在のヘッドルームに基づいた明るさで 妥当な最大値に制限する必要があります
使用するリミットは適用するエフェクトの 外観によって異なります
color0は固定されていない線形色空間で 白レベルを使用して作成します
color1はクリアカラーに設定されています
Point0とpoint1は鏡面反射光を 左上方向から表示するように 座標に設定されています
フィルターのoutputImageが波紋フィルターに 必要なサイズにトリミングされます
結果として生じる鏡面反射効果のある波紋は 単純なプロキシに過ぎません これは重要な原則を示しています 通常 明るい画素は適度に使用します 少ないほうがいいのです 明るい画素の方がインパクトがあります
2つの内蔵CIFiltersを使うAppが完成しました 他の内蔵EDRフィルターも試してみてください 次に CIColorCubeフィルタの最適な使用方法と 独自のフィルタを作成する際の 注意点について説明します
IColorCubeWithColorSpaceは 大変人気あるフィルタです SDR画像に外観を適用するために使用されます Process Instant Tonalなどの効果を 写真Appに実装するためにも使用されます
このような外観に使うキューブデータには 0から1の範囲のRGBカラーしか 入出力できないという 重大な制約がありました
制限を回避する一つの方法としては CIColorCubeWithColorSpaceフィルターで EDR色空間を使用するように指示をします
最適な結果をもたらすように 色空間範囲に有効な新しいキューブデータを 作成する必要があります キューブのサイズを増やす 必要があるかもしれません 代わりにSDRキューブデータを使えます SDRキューブデータ推定と フィルターに指示できます 機能を有効にするには SDRキューブデータを設定します フィルターの新しいextrapolate プロパティを設定します
「true」に設定するとフィルターに EDR入力画像を与えられて EDR出力画像を得られます
最後のトピックは 独自のカスタムCIKernelを 作成する場合の最適な方法についてです
まずカーネルのコードを見直して clamp min maxなどの関数で RGBの値を0から1の範囲に制限するような 計算が行われていないかを確認します
多くの場合 制限は安全に取り除くことができ カーネルは正しく機能します
RGB値が0~1の範囲を 超える可能性がある場合でも アルファ値は0~1の間でなければなりません そうしないと 画像をブレンド または表示するときに 未定義の動作が発生します
正しい動作はRGB値に5を乗じるだけなのに カーネルが誤ってアルファチャンネルに 5を乗じてしまっています
以上でプレゼンテーションを終わります 今日はCore Image SwiftUI Appに EDRヘッドルームのサポートを追加する方法と EDRコンテンツを作成変更するために 様々な内蔵CIFiltersを 使用する方法を学びました ご視聴ありがとうございました
-
-
5:17 - Metal View
// Metal View struct MetalView: ViewRepresentable { @StateObject var renderer: Renderer func makeView(context: Context) -> MTKView { let view = MTKView(frame: .zero, device: renderer.device) view.delegate = renderer // Suggest to Core Animation, through MetalKit, how often to redraw the view. view.preferredFramesPerSecond = 30 // Allow Core Image to render to the view using Metal's compute pipeline. view.framebufferOnly = false return view }
-
7:12 - Renderer
// Renderer func draw(in view: MTKView) { if let commandBuffer = commandQueue.makeCommandBuffer(), let drawable = view.currentDrawable { // Calculate content scale factor so CI can render at Retina resolution. #if os(macOS) var contentScale = view.convertToBacking(CGSize(width: 1.0, height: 1.0)).width #else var contentScale = view.contentScaleFactor #endif let destination = CIRenderDestination(width: Int(view.drawableSize.width), height: Int(view.drawableSize.height), pixelFormat: view.colorPixelFormat, commandBuffer: commandBuffer, mtlTextureProvider: { () -> MTLTexture in return drawable.texture }) let time = CFTimeInterval(CFAbsoluteTimeGetCurrent() - self.startTime) // Create a displayable image for the current time. var image = self.imageProvider(time, contentScaleFactor) image = image.transformed(by: CGAffineTransform(translationX: shiftX, y: shiftY)) image = image.composited(over: self.opaqueBackground) _ = try? self.cicontext.startTask(toRender: image, from: backBounds, to: destination, at: CGPoint.zero)
-
8:09 - ContentView
// ContentView import CoreImage.CIFilterBuiltins init(struct ContentView: View { var body: some View { // Create a Metal view with its own renderer. let renderer = Renderer( imageProvider: { (time: CFTimeInterval, scaleFactor: CGFloat) -> CIImage in var image: CIImage // create image using CIFilter.checkerboardGenerator... return image }) MetalView(renderer: renderer) } }
-
9:17 - MetalView changes
if let caMtlLayer = view.layer as? CAMetalLayer { caMtlLayer.wantsExtendedDynamicRangeContent = true view.colorPixelFormat = MTLPixelFormat.rgba16Float view.colorspace = CGColorSpace(name: CGColorSpace.extendedLinearDisplayP3) }
-
9:35 - Get headroom
let screen = view.window?.screen; #if os(macOS) let headroom = screen?.maximumExtendedDynamicRangeColorComponentValue ?? 1.0 #else let headroom = screen?.currentEDRHeadroom ?? 1.0 #endif var image = self.imageProvider(time, contentScaleFactor, headroom)
-
10:05 - Use headroom
imageProvider: { (time: CFTimeInterval, scaleFactor: CGFloat, headroom: CGFloat) -> CIImage in var image: CIImage // Use CIFilters to create image for time / scale / headroom / ... return image })
-
12:42 - Ripple effect
let ripple = CIFilter.rippleTransition() ripple.inputImage = image ripple.targetImage = image ripple.center = CGPoint(x: 512.0, y: 384.0) ripple.time = Float(fmod(time*0.25, 1.0)) ripple.shadingImage = shading image = ripple.outputImage
-
13:34 - Generating the shading image
let gradient = CIFilter.linearGradient() let w = min( headroom, 8.0 ) gradient.color0 = CIColor(red: w, green: w, blue: w, colorSpace: CGColorSpace(name: CGColorSpace.extendedLinearSRGB)!)! gradient.color1 = CIColor.clear gradient.point0 = CGPoint(x: sin(angle)*90.0 + 100.0, y: cos(angle)*90.0 + 100.0) gradient.point1 = CGPoint(x: sin(angle)*85.0 + 100.0, y: cos(angle)*85.0 + 100.0) let shading = gradient.outputImage?.cropped(to: CGRect(x: 0, y: 0, width: 200, height: 200))
-
16:13 - CIColorCube and EDR
let f = CIFilter.colorCubeWithColorSpace() f.cubeDimension = 32 f.cubeData = sdrData f.extrapolate = true f.inputImage = edrImage let edrResult = f.outputImage
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。