新・AA動画の作り方

元ネタ動画


youtube↓

http://jp.youtube.com/watch?v=ZEtk8jsI2hI



前置き

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

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

前にも一度似たような事はしたのですが、

今回はプログラムが進化したので

改めて作ってみました。

以前はメモリが3G無いと実行できないような

むちゃなプログラムでしたが

今回は一旦AAデータを生成して、

描画のプログラムとは切り離した形になっているので

メモリは少しで大丈夫です。

また、正確なFPS制御が可能です。

23FPS のような中途半端なFPSでも

定義を変更するだけで変更可能です。

置換する為の素材も増やしたので、

多彩な文字列で表現可能です。

・・・と、前置きが長くなりましたね。

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




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





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

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

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

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

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


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



まず、流れとして

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


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




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




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

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

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

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

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

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

見ればわかります。




2 動画をAVIに変換する。




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

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

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

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




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




まずは実行するための環境を作ります。

どこかに「AA」というフォルダを作り、その中に

「convAA」と「dispAA」という2つのフォルダを作って下さい。

後は作成したい動画のフォルダを作って下さい。

こんな感じです。



では動画をビットマップに変換していきます。



AVI2JPGというソフトを使うと

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

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

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

注意してください。

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

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



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

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

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




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




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

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

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

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

プロジェクトは2つ用意する必要があります。

先ほど作った

convAA と dispAA

にDXライブラリがコンパイル出来るプロジェクトを作って下さい。

convAAには下に書いてある上のプログラムを

dispAAには下に書いてある下のプログラムをコピペして下さい。

convAAでは先ほど変換した大量のビットマップから

AAデータを生成します。

dispAAではそのデータを描画します。

下のプログラムのコメント箇所を任意に変更し、

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

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



Sキーを押しながら左右キーを押すと文字サイズが変わります。

Escボタンで終了します。







連続したbmpを読み込み、適切な文字に置き換えるプログラム



#include "DxLib.h"

/* ビットマップ画像の横サイズ */
#define BMP_YOKO 640
/* ビットマップ画像の縦サイズ */
#define BMP_TATE 480

/* ビットマップのヘッダサイズ */
#define HEAD 54
/* トータルサイズ */
#define TOTAL (BMP_YOKO*BMP_TATE*3+HEAD)

/* ビットマップのあるフォルダ名 */
#define FOLDERNAME "マトリックス"
/* ビットマップのファイル名サイズ */
#define FILENAME "マトリックス】ネオvs100人スミス【MATRIX"

/* AAデータを作成するビットマップのスタート番号 */
#define START 4000
/* AAデータを作成するビットマップの終了番号 */
#define END   4500
/* AAデータを作成する合計枚数 */
#define MAX (END-START)

/* ビットマップの色情報を格納するための構造体 */
typedef struct{
        unsigned char col[3];
}img_t;
/* コントラストと色を格納するための構造体 */
typedef struct{
        unsigned char cont;
        int col;
}AAimg_t;

/* 縦サイズ */
#define TATE 5
/* 横サイズ */
#define YOKO 3

/*
定義した縦と横から成る長方形の平均の色とコントラストを計算し、
ビットマップ画像からAAデータにコンバートする関数
*/
void convert_AA(img_t img[BMP_TATE][BMP_YOKO], AAimg_t AAimg[BMP_TATE/TATE+1][BMP_YOKO/YOKO+1]){
        int i,j,x,y,AAcolor[3];
        double sum,cont;
        for(y=0;y<BMP_TATE-TATE;y+=TATE){
                for(x=0;x<BMP_YOKO-YOKO;x+=YOKO){
                        sum=0;
                        AAcolor[0]=AAcolor[1]=AAcolor[2]=0;
                        for(i=0;i<TATE;i++){
                                for(j=0;j<YOKO;j++){
                                        sum+= img[y+i][x+j].col[0]
                                                  +img[y+i][x+j].col[1]+img[y+i][x+j].col[2];
                                        AAcolor[0]+=img[y+i][x+j].col[0];
                                        AAcolor[1]+=img[y+i][x+j].col[1];
                                        AAcolor[2]+=img[y+i][x+j].col[2];
                                }
                        }
                        sum/=TATE*YOKO*3;
                        cont=sum/17.0;
                        for(i=0;i<3;i++)
                                AAcolor[i]/=TATE*YOKO;
                        AAimg[y/TATE][x/YOKO].col=GetColor(AAcolor[2],AAcolor[1],AAcolor[0]);
                        if(cont<0)cont=0;
                        if(cont>14)cont=14;
                        AAimg[y/TATE][x/YOKO].cont=(int)cont;
                }
        }
}

