JXA: Image Resize and Save

今回は JXA にて画像のリサイズと保存。

(旧) Cocoaの日々: キャプチャ画像を縮小して保存する

Cocoa は他の OS のグラフィック(デバイス)コンテキストと違っていて
現在フォーカスがあるグラフィックコンテキストに描写する、という手法。
つまり NSImage に NSImage をリサイズして描写するってことみたい。

各画像フォーマットのデータを作成する

NSDictionary を作るのが面倒だなぁ。
変換で楽したいし NSImageCompressionFactor は調べると NSString だし。
ってことで。

ObjC.import("Cocoa");

console.log($.NSImageCompressionFactor.js);
//=> NSImageCompressionFactor

まんまカヨ!

クオリティは 1.0 が最高値みたい
筆者はいつものように GIMP に合わせて 0.85 にしてみる。
ということでこんなコードになりました。

#!/usr/bin/osascript

ObjC.import("Cocoa");

function run(argv) {
    let srcImage = $.NSImage.alloc.initWithContentsOfFile(argv[0]);
    // Create 300x200 Image
    let newImage = $.NSImage.alloc.initWithSize($.NSMakeSize(300, 200));
    newImage.lockFocus;
    $.NSGraphicsContext.saveGraphicsState;
    $.NSGraphicsContext.currentContext.setImageInterpolation($.NSImageInterpolationHigh);
    srcImage.drawInRectFromRectOperationFraction(
        $.NSMakeRect(0, 0, 300, 200),
        $.NSZeroRect,
        $.NSCompositeSourceOver,
        1.0);
    $.NSGraphicsContext.restoreGraphicsState;
    newImage.unlockFocus;
    // JPEG Write;
    let bmp = $.NSBitmapImageRep.imageRepWithData(newImage.TIFFRepresentation);
    bmp.alpha = false;
    let prop = $({"NSImageCompressionFactor": 0.85});
    let data = bmp.representationUsingTypeProperties($.NSJPEGFileType, prop);
    data.writeToFileAtomically($(`${argv[0]}_300x200.jpg`), true);
}

prop の値を変更すると画像のサイズと画質が変わるのが確認できた。
これでサムネイル選択の作成に取りかかれる、最大の難関みたいな気がするけど。

JXA: NSApplication argv

JXA で osacompile にて app 化すると argv が仕事しない件。
NSProcessInfo を使うことで解決した。

function run(argv) {
    $.NSApplication.sharedApplication;
    const window = new MyWindow();
    $.NSApp.setActivationPolicy($.NSApplicationActivationPolicyRegular);
    ObjC.registerSubclass({
        name: "AppDelegate",
        protocols: ["NSApplicationDelegate"],
        methods: {
            "applicationDidFinishLaunching:": {
                types: ["void", ["id"]],
                implementation: (notification)=> {
                    let arr = $.NSProcessInfo.processInfo.arguments.js;
                    if (arr.length > 2) window.setPath(arr[2].js);
                }
            }
        }
    });
    //
    // etc..
    //
    // Not work after compiling
    //if (argv.length < 0) window.setPath(argv[0]);
    $.NSApp.delegate = $.AppDelegate.new;
    $.NSApp.run;
}

引数の数に注意。
[“osascript”, “src”, parm1, param2, …]

これで js のままでも app にコンパイルしても両方仕事する。
スクリプトのまま使うなら argv を使ったほうが簡単だけどね。

ところで前回の zipinfo の件だけど。
macOS の zip は -U オプションすら使えないじゃん。
パッチが当たっていないどころか UNICODE サポートですらないってことだ。
ということでこいつの出力からの変換は不可能であるようだ。

で、zipdetails という perl のコマンドを見つけた。
こいつの出力からなんとかならないかな?
かなり意地悪な zip ファイルを用意して試す。

改行が UNOCODE 未対応のようで化ける、駄目だ。
でも perl でできるということはスクリプトで得られるということだ。
バイナリを見ると、単純にファイル名は構造体にそのまんま入っているヤン。

