Ruby: String#sub String#gsubの置換文字列における特殊文字

Rubyで文字列置換を行う際に頻繁に使用するStringクラスのString#subやString#gsubを下記のように使用する場合

'OriginalText'.sub(pattern, replace)

第二引数のreplaceには、patternにマッチした結果によって置換内容を変化させるための「\」から始まる特殊文字がいくつか用意されています。
instance method String#sub

これを知らずにreplaceに「\」から始まる文字を含めてしまうと意図に反した結果となることがあります。

#例:「I'm John Doe.」を「I\'m John Doe.」に変換したい
"I'm John Doe.".sub("'", "\\'")
#=>Im John Doe.m John Doe.

これは下記のようにreplaceをブロックとして渡すことで意図通りに実行結果となります。

'OriginalText'.sub(pattern){replace}
#例:
"I'm John Doe.".sub("'"){"\\'"}
#=>I\'m John Doe.

以前知らずにハマったのを思い出したのでメモ。

Qt環境のアプリから別のMac OS X .app形式のアプリでファイルを開く メモ

Qt環境のアプリから別のMac OS X .app形式のアプリでファイルを開きたかったので、QProcessから.app形式のアプリに引数でファイルパスを渡してやれば大丈夫だろうと思っていたのですが、あまり上手くいきませんでした。

例えばPreview.apphoge.pngを開きたい場合

QProcess::execute( "/Applications/Preview.app hoge.png" );

を実行してみたところPreview.appでhoge.pngは開いたのですが、Preview.appを終了しても処理が戻って来ず、アプリがハングアップした状態になりました。
なのでexecute関数をプロセスをアプリとは切り離して実行するstartDetachedに変えてみると、こちらはPreview.appすら起動せずboolの戻り値もfalseになっていました。

そこでいろいろ調べたり考えたりしたところ、Mac標準のPythonPyObjCというモジュールが統合されており、これを用いれば簡単にcocoaAPIを呼べそうということが分かりました。

ということで早速試してみたのですが、結論を言えば無事成功しました。

以下がそのコードです。

const QString script =
          "# -*- coding: utf-8-mac -*-\n"
          "import AppKit\n"
          "w = AppKit.NSWorkspace.sharedWorkspace()\n"
          "w.openFile_withApplication_( '/Users/user/Desktop/hoge.png', 'Preview')\n";

QStringList args;
args.push_back( "-c" );
args.push_back( script );
QProcess::execute( "/usr/bin/python", args );

-cというオプションをつければPythonは直接コマンドライン引数で実行するスクリプトを渡せます。
また文字コードutf-8-macを指定しないと日本語が入ったファイルパスでは失敗します。

上記を関数にしておけば簡単に呼び出せるようになりますね。

まとめ

PyObjCは今回はじめて利用しましたが、Objectiv-C以外の環境でちょこっとcocoaのAPIを利用したい場合には結構使い所がありそういうことが分かりました。さらにRubyにもRubyCocoaというモジュールがMacでは搭載されているためこちらを使っても問題なさそうです。

また、PyObjCとか使わなくてももっと簡単な方法があるよー って場合は教えていただけるとうれしいです。

MacDevCenter.com
Introduction — PyObjC — the Python ⟷ Objective-C bridge
PyObjC - Wikipedia

Qt Macでアプリケーションアイコンへのドラッグ&ドロップに対応する

この前Qtを使ってMacでアプリケーションを作成したとき、アプリケーションのアイコンへファイルのドラッグ&ドロップしても何も反応しないことに気が付きました。
Windowsだとコマンドライン引数としてドロップしたファイルのパスが渡された状態でアプリケーションが起動しますよね。
とりあえずいろいろ調べてみてMacでもファイルのドラッグ&ドロップに対応することができたので、自分のやった方法をまとめようと思います。

一番参考になったのは下のブログの記事のコメント欄でした。
Macだと arguments が渡ってこない - Qtプログラミング日記

ドラッグ&ドロップ対応に必要なこと

ドラッグ&ドロップ対応に必要なことですが以下の2つのことをする必要がありました。

