Failed to instantiate the default view controller

[WindowScene] Failed to instantiate the default view controller for UIMainStoryboardFile ‘Main’ – perhaps the designated entry point is not set?

“Is Initial View Controller” がオフになっていると、上記エラーが発生する場合がある。mainstoryboard の最初に起動すべき ViewController の Attribute Inspector から、”Is Initial View Controller” の状態を確認し、オフの場合は、オンにする。

Python + PIL でポスター風画像

変換後の画像
Original Image
""" docustring """
import os
from PIL import Image, ImageOps, ImageEnhance, ImageFilter, ImageChops, ImageDraw

os.chdir("/users/uchukamen/desktop/python/poster/images")
files = os.listdir(".")

for imageFile in files:
    if not imageFile.endswith("jpeg"):
        continue
    print(imageFile)

    origImage = Image.open(imageFile)

    # 1/4 縮小画像を作成
    w, h = origImage.size
    smallSize = (int(w/8), int(h/8))
    smallImage = origImage.resize(smallSize, Image.NEAREST)

    # 色強調 enhancerオブジェクト生成
    enhancer = ImageEnhance.Color(smallImage)
    # enhancerオブジェクトの強調
    enhancedSmallImage = enhancer.enhance(8)

    # 減色
    img_res = enhancedSmallImage.quantize(8)

    # 線画の作成
    maskImage = img_res.convert("L").filter(ImageFilter.CONTOUR)

    # 黒背景画像を作成
    blackImage = Image.new('RGBA', smallSize, 'black')

    # 色強調画像と線画の合成
    mergedImage = Image.composite(img_res, blackImage, maskImage)

    # 色強調 enhancerオブジェクト生成
    enhancer2 = ImageEnhance.Color(mergedImage)
    # enhancerオブジェクトの強調
    enhancedSmallImage = enhancer2.enhance(1)
    enhancedSmallImage.show()

Animation可能な LaunchScreenを表示する方法

LaunchScreen.storyboard はアニメーションできない

アニメーションするLaunchScreen を作ろうとして、LaunchScreen.storyboard に、独自のクラスを適用してみたところ、”Launch screens may not set custom classnames” というエラーが発生する。このため、Launch Screenではアニメーションができない。

起動画面にViewController により、アニメーションを行なう

そこで、起動画面に ViewController によりアニメーションを表示し、アニメーションが終わったら、メインのViewController に遷移する方法を試みる。

遷移先のメインのViewController に Storyboard ID を付与する。

 ここでは、mainViewControllerという Storyboard ID を付与する

LaunchScreen の代わりとなる ViewController を追加する。

SplashViewController.swift を追加し、カスタムクラスを設定・実装する。

SplashViewController は次のとおり。

import UIKit

class SplashViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        
        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        let nextViewController = storyboard.instantiateViewController(withIdentifier: "mainViewController")
        self.present(nextViewController, animated: true, completion: nil)
    }
}

注意

instantiateViewController をviewDidLoad() から呼び出すと、次の警告が表示されて、Viewの遷移ができない。

Warning: Attempt to present <***.ViewController: 0x104910db0> on <***.SplashViewController: 0x10490a030> whose view is not in the window hierarchy!

原因については、次の記事にあるように、まだ ViewController が存在していないのに、遷移しようとしたため。

対応としては、上記のように viewDidAppear の中で遷移する。

whose view is not in the window hierarchy! 初心者向け エラー

LaunchScreen のガイドライン

Launch Screen – Apple Developer により

重要
起動画面に static 画像を使用しないでください。デバイスの画面サイズと向き」を参照してください。

アプリの最初の画面と同じ起動画面 
アプリの起動が完了したときに外観が異なる要素を含めると、起動画面とアプリの最初の画面の間で不快なフラッシュが発生する可能性があります。また、起動画面がデバイスの現在の外観モードと一致していることを確認してください。

起動画面にテキストを含めない 
起動画面のコンテンツは変更されないため、表示されるテキストはローカライズされません。

高速な起動 
あまり凝ったローンチスクリーンは避けて、コンテンツにすばやくアクセスしてタスクを実行できるようにする。

宣伝しない 
起動画面はブランディングの機会ではありません。スプラッシュ画面や「About」ウィンドウのようなエントリエクスペリエンスを設計しないでください。ロゴやその他のブランド要素は、アプリの最初の画面の固定部分でない限り、含めないでください。ゲームまたは他のイマーシブアプリが最初の画面に移行する前に無地を表示する場合、その無地のみを表示する起動画面を作成できます。

ということなので、高速、シンプル、Autoresizable、宣伝なしの Launch Screen を設計する必要がある。

