JXA」タグアーカイブ

Image conversion @ PyObjC and JXA

前回書いた検索で簡単に見つかる画像変換ですが。
Objective-c での公式は以下に。

Viewing, Editing, and Saving Images in an Image View

PyObjC でしたら以下がありますね。

[PyObjC CoreGraphics API Example] Shows how to use the functional API of the CoreGraphics framework through PyObjC and Python 2.7. #pyobjc #python #coregraphics #quartz #macos #examples ? GitHub

ただし Python2 コードですし kUTTypeJPEG はとうの昔に非推奨です。
Python3 にて UniformTypeIdentifiers.UTTypeJPEG を使うよう書き換え。

System-declared uniform type identifiers | Apple Developer Documentation

したんだけど何度やってもエラー、原因が解らず困っていた。
本日原因が判明した、こんな理由だったとは。

#!/usr/bin/env python3

import UniformTypeIdentifiers, LaunchServices

print(type(LaunchServices.kUTTypeJPEG))
print(type(UniformTypeIdentifiers.UTTypeJPEG))

''' output
<class 'objc.pyobjc_unicode'>
<objective-c class _UTCoreType at 0x1e3f02bd0>

'''

まさかの。
PyObjC の kUTTypeJPEG は単なる文字列だった。

UTTypeJPEG は普通にポインタ、そりゃエラーになるよと。
ということで PyObjC ではこんなコードになります。
破棄は Python にまかせないと二重破棄でエラー。

#!/usr/bin/env python3

from AppKit import *
from Quartz import *
from UniformTypeIdentifiers import *

src = NSURL.fileURLWithPath_('panaleica.heic')
dest = NSURL.fileURLWithPath_('panaleica.jpg')

isr = CGImageSourceCreateWithURL(src, None)
image = CGImageSourceCreateImageAtIndex(isr, 0, None)

destimg = CGImageDestinationCreateWithURL(dest, str(UTTypeJPEG), 1, None)
if destimg:
    CGImageDestinationAddImage(destimg, image, None)
    res = CGImageDestinationFinalize(destimg)
    print(res)
    #CFRelease(destimg) # Error
else:
    print('error')

HEIF や WebP からでも JPEG に変換できる。
ただし iPhone 写真等の回転情報は継承されず横向きになるので注意。

rotate

いやオートメーションで使うなら AppleScript か JXA にしないと。
こちらでは UniformTypeIdentifiers が使えないので直接文字列で。

#!/usr/bin/osascript

ObjC.import('Cocoa');
ObjC.import('Quartz');
//ObjC.import('UniformTypeIdentifiers');

let src = $.NSURL.fileURLWithPath('gara.webp');
let dest = $.NSURL.fileURLWithPath('gara.heic');

let isr = $.CGImageSourceCreateWithURL(src, null);
let image = $.CGImageSourceCreateImageAtIndex(isr, 0, null);

//let destimg = $.CGImageDestinationCreateWithURL(dest, $('public.jpeg'), 1, null); // jpeg
//let destimg = $.CGImageDestinationCreateWithURL(dest, $('public.png'), 1, null); // png
let destimg = $.CGImageDestinationCreateWithURL(dest, $('public.heic'), 1, null);
if (destimg) {
    $.CGImageDestinationAddImage(destimg, image, null);
    let res = $.CGImageDestinationFinalize(destimg);
    console.log(res);
} else {
    console.log('error');
}

後は同じですね。
PyObjC を先にやらなかったら文字列ということに気が付かなかったかも。
HEIF にもコレで変換可能でした。

#!/usr/bin/osascript

// webp is bad

ObjC.import('Cocoa');
ObjC.import('Quartz');

let src = $.NSURL.fileURLWithPath('panaleica.heic');
let dest = $.NSURL.fileURLWithPath('panaleica.webp');

let isr = $.CGImageSourceCreateWithURL(src, null);
let image = $.CGImageSourceCreateImageAtIndex(isr, 0, null);

let destimg = $.CGImageDestinationCreateWithURL(dest, $('org.webmproject.webp'), 1, null);
if (destimg) {
    $.CGImageDestinationAddImage(destimg, image, null);
    let res = $.CGImageDestinationFinalize(destimg);
    console.log(res); // false
} else {
    console.log('error');
}