1. info.plistを編集してドラッグ&ドロップを有効化する
2. QApplicationのevent関数をオーバーライドしてドロップしたときの処理を書く

info.plistの編集

まずinfo.plistを編集してアプリケーションが対応するファイル形式を設定する必要があります。
info.plist*.app/Contentsの中にあり、アプリケーションの設定などが記述されたxml形式のファイルです。
この中にCFBundleDocumentTypesという項目を追加して対応させたいファイル形式を設定します。

CFBundleDocumentTypesについてはiOS向けの記事ですが以下の記事が参考になりました。
Safx: iOSアプリケーションを特定のファイル形式に対応させる方法について

例えば以下のような項目を追加するとファイルの種類を問わず(ディレクトリでも)ドラッグ&ドロップが有効になります。

<key>CFBundleDocumentTypes</key>
    <array>
        <dict>
            <key>CFBundleTypeName</key>
            <string></string>
            <key>LSHandlerRank</key>
            <string>Alternate</string>
            <key>LSItemContentTypes</key>
            <array>
                <string>public.item</string>
            </array>
        </dict>
    </array>

Xcodeがインストールされていれば下の画像のような専用のエディタで開くことができます。

※赤枠部分が追加部分
f:id:nukesaq88:20130501223233p:plain

また.proファイルに以下のように記述することで予め用意したInfo.plistを指定することができます。

QMAKE_INFO_PLIST = Info.plist

ドロップされた場合の処理

次にQApplicationのevent関数をオーバーライドしてドロップしたときの処理を書きます。
例として以下ではMyApplicationというQApplicationを継承したクラスを作成し、その中でevent関数をオーバーライドしています。

class MyApplication : public QApplication
{
public:
  MyApplication( int &argc, char **argv ) :
    QApplication( argc, argv )
  { }

protected:

  virtual bool event( QEvent * e )
  {
    if( e->type() == QEvent::FileOpen )
    {
      const QFileOpenEvent * const file_event = static_cast<QFileOpenEvent *>(e);
      const QString file = file_event->file();
      /*
      fileをごにょごにょ
      */
      return true;
    }
    return QApplication::event(e);
  }

};

このクラスのインスタンスがQApplicationの代わりにmain関数で生成されるようにすればOKです。

まとめ

このような方法でMacでもドラッグ&ドロップに対応したQtアプリを作成することができました。
少々めんどくさいですが、対応させるファイル形式を指定できたり、起動中もドロップイベントを処理できたりと便利な面も多いですね。

参考
Macだと arguments が渡ってこない - Qtプログラミング日記
Mac OS X: Handling Apple Events
Safx: iOSアプリケーションを特定のファイル形式に対応させる方法について
System-Declared Uniform Type Identifiers

Qt QProcessで書き込みチャンネルを終了させる(プロセスにEOFを送る)方法

QProcessで起動したプロセスが例えば下記のようなコードを含んでおり標準入力のEOFを待っている場合があります。

int c;
while( ( c = getchar() ) != EOF )
{
  /*ごにょごにょ*/
}

このときQProcessのcloseWriteChannel()という関数を実行することで書き込みチャンネルを終了させることができます。

下の参考の公式ドキュメントではコマンドラインでテキストファイルの中身を確認するときなどによく使用するmoreコマンドをQProcessから実行する例がサンプルとして記載されています。

参考
QProcess Class Reference | Documentation | Qt Project

PNG画像 一括圧縮アプリ Pngyu β版 公開

先週のこのエントリの最後に書きましたが、PNG画像の圧縮コマンドラインツールのpngquantのフロントエンドGUIアプリを作成して公開しました。
機能的にはImageAlphaを一括処理できるようにしたアプリです。
ImageAlphaも圧縮にはpngquantを使用しているので、圧縮機能としてはほとんど同じかと思います。

ダウンロードは下記リンクから可能です。

Pngyu

実行画面

f:id:nukesaq88:20130425015119p:plain

