更新情報
広告
PRサイト
関連サイト
< 2007/11/23 >

CGLayerでRepeating Motif Generatorを改善


CGLayerを発見

QuickLookプラグインを作ろうとする過程で、今まで全く読んだ事のなかったCoreGraphicsのドキュメントを拾い読みしました。そのときにCGLayerというものがある事を知りました。

Cocoaフレームワークからは、ほとんどのCoreGraphicsの機能を使う事ができる様になっているのですが、中には例外もあります。そんな例外の中の一つがCGLayerというオブジェクトです。これを使うと同じ画像を繰り返し使う場合に、その画像をビデオカードにキャッシュしてくれるので、パフォーマンスが改善されるそうです。まさにRepeating Motif Generator向き。

ただし、これを使うとアプリケーションがPantherでは動かなくなりTiger以降対応となります。またビデオカードが対応している必要があります。

CocoaとCoreGraphicsの違いにとまどう

パフォーマンスが改善されるといっても果たしてどの程度なのか、やる価値がある程なのかどうかは試してみなくてはわかりません。そこで最も簡単なP1の対称性パターンだけCGLayerで描画する様に改造する事にしました。

機能が同じなのだから、CocoaとCoreGraphicsを混ぜて使うのも簡単かと思いきや、そうでもありません。たとえば原図を描画するのにNSBezierPathを使っているのですが、CoreGraphicsには対応するオブジェクトがありません。どうやらgraphics contextというものに対してパスを描画するコマンドを指定していく事で線を描いていく様です。肝心なところがいきなりCocoaと違うので、面食らいます。

CGLayerオブジェクトを作成して、そのgraphics contextに対して原図を一つ描きます。それから線の太さや線のつなぎ方、尖る限界、線の色、塗りつぶしの色などをCGLayerオブジェクトのgraphics contextに設定していきます。その後CGLayerオブジェクト自体を使って原図を並べていくという手順になります。

線の太さゼロの意味が違う

NSBezierPathでは線の太さをゼロにしても細い線が描かれていたのですが、CoreGraphicsで線の太さをゼロにすると線が全く描かれなくなります。

オブジェクトの変換も面倒

NSColorをCGColorに変換するのも簡単ではありません。色空間の違いなどを考慮すると面倒になってしまう様です。そのくらい簡単な方法を用意しておいてくれればいいのに・・・

cocoabuilder.comに以下の記事がありました。

Re: NSColor to CGColor

動く様になりました

そんなこんなでかなり手間取ってしまいましたが、とりあえず動く様になりました。RepeatingMotifGeneratorクラスをコピーしてRepeatingMotifGeneratorCGというクラスを作り、その中のgenerateP1:メソッドを書き換えます。RepeatingMotifGeneratorクラスのオブジェクトを呼び出している部分も書き換えます。これまでCocoa+Objective-Cでずっとやってきたのですが、CoreGraphics+Cになるとだいぶ雰囲気が変わりますね。

さて肝心のパフォーマンスですが、はっきりと差がわかる結果となりました。原図のサイズを30×30にして線を太くすると描画にもたついていたのですが、瞬時に画像が更新される様になります。

PDFとして画像を書き出した場合のファイルサイズも小さくなりました。876KBが44KBになります。約20分の1です。原図一つ一つの描画コマンドが全部入っていたものが、一つの原図の描画コマンド+それの位置指定というデータになったと推測できます。原図のサイズを30×30にした場合なので差が極端にでているのでしょうが、それにしてもすごい差です。これならCGLayerを使う価値がありそうです。

ソースコード

参考までにコードを載せておきます。他のメソッドへの影響を抑える為になるべくここで全部の仕事をやる様にしているので、拡張する時にこれでは困ります。あくまでテスト用のコードです。

