今日 昨日



















AA動画の作り方

元ネタ動画

youtube↓
http://jp.youtube.com/watch?v=77c1GR1itEM


前置き

皆様アクセスありがとうございます。

動画の方いかがだったでしょうか?

え?すごいのはプログラムじゃなくてPCの性能だって?

その通り

PCのスペックは

CPU: Core2Quad Q6700
メモリ: DDRII 8GB
(XP時は4GB)
HDD: 1TB
VGA: GeForce8800GTX



フフフ、素晴らしいスペックでしょう。

もちろん
自分のパソコン大学の研究室のパソコンです。

((((うp主は貧乏ですorz

家のパソコンで実行できないので、

プログラムを書いては大学まで実行しに

通いました^^;

はぁ、自分のPCもこれ位ハイスペックだったらな・・(ブツブツ


・・・と、くだらない話はさておき、

AA動画の作り方を紹介します。




アスキーアート動画の作り方





プログラム言語はC言語を使用しますが、

Cにあまり詳しくない人でもコンパイル環境さえあれば

簡単に出来るのでよければ作ってみてください。

・C言語がコンパイル出来る環境がある

・DXライブラリをインストールしている


という環境の元、下をお読みください。



まず、流れとして

1 変換したい動画を用意する。
2 動画をAVIに変換する。
3 AVIをbmp画像ファイルに変換する。
4 bmpファイルを読み込みCでAAを表示する。


という、4つの工程をふみます。

動画の中では、一旦AAデータファイルを生成していましたが、

bmpファイルから一度に表示できるように

プログラムを変更したので工程が少なくなりました。

ただプログラムでやってる作業は動画で言ってる様に

そこまでは単純じゃありませんが、

コードは完成してるのでコピペでOKです。




1 変換したい動画を用意する。




まず、変換したい動画がないと始まりません。

youtubeやニコニコ動画から持ってくるのでしたら

http://www.butsu-yoku.com/

こちらのソフトを使用すると

ダウンロードと同時にAVIに変換してくれます。

使い方の説明は全く要らないほど操作が簡単です。

見ればわかります。




2 動画をAVIに変換する。




既に持っているmpegであったり、mp4であったりする動画は

superという動画変換ソフトで変換すると良いです。

http://cowscorpion.com/MultimediaTools/SUPER.html

かなりの形式に対応するので、何でも変換出来ます。




3 AVIを静止画bmpファイルに変換する。






AVI2JPGというソフトを使うと

http://www.vector.co.jp/soft/win95/art/se318941.html

いとも簡単に変換出来てしまいます。

この時、「変換前の動画の名前がbmpのファイル名になる」ことに

注意してください。

プログラム中でファイル名を指定する必要があるので、

ファイル名は注意してください。

プログラムコードのデフォルトでは「raki.avi」である事としています。



トンでもない量のファイルが出来るので、容量には気をつけて。

スペックの低いPCだと長い動画ならいつまでたっても終わらないかも・・。

プロジェクトがあるフォルダにimage0というフォルダをつくり、

そこに画像を入れてください。




4 bmpファイルをCで読み込んで表示する。




いよいよC言語を使った実装です。

こちらに私が使ったプログラムコードを提示しますので、

よければお使いください。

プログラムコードはこの
ページの一番下にあります。

画像再生と同時に流したい曲を「raki.ogg」で

プロジェクトのあるフォルダに保存してください。

ファイル形式はwavかmp3かoggが使えます。

ファイル名を変えた時は、読み込み時のファイル文字列も

変更してください。

ここまできちんと準備できていれば、

下のコードをコピペしてコンパイル、実行すれば

AAで動画が再生されます。



左がAAの画像。右が極端に拡大した画像です。

キーボードの十字キー「上キー」と「下キー」で

拡大縮小が出来るようになっています。

F12キーを押せばいつでも最初から見直すことが出来ます。

Escボタンで終了します。






連続したbmpを読み込み、適切な文字に置き換え、AAを生成し、連続で表示するプログラム



#include "DxLib.h"

#define MAX 1000

#define GAMEN_YOKO 1024
#define GAMEN_TATE 768

#define IMAGE1

#ifdef IMAGE0
#define BMP_YOKO 1024
#define BMP_TATE 576
#endif
#ifdef IMAGE1
#define BMP_YOKO 512
#define BMP_TATE 384
#endif
#ifdef IMAGE2
#define BMP_YOKO 400
#define BMP_TATE 300
#endif
#ifdef IMAGE3
#define BMP_YOKO 320
#define BMP_TATE 240
#endif

#define HEAD 54
#define TOTAL ((BMP_YOKO*BMP_TATE)*3+HEAD)
unsigned char data[TOTAL];

typedef struct{
        unsigned char col[3];
}img_t;

#define TATEHI 3
#define YOKOHI 2
char img_AA[MAX][BMP_TATE/TATEHI+1][BMP_YOKO/YOKOHI+1];
int img_AAcol[MAX][BMP_TATE/TATEHI+1][BMP_YOKO/YOKOHI+1];
img_t img[MAX][BMP_TATE][BMP_YOKO];

int sound;


#define BARANCE 6
#define CONTRAST 1.3

//画像のコントラストを調整する。確定した15階調の幅を広げたり、
//白方向、黒方向にずらしたり出来ます。
//コントラストはCONTRAST
//輝度はBARANCE変数を変更して下さい。
void change_contrast(double *col){
        *col-=BARANCE;
        *col*=CONTRAST;
        if(*col<-7)*col=-7;
        if(*col>7)*col=7;
        *col+=BARANCE;
        *col-=2;
}

//読み取った色情報からAAになる文字に変換する関数です。
//input配列にいれてある文字は左に行くほど濃く、右に行くほど薄い文字です。
//色はTATEHI、YOKOHIの範囲の色を全て足して、平均をとって決定します。
//1ピクセルが1文字に対応するわけではないので、お間違いなく。
void convert_AA(int cnt){
        double sum;
        int i,j,x,y;
        int AAcolor[3];
        double col;
        char input[15]={'H','D','E','J','L','I','c','(','ワ','*','+',':','-','.',' '};
        for(y=0;y<BMP_TATE-TATEHI;y+=TATEHI){
                for(x=0;x<BMP_YOKO-YOKOHI;x+=YOKOHI){
                        sum=0;
                        AAcolor[0]=AAcolor[1]=AAcolor[2]=0;
                        for(i=0;i<TATEHI;i++){
                                for(j=0;j<YOKOHI;j++){
                                        sum+= img[cnt][y+i][x+j].col[0]
                                              +img[cnt][y+i][x+j].col[1]+img[cnt][y+i][x+j].col[2];
                                        AAcolor[0]+=img[cnt][y+i][x+j].col[0];
                                        AAcolor[1]+=img[cnt][y+i][x+j].col[1];
                                        AAcolor[2]+=img[cnt][y+i][x+j].col[2];
                                }
                        }
                        sum/=TATEHI*YOKOHI;
                        sum/=3.0;
                        col=sum/17.0;
                        
                        for(i=0;i<3;i++)
                                AAcolor[i]/=TATEHI*YOKOHI;

                        img_AAcol[cnt][y/TATEHI][x/YOKOHI]=GetColor(AAcolor[2],AAcolor[1],AAcolor[0]);

                        change_contrast(&col);

                        if(col<0)col=0;
                        if(col>14)col=14;
                        img_AA[cnt][y/TATEHI][x/YOKOHI]=input[(int)col];
                }
        }
}

//bmpファイルを読む関数です。
//ファイル名が違う場合はダブルコーテーション内をいじってください
//配列に0を埋めている作業は、ファイル名が00000001になるようにするためです。
void bmp_read(int cnt){
        int i;
        char name[64];
        FILE *fp;

        sprintf(name,"image0/raki %7d.bmp",cnt+1);
        for(i=12;name[i]!='\0';i++){
                if(name[i]==' ')
                        name[i]='0';
        }
        fp = fopen( name , "rb" );
        if( fp == NULL ){
                printfDx( "%sが見つかりません。",name);
                return;
        }
        fread( data, 1, TOTAL, fp );
        fclose(fp);
}

//ロード中の画面を表示します。
void load(){
        int x,y,c,t=0;
        static int cnt=1;
        for(cnt=0;cnt<MAX;cnt++){
                DrawBox(0,0,GAMEN_YOKO*cnt/MAX,50,GetColor(255,255,255),TRUE);

                bmp_read(cnt);

                t=HEAD;
                for(y=BMP_TATE-1;y>=0;y--){
                        for(x=0;x<BMP_YOKO;x++){
                                for(c=0;c<3;c++){
                                        img[cnt][y][x].col[c]=data[t];
                                        t++;
                                }
                        }
                }

                convert_AA(cnt);
        }
}

char Key[256],oldKey[256];

//今押したかどうかを判定する関数です。
void check_key(){
        int i;
    for(i=0;i<256;i++){
            if(oldKey[i]==0 && Key[i]==1)
                                Key[i]=2;
            oldKey[i]=Key[i];
    }
}

//表示文字列サイズを変更する関数です。
void change_size(int *size){
        if(Key[KEY_INPUT_UP]==2){
                *size+=5;
                SetFontSize(*size);
        }
        if(Key[KEY_INPUT_DOWN]==2){
                *size-=5;
                SetFontSize(*size);
        }
}

void play_music(){
        PlaySoundMem(sound,DX_PLAYTYPE_BACK);
}

//AAを描画します。
//グラボによって、GOSAを0にした方がきれいになる場合と1の方がキレイな場合があるので
//1でキレイに見えなかったら0にしてください。
#define GOSA 1
void draw_AA(int size, int cnt){
        int x,y;
        DrawBox(0,0,GAMEN_YOKO,GAMEN_TATE,GetColor(255,255,255),TRUE);

        for(y=0;y<BMP_TATE/TATEHI;y++){
                for(x=0;x<BMP_YOKO/YOKOHI;x++){
                        DrawFormatString(x*(size*3/5-GOSA)-(BMP_YOKO/YOKOHI)/2*(size-5)/2,
                                        y*(size-GOSA)-(BMP_TATE/TATEHI)/2*(size-5)/2,
                                        img_AAcol[cnt][y][x],"%c",img_AA[cnt][y][x]);
                }
        }
}

void stop_music(){
        StopSoundMem(sound);
}

int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
                                        LPSTR lpCmdLine, int nCmdShow ){
 
        int cnt=0,size=5;

        SetGraphMode( GAMEN_YOKO , GAMEN_TATE , 16 ) ;
//上のイフ文のコメントアウトを解除し、下をコメントアウトすると全画面になります。
//      if(DxLib_Init() == -1 ) return -1;
        if( ChangeWindowMode(TRUE) != DX_CHANGESCREEN_OK || DxLib_Init() == -1 ) return -1;

        load();

        sound=LoadSoundMem("raki.ogg");

        SetDrawScreen( DX_SCREEN_BACK );
        SetFontSize(size);

        while(!ProcessMessage() && !ClearDrawScreen() && !GetHitKeyStateAll( Key )){
                check_key();
                change_size(&size);
                
                if(cnt==0)
                        play_music();

                draw_AA(size,cnt);

                cnt++;

                if(Key[KEY_INPUT_F12]==1 || cnt>=MAX){
                        cnt=0;
                        stop_music();
                }

                //WaitTimer(???); //<-時間がずれる場合はここで時間調整してください。

                if(Key[KEY_INPUT_ESCAPE]==1)break;
                ScreenFlip();
        }
    DxLib_End();
    return 0;
}



#define MAX 1000

の値は、静止画のある枚数です。

#define GAMEN_YOKO 1024
#define GAMEN_TATE 768

は生成したい、画面サイズです。

#define IMAGE1

は読み込むbmpファイルのサイズです。
デフォルトでは512になっています。

#define TATEHI 3
#define YOKOHI 2

はたてと横の比率です。

raki.oggは再生する任意のファイルに変更してください。