開発開始してからまだ1週間ちょっとしか経っていないのでまだまだ完成度は高くないかもしれませんが、バグ報告などを期待して公開することにしました。
なので不具合報告や機能追加要望などお待ちしております。

Macで実行ファイルの依存共有ライブラリを調べる方法 メモ (Unix lddコマンドの代用)

Macで実行ファイルの依存共有ライブラリを調べる方法のメモです。

otool -L 実行ファイル

というコマンドで調べられるようです。
Macには無いUnixのlddコマンドの代用に使えます。

以上ただの個人的なメモ。

C++ boostを使用してSHA-1 ハッシュ値の計算 メモ

C++ Boost.Uuidのdetailにあるsha1クラスを用いてSHA-1 ハッシュ値を計算する方法のメモです。
SHA-1の場合ハッシュ値は20バイト固定なのでboost::array<boost::uint8_t,20>を返すような関数を作りました。

main.cpp

#include <boost/uuid/sha1.hpp>
#include <boost/cstdint.hpp>
#include <boost/array.hpp>
#include <iostream>

typedef boost::array<boost::uint8_t,20> hash_data_t;

hash_data_t
get_sha1_hash( const void *data, const std::size_t byte_count )
{
  boost::uuids::detail::sha1 sha1;
  sha1.process_bytes( data, byte_count );
  unsigned int digest[5];
  sha1.get_digest( digest );
  const boost::uint8_t *p_digest = reinterpret_cast<const boost::uint8_t *>( digest );
  hash_data_t hash_data;
  for( int i = 0; i < 5; ++i )
  {
    hash_data[ i * 4 ]     = p_digest[ i * 4 + 3 ];
    hash_data[ i * 4 + 1 ] = p_digest[ i * 4 + 2 ];
    hash_data[ i * 4 + 2 ] = p_digest[ i * 4 + 1 ];
    hash_data[ i * 4 + 3 ] = p_digest[ i * 4 ];
  }
  return hash_data;
}

int main()
{
  std::cout << std::hex;

  hash_data_t hash = get_sha1_hash( "foobar", 6 );
  hash_data_t::const_iterator itr = hash.begin();
  const hash_data_t::const_iterator end_itr = hash.end();
  for( ; itr != end_itr; ++itr )
  {
    std::cout << ( (*itr  & 0xf0 ) >> 4 )
              << (*itr  & 0x0f );
  }
  std::cout << std::endl;

  return 0;
}

出力

8843d7f92416211de9ebb963ff4ce28125932878

下のサイトでチェックしたところ同じ値がでました。
SHA1 Hash Generator » Joe's Web Tools


参考
SHA - Wikipedia
SHA-1 With Boost
BoostでSHA-1 - Faith and Brave - C++で遊ぼう

CodeIQ 小飼弾さんからの出題 「百聞は一見に如かず~文字列処理+画像処理=?」の解答

だいぶ前の話ですが去年の年末にブログ404 Blog Not Foundで有名な小飼弾さんからgihyo.jp経由でCodeIQ百聞は一見に如かず~文字列処理+画像処理=?という題名で出題されていた問題に挑戦した際の解答をブログにメモしておこうと思います。

既に締め切りが過ぎていますが、問題の内容は以下を参照してください。

第1回 百聞は一見に如かず~文字列処理+画像処理=? ─小飼弾からの挑戦:エンジニアのスキルを試すコードパズル ─この問題,あなたは解けますか?|gihyo.jp … 技術評論社
挑戦者求む!【言語不問】百聞は一見にしかず by 404 Blog Not Found 小飼 弾│CodeIQ

小飼さん公式の解説はこちらにあります。

解説については小飼さんのエントリに書かれていますので、今回は自分の解答と、それをどうやって解いたのかを書こうと思います。

どうやって解いたか

まずこの問題を簡単に説明すると、人の見た目では気付かないようにテキストが埋め込まれたpng画像が公開されており、それと同じ方法で画像にテキストを埋め込むプログラムと、画像から埋め込まれたテキストを読みだすプログラムを作成するというものです。