ただ WebP から変換はできるけど WebP へはどちらでも変換できなかった。
多分 Objective-c や Swift でも、public.*** しか駄目なのだろうか?
一番やりたいことなのに、もう少し調べます。

ところで、こんなことをやって今頃知ったんですけど。
UTTypeCreatePreferredIdentifierForTag が廃止になっています。

UTTypeCreatePreferredIdentifierForTag | Apple Developer Documentation

筆者が書いた UTI を調べるコマンドが使えなくなっていて焦った。
というか UTI を調べるコードを書いているブログは全滅だ、わーい!
上記で使おうとして動かなかった、新しい方法を探さなくちゃ。

Dinamic Import: Gjs and JXA

現在の JavaScript はダイナミックインポートが可能。
下記ページで Chrome や Safari で動くと解りますね。

全モダンブラウザで使えるJavaScriptのdynamic import(動的読み込み) – Qiita

GNOME の Gjs は imports 関数があるので関係ないんだけど。
そういえば macOS の JXA はファイル分割ができないのが困る。
コレを利用してクラス毎に分割できるかな。

#!/usr/bin/osascript

import('./sub.js')
	.then((module) => {
        console.log('Start!');
		const sub = new module.Sub();
        sub.subMethod();
	});

main.js

export class Sub {
    subMethod() {
        console.log('this is Sub Class Method');
    }
}

sub.js

osa

なんでだよ!
Promise だと認識はしているけど動作しないってことみたい。
JavaScriptCore が全部やってくれるってわけじゃないんかい。

Gjs でも同じかな?
console.log を print に書き換えて実験。

#!/usr/bin/gjs

import('./sub.js')
	.then((module) => {
		const sub = new module.Sub();
        sub.subMethod(); 
	});

main.js

export class Sub {
    constructor(app) {
        print('Hello');
    }
    subMethod() {
        print("this is Sub Class Method");
    }
}

gjs

動いたのでコンストラクタも入れてみた、普通に Class ですね。
いや Gjs は imports を使ったほうが簡単ですけど。
というか、Apple は内製スクリプトには本当にヤル気が無いなって。

macOS Monterey JXA

macOS Monterey の JXA は NSRect のバグが無くなった。
ということで、今の知識で NSWindow を作ってみたらどうなるか。

いや PyObjC のほうが簡単なんだけど、デフォルトで入っていないのが。
JXA ならどんな Mac でもそのまま動かせるという利点があるので捨て難い。

ただ JXA では PyObjC みたいに class にできないんだよなぁ。
ネットで簡単に見つかる方法では全部グローバル変数にするしかないのが。
そもそも JavaScript の class は他の言語の class とは違うし。
なので GNOME の Gjs もアクロバットな手段で class っぽくしている。

ObjC.registerSubclass を上手く利用してソレっぽくやってみよう。
追加メソッドはどう書けばいいのかな?とか。

#!/usr/bin/osascript

ObjC.import('Cocoa');

//let wins = [];

ObjC.registerSubclass({
    name: 'AppDelegate',
    protocols: ['NSApplicationDelegate'],
    methods: {
        'applicationDidFinishLaunching:': function (notification) {
            let window = $.MyWindow.alloc.initWithContentRectStyleMaskBackingDefer(
                $.NSMakeRect(0, 0, 300, 100),
                $.NSTitledWindowMask
                | $.NSClosableWindowMask
                | $.NSMiniaturizableWindowMask
                | $.NSResizableWindowMask,
                $.NSBackingStoreBuffered,
                false
            );
            window.makeKeyAndOrderFront(window);
            $.NSApp.activateIgnoringOtherApps(true);
            //wins.push(window);
        }
    }
});

ObjC.registerSubclass({
    name: 'WinDelegate',
    protocols: ['NSWindowDelegate'],
    methods: {
        'windowWillClose:': function(notification) {
            console.log('MyApp Close !');
            return $.NSApp.terminate(0);
        }
    }
});