って、だから JavaScript じゃバイナリは直接扱えないんだってばさ!
UInt8Array に変換すると超激遅なのは Gjs で経験済み。
Objective-c での手段も見つからない、みんな C でやっているの?

もう macOS 版は日本語アーカイブをサポートしない、で終わりにしよう。

mac: unzip 文字化け

macOS 版 Comipoli が cbz 内部で日本語があるとエラー。
原因を色々探ってみたら zipinfo がこういうことになっていた。

Fedora

macOS

macOS の zipinfo が日本語ファイル名を出力できないだけだった。
どちらも LANG=ja_JP.UTF-8 だし、Terminal.app も日本語出力は普通にできるし。
でも何故か展開すると普通に日本語ファイル名になる。

個別取り出しも日本語指定で取り出せる、意味ワカンネ!
つまり、この出力を日本語にて得られないと個別取り出しができないってことだ。

いくら検索しても shift-jis のことしか出てこない、んなもん常識以前だろ!
UTF-8 が化けることに誰も気がついていないのか。。。。。

NSStringEncoding – Foundation | Apple Developer Documentation

コイツを色々試したけどドレも日本語に戻せない。
iconv も試しているけどお手上げ状態。
日本語は非サポートってことで済ませようかな。。。。。

現状を置いておきます、日本語ファイル名でなければ動くんだけど。
comimac-0.0.2.tar.gz

ところでオプションでなんとかしようと漁っていたらこんなのが。
macOS ではオプションに出ない -O, -I って何だろう?

Unzip の日本語ファイル名の取り扱いについて | FreeBSD | daily memorandum 3.0.0

ってつまり。

#!/bin/sh
unzip -Ocp932 "$@"


_Zip932 とかの名前で Nautilus Script に登録しておけば Fedora も例の文字化けを回避。
macOS の奴はパッチが当たっていないようなのでこの手段は使えない。
Ubuntu の奴はコレを自動判別しているだけ、ってことみたい。

Comipoli は進まなかったけど面白いことを知った。

Gedit RepeatLine Plugin

筆者は macOS で Visual Studio Code を使っている。
しかし Fedora では Gedit を使い続けている。
Fedora でも併用しようと考えたけど結局 Gedit しか使わない。

しかし Visual Studio Code には便利すぎる機能がある。
opthon(alt)+shift+down で行の複製ができる、これが超スバラシイ。

fn+left
shift+fn+right
command+c
fn+right
return
command+v

とやっていたことを一発だ、よく使うんだな行の複製って。
ちなみに fn の所は TextEdit.app 同様に command でもいい。
筆者は US 配列なので fn のほうが楽だということで。

てか mac の日本語キーボードは何故 fn が右なのか、マジで糞。
US 配列を店頭でも普通に買えるようにしてくれないかなぁ。
それは今は関係なくて。

Gedit でも同じことがやりたいぞ。
ということで Plugin を探し、、、じゃなくて作る!
何年ぶりの新規プラグイン作りだろう、ワクワク。

#-*- coding:utf-8 -*-

#    Gedit repeat plugin version 3.22.0
#    Copyright © 2018 sasakima-nao <sasakimanao@gmail.com>
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; _endher version 2 of the License, or
#    (at your option) any later version.

import gi, os
gi.require_version("Gtk", "3.0")
gi.require_version("Gedit", "3.0")
gi.require_version("Peas", "1.0")

from gi.repository import GObject, Gedit, Gtk, Gio, GLib

class RepeatLineAppActivatable(GObject.Object, Gedit.AppActivatable):
    """
        Set GMenu and Accelerator
    """
    app = GObject.Property(type=Gedit.App)
 
    def __init__(self):
        GObject.Object.__init__(self)
 
    def do_activate(self):
        # "<Alt><Shift>Down" Not Work
        self.app.add_accelerator("<Alt><Shift>d", "win.repeatline", None)
        self.menu_ext = self.extend_menu("tools-section")
        item = Gio.MenuItem.new("Repeat Line",  "win.repeatline")
        self.menu_ext.append_menu_item(item)
 
    def do_deactivate(self):
        self.app.remove_accelerator("win.repeatline", None)

