<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[てくにかるむ]]></title><description><![CDATA[「エラーをなくすことは非常に有益で時には新しい真実や事実を作り上げるよりも勝る」
ー チャールズ・ダーウィン]]></description><link>http://multix.jp/</link><generator>Ghost 0.6</generator><lastBuildDate>Fri, 24 Apr 2026 09:29:07 GMT</lastBuildDate><atom:link href="http://multix.jp/tag/perl/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[ModefierKeyを取得する]]></title><description><![CDATA[<p><a href="https://ghost.multix.jp/dmm-playing-viewer/">艦板-KanPan-</a>では起動時にShiftキー/Ctrlキーの押し下げ状態を見てキャッシュやCookieを消去しつつ起動できたりする。この動作はもともとOptionキーを押しながらクリックしたり実行したりすると振る舞いが変わるiTunesやAirMac Utilityに触発された小技だ・・・が、ではどうやってこれらのModefierKey状態を取得する？</p>

<ul class="index"></ul>

<hr>

<h4 id="osx">OSXの場合</h4>

<p>OSXの場合、AppKit/Frameworkの<code>NSEvent-&gt;modifierFlags</code>を利用することでModefierKeyのビットフラグ値<sup id="fnref:1"><a href="http://multix.jp/get-modefierkey-propety/#fn:1" rel="footnote">1</a></sup>を取得することが出来る。OSX付属のPerlやPythonにはAppKitのバインディングが標準でインストールされているため、例えばPerlでは以下のようにすればビットフラグ値を標準出力へ返すことが出来る。コメント他を除けば実質5行だ。</p>

<pre><code class="language-brush:perl gutter:true title:modefierkey.pl">#!/usr/bin/perl

=comment=
enum {  
   NSAlphaShiftKeyMask  = 1 &lt;&lt; 16,
   NSShiftKeyMask       = 1 &lt;&lt; 17,
   NSControlKeyMask     = 1 &lt;&lt; 18,
   NSAlternateKeyMask   = 1 &lt;&lt; 19,
   NSCommandKeyMask     = 1 &lt;&lt; 20,</code></pre>]]></description><link>http://multix.jp/get-modefierkey-propety/</link><guid isPermaLink="false">9097cc8e-f724-4a61-8eae-f8c98cafd3f7</guid><category><![CDATA[てくにかるむ]]></category><category><![CDATA[Windows]]></category><category><![CDATA[Machintosh]]></category><category><![CDATA[Perl]]></category><dc:creator><![CDATA[朝日薫]]></dc:creator><pubDate>Thu, 07 May 2015 08:17:04 GMT</pubDate><content:encoded><![CDATA[<p><a href="https://ghost.multix.jp/dmm-playing-viewer/">艦板-KanPan-</a>では起動時にShiftキー/Ctrlキーの押し下げ状態を見てキャッシュやCookieを消去しつつ起動できたりする。この動作はもともとOptionキーを押しながらクリックしたり実行したりすると振る舞いが変わるiTunesやAirMac Utilityに触発された小技だ・・・が、ではどうやってこれらのModefierKey状態を取得する？</p>

<ul class="index"></ul>

<hr>

<h4 id="osx">OSXの場合</h4>

<p>OSXの場合、AppKit/Frameworkの<code>NSEvent-&gt;modifierFlags</code>を利用することでModefierKeyのビットフラグ値<sup id="fnref:1"><a href="http://multix.jp/get-modefierkey-propety/#fn:1" rel="footnote">1</a></sup>を取得することが出来る。OSX付属のPerlやPythonにはAppKitのバインディングが標準でインストールされているため、例えばPerlでは以下のようにすればビットフラグ値を標準出力へ返すことが出来る。コメント他を除けば実質5行だ。</p>

<pre><code class="language-brush:perl gutter:true title:modefierkey.pl">#!/usr/bin/perl

=comment=
enum {  
   NSAlphaShiftKeyMask  = 1 &lt;&lt; 16,
   NSShiftKeyMask       = 1 &lt;&lt; 17,
   NSControlKeyMask     = 1 &lt;&lt; 18,
   NSAlternateKeyMask   = 1 &lt;&lt; 19,
   NSCommandKeyMask     = 1 &lt;&lt; 20,
   NSNumericPadKeyMask  = 1 &lt;&lt; 21,
   NSHelpKeyMask        = 1 &lt;&lt; 22,
   NSFunctionKeyMask    = 1 &lt;&lt; 23,
   NSDeviceIndependentModifierFlagsMask  = 0xffff0000U 
};
=cut=

use strict;  
use warnings;  
use Foundation;

@NSEvent::ISA = qw(PerlObjCBridge);

my $AppKit = '/System/Library/Frameworks/AppKit.framework';  
NSBundle-&gt;bundleWithPath_($AppKit)-&gt;load;

print +(NSEvent-&gt;modifierFlags);  
exit 0;

__END__  
</code></pre>

<p>Bashスクリプト等からModefierKey状態を扱いたい場合はこのスクリプトコマンドを呼び出せば充分用が足りる。</p>

<pre><code class="language-brush:bash title:ビット列を16進数表示する">#/bin/bash
printf '%x¥n' $(modefierkey.pl)  
</code></pre>

<pre><code class="language-brush:bash title:ビットを判定する">#/bin/bash
(($(modefierkey.pl) &amp;&amp; 2**17)) &amp;&amp; echo ShiftDown || echo ShiftUp
</code></pre>

<hr>

<h4 id="windows">Windowsの場合</h4>

