ラベル vImage の投稿を表示しています。 すべての投稿を表示
ラベル vImage の投稿を表示しています。 すべての投稿を表示

2011年6月1日水曜日

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

前回の続きで、ヒストグラムの平坦化してみました。ほぼ同じです。データの読込みをCore Graphics でなくて NSBitmapImageRep でおこなっています。また引数を指定してイメージ・データのを指定できるようにしました。いつものように決め打ち、手抜きのだめコードです。

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

int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    if (argc < 3) {
        
        NSLog(@"Too few arguments required for.");
        [pool drain];
        return EXIT_FAILURE;
        
    }
    
    NSMutableDictionary *paths = [NSMutableDictionary dictionary];
    NSFileManager *fManager = [NSFileManager defaultManager];
    
    NSString *path1
        = [[NSString stringWithUTF8String:argv[1]] stringByExpandingTildeInPath];
    
    NSString *path2
        = [[NSString stringWithUTF8String:argv[2]] stringByExpandingTildeInPath];
    
    if ([fManager fileExistsAtPath:path1] == YES) {
        [paths setValue:path1 forKey:@"source"];
        [paths setValue:path2 forKey:@"distination"];
    }
    else {
        NSLog(@"Can not find a source path.");
        [paths removeAllObjects];
        [pool drain];
        return EXIT_FAILURE;
    }
    
    NSData *data = [NSData dataWithContentsOfFile:[paths valueForKey:@"source"]];
    NSBitmapImageRep *bitmapImageRep = [NSBitmapImageRep imageRepWithData:data];
    
    if (!bitmapImageRep) {
        NSLog(@"\"%@ \"format can not be handled."
              ,[[paths valueForKey:@"source"] pathExtension]);
        [paths removeAllObjects];
        [pool drain];
        return EXIT_FAILURE;
    }
    
    NSBitmapFormat alphaFirst, nonPremultiply, floatingPoint;
    NSInteger bpp, bpr, spp, pixelsHigh, pixelsWide;
    BOOL isPlanar, hasAlpha;
    NSColorSpace *colorSpace;
    NSColorSpaceModel colorSpaceModel;

    alphaFirst = [bitmapImageRep bitmapFormat] & NSAlphaFirstBitmapFormat;
    nonPremultiply = [bitmapImageRep bitmapFormat] & NSAlphaNonpremultipliedBitmapFormat;
    floatingPoint = [bitmapImageRep bitmapFormat] & NSFloatingPointSamplesBitmapFormat;
    bpp = [bitmapImageRep bitsPerPixel];
    bpr = [bitmapImageRep bytesPerRow];
    spp = [bitmapImageRep samplesPerPixel];
    pixelsHigh = [bitmapImageRep pixelsHigh];
    pixelsWide = [bitmapImageRep pixelsWide];
    isPlanar = [bitmapImageRep isPlanar];
    hasAlpha = [bitmapImageRep hasAlpha];
    colorSpace = [bitmapImageRep colorSpace];
    colorSpaceModel = [colorSpace colorSpaceModel];
    
    if (colorSpaceModel != NSRGBColorSpaceModel ||
        (bpp/spp) != 8 ||
        (floatingPoint == NSFloatingPointSamplesBitmapFormat) ) {
        
        NSLog(@"A color space is supported 8-bit RGB model only:%@, "
              @"Bits per pixel:%d, "
              @"Samples per pixel:%d, "
              ,[bitmapImageRep colorSpaceName]
              ,bpp
              ,spp);
        
        [paths removeAllObjects];
        [pool drain];
        return EXIT_FAILURE;
    }
    
    // -------------------- vImage_Bufferの作成 --------------------//
    vImage_Buffer org, src, dist;
    vImage_Error error;
    
    org.data = [bitmapImageRep bitmapData];
    org.height = (vImagePixelCount)pixelsHigh;
    org.width = (vImagePixelCount)pixelsWide;
    org.rowBytes = bpr;
    
    src.data = (unsigned char *)calloc(pixelsHigh * pixelsWide * 4, sizeof(unsigned char));
    src.height = (vImagePixelCount)pixelsHigh;
    src.width = (vImagePixelCount)pixelsWide;
    src.rowBytes = pixelsWide * 4;
    
    dist.data = (unsigned char *)calloc(pixelsHigh * pixelsWide * 4, sizeof(unsigned char));
    dist.height = (vImagePixelCount)pixelsHigh;
    dist.width = (vImagePixelCount)pixelsWide;
    dist.rowBytes = pixelsWide * 4;
    

    // RGB,RGBA,ARGB -> ARGB にする。
    if (spp == 3) {
        vImageConvert_RGB888toARGB8888(&org, NULL, 255, &src, FALSE, 0);
    }
    else if (alphaFirst == NSAlphaFirstBitmapFormat){
        
        uint8_t permutMap[4] = {3,0,1,2};
        vImagePermuteChannels_ARGB8888(&org, &src, permutMap, 0);
        
    }
    else {
        
        uint8_t permutMap[4] = {0,1,2,3};
        vImagePermuteChannels_ARGB8888(&org, &src, permutMap, 0);
        
    }
    
    // premultiply されてるものは解除。
    if (nonPremultiply != NSAlphaNonpremultipliedBitmapFormat) {
        vImageUnpremultiplyData_ARGB8888(&src, &src, 0);
    }
    
    // 平坦化
    error = vImageEqualization_ARGB8888(&src, &dist,kvImageLeaveAlphaUnchanged);
    
    // 解除したものをもとに戻す。
    if (nonPremultiply != NSAlphaNonpremultipliedBitmapFormat) {
        vImagePremultiplyData_ARGB8888(&dist, &dist, 0);
    }
    
    if (error != 0) {
        free(src.data);
        free(dist.data);
    }
    
    
    // もういらない
    free(src.data);
    
    //-------------------- 書き出し用の画像を作成 --------------------//
    CGContextRef context
    = CGBitmapContextCreate(dist.data,
                            pixelsWide,
                            pixelsHigh,
                            8,
                            pixelsWide * 4,
                            [colorSpace CGColorSpace],
                            kCGImageAlphaPremultipliedFirst);
    
    CGImageRef cgImage = CGBitmapContextCreateImage(context);
    
    // もういらない
    CGContextRelease(context);
    free(dist.data);
    
    NSBitmapImageRep *imageRep
        = [[NSBitmapImageRep alloc] initWithCGImage:cgImage];
    
    // もういらない
    CGImageRelease(cgImage);
    
    NSData *outputData = [imageRep TIFFRepresentation];
    
    [outputData writeToFile:[paths valueForKey:@"distination"]
                 atomically:YES];
    
    
    [paths removeAllObjects];
    [pool drain];
    return 0;
}

結果

Photoshop で確認。左が平坦化前、右が平坦化後のヒストグラムです。扱うイメージ・データの色に偏りがあると大幅に色相が変化します。

Equalization transforms an image so that it has a more uniform histogram. A truly uniform histogram is one in which each intensity level occurs with equal frequency. These functions approximate that histogram.

真に均一なヒストグラムは各強度レベルが同じになるが、vImage のヒストグラム関数は近似する。とあります。なるほど。

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 を使っても同様のことができます。今回はなんとなくです。