class RepeatLinePlugin(GObject.Object, Gedit.WindowActivatable):
    __gtype_name__ = "RepeatLinePlugin"
    window = GObject.Property(type=Gedit.Window)
    def __init__(self):
        GObject.Object.__init__(self)

    def do_activate(self):
        self.action = Gio.SimpleAction.new("repeatline", None)
        self.action.connect('activate', self.on_repeatline_activate)
        self.window.add_action(self.action)

    def do_deactivate(self):
        self.window.remove_action("repeatline")

    def do_update_state(self):
        self.action.set_enabled(self.window.get_active_document() != None)

    def on_repeatline_activate(self, action, param):
        view = self.window.get_active_view()
        buf = view.get_buffer()
        _start_ = buf.get_start_iter()
        _end_ = buf.get_end_iter()
        spos = buf.props.cursor_position
        epos = spos
        it = buf.get_iter_at_offset(spos - 1)
        line = None
        while 1:
            # search line start position
            if it.equal(_start_) or it.get_char() == "\n":
                line = it.copy()
                it = buf.get_iter_at_offset(epos)
                break
            spos -= 1
            it = buf.get_iter_at_offset(spos)
        while 1:
            # search line end position
            if it.equal(_end_) or it.get_char() == "\n":
                s = line.get_text(it)
                if line.equal(_start_):
                    s = "\n" + s
                buf.insert(it, s, -1)
                break;
            epos += 1
            it = buf.get_iter_at_offset(epos)

作ってみた。

残念ながら Alt+Shift+Down は無視された。
Alt+Shift+D でいいやもう。

os.getenv(GEDIT_CURRENT_LINE)
が使えると思ったけどこれはプラグインからは参照できないのね。
しかたがないので GtkTextIter で地味に \n 位置を探すことに。

先頭と最後は \n が無いけどこんな処理でイケた。
何をやっているかは GtkTextBuffer のドキュメントで。

とりあえずこれで Gedit でも同様なことができるぞい。
探せば既にあるかもだけど、プログラミングは経験値だよと一言。

JXA: is Command Exist

Comipoli JXA 版はココにきて色々問題が出て困っている。
Fedora で作成した日本語入り cbz が開けなかったり…
コンパイルすると起動引数を受け付けないとか…
他色々…

間違いを堂々と書いていたり、私ってほんとバカ(古い
JXA NSOpenPanel | PaePoi
後で修正するのも面倒なので確実なものだけ書くようにしたい。

そういえば unrar や 7za がインストールしてあるか確認しないと。
JXA では、えっと。

#!/usr/bin/osascript

ObjC.import("Cocoa");

function isCommandExist(cmd) {
    let task = $.NSTask.new;
    let pipe = $.NSPipe.pipe;
    task.standardOutput = pipe;
    task.standardError = pipe;
    task.launchPath = "/usr/bin/type";
    task.arguments = $([cmd]);
    task.launch;
    task.waitUntilExit;
    return task.terminationStatus === 0;
}
console.log(isCommandExist("unzip"));
console.log(isCommandExist("unrar"));

これでいいか。

stdout, stderror を吐かないように pipe に入れて捨てている。
> /dev/null を配列にいれるとエラーになった。

ところで zipinfo -1 という素敵な手段を今頃知った。
unzip -Z -1 でもいい。

 if (/\.(cbz|zip)$/i.test(path)) {
    this.status = 0;
    this.namelist = [];
    //let output = this.getString(["unzip", "-Z", path]);
    let output = this.getString(["zipinfo", "-1", path]);
    let list = output.split("\n");
    for (let line of list) {
        if (PICEXT.test(line)) {
            this.namelist.push(line);
        }
        /*if (line.startsWith("-")) {
            let name = line.slice(53);
            if (PICEXT.test(name)) {
                this.namelist.push(name);
            }
        }*/
    }
    this.namelist.sort();
}

今まで何を。。。。。

Fedora も同様だった。
オリジナルも次はこれにしよう。