<p>前述のコードを Windows Visual Basic<sup id="fnref:2"><a href="http://multix.jp/get-modefierkey-propety/#fn:2" rel="footnote">2</a></sup> に転写すると次のようになる。OSXで言うcommandキー、fnキーに相当するものはないが、代わりにScrollLockキーを取得することが出来る。</p>

<pre><code class="language-brush:vb gutter:true title:modefierkey.vb">Module Module1

    Sub Main()

        'OSX Cocoa
        'enum {
        '   NSAlphaShiftKeyMask  = 1 &lt;&lt; 16,
        '   NSShiftKeyMask       = 1 &lt;&lt; 17,
        '   NSControlKeyMask     = 1 &lt;&lt; 18,
        '   NSAlternateKeyMask   = 1 &lt;&lt; 19,
        '   NSCommandKeyMask     = 1 &lt;&lt; 20,
        '   NSNumericPadKeyMask  = 1 &lt;&lt; 21,
        '   NSHelpKeyMask        = 1 &lt;&lt; 22,
        '   NSFunctionKeyMask    = 1 &lt;&lt; 23,
        '   NSDeviceIndependentModifierFlagsMask  = 0xffff0000U 
        '};

        Dim intMask As Integer = 0

        If My.Computer.Keyboard.CapsLock Then
            intMask += 1 &lt;&lt; 16
        End If
        If My.Computer.Keyboard.ShiftKeyDown Then
            intMask += 1 &lt;&lt; 17
        End If
        If My.Computer.Keyboard.CtrlKeyDown Then
            intMask += 1 &lt;&lt; 18
        End If
        If My.Computer.Keyboard.AltKeyDown Then
            intMask += 1 &lt;&lt; 19
        End If
        If My.Computer.Keyboard.NumLock Then
            intMask += 1 &lt;&lt; 21
        End If
        If My.Computer.Keyboard.ScrollLock Then
            intMask += 1 &lt;&lt; 24
        End If

        Console.Write(intMask)
    End Sub

End Module  
</code></pre>

<hr>

<h4 id="nwjs">Nw.jsの場合</h4>

<p>そもそもコンソールプラットフォームであるNode.jsではModefierKeyを取得する手段は用意されていない<sup id="fnref:3"><a href="http://multix.jp/get-modefierkey-propety/#fn:3" rel="footnote">3</a></sup>が、Nw.jsではGUIウィンドウが存在するのでそちらのwindow.eventからAlt/Ctrl/Shiftの3種類のキー状態真偽値を取得することが出来る。ChromiumベースのGUIプラットフォームなのだからこれは出来て当然だ。</p>

<pre><code class="language-brush:javascript">&amp;lt;script language="javascript"&amp;gt;
switch (true) {  
  case window.event.altKey   : console.log('altKey');   break;
  case window.event.ctrlKey  : console.log('ctrlKey');  break;
  case window.event.shiftKey : console.log('shiftKey'); break;
}
&amp;lt;/script&amp;gt;
</code></pre>

<p>ただし注意が必要なのは、ウィンドウ本体を示すwindowグローバルオブジェクトはGUI内の<code>&lt;script&gt;</code>タグでロードされたコードのほうにしか継承されていない点<sup id="fnref:4"><a href="http://multix.jp/get-modefierkey-propety/#fn:4" rel="footnote">4</a></sup>だ。つまりpackage.jsonの<code>node-main</code>でバックグラウンドロードされたNode.jsネイティブなコードの方からは直接使えない。また当然のことながらウィンドウ表示前・初期化前にはこれらのキー状態を取得出来ないという制約もある。故に「Altキーを押しながらドロップダウンメニューを開いた場合は内容をかえる」といった使いドコロになる。</p>

<hr>

<h4 id="">艦板ではどうしたか</h4>

<p>艦板では結局GUI初期化前にModefierKey状態をしたかったので、外部コマンドを <code>child_process.execSync()</code> で呼び出して結果を得るようになっている。ちなみにこの時の動作はWindows版の場合；</p>

<ul>
<li>ModefierKeyを調べて押されていなければそのまま起動継続する</li>
<li>ModefierKeyが押されていた場合でも、キャッシュクリア済であったならそのまま起動継続する</li>
<li>キャッシュクリアされていなかったら外部キャッシュクリアコードにexecして自分は終了する</li>
<li>外部キャッシュクリアコード<sup id="fnref:5"><a href="http://multix.jp/get-modefierkey-propety/#fn:5" rel="footnote">5</a></sup>は自分でModefierKeyを再確認し、キャッシュフォルダを掃除したあとに艦板をexecして自身は終了する</li>
</ul>

<p>・・・などと存外面倒なことをやっている。</p>

<hr>

