果物に絵を描く画像を作るアルゴリズム


この記事は ドワンゴ Advent Calendar 2018 の16日目です。(遅刻しました😭)

おばんです。今日は昔作った、【りんご】や【栗】、【スイカ】に、焼けたような、くりぬいたような加工をするアルゴリズムをご紹介したいと思います。

果物に絵を描くとは

『合格林檎』を知ってますか?収穫前のりんごに「合格」「寿」という黒いシールを貼ると、そこだけ色付かず、白っぽい黄色の字が書かれる、農家のソリューションです。
それをアプリで実現した『合格林檎』というアプリを公開していました。 1

これは宣材画像なので、農家のソリューションが知りたい方は『合格祈願りんご』2で画像検索してください。

りんごだけだとつまらないので 3、栗をくりぬいたような画像と、いもの皮をむいたような画像が作れる機能も盛り込みました。今回は栗のアルゴリズムもご紹介します。いもは改善したら書きます 4

くり画像

いも画像(今回紹介はなしです)

あと、おまけに夏のアップデートで加えようと思っていた、スイカのアルゴリズムもご紹介します。

スイカの画像

りんごのアルゴリズム

このアプリは基本的には、加工前の画像と、加工後の画像を、指で描いた跡の白黒マスクで重ねています。
加工後の画像は、黄色や黄緑色をフォトショのフィルターを使ってりんご部分に重ねて作成しました。

-(void)penToBGImg:(UInt8*)penimg Before:(UInt8*)apple_before After:(UInt8*)apple_after
{
    UInt8* bgimg_tmp;
    UInt8* bgimg_white_tmp;
    for (int y = 0; y < pheight; y++) {
        for (int x = 0; x < pwidth; x++) {
    UInt8 pg = 0;
    if (pbitOffsetGreen != -1) {
        pg = *(penimg + y * pbytesPerRow + x * 4 + pbitOffsetGreen);
    }
    if(pg > 0.0f){

        bgimg_tmp = apple_before + y * bytesPerRow + x * 4;
        UInt8 r = 0;
        UInt8 g = 0;
        UInt8 b = 0;
        if (bitOffsetRed != -1) {
            r = *(bgimg_tmp + bitOffsetRed);
        }
        if (bitOffsetGreen != -1) {
            g = *(bgimg_tmp + bitOffsetGreen);
        }
        if (bitOffsetBlue != -1) {
            b = *(bgimg_tmp + bitOffsetBlue);
        }

        bgimg_white_tmp = apple_after + y * bytesPerRow + x * 4;
        UInt8 wr = 0;
        UInt8 wg = 0;
        UInt8 wb = 0;
        if (bitOffsetRed != -1) {
            wr = *(bgimg_white_tmp + bitOffsetRed);
        }
        if (bitOffsetGreen != -1) {
            wg = *(bgimg_white_tmp + bitOffsetGreen);
        }
        if (bitOffsetBlue != -1) {
            wb = *(bgimg_white_tmp + bitOffsetBlue);
        }

        // ビットマップに効果を与える
        float pa = pg / 255.0f;
        float ar = r * (1.0f - pa) + wr * pa;
        float ag = g * (1.0f - pa) + wg * pa;
        float ab = b * (1.0f - pa) + wb * pa;

        *(bgimg_tmp + bitOffsetRed) = ar;
        *(bgimg_tmp + bitOffsetGreen) = ag;
        *(bgimg_tmp + bitOffsetBlue) = ab;
    }
        }
    }
    bgimg_tmp = nil;
    bgimg_white_tmp = nil;
//    return apple_before;

}

りんごの場合は、マスクの前に、境界線を 3px ぼかしています。

ぼかしなし画像

ぼかしあり画像

// りんごは ratio = 1 です
-(UInt8 *)blur:(UInt8 *)image myRatio:(int)ratio{
    for(int y=0; y<(int)pheight; y++){
        for(int x=0; x<(int)pwidth; x++){
            UInt8* bgimg_tmp;
            float r = 0;
            float g = 0;
            float b = 0;
//            float a = 0;
            int count = 0;
            for(int ay = -ratio; ay <= ratio; ay++){
                for(int ax = -ratio; ax <= ratio; ax++){
                    if(y + ay > 0 && y + ay < pheight
                       && x + ax > 0 && x + ax < pwidth){

                        bgimg_tmp = image + (y + ay) * pbytesPerRow + (x + ax) * 4;
                        if (pbitOffsetRed != -1) {
                            r += *(bgimg_tmp + pbitOffsetRed);
                        }
                        if (pbitOffsetGreen != -1) {
                            g += *(bgimg_tmp + pbitOffsetGreen);
                        }
                        if (pbitOffsetBlue != -1) {
                            b += *(bgimg_tmp + pbitOffsetBlue);
                        }

                        count++;
                    }
                }
            }
            r /= count;
            g /= count;
            b /= count;
//            a /= count;

            bgimg_tmp = image + y * pbytesPerRow + x * 4;
            *(bgimg_tmp + pbitOffsetRed) = r;
            *(bgimg_tmp + pbitOffsetGreen) = g;
            *(bgimg_tmp + pbitOffsetBlue) = b;
//            *(bgimg_tmp + pbitOffsetAlpha) = a;


        }
    }
    return image;
}

