24
18

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

進化した macOS の sips コマンド + JavaScript でお絵描きする

Last updated at Posted at 2021-05-23

sips コマンドとは

macOS には標準で /usr/bin/sips に画像処理用のコマンドがあります。
ImageMagick などに頼らなくても簡単な画像処理ができるツールとして重宝されています。

従来の sips コマンドの使い方については Qiita にもいくつか記事が上がっています。

最近 (おそらく Big Sur 以降 1) になって、sips コマンドがこっそり強化され、JavaScript を使って画像の操作が行えるようになりました。

インターフェースとしては <canvas> 要素のものを模倣しており、内部的には CoreGraphics を使って実装されているようです。
これによって従来 sips ではできなかった、柔軟な画像処理ができるようになってきました。

実装としては JavaScript の実行環境は JavaScriptCore.framework を使っていて、 Canvas APIは CoreGraphics.framework で同等のものを実装して、JavaScript とブリッジしているようです。

JavaScript インターフェースの使い方

さっそく sips の manpage を見てみましょう。

$ man sips
...
     -j file
     --js file
	   Execute JavaScript file
...
JavaScript
     HTML Canvas objects can be created and used to create a 2D drawing context.  The com-
     mands for drawing into the context are well documented elsewhere.	This section will
     describe the sips global object and other interesting classes.
...

-j --js オプションに JavaScript ファイルを渡すことで、画像の操作ができそうです。
また、HTML の <canvas> で使える操作ができるとのことですが、その詳細はドキュメントには書かれていません。

Hello world

というわけで早速 Hello world してみましょう。

hello.js
console.log('Hello, world!');
$ sips -j hello.js
Hello, world!

はい、無事 JavaScript が実行されています。
console.log 以外に print 関数も定義されているので、書き換えても動作します。

また、JavaScriptCore では shebang が使えるのでスクリプト単体を配布することもできます。

hello.js
#!/usr/bin/sips -j

console.log('Hello, world!');

このファイルに実行属性をつければ、以下のようにスクリプト単体で実行できます。

$ ./hello.js
Hello, world!

ゼロから画像を生成

<canvas> API を使ってゼロから画像を生成することもできます。

試しに MDN の Canvas API Tutorial のページに載っているサンプルを描いてみましょう。

rect.js
const canvas = new Canvas(150, 150);
canvas.fillStyle = 'rgb(200,0,0)';
canvas.fillRect(10, 10, 50, 50);
canvas.fillStyle = 'rgba(0,0,200,0.5)';
canvas.fillRect(30, 30, 50, 50);
const output = new Output(canvas, 'rect.png');
output.addToQueue();
$ sips -j rect.js

rect.png:
rect.png

はい、無事2つの四角形が描画されました。
MDN のサイトに載っている Canvas API を使ったサンプルと異なるところがいくつかあるので見ていきましょう。

まず最初は Canvas オブジェクトの生成です。
通常ブラウザの API では HTML で <canvas> 要素を定義しておいて、document.getElementById() などを呼んで HTMLCanvasElement を取得しますが、sips コマンドの API では直接 new Canvas(width, height) の形でインスタンス化できます。

次に、 canvas.getContext('2d') を読んで 2D 描画コンテキストを取得する必要がない点です。
この API は定義自体はされているので、呼んでも構いませんが、実装としては単に this を返すだけになっているようです。したがって Canvas API の CanvasRenderingContext2D オブジェクトに対する操作は Canvas オブジェクト自体に対してすることができます。

最後に、描画した画像を書き出す API が存在していることです。

Output オブジェクトは new Output(context, name, type) の形でインスタンス化できます。
context 引数には Canvas オブジェクト、name 引数には出力先のパス、省略可能な type 引数には出力ファイルの拡張子か UTI を渡します。
type 引数が省略された場合は name 引数の拡張子から判断されるようです。

UTI の概念は macOS 独特のもので馴染みがない方もいると思うので、利用可能な拡張子と UTI を載せておきます。

拡張子 UTI
png public.png
jpeg / jpg public.jpeg
gif com.compuserve.gif
tiff public.tiff
bmp com.microsoft.bmp

パスを使った例

Canvas API と同じく、パスを使った描画も可能です。
やはり MDN のサンプルがほぼ変更なしに動作します。

smile.js
const canvas = new Canvas(150, 150);
canvas.beginPath();
canvas.arc(75, 75, 50, 0, Math.PI * 2, true);
canvas.moveTo(110, 75);
canvas.arc(75, 75, 35, 0, Math.PI, false);
canvas.moveTo(65, 65);
canvas.arc(60, 65, 5, 0, Math.PI * 2, true);
canvas.moveTo(95, 65);
canvas.arc(90, 65, 5, 0, Math.PI * 2, true);
canvas.stroke();
const output = new Output(canvas, 'smile.png');
output.addToQueue();
$ sips -j smile.js

smile.png

smile.png

画像を使った例

別の画像を読み込むこともできます。
先程までの例で生成された画像を一枚の画像にしてみましょう。

composite.js
const canvas = new Canvas(150, 150);
sips.images.forEach(image => {
  canvas.drawImage(image, 0, 0);
});
const output = new Output(canvas, 'composite.png');
output.addToQueue();
$ sips -j composite.js rect.png smile.png

composite.png

composite.png

テキストを描画する

sips コマンドには CoreText.framework を使ったテキスト描画の機能も Canvas API の一部として提供されています。

text.js
const canvas = new Canvas(100, 100);
canvas.filllStyle = 'black';
canvas.fillRect(0, 0, canvas.width, canvas.height);
canvas.font = '42pt Futura';
canvas.textAlign = 'center';
canvas.textBaseline = 'middle';
canvas.fillStyle = 'skyblue';
canvas.fillText('TEXT', canvas.width / 2, canvas.height / 2);
const output = new Output(canvas, 'text.png');
output.addToQueue();
$ sips -j text.js

text.png

実装されていない機能

ここまで紹介した機能で十分高度な画像編集ができそうですが、一部 Canvas API に比べて動作が異なる機能があります。

例えば、Canvas API の CanvasRenderingContext2D.createPattern()sips だと Canvas.createPattern() にあたり、ここまでは実装されているものの、作成したオブジェクトを利用する API が実装されていないようです。
通常は canvas.fillStyle などに渡すのですが、canvas.fillStyle = canvas.createPattern(...) では渡せず、sips バイナリの中にもそのような処理は実装されていないように見えます。

まとめ

これまで ImageMagick などで行っていた画像処理が macOS 標準コマンドでできるようになりました。
API もブラウザの API をよく再現していて知識を使いまわせるので、これから使っていくと良さそうです。

おまけ : 非公式 API リファレンス

公式の manpage にも簡単な使い方が載っていますが、網羅的ではないため Hopper Disassember を使って sips コマンドの中で実装されている API を解析してみました。
よく使いそうな API は実際に試して動作を確認し、typedoc でドキュメントを作成しました。
以下に置いておいたので、もしよければ参考にしてください。

  1. Catalina でも manpages に -j --js オプションの記載はあり一見できそうですが、クラッシュしてしまい動作を確認できなかったためバグレポートを起票しています https://openradar.appspot.com/radar?id=5061043655016448

24
18
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
24
18

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?