NodObjCを使ってNode.jsでPasteboardの画像データを取得する
Node.jsでスクリーンショットを扱いたいことがあって、外部コマンドscreencaptureを起動してファイルを生成してそれを読み込むという処理をしていました。
ファイルの生成場所にはRAMディスクを使うなど高速化を図っていたのですが、やはりオーバーヘッドがあるようで、Pasteboardから直接コピーしてみたいと思いました。(screencaptureにはファイルではなくPasteboardにコピーするというオプションがあります)
早速ですが、以下がその関数実装です。
const $ = require('nodobjc'); const ref = require('ref'); $.framework('Foundation'); $.framework('AppKit'); const PASTEBOARD = $.NSPasteboard('generalPasteboard'); const CLASS_ARRAY = $.NSArray('arrayWithObject', $.NSImage('class')); // $.NSImage('class')は何度も呼び出すとSegmentation faultを起こすので注意。なぜかはよくわからない const EMPTY_OPTIONS = $.NSDictionary('dictionary'); function getNSFileType(format) { switch (format) { case 'tiff': return $.NSTIFFFileType; case 'bmp': return $.NSBMPFileType; case 'gif': return $.NSGIFFileType; case 'jpg': return $.NSJPEGFileType; case 'png': return $.NSPNGFileType; case 'jp2': return $.NSJPEG2000FileType; default: throw new Error('unsupported format'); } } function getImageFromPasteboard(format) { const pool = $.NSAutoreleasePool('new'); let result = null; if (PASTEBOARD('canReadObjectForClasses', CLASS_ARRAY, 'options', EMPTY_OPTIONS)) { const nsimage = PASTEBOARD('readObjectsForClasses', CLASS_ARRAY, 'options', EMPTY_OPTIONS)('objectAtIndex', 0); const rep = $.NSBitmapImageRep('imageRepWithData', nsimage('TIFFRepresentation')); const data = rep('representationUsingType', getNSFileType(format), 'properties', null); result = new Buffer(ref.reinterpret(data('bytes'), data('length'))); // poolをdrainするにはコピーが必要 } pool('drain'); return result; } exports.getImageFromPasteboard = getImageFromPasteboard;
結果ですが、screencaptureでPNGフォーマットで保存していた場合と比べて、PasteboardでBMPを取り出すと倍ぐらい速くなりました。TIFFフォーマットで取り出すのが一番速そうですが、Node.js側でTIFFフォーマットを扱ういいライブラリがなくBMPにしました。
今、screencaptureのマニュアルを読むと、保存フォーマットにBMPも選べるらしく、BMPで保存していたらどうだったか気になります。RAMディスクのファイル読み書きよりPNGのエンコード、デコードが重かったのかもしれません。