公開されている画像は埋め込み前が下の画像で
(追記:あとで気づいたのですが、はてなフォトライフはPNG画像を変換するみたいなので元の画像と差異がある可能性が高いです)

f:id:nukesaq88:20130418185453p:plain

下の埋め込み画像にUTF-8

漢字,カタカナ,ひらがなの入ったPNG。¥n

という文字列が埋め込まれています。

f:id:nukesaq88:20130418185507p:plain

埋め込み前と後の差分を見てみる

まず自分は埋め込み前と後の画像でどのように変化しているのかを見るために、2つの画像を差分を見てみることにしました。

以下が2つの画像のピクセル毎に差分の絶対値をとったものです。
画像はGimpを用いてレイヤーモードを"差の絶対値"にして重ねあわせて作成しました。

f:id:nukesaq88:20130418185556p:plain

このように見た目にはほぼ真っ黒で差はほとんどないように見えますが、この画像中の輝度の最大値を調べ、その値が255になるようにに正規化(ノーマライズ)してあげると以下のようになります。

f:id:nukesaq88:20130418185606p:plain

差がはっきり見えるようになりましたね。
そして、ここで画像の上部に注目すると。

f:id:nukesaq88:20130418185612p:plain


画像の最上段だけ、他の場所と違っていることに気がつきます。
ということはおそらく、この部分にテキストが埋め込まれており、埋め込むテキストは画像の上段のピクセルに順番に埋め込んでいっている可能性が高いことが推測できます。

どうやってテキストがピクセルに埋め込まれているのか