栗のアルゴリズム

栗も同じく加工前と加工後の画像があって、マスクしています。
加工後の写真は、フォトショでむきぐりの写真を切り抜いて、回転させて貼り付けて作りました。
加工後の写真の、むき栗の影はブラシ。

また、マスクの境界部分(ペンの太さの境界線の、半透明部分)はこげ茶色にする処理をしています。

// 呼び出し元
        [self suicaMask:apple_before Pen:penimg Mask:@"mask_marron.png" R:137 G:3 B:0 A:1.0];

// スイカと共通のメソッド
-(void)suicaMask:(UInt8*)apple_before Pen:(UInt8*)pen_arr Mask:(NSString*)mask_str R:(float)red G:(float)green B:(float)blue A:(float)alpha
{
    UIImage* mask = [UIImage imageNamed:mask_str];
    UInt8* mask_arr = [self image2Array_dont_insert:mask];
    float r,g,b;

    for (int y = 0; y < height; y++) {
        for (int x = 0; x < width; x++) {
            // スイカの形マスクから適当な一色を取得して、黒じゃなかったら画像処理
            UInt8 pr = 0;
            if (bitOffsetRed != -1) {
                pr = *(mask_arr + y * bytesPerRow + x * 4 + bitOffsetRed);
            }
            // 黒だったら処理をエスケープ(軽くしたい)
            if(pr > 0){
                // ペンマスクを取得
                UInt8 draw_px = 0;
                if (pbitOffsetRed != -1) {
                    draw_px = *(pen_arr + y * pbytesPerRow + x * 4 + pbitOffsetRed);
                }

                // ペンで描かれてるピクセルの場合
                if(draw_px > 0){
                    // すいか赤い部分を取得
                    UInt8* bgimg_tmp = apple_before + y * bytesPerRow + x * 4;
                    if (bitOffsetRed != -1) {
                        r = *(bgimg_tmp + bitOffsetRed);
                    }
                    if (bitOffsetGreen != -1) {
                        g = *(bgimg_tmp + bitOffsetGreen);
                    }
                    if (bitOffsetBlue != -1) {
                        b = *(bgimg_tmp + bitOffsetBlue);
                    }

                    // 透明度にあわせて白を代入
                    float pa = pr * alpha / 255.0f;
                    float ar = r * (1.0f - pa) + red * pa;
                    float ag = g * (1.0f - pa) + green * pa;
                    float ab = b * (1.0f - pa) + blue * pa;

                    if(ar > 255.0f)ar = 255.0f;
                    if(ar < 0.0f)ar = 0.0f;
                    if(ag > 255.0f)ag = 255.0f;
                    if(ag < 0.0f)ag = 0.0f;
                    if(ab > 255.0f)ab = 255.0f;
                    if(ab < 0.0f)ab = 0.0f;

                    *(bgimg_tmp + bitOffsetRed) = ar;
                    *(bgimg_tmp + bitOffsetGreen) = ag;
                    *(bgimg_tmp + bitOffsetBlue) = ab;

                }
            }
        }
    }
//    [mask release];
    mask = nil;
    mask_arr = nil;
}

スイカのアルゴリズム

スイカも栗と同じです。
加工後の写真は、半切りにしたスイカの画像をフォトショではめ込みました。(回転率が。。)

マスクします。
さっきのメソッドを、白を引数にして呼び出します。
(ペンの境界線の半透明の部分を、スイカは白で塗っています。)

[self suicaMask:apple_before Pen:penimg R:255.0f G:255.0f B:255.0f];

おわりに

昔の技術なので「ピクセル加工とか草」と呆れる方もいらっしゃるかもしれません。でも、ピクセル加工はまだ息があると思っ...てるんですが...
このアプリで使用してる果物や野菜の画像は全て pixta でライセンス付購入しています。個人使用ならバレませんが、インターネットに載せる際は私と同じように画像をライセンス付きで買わないといけないので、気をつけてください。このアプリで作成した画像は大丈夫みたいです。


  1. いた、というのは、https対応できず、消えてしまいました(復活がんばります) 

  2. 本アプリで『合格林檎』という単語は検索汚染されてしまいました。 

  3. (落とされました) 

  4. (不評なので)