<div class="footnotes"><ol><li class="footnote" id="fn:1"><p>キーとビット値の対応とcocoaでの名称はコメントを参照して頂きたいが、AlphaShiftKeyはcaps/CapsLockキー、AlternateKeyはalt/option/appleキーのことである。 <a href="http://multix.jp/get-modefierkey-propety/#fnref:1" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:2"><p>要コンパイル。VS Express版だと for Desktopエディションが必要。 <a href="http://multix.jp/get-modefierkey-propety/#fnref:2" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:3"><p>プラットフォーム別に全く異なる実装をビルドする必要があるし、そもそもNode.jsの主戦場はCLIやサーバホスティング用途なので、ModefierKeyを見る要件は殆ど無い。なのでこれが必要になるのはNw.jsのようなGUIフレームワークと組み合わせた場合に概ね限られるといえる。 <a href="http://multix.jp/get-modefierkey-propety/#fnref:3" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:4"><p>親ウィンドウが生成される前から動き出すインスタンスなので継承しようがないとも言える。だが単に継承されていないから使えないだけなので、知ってるほう(GUI)が知らないほう(Native)に module.exports 経由で教えてやれば利用できる。同様にNw.jsのguiオブジェクトも教えてやればバックグラウンド側から各種ウィンドウ操作ができる。 <a href="http://multix.jp/get-modefierkey-propety/#fnref:4" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:5"><p>Windows版同梱のWinRapper.exeがそれ。OSXのほうは Info.plistに登録された Bashシェル (launch.sh) が modefierkey.pl を呼び出し、必要ならキャッシュ削除処理をしてから Nw.js を起動する段取りだからずっとシンプルだ。 <a href="http://multix.jp/get-modefierkey-propety/#fnref:5" title="return to article">↩</a></p></li></ol></div>]]></content:encoded></item><item><title><![CDATA[OSX用アイコンファイルをWindows上で作る]]></title><description><![CDATA[<p>Macintosh OSX用アイコンセットをWindows上で作ってしまう方法へ至る道。OSXの.icnsファイルフォーマットについて解説する。</p>

<ul class="index"></ul>

<hr>

<h4 id="">動機</h4>

<p>艦板-KanPan- を開発するとき、最初からMacintoshとWindows両方に対応すると決めていたが、その際両者でフォーマットが異なるアイコンリソースをどうするかという問題があった。OSXでは<mark>.icns</mark>、Windowsでは<mark>.ico</mark>というファイル形式が使われる。それぞれを他方のプラットフォーム上のコマンドラインから作成できれば、make作業を一方の上で完結できる。とりあえず資料が豊富な.icoは放っておいて.icnsについて調べてみたところ意外と簡単な方法で作成できることが分かった。</p>

<hr>

<h4 id="iconutil">iconutil ユーティリティ</h4>

<p>OSX添付の純正アイコンリソース作成ユーティリティは<code>iconutil</code>という。このCLIコマンドは以下のように使用する。</p>

<ul>
<li>以下の10個のフルカラーPNG画像(RGBA)を格納した<code>foo.iconset</code>というフォルダを作成する。アイコンファイル名は厳密にこの通りであり、フォルダ名の<mark>.iconset</mark>という拡張子もこの通りでなければならない。</li>
</ul>

<pre><code class="language-table title:iconutilが要求する画像ファイル">|ファイル名|画像サイズ|
|:-|:-:|
|icon_16x16.png|16x16|
|icon_16x16@2x.png|32x32|
|icon_32x32.</code></pre>]]></description><link>http://multix.jp/create-osx-icns-for-windows/</link><guid isPermaLink="false">f88729c6-221d-4514-bf83-8a8ffb519052</guid><category><![CDATA[Windows]]></category><category><![CDATA[Machintosh]]></category><category><![CDATA[めもらんだむ]]></category><category><![CDATA[Perl]]></category><dc:creator><![CDATA[朝日薫]]></dc:creator><pubDate>Thu, 07 May 2015 08:11:59 GMT</pubDate><content:encoded><![CDATA[<p>Macintosh OSX用アイコンセットをWindows上で作ってしまう方法へ至る道。OSXの.icnsファイルフォーマットについて解説する。</p>

<ul class="index"></ul>

<hr>

<h4 id="">動機</h4>

<p>艦板-KanPan- を開発するとき、最初からMacintoshとWindows両方に対応すると決めていたが、その際両者でフォーマットが異なるアイコンリソースをどうするかという問題があった。OSXでは<mark>.icns</mark>、Windowsでは<mark>.ico</mark>というファイル形式が使われる。それぞれを他方のプラットフォーム上のコマンドラインから作成できれば、make作業を一方の上で完結できる。とりあえず資料が豊富な.icoは放っておいて.icnsについて調べてみたところ意外と簡単な方法で作成できることが分かった。</p>

<hr>

<h4 id="iconutil">iconutil ユーティリティ</h4>

<p>OSX添付の純正アイコンリソース作成ユーティリティは<code>iconutil</code>という。このCLIコマンドは以下のように使用する。</p>

<ul>
<li>以下の10個のフルカラーPNG画像(RGBA)を格納した<code>foo.iconset</code>というフォルダを作成する。アイコンファイル名は厳密にこの通りであり、フォルダ名の<mark>.iconset</mark>という拡張子もこの通りでなければならない。</li>
</ul>

<pre><code class="language-table title:iconutilが要求する画像ファイル">|ファイル名|画像サイズ|
|:-|:-:|
|icon_16x16.png|16x16|
|icon_16x16@2x.png|32x32|
|icon_32x32.png|32x32|
|icon_32x32@2x.png|64x64|
|icon_128x128.png|128x128|
|icon_128x128@2x.png|256x256|
|icon_256x256.png|256x256|
|icon_256x256@2x.png|512x512|
|icon_512x512.png|512x512|
|icon_512x512@2x.png|1024x1024|
</code></pre>

<ul>
<li>ターミナルで以下のように実行するとカレントディレクトリに<code>foo.icns</code>というアイコンファイルが作成される。</li>
</ul>

<pre><code class="language-brush:plain">iconutil -c icns foo.iconset  
</code></pre>

<p>10個のファイル（画像サイズ自体は7種類）を用意しろというのは御無体だが、これはまあ原画をSVG形式で作成して所定サイズとファイル名のPNG画像にコンバートすればなんとかなる。ともかく出来上がった.icnsファイルを調べてみると次のようになっていた。</p>

<hr>

<h4 id="icns">ICNSファイルフォーマット</h4>

<p>ICNSファイルは次のような構造になっている</p>