/*
ビットマップを読み込みdataに格納する関数
*/
int bmp_read(int cnt, unsigned char data[]){
        int i;
        char name[256];
        FILE *fp;
        sprintf(name,"../" FOLDERNAME "/" FILENAME " %07d.bmp",cnt+1);
        fp = fopen( name , "rb" );
        if( fp == NULL ){
                printfDx( "%sが見つかりません。",name);
                return -1;
        }
        fread( data, TOTAL, 1, fp );
        fclose(fp);
        return 0;
}

unsigned char data[TOTAL];
img_t img[BMP_TATE][BMP_YOKO];
AAimg_t AAimg[BMP_TATE/TATE+1][BMP_YOKO/YOKO+1];

/*
ビットマップデータを読み込みながらAAデータを書き込む関数
*/
void load_write(){
        int x,y,c,t=0;
        FILE *fp;

        fp = fopen( "AAdat.dat" , "wb" );
        if( fp == NULL ){
                printfDx( "エラー" );
                return;
        }
        int cnt=1;
        for(cnt=START;cnt<END;cnt++){
                if(bmp_read(cnt, data)==-1){
                        printfDx("エラー発生\n");
                        return ;
                }
                t=HEAD;
                for(y=BMP_TATE-1;y>=0;y--){
                        for(x=0;x<BMP_YOKO;x++){
                                for(c=0;c<3;c++){
                                        img[y][x].col[c]=data[t];
                                        t++;
                                }
                        }
                }
                convert_AA(img,AAimg);
                fwrite( AAimg, sizeof(AAimg_t), (BMP_TATE/TATE+1)*(BMP_YOKO/YOKO+1), fp );
                DrawBox(20,100,20+600           ,150,0xffffff,FALSE);
                DrawBox(20,100,20+600*(cnt-START)/MAX,150,0xffffff,TRUE);
        }
        fclose(fp);
}

/*
キー入力チェック関数
*/
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];
        }
}

int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
                                                                                LPSTR lpCmdLine, int nCmdShow ){
        SetGraphMode( 640, 480 , 16 ) ;
        if( ChangeWindowMode(TRUE) != DX_CHANGESCREEN_OK || DxLib_Init() == -1 ) return -1;
        load_write();
        DxLib_End();
        return 0;
}



変更すべき箇所は定義の

FOLDERNAMEとFILENAMEそしてSTARTとENDです。

FOLDERNAMEはビットマップデータが存在するフォルダ名です。

FILENAMEはそのビットマップデータのファイル名です。

連番と直前のスペースは含みません。bmpに変換する前のAVIファイルのファイル名です。

STARTは変換するビットマップの連番の開始番号です。 ENDは変換するビットマップの連番の終了番号です。

例えば

マトリックス 0003000.bmp
マトリックス 0003001.bmp
マトリックス 0003002.bmp
マトリックス 0003003.bmp
マトリックス 0003004.bmp

今上のような5枚のビットマップを変換したい場合はSTARTに3000、ENDに3004を指定します。



生成したAAデータを読み込み、描画するプログラム


#include "DxLib.h"

/* convAAで設定したMAXと同じ数値をいれる。 */
#define MAX 500

/* ビットマップの横サイズ */
#define BMP_YOKO 640
/* ビットマップの縦サイズ */
#define BMP_TATE 480

/* AAデータを入れるための構造体 */
typedef struct{
        unsigned char cont;
        int col;
}AAimg_t;

/* 直前のAAデータを記憶するための構造体 */
typedef struct{
        unsigned char num,cont;
}rem_img_t;

/* 縦サイズ */
#define TATE 5
/* 横サイズ */
#define YOKO 3

int font,font2,color,size,cnt,contrast;
char Key[256];

char input[15][27]={
        {"HMNRW"},
        {"DKUhkHMNRW"},
        {"$BEGOPQX\\b08"},
        {"#@ACFJVYZ[]234569dfntwヲオセヒ"},
        {"%&LSTa17jpuアカサホル"},
        {"I/egiloqsxz{|}ォウキクケチナムモヤリレ"},
        {"?crvyャイシトハミヨロ"},
        {"!<>()ァョッエコスタテヌネノメユラ"},
        {"ィゥソツフマワン"},
        {"=*ェニ"},
        {"+ュヘ"},
        {":;"},
        {"\"^_-~"},
        {"'."},
        {",`"},
};
//char input_n[15]={2,2,5,2,3,4,8,19,13,26,16,26,12,10,5};
  char input_n[15]={5,10,12,26,16,26,13,19,8,4,3,2,5,2,2};

AAimg_t AAimg[BMP_TATE/TATE+1][BMP_YOKO/YOKO+1];

extern void fps_wait();
extern void draw_fps(int,int,int,int);
extern void check_key(char Key[]);
extern void draw_setumei(int font, int color, int size, int contrast);

