> blog

雑多に書いていくブログ

Processing.jsのPImageでonLoadイベントを受け取る

はじめに

Processing.jsをよく使っていて、PImageインスタンスをloadImageで生成する際に、
画像の読み込み完了を検知して何かしらの処理を行いたいことがよくある。

方法

PImageインスタンス内にsourceImgプロパティがあり、
これが単なるimg要素なのでイベントハンドラを登録してやれば良い。

PImage sampleImage = loadImage("http://www.hatena.ne.jp/images/portal/logo-portal-top2@2x.png");

// sampleImage.sourceImgはimg要素
var previousHandler = sampleImage.sourceImg.onload;
sampleImage.sourceImg.onload = function(evt) {
    console.log("読み込み完了");
    previousHandler(evt);
};

元々sourceImgプロパティ内のimg要素のonloadイベントには、PImageを使ってimg要素内の画像を表示するための処理が記述されているっぽいので、まずはsourceImgのonloadを一旦別の変数に保持しておき、後からハンドラの中でわざわざ呼ばないといけないのがかっこ悪い。
この辺は工夫すればもっと綺麗に書けると思う。

ちなみに

普通にProcessing使うときにloadImageの読み込み終了を検知する方法を知らない。
元々ProcessingではloadImageを非同期に使われることを想定してなかったりするのかな?

CoreTextで文字列の矩形領域を取得する

はじめに

iOSで表示する文字列の矩形領域を取得する必要に駆られたのですが,
CoreTextを使って実現することができたのでメモ.

方法

CTLineRefから取得できるCTRunRefは一文字分の情報を持っているため,
それを利用して全文字列の矩形領域を一文字一文字取得するという処理を行っています.

NSString *content; // 表示したい文字列
CGRect bounds; // 文字列を表示する矩形領域
    
NSMutableDictionary *attr = [[NSMutableDictionary alloc] init];
CTFontRef ctFont = CTFontCreateWithName((CFStringRef)@"Helvetica", 13, nil);
[attr setObject:(__bridge id)ctFont forKey:(NSString *)kCTFontAttributeName];
[attr setObject:[UIColor blackColor] forKey:(NSString *)kCTForegroundColorAttributeName];
    
NSAttributedString *attrString = [[NSAttributedString alloc] initWithString:content attributes:attr];
CTFramesetterRef frs = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)(attrString));

CGPath path = CGPathCreateMutable();
CGPathAddRect(path, NULL, bounds);
CTFramesetter ctFrame = CTFramesetterCreateFrame(frs, CFRangeMake(0, attrString.length), path, NULL);
 
CFRelease(frs);

CGContextRef ctx = UIGraphicsGetCurrentContext();

CGAffineTransform transform = CGAffineTransformMake(1, 0, 0, -1, 0, CGRectGetHeight(bounds));
CGContextConcatCTM(ctx, transform);
    
CFArrayRef lines = CTFrameGetLines(ctFrame);
    
CGPoint *origins = malloc(sizeof(CGPoint) * CFArrayGetCount(lines));
CTFrameGetLineOrigins(ctFrame, CFRangeMake(0, CFArrayGetCount(lines)), origins);
        
for (int i = 0; i < CFArrayGetCount(lines); ++i) {
        
    CGContextSaveGState(ctx);
    CTLineRef line = CFArrayGetValueAtIndex(lines, i);

    CGPoint origin = *(origins + i);
    CGContextSetTextPosition(ctx, origin.x ,origin.y);
    CGPoint textPosition = origin;

    CFArrayRef runs = CTLineGetGlyphRuns(line);
    CFIndex runCount = CFArrayGetCount(runs);

    CGFloat offsetX = origin.x;

    for (CFIndex runIndex = 0; runIndex < runCount; runIndex++) {

        CTRunRef run = CFArrayGetValueAtIndex(runs, runIndex);
        CFRange runRange = CTRunGetStringRange(run);

        NSString *substring = [content substringWithRange:NSMakeRange(runRange.location, runRange.length)];

        for (CFIndex glyphIndex = 0; glyphIndex < CTRunGetGlyphCount(run); glyphIndex++) {

            CFRange glyphRange = CFRangeMake(glyphIndex, 1);
            CTRunDraw(run, ctx, CFRangeMake(glyphIndex, 1)); // 文字の出力は一文字づつ...

            // Center this glyph by moving left by half its width.
            CGFloat glyphWidth = CTRunGetTypographicBounds(run, glyphRange, NULL, NULL, NULL);
            CGFloat halfGlyphWidth = glyphWidth / 2.0;
            CGPoint positionForThisGlyph = CGPointMake(textPosition.x - halfGlyphWidth, textPosition.y);

            textPosition.x -= glyphWidth;

            CGFloat ascent, descent;
            CTRunGetTypographicBounds(run, glyphRange, &ascent, &descent, NULL);

            // The glyph is centered around the y-axis
            CGRect lineMetrics = CGRectMake(offsetX, positionForThisGlyph.y - descent, glyphWidth, ascent + descent);
            offsetX += glyphWidth;

            unichar c = [substring characterAtIndex:glyphIndex];

            // ここで取得できるcharacterFrameが一文字分の矩形領域
            CGRect characterFrame = CGRectApplyAffineTransform(lineMetrics, transform);
        }
    }
    CFRelease(line);
}