<pre><code class="language-table table:ICNSファイル構造">|:-:|
|ICNSファイルヘッダ|
|アイコンヘッダ|
|アイコンデータ|
|アイコンヘッダ|
|アイコンデータ|
|:|
</code></pre>

<p>まずファイル先頭にICNSファイルヘッダがあり、その後ろにアイコンヘッダとアイコンデータの組（ブロック）が任意個数続く。</p>

<hr>

<h4 id="icns">ICNSファイルヘッダ</h4>

<p>ICNSファイルヘッダの大きさは8バイトで、以下の2フィールドを持つ。</p>

<pre><code class="language-table table:ICNSファイルヘッダ">|Offset|Length|内容|
|-:|-:|:-|
|0|4|ファイル識別子`ICNS` (リテラルバイト文字列 0x69 0x63 0x6e 0x73)|
|4|4|ICNSファイル全体のバイナリサイズ (MSB First/Big Endian)|
</code></pre>

<p>先頭の4バイトがファイル識別子なのはMachintosh系データフォークの伝統だ。</p>

<hr>

<h4 id="">アイコンヘッダ</h4>

<p>アイコンヘッダの大きさは8バイトで、以下の2フィールドを持つ。</p>

<pre><code class="language-table title:アイコンブロックヘッダ">|Offset|Length|内容|
|-:|-:|:-|
|0|4|アイコンタイプ識別子（リテラルバイト文字列）|
|4|4|ブロック長(アイコンヘッダ＋アイコンデータの合計バイト長) (MSB First/Big Endian)|
</code></pre>

<p>アイコンタイプ識別子もまた４バイトのリテラル文字列で、これがアイコンデータの画像表示サイズと画像フォーマットとを示している。続くフィールドは後続するアイコンデータのバイナリサイズ+8に一致する。</p>

<hr>

<h4 id="">アイコンデータ</h4>

<p>これのデータフォーマットはアイコンタイプ識別子によって規定されるが、iconutilが作成するファイルの場合は<strong>PNG画像ファイルデータ</strong>そのままか<strong>RGB生データ</strong>か<strong>マスクデータ</strong>である。とにかくPNG形式のアイコン識別子が付いていたならPNGファイルがそのまま埋め込まれているので、何も難しいことはない。</p>

<hr>

<h4 id="">アイコンタイプ識別子一覧</h4>

<p>iconutilが作成するアイコンファイルに含まれるアイコン識別子の一覧を次に示す。なお識別子のレターケースは区別される。</p>

<pre><code class="language-table title:iconutilが生成するアイコンタイプ識別子一覧">|Type|Length|Size|MacOS|詳細|
|:-:|:-:|:-:|:-:|:-|
|ic07|可変|128x128|10.7+|128x128 PNG/JPEG2000|
|ic08|可変|256x256|10.5+|256x256 PNG/JPEG2000|
|ic09|可変|512x512|10.5+|512x512 PNG/JPEG2000|
|ic10|可変|1024x1024|10.7+|1024x1024 PNG/JPEG2000 (1024x1024, 512x512@2x)|
|ic11|可変|32x32|10.8+|32x32 PNG/JPEG2000 (16x16@2x)|
|ic12|可変|64x64|10.8+|64x64 PNG/JPEG2000 (32x32@2x)|
|ic13|可変|256x256|10.8+|256x256 PNG/JPEG2000 (128x128@2x)|
|ic14|可変|512x512|10.8+|512x512 PNG/JPEG2000 (256x256@2x)|
|il32|可変|32x32|8.5+|32x32 24bit TrueColor RGB|
|is32|可変|16x16|8.5+|16x16 24bit TrueColor RGB|
|l8mk|1,024|32x32|8.5+|32x32 8bit MASK|
|s8mk|256|16x16|8.5+|16x16 8bit MASK|
</code></pre>

<p>ic11からic14はRetinaディスプレイ用に追加されたもので、それぞれその半分の画像サイズのアイコンと対応している。ic10はRetina対応であると同時に<strong>OSX10.7以降のデフォルトアイコン</strong>サイズである。極端な話、このic10さえあれば、OSX10.7以降のファインダー＆ドック表示はすべて可能になる。</p>

<p>一方で以下のアイコンタイプも10.7で登場したのだが iconutil では生成されないようだ。代わりに非Retina用等倍の16x16と32x32についてはOSX10.7のものではなくMaxOS8.5のアイコンフォーマットにコンバートして収録されている。これは後方互換性を維持するためだろう。</p>

<pre><code class="language-table title:その他のアイコン識別子一覧">|Type|Length|Size|MacOS|詳細|
|:-:|:-:|:-:|:-:|:-|
|icp4|可変|16x16|10.7+|16x16 PNG/JPEG2000|
|icp5|可変|32x32|10.7+|32x32 PNG/JPEG2000|
|icp6|可変|64x64|10.7+|64x64 PNG/JPEG2000|
</code></pre>

<hr>

<h4 id="macos85">MacOS8.5形式アイコンフォーマット</h4>

<p>il32とl8mk、is32とs8mkはそれぞれアイコンカラーデータとアイコンマスクデータのペアとして使用される。常にペアであるため個々に単独の画像ファイルとしては現れず、少なくとも2ブロックから成る.icnsファイルとして存在する。</p>

<p>アイコンカラーデータは1画素あたりRGB24bitだが、RGBそれぞれ別の(8bpp)プレーン毎に、連続する左上原点ビットマップデータ列をPackBitsしてまとめられている。</p>

