2011年5月24日火曜日

イメージを描画する(5)

昨年の出版されたものの話ですが 「photographer's gallery press no.9」 でマイケル・フリード氏のインタビューが掲載されています。聞き手の甲斐氏に答えるかたちで  --前略-- the photographic surface just can never play a really active role.-- と述べています。そこで甲斐氏 -- Do you think that's really true? -- と踏み込み(いいぞ!)ます。このやり取りに興味をおぼえ、氏の著作「 Why Photography Matter s as Art as Never Before」 を読むことにしました。

余談ですが僕の考えは、写真の物質的な表面は真に能動的な役割を常に果たしている、です。

なにするの?
というわけで今日は vImage の畳み込み(Convolution)関数を使って簡単なエンボス・フィルタを作り、写真に擬似的な表面を演出してやろうと思います。vImage はCPUのベクトル演算ユニットを使う命令をはいてくれます。操作は C言語を使います。

Image formats are either planar or interleaved. A planar image format stores image data so that the data for each channel (plane) is in a separate buffer. For example, a typical planar image would have separate buffers for the red, green, blue, and alpha channels. An interleaved image format stores image data so that the data from each pixel alternates: ARGBARGBARGB . . .

vImage で使用できる画像はプレーンで構成されているか、インターリーブされて構成されているもののどちらかになります。また、それぞれ

Data values for images can be integer or floating-point. In vImage, image formats that use integer values represent an intensity level as an 8-bit unsigned value. Values can range from 0 to 255, inclusive, with 255 indicating full intensity and 0 no intensity. Image formats that use floating-point values typically use values in the range of 0.0 (lowest intensity) to 1.0 (full intensity).

0 から 255 までの符号なし整数(unsigned char)か 0.0 から 1.0 までの浮動小数点(float)で構成されている必要があります。そうでないものについては vImage の変換(Conversion)関数を使って変換してやります。

C ばかり
今回はインターリーブ、符号なし整数の決め打ちでいきます。新規プロジェクトを Command Line Tool、Foundation を選択して、適当なところに適当な名前で作成します。個別にフレームワークを読込むのが手間なので Founfdation フレームワークを削除してから、Cocoa, Accelerate フレームワークを追加してそれぞれインポートしました。デスクトップに image.tif というファイルがあることを仮定してます。

#import <Cocoa/Cocoa.h>
#import <Accelerate/Accelerate.h>