ScrollView でズームした部分画像

        guard let ciImage = self.imageViewOut.image?.ciImage else { return }
        guard let imageSize = self.imageViewOut.image?.size else { return }
        
        let frameSize = self.scrollView.frame.size
        let ciContext = CIContext(options: nil)

        let zoomScale = self.scrollView.zoomScale
        // ズームしない場合、contentSizeがセットれさず、ゼロのまま。
        // そこで、frameSizeと、zoomScaleから contentSizeを計算する。
        // let contentSize = self.scrollView.contentSize
        let contentSize = CGSize(width: frameSize.width * zoomScale, height: frameSize.height * zoomScale)
        let contentOffset = self.scrollView.contentOffset

        let x = contentOffset.x * imageSize.width / contentSize.width
        let y1 = contentSize.height - contentOffset.y - frameSize.height
        let y2 = imageSize.height / contentSize.height
        let y = y1 * y2
        let newOffset = CGPoint(x: x, y: y)
        
        let newSize = CGSize(width: imageSize.width/zoomScale, height: imageSize.height/zoomScale)
        let newExtent = CGRect(origin: newOffset, size: newSize)
        
        let cgImage = ciContext.createCGImage(ciImage, from: newExtent)
        let uiImageToSave = UIImage(cgImage: cgImage!, scale: 1.0, orientation: .up)
        
        UIImageWriteToSavedPhotosAlbum(uiImageToSave,
                                       self, #selector(didFinishSavingImage), nil)

CIFilter した画像を UIImageWriteToSavedPhotosAlbum で保存できない

CIFilter したUIImageを UIImageWriteToSavedPhotosAlbum で保存できない。CIFilter を適用する前のUIImageは、保存できる。

原因

CIFilterを適用後、CIImageから UIImage を作成したときにプロパティがNULL になっているため。

参考

UIImageWriteToSavedPhotosAlbum() doesn’t save cropped image

CoreImageでエフェクトした画像がUIActivityViewControllerで画像保存できない

対応

    func savePhoto(image: UIImage)
        let ciImage = image?.ciImage
        let ciContext = CIContext(options: nil)
        let cgImage = ciContext.createCGImage(ciImage!, from: ciImage!.extent)
        let uiImageToSave = UIImage(cgImage: cgImage!, scale: 1.0, orientation: .up)
        
        UIImageWriteToSavedPhotosAlbum(uiImageToSave,
                                       self, #selector(didFinishSavingImage), nil)
    }
    
    // エラーの場合アラートを出力
    @objc func didFinishSavingImage(_ image: UIImage, didFinishSavingWithError error: NSError!, contextInfo: UnsafeMutableRawPointer) {
        if error != nil {
            let alertController = UIAlertController(title: "Error", message: "Save Error", preferredStyle: .alert)
            alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
            self.present(alertController, animated: true, completion: nil)
        }
    }

OpenCV on Mac with Visual Studio Code

OpenCV on Mac のための環境設定のメモ

2020/7/22 時点の情報です。

Homebrew

OpenCV をインストールするためにまず Homebrew をインストールする。

/bin/bash… を実行する。
xcode 用のコンソールもインストールされる。
$ brew install opencv で、opencv をインストール

Visual Studo Code on Macをインストール

Visual Studio Code on Mac にしたがって、Visual Studio Code をインストールする。

Getting Started with Python in VS Code

Getting Started with Python in VS Code にしたがって、Python の環境をセットアップする。

これで、numpy, matplotlib が使えるようになった。

OpenCV のインストール

$brew install opencv を実行する。

pip3 install opencv-python を実行する。

OpenCV Extentions をインストール

Opencv Snippets, OpenCV-intellisense を追加する。必須ではない。

OpenCVの実行例

cv2に対する lint エラー対応

cv2 に対して、pylint エラーが表示される。

この対応としては、メニューから、[Code] -> [Preferences] -> [Settings]

Python > Linting : Pylint Argsに

–extension-pkg-whitelist=cv2

を追加する。

自動フォーマッター対応

メニューから、[Code] -> [Preferences] -> [Settings]

Editor: Format On Save、 にチェックを入れる。

Editor: Format On Save、 にチェックを入れると、autopep8 をインストールするかと聞いてくるので、インストールする。

これで、セーブ時に自動フォーマットしてくれるようになる。

Metal

Metal はApple A7以降を搭載したiOS機器、およびOS X El Capitan以降が動作する一部のMacコンピュータで利用可能な、GPU を利用可能にする機能。

以下、wikiより。

2017年10月時点でのMetal及びMetal2対応Macコンピュータは以下の通り[2]

  • iMac(Late 2012 以降)
  • MacBook(Early 2015 以降)
  • MacBook Pro(Mid 2012 以降)
  • MacBook Air(Mid 2012 以降)
  • Mac mini(Late 2012 以降)
  • Mac Pro(Late 2013 以降)
  • Mac Pro(Mid 2010 以降)でMetalをサポートするGPU(Nvidia Kepler以降、ATI Graphics Core Next以降)を搭載したもの

MetalはWWDC 2014にて発表され、iOS 8で初めて導入された[3]

MetalはC++11をベースとした新しいシェーディング言語Metal Shading Language(MSL)を利用する。

Metal Shading Language での実装例

#include <CoreImage/CoreImage.h>
 
extern "C" {
    namespace coreimage {
        float4 do_nothing(sample_t s) {
            return s;
        }
    }
}

Core Image Kernel Language での実装例

kernel vec4 do_nothing(__sample s) {
    return s.rgba;
}

CIColorKernel 参照

Color Kernel の特性

  • 戻り値の型はvec4(Core Image Kernel Language)またはfloat4(Metal Shading Language)で、出力画像のピクセルカラーを返します。
  • 0個以上の入力画像を使用する場合があります。各入力画像は次のパラメータで表される。__sample(Core Image Kernel Language)または sample_t(Metal Shading Language)。これらは、vec4型の画素(Core Image Kernel Language)または float(Metal Shading Language)で扱うことができる。

Creating a General Kernel in Swift

guard
    let url = Bundle.main.url(forResource: "default", withExtension: "metallib"),
    let data = try? Data(contentsOf: url) else {
    fatalError("Unable to get metallib")
}
 
guard let generalKernel = try? CIKernel(functionName: "myKernel", fromMetalLibraryData: data) else {
    fatalError("Unable to create CIKernel from myKernel")
}

詳細は、init(functionName:fromMetalLibraryData:) を参照。

カーネルの作成

最初のステップは、デフォルトのMetalライブラリーを表す Data オブジェクトを作成する。これをXcodeでビルドし、default.metallib として、url.Bundle メソッドでロードする。

コンパイラとリンカーのオプションの指定

MSLをのシェーダー言語として使用するには、Xcodeでプロジェクトのターゲットの[Build Settings ]タブにあるいくつかのオプションを指定する必要があります。指定する必要がある最初のオプションは、Other Metal Compiler Flags オプションの ”-fcikernel” フラグです。2つ目は、user-defined に、MTLLINKER_FLAGS キーで “-cikernel” を追加する。

CIKernel の呼び出し方

import UIKit

class MyFilter: CIFilter {
    private let kernel: CIColorKernel

@objc dynamic var inputImage : CIImage?

    override init() {
        let url = Bundle.main.url(forResource: "default", withExtension: "metallib")!
        let data = try! Data(contentsOf: url)
        kernel = try! CIColorKernel(functionName: "myColor", fromMetalLibraryData: data)
        super.init()
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override var outputImage: CIImage? {
        guard let inputImage = inputImage else {return nil}
        return kernel.apply(extent: inputImage.extent, arguments: [inputImage])
    }
}

Metal Shading Language Specification


Metal Shading Language Specification – Apple Developer

MSL Vector 型

// .xyzw または .rgba で各要素にアクセスできる。
int4 test = int4(0, 1, 2, 3);
int a = test.x; // a = 0
int b = test.y; // b = 1
int c = test.z; // c = 2
int d = test.w; // d = 3
int e = test.r; // e = 0
int f = test.g; // f = 1
int g = test.b; // g = 2
int h = test.a; // h = 3

//xyzw の並びで 複数の要素にアクセスできる
float4 c;
c.xyzw = float4(1.0f, 2.0f, 3.0f, 4.0f);
c.z = 1.0f;
c.xy = float2(3.0f, 4.0f);
c.xyz = float3(3.0f, 4.0f, 5.0f);

// 順列
float4 pos = float4(1.0f, 2.0f, 3.0f, 4.0f);
float4 swiz = pos.wzyx; // swiz = (4.0f, 3.0f, 2.0f, 1.0f)
float4 dup = pos.xxyy; // dup = (1.0f, 1.0f, 2.0f, 2.0f)

// 代入
float4 pos = float4(1.0f, 2.0f, 3.0f, 4.0f);
// pos = (5.0, 2.0, 3.0, 6.0)
pos.xw = float2(5.0f, 6.0f);
// pos = (8.0, 2.0, 3.0, 7.0)
pos.wx = float2(7.0f, 8.0f);
// pos = (3.0, 5.0, 9.0, 7.0)
pos.xyz = float3(3.0f, 5.0f, 9.0f);

MSL Matrix 型

halfnxm // n x m 16bit floating point
floatnxm // n x m 32bit floating point

float4x4 m;
// This sets the 2nd column to all 2.0.
m[1] = float4(2.0f);
// This sets the 1st element of the 1st column to 1.0.
m[0][0] = 1.0f;
// This sets the 4th element of the 3rd column to 3.0.
m[2][3] = 3.0f;

カスタムフィルターの作成

参考: Writing Custom Kernels

カスタムフィルター

class CIKernel
カスタムCore Imageフィルターの作成に使用されるGPUベースの画像処理ルーチン。

class CIColorKernel
カスタムCore Imageフィルターの作成に使用される、画像の色情報のみを処理するGPUベースの画像処理ルーチン。

class CIWarpKernel
カスタムCore Imageフィルターの作成に使用される、画像内のジオメトリ情報のみを処理するGPUベースの画像処理ルーチン。

class CIBlendKernel
2つの画像をブレンドするために最適化されたGPUベースの画像処理ルーチン。

class CISampler
フィルターカーネルによる処理のためにピクセルサンプルを取得するオブジェクト。

参考

Core Image

What You Need to Know Before Writing a Custom Filter

Core Image Kernel Language

Container View とMainView間でのデータ交換

Container View とMainView間でのデータ交換をNotificationCenterで実施する。

メインの ViewController

import UIKit

class OrigViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        NotificationCenter.default.addObserver(self, selector: #selector(onNotification(notification:)), name: .notifyName, object: nil)
    }
    
    @objc func onNotification(notification:Notification)
    {
        print("OrigViewController: onNotification called")
        
        label1.text = notification.userInfo?["data"] as? String
    }
    
    
    @IBOutlet weak var label1: UILabel!
    @IBAction func mainButtonPressed(_ sender: Any) {
        
        NotificationCenter.default.post(name: .notifyName,
                                        object: nil,
                                        userInfo: ["data": "data from OrigViewController", "isImportant": true])
    }
}

extension Notification.Name {
    static let notifyName = Notification.Name("notifyName")
}

Containerの ViewController

import UIKit

class ContainerViewController1: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        NotificationCenter.default.addObserver(self, selector: #selector(onNotification(notification:)), name: .notifyName, object: nil)
    }
    
    @objc func onNotification(notification:Notification)
    {
        print("ContainerViewController1: onNotification called")
        
        label1.text = notification.userInfo?["data"] as? String
        
    }
    
    @IBOutlet weak var label1: UILabel!
    @IBAction func button1Pressed(_ sender: Any) {
        NotificationCenter.default.post(name: .notifyName,
                                        object: nil,
                                        userInfo: ["data": "data from Container", "isImportant": true])
    }
}
画面の関係
Container 側のボタンを押した場合。メインにも通知される。

