PyObjC」タグアーカイブ

pinch

今日の大口町。

chougenbou

チョウゲンボウ、ハヤブサ(スズキ)じゃないようで残念。
今日の収穫は以上、いやアオサギやダイサギは沢山いたんだけど。

って本当に野鳥ブログにするんかい。
いいかげんにプログラミングを再開しよう。
とりあえず macOS 用 RAW 画像選別アプリのほうを。

まずピンチを実装したい、トラックパッドで拡大縮小ね。
標準アプリはあたりまえにできるけど実装しなきゃできない。

cocoa – -[NSResponder swipeWithEvent:] not called – Stack Overflow

最初にまんまコレやって失敗した。
だってメソッド名がまぎらわしいんだもんもん。

Cocoaの日々: 1月 2011

こっちでいい、なんだ日本語で見つかるジャン。
NSResponder の magnifyWithEvent メソッドね。

ただ setFrameSize するのはどうなんだ。。。。。
拡大や縮小を指定し再描写にする。

#!/usr/bin/env python3

from AppKit import *
# Kill of control+C
import signal
signal.signal(signal.SIGINT, signal.SIG_DFL)

PATH = 'P1012925.RW2' # LUMIX(Panasonic) RAW image
RECT = NSMakeRect(0, 0, 600, 400)
wins = []

class RawView(NSView):
    def initWithFrame_(self, rect):
        objc.super(RawView, self).initWithFrame_(RECT)
        self.image = NSImage.alloc().initWithContentsOfFile_(PATH)
        self.draw_size  = 1
        #self.setWantsRestingTouches_(True) # ?
        return self

    def acceptsFirstResponder(self):
        return True

    def magnifyWithEvent_(self, event):
        print(event.magnification())
        if event.magnification() != 0:
            self.draw_size = event.magnification() + 1
            self.display()

    def swipeWithEvent_(self, event):
        print('swipe')

    def keyDown_(self, event):
        print('key down test')

    def mouseDown_(self, event):
        print('mouse down test')

    def drawRect_(self, rect):
        NSColor.blackColor().set()
        NSRectFill(rect)
        aw = rect.size.width * self.draw_size
        ah = rect.size.height * self.draw_size
        w = self.image.size().width
        h = self.image.size().height
        if aw * h > ah * w:
            width = w * ah / h
            height = ah
            x = (aw - width) / 2
            y = 0
        else:
            width = aw
            height = h * aw / w
            x = 0
            y = (ah - height) / 2
        r1 = NSMakeRect(x, y, width, height)
        self.image.drawInRect_(r1)

class MyWindow(NSWindow):
    def init(self):
        objc.super(MyWindow, self).initWithContentRect_styleMask_backing_defer_(
            RECT,
            NSTitledWindowMask | NSClosableWindowMask |
            NSResizableWindowMask | NSMiniaturizableWindowMask,
            NSBackingStoreBuffered, False)
        # NSView
        self.rawview = RawView.alloc().initWithFrame_(RECT)
        self.contentView().addSubview_(self.rawview)
        # self
        self.setTitle_('RAW image Viewer')
        return self

class AppDelegate(NSObject):
    def applicationDidFinishLaunching_(self, notification):
        window = MyWindow.new()
        window.makeKeyAndOrderFront_(window)
        # save
        wins.append(window)
        # Activate
        NSApp.activateIgnoringOtherApps_(True)

class AppMenu(NSMenu):
    def init(self):
        objc.super(AppMenu, self).init()
        item_app  = NSMenuItem.new().autorelease()
        self.addItem_(item_app)
        menu_app = NSMenu.new().autorelease()
        item_app.setSubmenu_(menu_app)
        # quit menu
        item_quit = NSMenuItem.new().autorelease()
        item_quit.initWithTitle_action_keyEquivalent_('Quit App', 'terminate:', 'q')
        menu_app.addItem_(item_quit)
        return self

pool = NSAutoreleasePool.new()

NSApplication.sharedApplication()
NSApp.setMainMenu_(AppMenu.new().autorelease())
NSApp.setDelegate_(AppDelegate.new().autorelease())
NSApp.run()

できたけど。
拡縮の原点は左下、ジェスチャ終了でゼロを投げてくる。
数値はアクション毎にリセット、うーん計算が難しい。
てかレスポンスが酷い、Preview.app 等と全然違う。

コレ違うだろって感じ、もう少し調べなきゃ。

Eye of Mac

筆者は Fedora と Mac を併用しています。
写真関連は現在全部 Mac でやっている。
そして思う、Eye of GNOME(eog) を Mac で使いたい。

大量画像の Exif 情報をサクッと確認したくなる場合が多い。
eog は左右矢印キーでディレクトリ内を巡回できるのがいい。
Finder のギャラリー表示はなんかもどかしくて。

Photo management | Paepoi Blog