<pre><code class="language-table title:il32アイコンブロック (32x32画素カラー)">|Offset|Length|詳細|
|-:|-:|:-|
|0|4|アイコンタイプ識別子`il32` (0x69 0x6c 0x33 0x32)|
|4|4|ブロック長 (MSB First)|
|8|可変|Rプレーン1,024バイトのPackBitsデータ|
|?|可変|Gプレーン1,024バイトのPackBitsデータ|
|?|可変|Bプレーン1,024バイトのPackBitsデータ|
</code></pre>

<pre><code class="language-table title:is32アイコンブロック (16x16画素カラー)">|Offset|Length|詳細|
|-:|-:|:-|
|0|4|アイコンタイプ識別子`is32` (0x69 0x73 0x33 0x32)|
|4|4|ブロック長 (MSB First)|
|8|可変|Rプレーン256バイトのPackBitsデータ|
|?|可変|Gプレーン256バイトのPackBitsデータ|
|?|可変|Bプレーン256バイトのPackBitsデータ|
</code></pre>

<p>アイコンマスクデータも同様に1画素あたり8bit(8bpp)の連続する左上原点ビットマップデータ列だが、無圧縮のベタデータとして定義されている。</p>

<p>これはPNG画像フォーマットのアルファチャンネルと実質同じもので、アイコン画像の背景デスクトップからの切り抜きとアンチエイリアス表現を実現する。 なお画像縦横サイズについてはアイコンタイプ識別子で一意に決定されるためデータブロック内には現れない。</p>

<pre><code class="language-table title:l8mkマスクブロック (32x32画素マスク)">|Offset|Length|詳細|
|-:|-:|:-|
|0|4|アイコンタイプ識別子`l8mk` (0x6c 0x38 0x6d 0x6b)|
|4|4|ブロック長 (MSB First)|
|8|1,024|MASKプレーンの8bppビットマップデータ|
</code></pre>

<pre><code class="language-table title:s8mkマスクブロック  (16x16画素マスク)">|Offset|Length|詳細|
|-:|-:|:-|
|0|4|アイコンタイプ識別子`s8mk` (0x73 0x38 0x6d 0x6b)|
|4|4|ブロック長 (MSB First)|
|8|256|MASKプレーンの8bppビットマップデータ|
</code></pre>

<hr>

<h4 id="packbits">PackBits連長圧縮アルゴリズム</h4>

<p>カラープレーンのビットマップは生データではなく、PackBitsと呼ばれる連長圧縮アルゴリズムでコンバートされている。これは古き良きMacintosh時代の、PICT (QuickDraw PICTure)ファイルフォーマットで用いられていたものだからとうぜん当時の資料を調べれば詳細を知ることが出来る。そのPackBitsデータ列は次のように解釈すれば元のビットマップデータへ復元・展開できる。</p>

<ul>
<li>ポインタ上の1バイトが128未満（MSBが0）であるならこのバイトが示す0〜127に1を加え、次のそのバイト数（1〜128バイト）を取り出してそのまま出力し、ポインタを進める。</li>
<li>ポインタ上の1バイトが128以上
（MSBが1）であるならこのバイトが示す128〜255から125を引き、取り出した次の1バイトをその回数（3〜130回）繰り返して出力し、ポインタを進める。</li>
</ul>

<p>このPackBitsの圧縮アルゴリズムを真面目に実装しようとするとそれなりに面倒だが抜け道はある。無圧縮でよしと妥協すれば、128バイト毎にバイトストリームを切り刻み、0x7Fを前置した129バイトブロックに整えて横流しすればすむのである。32x32画素プレーンと16x16画素プレーンのいずれも128バイトの整数倍バイト長なのだから全く不都合はない。32x32画素プレーンなら計8ブロック1,032バイト、16x16画素プレーンなら計2ブロック258バイトの固定長データとして処理してしまえる。</p>

<hr>

<h4 id="unosxicnspl">unosxicns.pl</h4>

<p>ここまで解ってしまえばあとはもう悩むことはない。<code>unosxicns.pl</code>は以上の情報をまとめて .icnsファイルから .png<sup id="fnref:1"><a href="http://multix.jp/create-osx-icns-for-windows/#fn:1" rel="footnote">1</a></sup>ファイルを抽出するように記述したものだ。ActivePerl(5.10+)<sup id="fnref:2"><a href="http://multix.jp/create-osx-icns-for-windows/#fn:2" rel="footnote">2</a></sup>と ImageMagick(PerlMagick)<sup id="fnref:3"><a href="http://multix.jp/create-osx-icns-for-windows/#fn:3" rel="footnote">3</a></sup>で動作する。OSX他でも MacPortsで同等の環境を揃えれば動作する。</p>

<p>使い方は引数に .icnsファイルを指定すると同名の .iconsetディレクトリを作成してその中に抽出した<code>icon_NNNxNNN.png</code>を生成、あるいは<code>-t</code>オプションを付加した場合は<code>アイコンタイプ.png</code>を生成する。前掲のアイコンタイプ以外は対応外として無視する。</p>

<pre><code class="language-brush:plain">unosxicns.pl -t foo.icns

ls foo.iconset  
ic07.png              ic08.png              ic09.png  
ic10.png              ic11.png              ic12.png  
ic13.png              ic14.png              il32.png  
is32.png  
</code></pre>

<pre><code class="language-brush:perl collapse:true gutter:true title:unosxicns.pl">#!/usr/bin/perl
# $Id: unosxicns.pl 122 2015-03-16 04:06:57Z askn $ ＵＴＦ８

use 5.010;  
use strict;  
use warnings;  
use File::Basename;  
use Image::Magick;  
use Getopt::Std;