- (void)generateP1:(NSBezierPath *)path
{
    int i;
    float   opacity = [repeatingMotifLayer opacity];
    NSPoint p;
    CGPoint layerOrigin;
    NSColor *color;

    CGLayerRef          layer;
    CGContextRef        context,layerContext;

    context = [[NSGraphicsContext currentContext] graphicsPort];
    layer = CGLayerCreateWithContext(context,[self layerSize],NULL);
    layerContext = CGLayerGetContext(layer);

    CGContextBeginPath(layerContext);	
    [path elementAtIndex:0 associatedPoints:&p];
    CGContextMoveToPoint(layerContext,p.x,p.y);
    for(i=1;i<[path elementCount];i++)
    {
        [path elementAtIndex:i associatedPoints:&p];
        CGContextAddLineToPoint(layerContext,p.x,p.y);
    }
    CGContextClosePath(layerContext);
    
    switch([repeatingMotifLayer lineJoinStyle])
    {
        case NSMiterLineJoinStyle:
            CGContextSetLineJoin(layerContext,kCGLineJoinMiter);
            break;
        case NSRoundLineJoinStyle:
            CGContextSetLineJoin(layerContext,kCGLineJoinRound);
            break;
        case NSBevelLineJoinStyle:
            CGContextSetLineJoin(layerContext,kCGLineJoinBevel);
            break;
    }
    CGContextSetMiterLimit(layerContext,[repeatingMotifLayer miterLimit]);
    CGContextSetLineWidth(layerContext,[repeatingMotifLayer strokeWidth]);
    
    if([repeatingMotifLayer fill])
    {
        CGColorRef  cgColor;
        color = [self changeOpacity:opacity
                          withColor:[repeatingMotifLayer fillColor]];
        cgColor = CGColorFromNSColor(color);
        CGContextSetFillColorWithColor(layerContext,cgColor);
        CGColorRelease(cgColor);
    }
    if([repeatingMotifLayer stroke])
    {
        CGColorRef  cgColor;
        color = [self changeOpacity:opacity
                          withColor:[repeatingMotifLayer strokeColor]];
        cgColor = CGColorFromNSColor(color);
        CGContextSetStrokeColorWithColor(layerContext,cgColor);
        CGColorRelease(cgColor);
    }
    switch([repeatingMotifLayer windingRule])
    {
        case NSNonZeroWindingRule:
            if([repeatingMotifLayer fill])
                if([repeatingMotifLayer stroke])
                    CGContextDrawPath(layerContext,kCGPathFillStroke);
                else
                    CGContextDrawPath(layerContext,kCGPathFill);
            else
                if([repeatingMotifLayer stroke])
                    CGContextDrawPath(layerContext,kCGPathStroke);
            break;
        case NSEvenOddWindingRule:
            if([repeatingMotifLayer fill])
                if([repeatingMotifLayer stroke])
                    CGContextDrawPath(layerContext,kCGPathEOFillStroke);
                else
                    CGContextDrawPath(layerContext,kCGPathEOFill);
                else
                    if([repeatingMotifLayer stroke])
                        CGContextDrawPath(layerContext,kCGPathStroke);
            break;
    }
    
    for(i=0;;i++)
    {
        layerOrigin.x = 0;
        layerOrigin.y = masterHeight*i;
        if(layerOrigin.y > targetHeight)
            break;
        else
            for(;;)
            {
                CGContextDrawLayerAtPoint(context,layerOrigin,layer);
                layerOrigin.x += masterWidth;
                if(layerOrigin.x > targetWidth)
                    break;
            }
    }
    
    CGLayerRelease(layer);
}

CGColorRef CGColorFromNSColor(NSColor *color)
{
    NSColor* deviceColor = [color colorUsingColorSpaceName:
                            NSDeviceRGBColorSpace];
    float red = [deviceColor redComponent];
    float green = [deviceColor greenComponent];
    float blue = [deviceColor blueComponent];
    float alpha = [deviceColor alphaComponent];
    const float components[] = { red, green, blue, alpha };
    
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGColorRef cgColor = CGColorCreate(colorSpace, components);
    CGColorSpaceRelease(colorSpace);

    return  cgColor;
}

- (CGSize)layerSize
{
    float   width,height;
    
    switch([repeatingMotif symmetryTypeIndex])
    {
        case RMG_P1:
            width = masterWidth;
            height = masterHeight;
            break;
        case RMG_P2:
            break;
              .
              .
            (snip)
              .
              .
        case RMG_P6M:
            break;
    }
    
    return  CGSizeMake(width,height);
}


ページの先頭へ戻る