Mac にインスト、、、、、いや Gedit でやって劇遅だったじゃん。
だから Atom を Gedit 風にカスタムして使っているのだし。
コレも似たようなビューアを探す?いやどうせなら自分で作ろうかなって。
カスタムするくらいなら最初から自分好みに作ったほうがいいので。

まて、その前に RAW 画像を NSImage は表示できるのか?
ということで試してみた。

#!/usr/bin/env python3

from AppKit import *

PATH = 'P1012925.RW2' # LUMIX(Panasonic) RAW image
RECT = NSMakeRect(0, 0, 600, 400)
wins = []

class RawView(NSView):
    def initWithFrame_(self, rect):
        objc.super(RawView, self).initWithFrame_(RECT)
        self.image = NSImage.alloc().initWithContentsOfFile_(PATH)
        return self

    def drawRect_(self, rect):
        NSColor.blackColor().set()
        NSRectFill(rect)
        aw = rect.size.width
        ah = rect.size.height
        w = self.image.size().width
        h = self.image.size().height
        if w - h > 0:
            if aw * h > ah * w:
                width = w * ah / h
                height = ah
                x = (aw - width) / 2
                y = 0
            else:
                width = aw
                height = h * aw / w
                x = 0
                y = (ah - height) / 2
            r1 = NSMakeRect(x, y, width, height)
            self.image.drawInRect_(r1)

class MyWindow(NSWindow):
    def init(self):
        objc.super(MyWindow, self).initWithContentRect_styleMask_backing_defer_(
            RECT,
            NSTitledWindowMask | NSClosableWindowMask |
            NSResizableWindowMask | NSMiniaturizableWindowMask,
            NSBackingStoreBuffered, False)
        # NSView
        view = RawView.alloc().initWithFrame_(RECT)
        self.contentView().addSubview_(view)
        # self
        self.setTitle_('RAW image Viewer')
        return self

class AppDelegate(NSObject):
    def applicationDidFinishLaunching_(self, notification):
        window = MyWindow.new()
        window.makeKeyAndOrderFront_(window)
        # save
        wins.append(window)
        # Activate
        NSApp.activateIgnoringOtherApps_(True)

class AppMenu(NSMenu):
    def init(self):
        objc.super(AppMenu, self).init()
        item_app  = NSMenuItem.new().autorelease()
        self.addItem_(item_app)
        menu_app = NSMenu.new().autorelease()
        item_app.setSubmenu_(menu_app)
        # quit menu
        item_quit = NSMenuItem.new().autorelease()
        item_quit.initWithTitle_action_keyEquivalent_('Quit App', 'terminate:', 'q')
        menu_app.addItem_(item_quit)
        return self

pool = NSAutoreleasePool.new()

NSApplication.sharedApplication()
NSApp.setMainMenu_(AppMenu.new().autorelease())
NSApp.setDelegate_(AppDelegate.new().autorelease())
NSApp.run()

rw2_view

普通に表示できるじゃん。
macOS が対応している RAW は全部表示できると思う。
RAW 対応は「システムレポート」で見ることができる。

mac_raw

EXIF は以下の方法で RAW でも取得できるようだ。

PyObjC Exif | Paepoi Blog

これは思ったより簡単に作れそうだぞ。
GTK4 もやらなきゃだけど今はこっちをやりたいかな。

M1 Mac JXA and PyObjC

会社の都合で休日出勤は無しなので M1 Air をしっかり試す。
たしかパワーは 2019 Air の五倍なので筆者の 2018 からだと八倍くらいか。
ログイン画面まで 15 秒、パスワードを打ち込みプラス 15 秒でもう使える。
他に Web でベタ褒めされている件、あれらは全部本当だった。
こんなの筆者の知っている Macbook Air じゃない、怪物にも程がある。

とりあえず再構築、Atom を公式サイトから入れよう。
起動しようとすると Rosetta2 をインストールしますか?とアラートが。
インストールを選ぶと一瞬で入ったから内部で有効化しただけだと思うけど。
ぶっちゃけ来年くらいまでは全員が入れると思う。

Atom を Gedit のように使う – Paepoi

再構築完了、書いてて良かった自前カスタムのすべて。
早速 JXA を試そう、ES2020 は対応されているか。
?? 演算子と BigInt と globalThis をまとめてテスト。

#!/usr/bin/osascript

const p = globalThis.print ?? console?.log;
p(12341234123412341234n *2n);

atom_run

動くじゃないの。

GNOME 3.38 Gjs | Paepoi Blog
import 関数は使えない。
Promise も動いた、ただ筆者には便利さが解らない。
matchAll も動く、Gjs と同じく詳細は出力されないけど。

だいたい Gjs と同じって感じ。
ただし、NSMakeRect のバグはそのまんまだ、やっぱりやる気無いのね。

しかしマジで Python2 がデフォルトで入っているのがなんとも。
PHP は 7.3.22 か、さくらインターネットでさえ 7.4.10 なのに。
perl と ruby はどうでもいい。