ここで埋め込み後画像上部のテキストが埋め込まれていそうな領域にあるピクセルをいくつか抜き出してみました。(16進 #rrggbb形式)
#fffcfb
#fdffff
#f3fc42
#040089
そして反対に埋め込まれてなさそうな部分
#888c88
#f8fcf8
#707070
#f0b488

これだけでは違いはよく分からないので、これを2進数で表現してみます。
埋め込み有り
r:11111111 g:11111100 b:11111011
r:11111101 g:11111111 b:11111111
r:11110011 g:11111100 b:01000010
r:00000100 g:00000000 b:10001001

埋め込み無し
r:10001000 g:10001100 b:10001000
r:11111000 g:11111100 b:11111000
r:01110000 g:01110000 b:01110000
r:11110000 g:10110100 b:10001000

これを埋め込み無しの方の下位ビットに注目すると、下位ビット部分が共通して0であることがわかります。
rでは3桁 gでは2桁 bでは3桁の下位ビット部分がこの4つピクセルに共通して0になっていますね。
埋め込み有りの方はこのような特徴はありません。

ということはこの部分に文字が埋め込まれているのだとすれば、1ピクセルごとに8ビットの領域を埋め込んでおり、領域が余った部分は0で埋めているのだろうということが推測できました。

そこで埋め込んだ画像の上段から8ビットずつNULL終端(0)が見つかるまで読み込むソフトを作成したところ、問題中にある

漢字,カタカナ,ひらがなの入ったPNG。¥n

という文字列が読み出せました。

ということで今回の問題のテキスト埋め込みアルゴリズムはこのように解析していきました。
この時点で、すでにデコードには成功したため、あとは逆の方法でエンコードソフトを作るだけとなりました。

解答

以上のようにして解いていった解答となるデコーダとエンコーダのソースを以下に公開しておきます。
言語はC++で、コンパイルにはQt環境が必要です。

一応公式の解説ページでC++を使用した解答者、正答者ともに1人となっているので正解をもらえたのだと思います。

デコーダ

#include <QImage>
#include <QByteArray>
#include <QFile>
#include <QDebug>


// decode steganographically hidden byte from pixel
char decode_from_pixel( const QRgb rgb )
{
    const unsigned char r = qRed( rgb );
    const unsigned char g = qGreen( rgb );
    const unsigned char b = qBlue( rgb );
    // 3 bits from r channel, 2 bits from g channel, 3 bits from b channel
    const char hidden_byte =
            ((r & 0x7) << 5) |
            ((g & 0x3) << 3) |
            (b & 0x7);
    return hidden_byte;
}


// decode steganographically hidden text from image
QByteArray decode_from_image( const QImage &image )
{
    const int w = image.width();
    const int h = image.height();
    QByteArray decoded_string;
    for( int y = 0; y < h; ++y )
    {
        for( int x = 0; x < w; ++x )
        {
            // decode a byte from a pixel
            const QRgb rgb = image.pixel( x, y );
            const char hidden_byte = decode_from_pixel( rgb );

            if( ! hidden_byte ) // return if charcter is null
                return decoded_string;

            decoded_string.push_back( hidden_byte );
        }
    }

    return decoded_string;
}


int main(int argc, char *argv[])
{
    if( argc != 3 )
    {
        qDebug() << "usage: decoder [embedded image path] [result text file path]";
        return -1;
    }

    // load image
    const char * const image_file_path = argv[1];
    QImage image;
    if( ! image.load( image_file_path ) )
        return -1;

    // decode
    const QByteArray &decoded_string = decode_from_image( image );

    // write result text
    const char * const result_file_path = argv[2];
    QFile result_file( result_file_path );
    if( ! result_file.open( QFile::WriteOnly ) )
        return -1;
    result_file.write( decoded_string );

    return 0;
}

エンコーダ

#include <QImage>
#include <QByteArray>
#include <QFile>
#include <QDebug>

// embed a byte to a rgb
QRgb make_embedded_pixel( const QRgb source_rgb, const char embed_byte )
{
    const unsigned char r = qRed( source_rgb );
    const unsigned char g = qGreen( source_rgb );
    const unsigned char b = qBlue( source_rgb );
    const unsigned char a = qAlpha( source_rgb );

    const unsigned char embed_r =
            ( r & 0xf8 ) | ( ( embed_byte >> 5 ) & 0x7 );
    const unsigned char embed_g =
            ( g & 0xfc ) | ( ( embed_byte >> 3 ) & 0x3 );
    const unsigned char embed_b =
            ( b & 0xf8 ) | ( ( embed_byte ) & 0x7 );

    return qRgba( embed_r, embed_g, embed_b, a );;
}


// decode steganographically hidden byte from pixel
QImage make_embedded_image( const QImage &source_image, const QByteArray &text )
{
    const int text_len = text.size();
    QImage embedded_image = source_image;
    const int w = embedded_image.width();
    const int h = embedded_image.height();
    for( int y = 0; y < h; ++y )
    {
        for( int x = 0; x < w; ++x )
        {
            const QRgb source_rgb = embedded_image.pixel( x, y );
            const int text_index = y * w + x;
            const char embed_byte = ( text_index < text_len ) ? text[text_index] : '\0';

            const QRgb embed_rgb = make_embedded_pixel( source_rgb, embed_byte );
            embedded_image.setPixel( x, y, embed_rgb );
        }
    }

    return embedded_image;
}

int main(int argc, char *argv[])
{
    if( argc != 4 )
    {
        qDebug() << "usage: encoder [source image path] [text file path] [embedded image path]";
        return -1;
    }

    // load source image
    const char * const source_image_file_path = argv[1];
    QImage source_image;
    if( ! source_image.load( source_image_file_path ) )
        return -1;

    // load text
    const char * const text_file_path = argv[2];
    QFile text_file( text_file_path );
    if( ! text_file.open( QFile::ReadOnly ) )
        return -1;
    const QByteArray &text = text_file.readAll();

    // encode and make embedded image
    const QImage &embedded_image = make_embedded_image( source_image, text );

    // save embedded image
    const char * const embedded_image_file_path = argv[3];
    if( ! embedded_image.save( embedded_image_file_path ) )
        return -1;

    return 0;
}

感想

CodeIQは今回はじめて挑戦してみましたが、なかなか楽しめたと思います。
特にテキスト埋め込みの方法が正解だとわかったときは、素直に嬉しかったです。

今回は小飼さんの出題ということもあり、人数制限で締め切られないよう出題を知ってからは早めに挑戦したのですが、最終的に人数制限まで達さなかったのは少し残念だったと思います。
もし最大の100人が挑戦していた場合の正答数や使用言語の割合も見てみたかったです。

zsh 補完で大文字小文字を区別しないよう(case insensitive)にする方法 メモ

タイトル通りzshの補完機能で大文字小文字を区別しないよう(case insensitive)にする方法のメモです。

~/.zshrc に以下の追加でできるようになります。

zstyle ':completion:*' matcher-list 'm:{a-zA-Z}={A-Za-z}'

以上ただのメモです。

pngquantでPNG圧縮

f:id:nukesaq88:20130416194206p:plain

png形式の画像ファイルを圧縮するためのツールとしてpngquantというコマンドラインから使用するソフトがあります。もちろん透過画像も圧縮することができます。


pngquant — lossy PNG compressor

このpngquantは少し前に話題になったpngをWeb上で圧縮してくれるサービスTinyPNGや、Mac用のpng圧縮ソフトのImageAlphaなどの内部で圧縮エンジンとして使用されているそうです。

圧縮の方法としては24/32-bitのフルカラーのpngファイルを256色以下に減色し8-bitのインデックスカラーに変換することで圧縮を行います。
pngquantでは、この減色の際のアルゴリズムやディザ処理が優秀なため、高品質な圧縮処理を実現しているようです。

インストール

実行環境は公式サイトのここから落としてくることができます。
Macの場合Homebrewで簡単にインストールすることだできます。

$ brew install pngquant

使い方

hoge.pngというファイルを圧縮して上書きしたい場合以下のようにします。

$ pngquant --ext .png --force 256 hoge.png

簡単に解説すると
--ext .pngの部分が保存するファイル名につける接尾辞の設定です。
.pngを指定すると元ファイルと同じファイル名になり、例えば_.pngとするとhoge_.pngとなります。
さらにそこに--forceを付け加えることでファイルが既に存在する場合に上書きを許可します。
この--extの設定を省略するとデフォルトで、-or8.png-fs8.pngが指定されます。
256は減色した色数を指定します。指定できる色範囲は2〜256となっており、省略した場合は256色になります。

なので一番シンプルなコマンドは以下のようになり、この場合256色に減色したファイルをhoge-fs8.pngまたはhoge-or8.pngで保存します。

$ pngquant hoge.png

複数の画像を一括変換

ファイル名をホワイトスペース区切りでつなげることで複数の画像を一括で変換することができます。

$ pngquant hoge.png moge.png fuga.png

またワイルドカード文字を用い以下のようにディレクトリ内にあるpngファイルを一括変換もできます。

$ pngquant *.png

その他

その他にもいくつかオプションがあるので簡単に紹介します。

  • 変換スピードの設定

1がもっとも高品質ですが一番処理時間がかかります。v1.8.3では10が最速で、デフォルトは3になっています。

$ pngquant --speed 1 hoge.png
  • IE6対応

Internet Explorer 6 の透過画像に対応します。

$ pngquant --iebug hoge.png
  • Floyd-Steinberg方式のディザ処理の無効化

標準で採用しているFloyd-Steinberg方式のディザ処理を無効化します。

$ pngquant --nofs hoge.png
  • 変換品質を指定
$ pngquant --quality 80-100 hoge.png

参考例

デフォルトの設定で圧縮した画像を以下に貼り付けておきます。
この例だと圧縮前の221kbから圧縮後82kbと半分以下のサイズまで圧縮されました。
2013/04/18 追記 はてなフォトライフにアップロードするとpngサイズが大きくなるようなので、下の画像は表記と実際のサイズに差異があります。参考程度にしかなりません。

圧縮前 221kb
f:id:nukesaq88:20130416184427p:plain

圧縮後 82kb
f:id:nukesaq88:20130416184442p:plain


ちなみにせっかくなのでもっと便利になるようにpngquantを使ったフロントエンドのGUIアプリを作成しようかと、昨日からプロトタイプを作成中です。
もし使えそうなら進捗やらを報告していきたいと思います。

2013/04/25 追記:
作成したアプリを公開しました。
Pngyu