my %iconset = (  
    ic07 =&gt; [128,  1],
    ic08 =&gt; [256,  1],
    ic09 =&gt; [512,  1],
    ic10 =&gt; [1024, 2],
    ic11 =&gt; [32,   2],
    ic12 =&gt; [64,   2],
    ic13 =&gt; [256,  2],
    ic14 =&gt; [512,  2],
    icp4 =&gt; [16,   1],
    icp5 =&gt; [32,   1],
    icp6 =&gt; [64,   1],
    is32 =&gt; [16,   0],
    il32 =&gt; [32,   0],
    s8mk =&gt; [16,   0],
    l8mk =&gt; [32,   0],
);

our($opt_t);  
getopts("t");

unless (scalar @ARGV) {  
    die "Usage: unosxicns.pl [-t] icon.icns [...]\n";
}

my %cache;  
my $dirname = dirname($0);  
foreach my $argv (@ARGV) {  
    foreach my $glob (glob $argv) {
        my $filename = $glob;
        my $basename = basename $filename, qw{.svg .png .ico .icns .icowin .iconset};
        my $folder = $basename . ".iconset";
        $filename = $basename . ".icns";
        next if $cache{$filename};
        die "file not found $filename\n" unless -f $filename;
        $cache{$filename} = 1;
        &amp;mkpict($filename, $folder);
    }
}
exit;

# アイコンブロックの抽出
sub mkpict {  
    my $filename = shift;
    my $folder = shift;
    my %maskset;
    mkdir $folder, 0777;
    my($FH, $GH, $buff);
    unless (open $FH, "&lt;", $filename) {
        die "Cant read input\n";
    }
    binmode $FH;

    # 先頭のアイコンヘッダ8バイトの取得
    die "Read error\n" unless sysread($FH, $buff, 8);
    my($filetype, $filesize) = unpack "A4N", $buff;
    die "Not ICNS format\n" unless $filetype eq "icns";
    die "File size unmatch\n" unless $filesize == -s $filename;
    say "$filename filesize $filesize";

    # 各アイコンブロックについて
    while (sysread $FH, $buff, 8) {
        my($set, $ext, $outname, $scale);

        # アイコンブロックヘッダ8バイト
        my($icontype, $blocksize) = unpack "A4N", $buff;
        my $readsize = $blocksize - 8;
        die "Format broken\n"
            if $readsize &lt; 1
                or $readsize != sysread $FH, $buff, $readsize;
        say "  type $icontype length $blocksize";

        # 未知のアイコンタイプ
        unless ($set = $iconset{$icontype}) {
            say "    undefined icon type";
            next;
        }

        # PNGまたはJPEG2000形式
        if ($scale = $set-&gt;[1]) {

            # PNGヘッダ確認
            $ext = ("\x89\x50\x4E\x47" eq substr $buff, 0, 4) ? "png" : "jp2";

            # -t付きの場合はアイコンタイプをファイル名にする
            if ($opt_t) {
                $outname = sprintf "%s.%s", $icontype, $ext;
            }

            # 倍密解像度の場合のファイル名
            elsif ($scale == 2) {
                $outname = sprintf "icon_%ux%u\@x2.%s",
                    $set-&gt;[0] &gt;&gt; 1, $set-&gt;[0] &gt;&gt; 1, $ext;
            }

            # 標準解像度のファイル名
            else  {
                $outname = sprintf "icon_%ux%u.%s",
                    $set-&gt;[0], $set-&gt;[0], $ext;
            }
            say "\t$outname size $readsize";

            # 画像データ書出
            unless (open $GH, "&gt;", "$folder/$outname") {
                die "Cant write output\n";
            }
            binmode $GH;
            print $GH $buff;
            close $GH;
            next;
        }

        # MacOS8.5 mask raw format
        if ($icontype =~ /(s8mk|l8mk)/) {
            die "Mask broken,  length missmatch\n"
                unless $set-&gt;[0] ** 2 == length $buff;
            $maskset{$icontype} = [unpack "C*", $buff];
        }

        # MacOS8.5 Color PackBits format
        if ($icontype =~ /(is32|il32)/) {
            my @data;
            my @input = unpack "C*", $buff;

            # PackBits unpack
            while (scalar @input) {
                my $code = shift @input;
                if ($code &lt; 128) {
                    push @data, splice @input, 0, $code + 1;
                }
                else {
                    my $next = shift @input;
                    push @data, ($next) x ($code - 125);
                }
            }
            die "PackBits broken,  length missmatch\n"
                unless $set-&gt;[0] ** 2 * 3 == scalar @data;
            $maskset{$icontype} = \@data;
        }
    }
    close $FH;
    &amp;mkpngicon(\%maskset, "il32", "l8mk", $folder);
    &amp;mkpngicon(\%maskset, "is32", "s8mk", $folder);
}