/* AAデータの表示0 */
void draw_AA0(){
        int cont,num;
        int xtm=3+(size-5)*2/5,ytm=5+(size-5)*2/5;
        DrawBox(0,0,640,480,color,TRUE);
        for(int i=0;i<BMP_TATE/TATE+1;i++){
                for(int j=0;j<BMP_YOKO/YOKO+1;j++){
                        cont=AAimg[i][j].cont+contrast;
                        if(cont>=15)
                                cont=14;
                        if(cont<0)
                                cont=0;
                        num =GetRand(input_n[cont]-1);
                        DrawFormatString(xtm*j-(size-5)*30,ytm*i-(ytm-5)*30,AAimg[i][j].col,"%c",input[cont][num]);
                }
        }
}

/* AAデータの表示 */
/* フォントデータを使って右画面だけアップに出来る */
void draw_AA(){
        int cont,num;
        int xtm=3+(size-5)*2/5,ytm=5+(size-5)*2/5;
        DrawBox(0,0,640,480,color,TRUE);
        for(int i=0;i<BMP_TATE/TATE+1;i++){
                for(int j=0;j<(BMP_YOKO/YOKO+1)/2;j++){
                        cont=AAimg[i][j].cont;
                        if(cont>=15)
                                cont=14;
                        if(cont<0)
                                cont=0;
                        num =GetRand(input_n[cont]-1);
                        DrawFormatStringToHandle( 3*j , 5*i , AAimg[i][j].col ,font2 ,"%c",input[cont][num] ) ;
                }
        }
        int st=(BMP_YOKO/YOKO+1)/2-1, end=BMP_YOKO/YOKO+1;
        for(int i=0;i<BMP_TATE/TATE+1;i++){
                for(int j=st;j<end;j++){
                        cont=AAimg[i][j].cont+contrast;
                        if(cont>=15)
                                cont=14;
                        if(cont<0)
                                cont=0;
                        num =GetRand(input_n[cont]-1);
                        DrawFormatString(3*j+(size-5)*(j-st)*2/5,ytm*i-(ytm-5)*30,AAimg[i][j].col,"%c",input[cont][num]);
                }
        }
}

/* convAAでコンバートしたAAデータを読み込む */
int read_draw(FILE *fp){
        if(cnt>=MAX){
                fseek(fp, 0L, SEEK_SET);
                cnt = 0;
        }
        fread( AAimg, sizeof(AAimg_t), (BMP_TATE/TATE+1)*(BMP_YOKO/YOKO+1), fp );
        draw_AA();
        cnt++;
        return 0;
}

int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
                                                                                LPSTR lpCmdLine, int nCmdShow ){
        FILE *fp;
        SetGraphMode( 640, 480 , 16 ) ;
        if( ChangeWindowMode(TRUE) != DX_CHANGESCREEN_OK || DxLib_Init() == -1 ) return -1;
        /* 裏画面描画 */
        SetDrawScreen( DX_SCREEN_BACK );
        SetFontSize(5);
        /* 描画領域を画面内に */
        SetDrawArea( 0,0,640,480 ) ;
        fp = fopen( "../convAA/AAdat.dat" , "rb" );
        if( fp == NULL ){
                printfDx( "見つかりません。" );
                return -1;
        }
        size = 5;
        font = CreateFontToHandle( "HGPゴシックE" , 18 , 2 , DX_FONTTYPE_ANTIALIASING_EDGE);
        font2 = CreateFontToHandle( NULL , 5 , -1 , DX_FONTTYPE_NORMAL);
        color = GetColor(255,255,255);
        contrast=0;
        int count=0;
        while(!ProcessMessage() && !ClearDrawScreen() && !GetHitKeyStateAll( Key )){
                check_key(Key);
                {
                        if(Key[KEY_INPUT_S]>0){
                                if(Key[KEY_INPUT_LEFT]==2){
                                        size--;
                                        if(size<5)size=5;
                                        if(size>=24)size-=4;
                                        SetFontSize(size);
                                }
                                if(Key[KEY_INPUT_RIGHT]==2){
                                        if(size>=20)size+=4;
                                        SetFontSize(size);
                                        size++;
                                }
                        }
                }
                read_draw(fp);//読み込んで描画
                fps_wait();//FPS制御
                draw_fps(0,0,font,color);//FPS表示
                draw_setumei(font, color, size, contrast);//説明表示
                if(Key[KEY_INPUT_ESCAPE]==1)break;
                ScreenFlip();
                count++;
        }
        DxLib_End();
        fclose(fp);
        return 0;
}




※ コメント ※

AAデータであるAAdat.datはこちらのプログラムのプロジェクトに移す必要はありません。

変更すべき箇所は定義のMAXだけです。何フレーム描画するのか、

convAAのMAXの値を確認して設定して下さい。