AlteryxのTipsを不定期にお届けしている週間(?)AlteryxTipsです。
今回は、レポートスニペットとして取り込んだ画像から、縦幅、横幅のピクセル数を取得してみたいと思います。どこかで役に立つ、、、とも思えないですが。
おさらい
Alteryx内で、レポートスニペットの画像は、ローカルファイルへのパスかもしくはBase64エンコーディングされたテキストデータとして保持します。これは、前回のTipsをご覧ください。
なお、ローカルファイルのパスの場合は、そこから直接ピクセルサイズを取得することはできません。パスから直接Blob入力ツール経由でBlobとして取り込むか、レポートテキストツールにエキスパートモードで貼り付け、ファイルの実体をDesingerに取り込む必要があります。
いずれにしても、Base64エンコードされているデータか、バイナリデータがあれば、そこからフォーマットを解析してピクセルサイズを取得することができます。
バイナリデータは、結局は特定の画像フォーマットのデータなので、決まったところに横幅、縦幅が格納されています(もしかしたら格納されていないファイルフォーマットがあるかもしれませんが、その場合はあきらめましょう)。
ただし、読込み時に画像の幅を固定するオプションを使っているのであれば、HTMLタグを調べるだけで最終的な横幅は特定できます。
制限事項
ピクセルサイズは、元画像のピクセルサイズになります。画像ツールで読込み時に指定したサイズは、HTMLタグで実現しているためです。つまり、実体のデータのサイズは何も変わっていない、ということになります。ただし、出力時にHTMLタグを元にリサイズはされているのでご安心ください。
画像からピクセルサイズを取得する
画像ツールが取り込んだ画像はどうなっていますか?
まず、画像ツールが取り込んだ画像はどのようになっているのでしょうか?前回のTipsでも軽く触れましたが、以下のようになっています。
「ランタイムでディスクから画像を取得する」の場合:
<img src="C:\temp\alteryx_icons\Input Data.png" width="100" />
※「固定幅を使用する」を「はい」にして、画像の幅を「100」にした場合
「ワークフローに静止画像を保存する」の場合:
<encsection id="206d6ca59982edf70dfdbf5f2a5f344d.PNG" ftype="PNG" src="206d6ca59982edf70dfdbf5f2a5f344d" olen="2654" enclen="3632">iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAK...(以下エンコードデータが続く)</encsection><img src="206d6ca59982edf70dfdbf5f2a5f344d.PNG" width="100" />
※「固定幅を使用する」を「はい」にして、画像の幅を「100」にした場合
「フィールド内のバイナリデータから画像を取得する」の場合:
<encsection id="206d6ca59982edf70dfdbf5f2a5f344d.PNG" ftype="PNG" src="206d6ca59982edf70dfdbf5f2a5f344d" olen="2654" enclen="3632">iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAK...(以下エンコードデータが続く)</encsection><img src="206d6ca59982edf70dfdbf5f2a5f344d.PNG" />
「ランタイムでディスクから画像を取得する」の場合については、画像の実体がまだ取り込まれていないため、別途別の手段で実体を取り込む必要があります。こちらは後ほど紹介します。
残り2つの違いは、HTMLタグにwidthオプションがあるかないか、です。「ワークフローに静止画像を保存する」でも、「固定幅を使用する」を「いいえ」にすると、「フィールド内のバイナリデータから画像を取得する」と同じ結果になります。
実際の画像のサイズを取得するためには、Base64エンコードされたデータを取得し、中身を解析する必要があります。
また、画像ツールが取り込んだ画像は、基本PNG形式かJPG形式になります(GIF形式は自動的にPNG形式に変換されます)。そのため、PNG形式またはJPG形式のファイルフォーマットをバイナリレベルで理解する必要があります。
手順としては以下のとおりとなります。
- Base64エンコードされたデータを取得する
- JPG/PNGフォーマットごとに、バイナリの指定箇所からピクセルサイズを取得する
- 読込み時のサイズ指定をしているのであれば、そのサイズから最終的な画像サイズを再計算する
1.Base64エンコードされたデータを取得する
画像ツールの「ランタイムでディスクから画像を取得する」を使って画像を読み込んでいる場合は、まず実体の画像を取得する必要があります。これは、取得したレポートスニペットの列をそのままレポートテキストツールをエキスパートモードに突っ込みます。
もしくは、ファイルパスが直接取れるので、直接バイナリを解析する手もあります。今回は手順を共通化するため、取得したレポートスニペットの列をそのままレポートテキストツールをエキスパートモードに突っ込む方法で進めます。つまり、画像ツールのあとにすぐレポートテキストツールを接続し、以下のように設定します。