Container View Error

Container View の追加

Container View を追加する。

部品を配置する。

Container View 用の ViewController クラスを実装する。

ここでは、ControllerViewController1.swift ファイルを追加し、次のコードを実装する。

import UIKit

class ContainerViewController1: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
    }
    
    @IBOutlet weak var label1: UILabel!
    
    @IBAction func button1Pressed(_ sender: Any) {
        label1.text = "Pressed"
    }
}

実行すると、次のエラーが発生する。

Thread 1: Exception: “-[UIViewController button1Pressed:]: unrecognized selector sent to instance 0x7f9e9b307890”

状況

スタックの詳細は次のとおり。

2020-07-17 10:05:13.714066+0900 TestScrollView1[10517:781374] [Storyboard] Unknown class ContainerViewController1 in Interface Builder file.

2020-07-17 10:05:18.843040+0900 TestScrollView1[10517:781374] -[UIViewController button1Pressed:]: unrecognized selector sent to instance 0x7f9e9b307890

2020-07-17 10:05:18.906229+0900 TestScrollView1[10517:781374] *** Terminating app due to uncaught exception ‘NSInvalidArgumentException’, reason: ‘-[UIViewController button1Pressed:]: unrecognized selector sent to instance 0x7f9e9b307890’

*** First throw call stack:

Custom Class の Module が None のままになっている。

原因

 [Storyboard] Unknown class ContainerViewController1 in Interface Builder file. のメッセージにあるように、追加したファイルが認識されていない。

対応

プロジェクトを一旦クローズし、再度プロジェクトを開く。

Custom Class の Module に値が入っている。

UserDefaults

    func init()
        if UserDefaults.standard.bool(forKey: "V1.0") == false
        {
            let defaults = [
                "Initialized" : true,
                "R" : 1.0,
                "G" : 1.0,
                "B" : 1.0
                ] as [String : Any]
            UserDefaults.standard.register(defaults: defaults)
        }
    }

    func loadUserDefaults()
    {
        if UserDefaults.standard.bool(forKey: "V1.0") == true
        {
            r = UserDefaults.standard.double(forKey: "R")
            g = UserDefaults.standard.double(forKey: "G")
            b = UserDefaults.standard.double(forKey: "B")
        }
    }

   func saveSettings() {
        UserDefaults.standard.set(true, forKey: "V1.0")
        UserDefaults.standard.set(r, forKey: "R")
        UserDefaults.standard.set(g, forKey: "G")
        UserDefaults.standard.set(b, forKey: "B")
    }