# RGBAからPNGファイルへの変換
sub mkpngicon {  
    my $maskset = shift;
    my $type = shift;
    my $color;
    return unless $color = $maskset-&gt;{$type};
    my $mask = shift;
    my $folder = shift;
    $mask = $maskset-&gt;{$mask};
    my $size = $iconset{$type}-&gt;[0];
    my $sqrt = $size ** 2;

    my $image = Image::Magick-&gt;new(magick=&gt;'png', size=&gt;"${size}x${size}", matte=&gt;"True");
    $image-&gt;Read("NULL:");

    for (my $y = 0; $y &lt; $size; $y++) {
        for (my $x = 0; $x &lt; $size; $x++) {
            my @pixcels;
            $pixcels[0] = $color-&gt;[$y * $size + $x]             / 255; # R
            $pixcels[1] = $color-&gt;[$y * $size + $x + $sqrt]     / 255;    # G
            $pixcels[2] = $color-&gt;[$y * $size + $x + $sqrt * 2] / 255;    # B
            $pixcels[3] = $mask ? 1.0 - $mask-&gt;[$y * $size + $x] / 255 : 0.0; # A (Opacity)
            my $err = $image-&gt;SetPixel(x=&gt;$x, y=&gt;$y, color=&gt;\@pixcels, channel=&gt;'RGBA', normalize=&gt;'True');
            say $err if $err;
        }
    }
    my $outname = sprintf "icon_%ux%u.png", $size, $size;

    # -t付きの場合はアイコンタイプをファイル名にする
    if ($opt_t) {
        $outname = sprintf "%s.png", $type;
    }

    say "\t$outname";
    my $err = $image-&gt;Write("$folder/$outname");
    die $err if $err;
}

1;  
__END__  
</code></pre>

<hr>

<h4 id="mkosxicnspl">mkosxicns.pl</h4>

<p>これでようやく本題に辿り着いた。<code>mkosxicns.pl</code> は<code>unosxicns.pl</code>とは逆に  .pngファイルから .icnsファイルを生成する。ActivePerl(5.10+)と ImageMagick(PerlMagick)の他、パスの通った場所に <code>PhantomJS</code><sup id="fnref:4"><a href="http://multix.jp/create-osx-icns-for-windows/#fn:4" rel="footnote">4</a></sup>がインストールされている必要がある。<code>iconrast.js</code>は <code>PhantomJS</code>スクリプトで <code>mkosxicns.pl</code>と同じ場所に置く。OSX他でもMacPortsで同等の環境を揃えれば動作する。</p>

<p>使い方は引数に<code>-b</code>オプションと .iconsetディレクトリを指定すると、iconutilと同等の動作を行って同名の .icnsファイルを生成する。</p>

<pre><code class="language-brush:plain">mkosxicns.pl -b foo.iconset  
</code></pre>

<p>もうひとつの使い方は<code>-m</code>オプションとSVGファイル<sup id="fnref:5"><a href="http://multix.jp/create-osx-icns-for-windows/#fn:5" rel="footnote">5</a></sup>を指定することで、iconutilが必要とする10個のPNGファイルを含む .iconsetディレクトリを自動的に生成する動作だ。</p>

<pre><code class="language-brush:plain">mkosxicns.pl -m foo.svg  
</code></pre>

<p>両者は<code>-mb</code>オプションを指定して実行することで同時に行うことが出来る。すなわち以下の場合 foo.svgファイルから foo.iconsetディレクトリを作成し foo.icnsファイルを生成する。</p>

<pre><code class="language-brush:plain">mkosxicns.pl -mb foo.svg  
</code></pre>

<pre><code class="language-brush:perl collapse:true gutter:true title:mkosxicns.pl">#!/usr/bin/perl
# $Id: mkosxicns.pl 122 2015-03-16 04:06:57Z askn $ ＵＴＦ８

use 5.010;  
use strict;  
use warnings;  
use File::Basename;  
use Image::Magick;  
use Getopt::Std;

my @fileset = (  
    [128,  "icon_128x128.png",     "ic07"],
    [256,  "icon_256x256.png",     "ic08"],
    [512,  "icon_512x512.png",     "ic09"],
    [1024, "icon_512x512\@2x.png", "ic10"],
    [32,   "icon_16x16\@2x.png",   "ic11"],
    [64,   "icon_32x32\@2x.png",   "ic12"],
    [256,  "icon_128x128\@2x.png", "ic13"],
    [512,  "icon_256x256\@2x.png", "ic14"],
    [32,   "icon_32x32.png",       "icp5"],
    [16,   "icon_16x16.png",       "icp4"],
);

our($opt_m, $opt_b);  
getopts("mb");  
unless (($opt_m || $opt_b) &amp;&amp; 1 == scalar @ARGV) {  
    die "Usage: mkosxicns.pl -mb icon[.svg] [...]\n";
}

my %cache;  
my $dirname = dirname($0);  
my $phantomjs = "phantomjs";  
my $rasterize = $dirname . "/iconrast.js";

foreach my $argv (@ARGV) {  
    foreach my $glob (glob $argv) {
        my $filename = $glob;
        my $basename = basename $filename, qw{.svg .png .ico .icns .icowin .iconset};
        my $folder = $basename . ".iconset";
        if ($opt_m) {
            $filename = $basename . ".svg";
            die "file not found $filename\n" unless -f $filename;
        }
        next if $cache{$filename};
        say $filename;
        $cache{$filename} = 1;
        &amp;mkpng($filename, $folder) if $opt_m;
        &amp;mkico($basename, $folder) if $opt_b;
    }
}
exit;

# SVGからPNGへの変換
sub mkpng {  
    my $filename = shift;
    my $folder = shift;
    mkdir $folder, 0777;
    foreach my $set (@fileset) {
        printf "%s/%s\n", $folder, $set-&gt;[1];
        system $phantomjs , $rasterize, $filename, $set-&gt;[0] , $folder . "/" . $set-&gt;[1];
    }
    say "complete";
}