それ以外の方法はすべて実体の画像が読み込まれているため、何もする必要はありません。
ここから共通の手順です。さて、実際のデータは以下のようになっています。encsectionというタグで囲まれた部分が、Base64エンコードされた画像データです。
<encsection id="206d6ca59982edf70dfdbf5f2a5f344d.PNG" ftype="PNG" src="206d6ca59982edf70dfdbf5f2a5f344d" olen="2654" enclen="3632">iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAK...(以下エンコードデータが続く)</encsection><img src="206d6ca59982edf70dfdbf5f2a5f344d.PNG" />
ここからBase64エンコードされた画像データを取得します。これにはまぁ正規表現ツールが適しているかと思います。例えば、以下のような設定で抜き出せます。

ここまでで、以下のようなワークフローになっています。

2.JPG/PNGフォーマットごとに、バイナリの指定箇所からピクセルサイズを取得する
今回のハイライトがここでしょうか?
といっても、Base64エンコードされていると、バイナリの指定箇所に到達できません。一度バイナリに変換する必要があります。
ここで登場するのが「Blob変換」ツールです。ここで、「Blobフィールドに変換する」オプションを使い、「フィールドはBase64エンコードバイナリデータである」を選択します。

これでバイナリになります。ただ、この状態だとAlteryxは手も足も出ないので、HEXに変換します。つまり、16進数の文字列データです。これも同様にBlob変換ツールを使います。

実際のデータは以下のような感じになります。

それでは実際にピクセルサイズを取得してみましょう。
PNG形式の場合
PNG形式の場合は、フォーマットがシンプルなので簡単です。データは決まった場所に格納されています。フォーミュラツールで以下の計算式で取得可能です。
横幅:
HexToNumber(Substring([Base64encoded],16*2,8))
縦幅:
HexToNumber(Substring([Base64encoded],20*2,8))

JPG形式の場合
JPGはピクセルサイズの取得場所が可変です(ファイルによって異なる)。そのため、計算式が面倒です・・・。
横幅:
IF REGEX_Match([data], ".*?(FFC[01235679ABDEF].*?)FF.*") THEN
HexToNumber(REGEX_Replace([data], ".*?FFC[01235679ABDEF].{10}(.{4}).*?FF.*", "$1"))
ELSE Null()
ENDIF
縦幅:
IF REGEX_Match([data], ".*?(FFC[01235679ABDEF].*?)FF.*") THEN
HexToNumber(REGEX_Replace([data], ".*?FFC[01235679ABDEF].{6}(.{4}).*?FF.*", "$1"))
ELSE Null()
ENDIF