CGContextRestoreGState(ctx);
free(origins);

終わりに

文字列の位置を一つ一つ取得することで色んな面白い機能が作ることができて,
例えばだいぶ前にProcessingで作ったようなこんなの

文字が指の位置を中心にして周りに広がるプロトタイプ - YouTube
ができるようになる.

前に書いた記事ではJavaScriptでの実現方法を紹介しましたが,
今回はiOSで同じ機能を実現する方法を紹介してみました!

JavaScriptで文字列の矩形領域を取得する

JavaScriptを使って任意の文字列の座標を取得する方法を探していたのですが,
なかなか良い方法が見つからなかったので僕なりに実装してみたコードを公開します.

function retrieveCharactersRects(elem) {
    if(elem.nodeType == elem.TEXT_NODE) {

        var range = elem.ownerDocument.createRange();

        // selectNodeContentsを実行することでText NodeにRangeをフォーカスさせ,
        // 文字列のoffsetを取得する
        range.selectNodeContents(elem);

        var current_pos = 0;
        var end_pos = range.endOffset;

        var results = [];

        while(current_pos + 1 < end_pos) {
            range.setStart(elem, current_pos);
            range.setEnd(elem, current_pos + 1);
            current_pos += 1;

            results.push({character: range.toString(), rect: range.getBoundingClientRect()});
        }

        range.detach();

        return results;

    } else {

        var results = [];
        for(var i = 0; i < elem.childNodes.length; i++) {
            results.push(retrieveCharactersRects(elem.childNodes[i]));
        }

        // 結果の配列をフラットにする
        return Array.prototype.concat.apply([], results);
    }
    return null;
}

方法

指定した要素内のText Nodeを取得し,Text Node内の文字列一つ一つをRangeオブジェクトの始点と終点で囲った後にgetBoundingClientRectしてやることで矩形領域を取得しています.

終わりに

文字列の矩形領域を取得する方法に関して他にも何人か実装を試みている人がいたのですが,文字列を一つ一つspanタグで囲ったりなかなか激しいアプローチをとっていたりしていたのでなるべくDOM要素に手を加えないようRangeオブジェクトを使ったアプローチを試してみました.

Rangeオブジェクトは文字列選択などに用いられたりしますが,こういう風に文字列の矩形領域の取得にも使えて便利です.

ちなみにiOSアプリでUIWebView内の全文字列の位置を取得してそれぞれの文字列の位置にUIViewをセットして矩形領域を視覚化してみたやつ.
f:id:mattatz:20130721160241j:plain

elementFromPointと併用するとマウス座標以下の文字列を取得できたりして色々な機能の実現に役立てられそう.

iOSでタッチサイズを取得する方法

iOSでタッチサイズを取得する方法がわかったのでメモ.

stackoverflowなどでも何故かそんなAPIはないっていう人が多くて半ば諦めかけていたんですが,
The fat thumbっていう研究の論文にタッチサイズを取得するコードが載っていたのを発見しました.

touchイベントが検出された際に実行されるdelegate method内で以下のようにすれば取得できます.
(ここの例ではtouchesBegan:withEvent:)

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    NSValue *val = [[touches anyObject] valueForKey:@"_pathMajorRadius"];
}

簡単ですね.

この_pathMajorRadiusはUITouchクラスのメンバ変数のようですが,
なぜこの変数の存在があまり知られていないんでしょうか...
僕もまさか論文で知るとは思いませんでした.

あとはタッチの圧力なども取得したいと思っているんですが,
調べてみてもUIAccelerationを用いた無理矢理感のある実装しか見つけ出すことができませんでした.
それだとタッチされた瞬間的な圧力しか検出できないんですよね.(加速度を用いた実装なので)

なんかいい方法はないんでしょうか.

マウス入力をプログラマブルに: jythonを使った方法

マウス入力をプログラムから操作したいとふと思い立ち,pythonでなんか良いのないかなーと探していたのですが,jythonからawtのRobotクラスを使ってやるのが簡単そうです.

import java.awt

robot = java.awt.Robot()

robot.mouseMove(x, y)
robot.mousePress(java.awt.event.InputEvent.BUTTON1_MASK)

これだけでマウスを(x, y)座標に移動してクリックさせることができます.
簡単ですね.

wxpythonとかpygtkとか,GUIアプリを作るためのツールキットを簡単に調べたんですが,マウスの移動はあってもクリックさせたりとかはざっと見た感じ見当たりませんでした.(もしかしたらあるのかも)

jythonを使ってマウス入力をプログラマブルにしたアプリケーションとしてはSikuliが面白げ.
スクリーンショットを利用してGUIアプリケーションを自動操作しようというもので,詳しくはデモ動画を参照.

Sikuli Script Demo (Automatically setting IP on Mac ...

SikuliはMITの学生が作ったアプリケーションで,2009年のUISTでBEST STUDENT PAPER AWARDを受賞した研究で実装されたものみたいです.acm