# ICNSファイル作成
my @maskset;  
sub mkico {  
    my $basename = shift;
    my $folder = shift;
    my $FH;
    unless (open $FH, "&gt;", $basename . ".icns") {
        die "Cant write output\n";
    }
    binmode $FH;

    my $total = 0;
    my $data = "";
    my @iconset;
    foreach my $set (@fileset) {

        # PNGファイル読込
        my $path = $folder . "/" . $set-&gt;[1];
        my $blob;
        if (open my $FI, "&lt;", $path) {
            binmode $FI;
            $blob = join '', &lt;$FI&gt;;
            close $FI;
        }
        unless (scalar $blob) {
            die "Cant read $path\n";
        }

        # MacOS8.5形式への変換
        if ($set-&gt;[2] eq "icp4") {
            &amp;buildmaskset($path, "is32", "s8mk", 16, 1, 3);
            next;
        }
        elsif ($set-&gt;[2] eq "icp5") {
            &amp;buildmaskset($path, "il32", "l8mk", 32, 0, 2);
            next;
        }

        # アイコンタイプヘッダ8バイトの付加
        my $length = length $blob;
        $data .= pack("A4N", $set-&gt;[2], $length + 8);
        $data .= $blob;
        printf "%u %s\n", length($data), $path;
    }

    # 全アイコンブロック連結
    $data .= join "", @maskset if scalar @maskset;

    # アイコンヘッダ8バイトの付加
    my $output = pack("A4N", "icns", length($data) + 8);
       $output .= $data;
    print $FH $output;
    close $FH;
    printf "%u complete", length($output);
}

# 24bitカラーと8bitマスク作成
sub buildmaskset {  
    my $path = shift;
    my $name = shift;
    my $mask = shift;
    my $size = shift;
    my $idxc = shift;
    my $idxm = shift;

    # PNGファイルを読み込んで全ピクセルを取得
    my $image = Image::Magick-&gt;new(magick=&gt;'png');
       $image-&gt;Read($path);
       $image-&gt;Set(magick=&gt;'png');
    my @input = $image-&gt;GetPixels(x=&gt;0, y=&gt;0,
        width=&gt;$size, height=&gt;$size, map=&gt;"RGBA", normalize=&gt;"true");

    # カラープレーン・マスクプレーンに分割
    my @plane = ([], [], [], []);
    for (my $y = 0; $y &lt; $size; $y++) {
        for (my $x = 0; $x &lt; $size; $x++) {
            for (my $i = 0; $i &lt; 4; $i++) {
                push @{$plane[$i]}, int(255 * shift(@input));
            }
        }
    }

    # カラープレーンをPackBits
    my @encode = ("", "", "", pack("C*", @{$plane[3]}));
    for (my $i = 0; $i &lt; 3; $i++) {
        my $buff = pack "C*", @{$plane[$i]};
        while (length $buff) {
            $encode[$i] .= chr(127) . substr($buff, 0, 128, "");
        }
    }

    # 各データを連結してヘッダ8バイトを付加
    my $buff = join "", @encode[0..2];
    $maskset[$idxc] = pack("A4N", $name, length($buff) + 8) . $buff;
    $maskset[$idxm] = pack("A4N", $mask, length($encode[3]) + 8) . $encode[3];
}

1;  
__END__  
</code></pre>

<pre><code class="language-brush:js collapse:true gutter:true title:iconrast.js">// $Id: iconrast.js 121 2015-03-13 10:01:44Z askn $

var page = require('webpage').create(),  
    system = require('system'),
    address, output, size;

if (system.args.length != 4) {  
    console.log('Usage: rasterize.js URL size filename');
    phantom.exit(1);
}
else {  
    address = system.args[1];
    size = system.args[2];
    output = system.args[3];
    page.viewportSize = { width: size, height: size };
    page.open(address, function (status) {
        if (status !== 'success') {
            console.log('Unable to load the address!');
            phantom.exit();
        } else {
            window.setTimeout(function () {
                page.render(output);
                phantom.exit();
            }, 200);
        }
    });
}
// End of script
</code></pre>

<hr>

<h4 id="">ダウンロード</h4>

<p>本項で述べた mkosxicns.pl unosxicns.pl iconrast.js の3点セット。</p>

<p><a href="https://secure.multix.jp/download/mkosxicns.zip">mkosxicns.zip</a></p>

<hr>

<div class="footnotes"><ol><li class="footnote" id="fn:1"><p>もしPNGヘッダではないアイコンブロックであった場合は JPEG2000と仮定して .jp2ファイルで出力する。 <a href="http://multix.jp/create-osx-icns-for-windows/#fnref:1" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:2"><p>ActivePerl <a href="http://www.activestate.com/activeperl">http://www.activestate.com/activeperl</a> <a href="http://multix.jp/create-osx-icns-for-windows/#fnref:2" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:3"><p>ImageMagick <a href="http://www.imagemagick.org">http://www.imagemagick.org</a><br>・・・正直使用している機能に対してオーバースペックなのだがMachintosh/Windows両方で動作する画像操作ライブラリは希少なので困ってしまう。 <a href="http://multix.jp/create-osx-icns-for-windows/#fnref:3" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:4"><p>PhantomJS <a href="http://phantomjs.org">http://phantomjs.org</a><br>・・・CLIで動作するWebkitヘッドレスブラウザ。ここではSVGファイルから任意画素数の透過PNG画像を生成するのに使用している。 <a href="http://multix.jp/create-osx-icns-for-windows/#fnref:4" title="return to article">↩</a></p></li>

<li class="footnote" id="fn:5"><p>SVGファイルはWidth値とHeight値が共に無指定（つまり規定値の'100%'）である必要がある。固定値が指定されていると意図した大きさのアイコン画像とはならない。 <a href="http://multix.jp/create-osx-icns-for-windows/#fnref:5" title="return to article">↩</a></p></li></ol></div>]]></content:encoded></item></channel></rss>