int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    
    //---------- 画像のURLを取得 image.tif がデスクトップにあると仮定します。----------/
    NSString *path
        = [NSHomeDirectory() stringByAppendingPathComponent:@"Desktop/image.tif"];
    NSURL *imageURL = [NSURL fileURLWithPath:path];
    
    // imageURL にある画像からソースを作成。
    CGImageSourceRef sourceRef
        = CGImageSourceCreateWithURL((CFURLRef)imageURL, NULL);
    
    // ソースが作られなければ終了。
    if (sourceRef == NULL) {
        return EXIT_FAILURE;
    }
    
    // ソースからイメージを作成
    CGImageRef imageRef
        = CGImageSourceCreateImageAtIndex(sourceRef,0,NULL);
    
    // もういらない。
    CFRelease(sourceRef);

    // イメージが作成できなければ終了。
    if (imageRef == NULL) {
        
        return EXIT_FAILURE;
    }

    //-------------------- イメージの情報を取得 --------------------//
    size_t bpr                          = CGImageGetBytesPerRow(imageRef);
    size_t height                       = CGImageGetHeight(imageRef);
    size_t width                        = CGImageGetWidth(imageRef);
    CGColorSpaceRef colorSpace          = CGImageGetColorSpace(imageRef);
    CGColorSpaceModel colorSpaceModel   = CGColorSpaceGetModel(colorSpace);
    CGBitmapInfo bitmapInfo             = CGImageGetBitmapInfo(imageRef);
    CGImageAlphaInfo alphaInfo          = bitmapInfo & kCGBitmapAlphaInfoMask;
    
    CGBitmapInfo infoMask = kCGBitmapFloatComponents |
                                                      kCGBitmapByteOrderMask;
    
    // 今回はごめん。
    if ((bitmapInfo & infoMask) > 0 ||
        colorSpaceModel != kCGColorSpaceModelRGB) {
        
        CGImageRelease(imageRef);
        return EXIT_FAILURE;
    }
        
    //-------------------- ビットマップを取得する --------------------//
    CGDataProviderRef provider  = CGImageGetDataProvider(imageRef);
    NSData *tmpData = (NSData *)CGDataProviderCopyData(provider);
    
    // もういらない
    CGImageRelease(imageRef);
    
    unsigned char *bitmapData
        = (unsigned char *)malloc([tmpData length] * sizeof(unsigned char));
    
    [tmpData getBytes:bitmapData length:[tmpData length]];
    
    // もういらない
    [tmpData release];
    
    
    // -------------------- vImage_Bufferの作成 --------------------//
    vImage_Buffer org, src, dist;
    vImage_Error error;
    
    org.data = bitmapData;
    org.height = (vImagePixelCount)height;
    org.width = (vImagePixelCount)width;
    org.rowBytes = bpr;
    
    src.data = (unsigned char *)calloc(width * height * 4, sizeof(unsigned char));
    src.height = (vImagePixelCount)height;
    src.width = (vImagePixelCount)width;
    src.rowBytes = width * 4;
    
    dist.data = (unsigned char *)calloc(width * height * 4, sizeof(unsigned char));
    dist.height = (vImagePixelCount)height;
    dist.width = (vImagePixelCount)width;
    dist.rowBytes = width * 4;

    // RGB,RGBA,ARGB -> ARGB にする。
    if (alphaInfo == kCGImageAlphaNone) {
        vImageConvert_RGB888toARGB8888(&org, NULL, 255, &src, FALSE, 0);
    }
    else if (alphaInfo == kCGImageAlphaLast ||
             alphaInfo == kCGImageAlphaPremultipliedLast ||
             alphaInfo == kCGImageAlphaNoneSkipLast){
        
        uint8_t permutMap[4] = {3,0,1,2};
        vImagePermuteChannels_ARGB8888(&org, &src, permutMap, 0);
        
    }
    else if (alphaInfo == kCGImageAlphaFirst ||
             alphaInfo == kCGImageAlphaPremultipliedFirst ||
             alphaInfo == kCGImageAlphaNoneSkipFirst){
        
        uint8_t permutMap[4] = {0,1,2,3};
        vImagePermuteChannels_ARGB8888(&org, &src, permutMap, 0);
        
    }
    
    // premultiply されてるものは解除。
    if (alphaInfo == kCGImageAlphaPremultipliedFirst ||
        alphaInfo == kCGImageAlphaPremultipliedLast) {
        
        vImageUnpremultiplyData_ARGB8888(&src, &src, 0);

    }
    
    
    // もういらない
    free(bitmapData);
    
    // -------------------- 畳み込みの計算 --------------------//
    // 3 x 3 カーネルを作成
    int16_t kernel[9] = {
        -2, -2,-2,
        -2, 1, 2,
        2, 2, 2
    };
    
    // 除数の作成
    int32_t divisor = 0;
    for (NSInteger i = 0; i < 9; ++i) {
        divisor += kernel[i];
    }
    
    // バックグランド・カラーの作成(使わない)
    Pixel_8888 bgColor = {0,0,0,0};
    
    // 畳み込みの計算
    error = vImageConvolve_ARGB8888(&src,
                                    &dist,
                                    NULL,
                                    0,
                                    0,
                                    kernel,
                                    3,
                                    3,
                                    divisor,
                                    bgColor,
                                    kvImageEdgeExtend +
                                    kvImageLeaveAlphaUnchanged);
    
    
    
    // エラーがでたら終了
    if (error < 0) {
        free(src.data);
        free(dist.data);
        return EXIT_FAILURE;
    }

    // premultiply されてたものは元にもどす。
    if (alphaInfo == kCGImageAlphaPremultipliedFirst ||
        alphaInfo == kCGImageAlphaPremultipliedLast) {
        
        vImagePremultiplyData_ARGB8888(&dist, &dist, 0);

    }

    // もういらない。
    free(src.data);
    
    //-------------------- 書き出し用の画像を作成 --------------------//
    CGContextRef context
        = CGBitmapContextCreate(dist.data,
                                width,
                                height,
                                8,
                                width * 4,
                                colorSpace,
                                kCGImageAlphaPremultipliedFirst);
    
    CGImageRef cgImage = CGBitmapContextCreateImage(context);
    
    // もういらない
    CGContextRelease(context);
    
    NSBitmapImageRep *imageRep
        = [[NSBitmapImageRep alloc] initWithCGImage:cgImage];
    
    // もういらない
    CGImageRelease(cgImage);
    free(dist.data);
    
    NSData *outputData = [imageRep TIFFRepresentation];
    
    NSString *pathToWrite
        = [NSHomeDirectory() stringByAppendingPathComponent:@"Desktop/out.tif"];
    
    [outputData writeToFile:pathToWrite
                 atomically:YES];
    
    [imageRep release];
    
    [pool drain];
    return EXIT_SUCCESS;
}

これをビルドして、コマンドラインから実行するとデスクトップに out.tif ができました。エンボスもかかってます。

まず画像情報を取得して 8ビット整数でないもの、RGB でないものは除外しました。それからビットマップ・データを作成してます。CG* 関数をいろいろ使いましたが、NSBitmapImageRep を使っても同様のことができます。今回はなんとなくです。

0 件のコメント:

コメントを投稿