どちらでも対応できるようにするには、以下のようにします。
横幅:
IF Uppercase([fType])="PNG" THEN
HexToNumber(Substring([data],16*2,8))
ELSEIF Uppercase([fType])="JPG" THEN
IF REGEX_Match([data], ".*?(FFC[01235679ABDEF].*?)FF.*") THEN
HexToNumber(REGEX_Replace([data], ".*?FFC[01235679ABDEF].{10}(.{4}).*?FF.*", "$1"))
ELSE Null()
ENDIF
ELSE Null()
ENDIF
縦幅:
IF Uppercase([fType])="PNG" THEN
HexToNumber(Substring([data],20*2,8))
ELSEIF Uppercase([fType])="JPG" THEN
IF REGEX_Match([data], ".*?(FFC[01235679ABDEF].*?)FF.*") THEN
HexToNumber(REGEX_Replace([data], ".*?FFC[01235679ABDEF].{6}(.{4}).*?FF.*", "$1"))
ELSE Null()
ENDIF
ELSE Null()
ENDIF
3.読込み時のサイズ指定をしているのであれば、そのサイズから最終的な画像サイズを再計算する
最後の仕上げに、読込み時にサイズ指定をしている場合、横幅はそのサイズになります。アスペクト比を崩すことなく縦幅が自動調整されるため、計算で求める必要があります。
横幅はHTMLタグからそのまま持ってくれば良いので、以下のように正規表現で抜いてこれます(今回はフォーミュラツールで行っていますが、正規表現ツールでも可能です。ただ、正規表現ツールの場合、対象がない場合に警告を発するのがあまりうれしくないポイントです)。
IF REGEX_Match([Image], '.*? width="\d+" .*') THEN
ToNumber(REGEX_Replace([Image], '.*? width="(\d+)" .*', "$1"))
ELSE
[width]
ENDIF
widthタグがある場合だけ、その値を使い、ない場合は画像の横幅を使います。
次は、縦の幅です。こちらは画像そのままのサイズが使えない場合は横幅とタグから取得できる設定値を使って再計算が必要になります。
IF REGEX_Match([Image], '.*? width="\d+" .*') THEN
ToNumber(REGEX_Replace([Image], '.*? width="(\d+)" .*', "$1"))/[width]*[height]
ELSE
[height]
ENDIF
実際の結果は以下のような感じになります。

ワークフロー的には、最終的に以下のようになります。

おまけ
PNG形式
PNG形式の最初の8バイトは、PNG識別子で「89 50 4E 47 0D 0A 1A 0A」となります。その後、チャンクと呼ばれるデータの塊になっています。最初のチャンクのIHDRチャンクに画像のサイズ情報が入っています。幅と高さについてはバイナリのかなり先頭の方に入っているので取得しやすいですね。幅は16バイト目から4バイト、高さは20バイト目から4バイトの位置にあります。
JPG形式
JPEGのバイナリはマーカと呼ばれるもので内部が分割されています。マーカは2バイトのデータですが、1バイト目がffで始まります。ファイルのスタートは、ffd8で始まり、ラストはffd9です。SOF(フレームヘッダー)内に基本的な画像の情報が入っているようです。SOFを探すのにマーカを探す必要がありますが、微妙にフォーマットによって異なるようで、ffc0、ffc1、ffc2、ffc3、ffc5、ffc6、ffc7、ffc9、ffca、ffcb、ffcd、ffce、ffcfのいずれかとなります(一般的にはffc0かffc2らしいですが)。SOFが見つかれば、オフセット5-6の2バイトがHeightで7-8の2バイトが画像の幅とのことです。
バイナリ直の場合
Alteryxに画像ツールで取り込まなくても、Blob入力ツールで直接バイナリとして判断することも可能です。この場合、Blob入力ツールのあとは、Blob変換ツールでHEXに変換すれば、あとはフォーミュラツールで画像の幅、高さを取得することができます。

まとめ
- 画像ツールで取り込んだファイルのBase64エンコードされたデータから、バイナリを復元し、PNG/JPGフォーマットそれぞれの画像の幅、高さを取得しました
- 画像ツールで取得する際にリサイズをかけていても最終的な画像のピクセル数を取得しました
- 今回は真面目に需要ってあるのかな、と疑問に思いながら書きました・・・。ぜひ何かに役立ててください。
サンプルワークフローダウンロード
次回
サイズのお話の続きをします。

コメント