ObjC.registerSubclass({
    name: 'MyWindow',
    superclass: 'NSWindow',
    propertyies: {},
    methods: {
        'initWithContentRect:styleMask:backing:defer:': function
        (contentRect, style, backingStoreType, flag) {
            let _this = ObjC.super(this).initWithContentRectStyleMaskBackingDefer(
               contentRect, style, backingStoreType, flag);
           _this.title = $('JXA NSWindow');
           _this.delegate = $.WinDelegate.new;
           // Button
           let button = $.NSButton.buttonWithTitleTargetAction('button', this, 'onButtonClick:');
           button.setFrame($.NSMakeRect(10, 10, 200, 36));
           _this.contentView.addSubview(button);
           // return
            return _this;
        },
        'onButtonClick:': {
            types: ['void', ['id']],
            implementation: function(sender) {
                sender.setTitle('Clicked!');
            }
        }
    }
});

/**
 * Application
 */
$.NSApplication.sharedApplication;
$.NSApp.setActivationPolicy($.NSApplicationActivationPolicyRegular);
$.NSApp.mainMenu = function() {
    let mainMenu = $.NSMenu.new;
    let itemApp  = $.NSMenuItem.new;
    mainMenu.addItem(itemApp);
    let menuApp  = $.NSMenu.new;
    itemApp.setSubmenu(menuApp);
    // quit menu
    let itemQuit = $.NSMenuItem.new;
    itemQuit.initWithTitleActionKeyEquivalent('Quit App', 'terminate:', 'q');
    menuApp.addItem(itemQuit);
    return mainMenu;
}();
$.NSApp.setDelegate($.AppDelegate.new);
$.NSApp.run;

nswindow

こんな感じになった、
ボタンのハンドラは delegate ではなく this に届くようだ。
オーバーライドは types 指定不要なのね、ふむふむ。
これだけ解れば応用でなんとかなりそう。

ただ PyObjC と違って NSWindow がガベージコレクションされないんだが。
let 指定ならばハンドラを抜けたら破棄されるはずなんだけど、何故だ?
Python とは破棄対象の選定方法が違うんだろう、知らんけど。

ところで Monterey で今頃気がついたんだけど。

power

iOS みたいに充電の保留機能が付いたんだね。
筆者は滅多に持ち歩かないんで意味はないかもだがけど。

macOS Monterey

macOS を Monterey に遅ればせながらアップグレード。
Fedora とは違ってスッゲェ時間が掛かると解っているので休日まで待った。
12.13GB か、前の 14GB より減ったとはいえ酷い。

そろそろ全部終わったかな?
って画面を表示させると「インストールします」ダイアログ。
ガックリ。。。。。

[続ける]-[同意] って感じでボタンを押さないとインストールが始まらない。
今度こそ終わったかな?って見ると「再起動」ダイアログ。
ションボリ。。。。。

その再起動が又時間が掛かる。
終わった!と思ったらフェイントだった。
ゲンナリ。。。。。

M1 でコレだぞ、Intel Air だったらドンダケだったんだ?
アップグレードくらいボタン一発でスパッとやってくださいよ。
あーやっとこさ終わった。

それはいいとして。

Apple、macOS 12 MontereyにPython 2.7.18などを同梱してリリース。Big Surまで同梱されていたphpは削除されるので注意を。

マジで PHP は削除されるのね。
httpd.conf が初期化されるのはいつものこと。

php

WordPress の時代にコレって macOS を Web 開発に使うなってメッセージ?
Fedora のように dnf コマンドで即座に導入できるってわけでもないのにさ。
いったいどんな政治的なやりとりがあったのか、みたいな。
これだから Fedora も併用しなきゃなんだよ、macOS で開発は自由が無い。

自力で Python3 を入れている人は残る。
3.10 になっているのか、アップグレードしとこ。
macOS 64-bit unibersal2 installer で M1 ネイティブだよ。

Python Release Python 3.10.0 | Python.org

Lightroom は問題なく動く、コレのために Mac を使っているのだし。

それと Fedora から SSH てか SFTP で簡単アクセスする用の
~/.ssh/id_rsa.pub
とか設置していたものも普通に残っているしアクセスもできる。

そんなことより何より。

#!/usr/bin/osascript

ObjC.import('Cocoa');
let r = $.NSMakeRect(0, 0, 10, 10);

console.log(`width = ${r.size.width}`);

jxa

JXA での NSRect バグがやっと、ついに修正されたぞ!
Big Sur アップデート毎にコレ動かしてションボリしてたのよね。
待ちわびた、待ちすぎて PyObjC 万歳になっちゃった。

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 もコレで問題なく動くじゃないの。
少し面倒だったけど混迷期だからしかたがないね。