端末で python3 と打ち込むと CommandLineTools のダイアログ。
インストールすると Python3 が使える、3.8.2 だ。
普通に使えるけど、PyObjC が pip3 でインストールできない。

Python Release Python 3.9.1 | Python.org

macOS 64-bit universal2 installer
というものが公式にあった、これならイケるかな。
CommandLineTools を先に入れないと 3.8 に上書きされるので注意。
インストーラに従うだけで 3.9 にアップグレードできる。
んで、pip3 のアップグレードを行う必要があった。

# pip upgrade
pip3 install --upgrade pip
# install
pip3 install -U pyobjc

PyObjC Tips – Paepoi

PyObjC

PyObjC もコレで問題なく動くじゃないの。
少し面倒だったけど混迷期だからしかたがないね。

PyObjC Exif

よし休日だ。
今日こそ、今度こそは五条川でカワセミを見つけて撮影してやるぞ!
と昼前に遊歩道へ行って十分後。

こんなにあっさり。
わざわざ八田川まで通っていた日々は何だったんだろう。
まあいい、次は狩りの瞬間を撮りたいな。

ところで。

Fedora の場合 Exif 情報を得るには GExiv2 を使えばいい。
macOS の場合はどうすればいいんだろう、ちゃちゃっと検索。

@macosx: NSImage Exif (metadata)

CGImageSource なんてものが SDK にあるみたい。
連想配列を使うのなら PyObjC で書くとスゲェ簡単だぞ。
ImageIO SDK の Objective-c Bridge は以下にあった。

/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/Quartz

ということでエラー処理を省いて PyObjC で書き換えてみた。
メソッドの場合は引数の数だけアンダーバーが必要なのを忘れずに。

#!/usr/bin/env python3

from AppKit import *
from Quartz.ImageIO import *
import sys

def get_exif_dict(filename):
    url = NSURL.fileURLWithPath_(filename)
    source = CGImageSourceCreateWithURL(url, None)
    data = CGImageSourceCopyPropertiesAtIndex(source, 0, None)
    return data #['{Exif}']

d = get_exif_dict(sys.argv[1])
print(d)

以上。

macos_exif

dict の中に更に {ExifAux} 等の dict があって細かく情報が得られる。
CFDictionaryRef も Python の dict 同様に添字でアクセスできる。
こんなに楽チンなのに PyObjC って全然流行らないのは何故だろう。

レンズの名前まで記録しているのね、こういう情報が残っていると助かる。
野鳥撮影は楽しいよ、プログラミングの楽しさになんか似ている。

macOS Get UTI

Apple 関連で開発をしていると UTI を調べる必要がある場合が多々ある。

Uniform Type Identifier – Wikipedia

Uniform Type Identifier Concepts

検索をしていたら素敵なページを見つけた。

Mac や iOS でファイルの種類を表す識別子 Uniform Type Identifiers を拡張子から調べる(Swiftで1行で出来る) – niwatakoのはてなブログ

てゆーか JXA でも PyObjC でもできる。
しかし困ったことに JXA では CFString が NSString にキャストできない。

objective c – JXA: Accessing CFString constants from CoreServices – Stack Overflow

上記を見つけてようやく解決。
console.log って C 言語の char[] を出力できる、初めて知った。
UTF16LE に変換は不要、CJK 文字列でも問題ないようです。

#!/usr/bin/osascript -l JavaScript

let jp = $('スズキ GSX250R').UTF8String;
console.log(jp);
//=> スズキ GSX250R

// ex: ft=js.jxa

ということで JXA にて簡単に調べるコマンドを作ってみる。

#!/usr/bin/osascript -l JavaScript

ObjC.import('CoreServices');

function run(argv) {
    for (let ext of argv) {
        let uti = $.UTTypeCreatePreferredIdentifierForTag(
            $.kUTTagClassFilenameExtension, $(ext), null);
        let s = $.CFStringGetCStringPtr(uti, 0);
        console.log(`${ext}: ${s}`);
    }
}

// ex: ft=js.jxa

getuti.js

せっかくなので基底タイプも調べたいぞ。
JXA で得る方法が解らなかったので PyObjC で書いてみる。
PyObjC は CFDictionary や CFString も Python の型と等価なので超簡単。
他の言語を使うのが馬鹿馬鹿しくなってしまうので注意が必要。

#!/usr/bin/env python3

import sys, CoreServices

for ext in sys.argv[1:]:
    uti = CoreServices.UTTypeCreatePreferredIdentifierForTag(
        CoreServices.kUTTagClassFilenameExtension, ext, None)
    arr = CoreServices.UTTypeCopyDeclaration(uti)['UTTypeConformsTo']
    con = ','.join(arr)
    print(f'{ext}: {uti} [{con}]')

# ex: ft=py

@PyObjC

getuti という拡張子の無い名前で +x のパーミッションを付けパスの通った場所へ。

getuti

JXA でやりたかったけどまだまだ修行が足りない。