SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

特集記事

画像からASCIIアートを自動生成する

画像に最も近い文字を判定する方法


  • X ポスト
  • このエントリーをはてなブックマークに追加

HTML形式のアスキーアートを作る

 テキスト形式のアスキーアートを作るだけなら、以上の内容がほとんどすべてです。ここからはHTML形式に対応するに当たって追加する機能について解説していきます。せっかく表現豊かなHTML形式に対応するのですから、最低限これくらいの機能は欲しいですよね。

  • フォントを選択できるようにする
  • 文字の色を原画像の色に合わせる

フォントを選択する

 まずはフォントを選択する方法です。これにはコモンダイアログボックスが用意されていて、ChooseFont関数で作成することができます。ChooseFont関数に渡すCHOOSEFONT構造体のFlagsメンバにCF_FIXEDPITCHONLYを指定すると等幅フォントのみがリストアップされます。

 フォント選択後にはCreateCharInfo関数を呼び出して、全文字を解析します。詳しくは次節および次々節で解説します。

// [フォント]ダイアログボックス作成
// LOGFONT *LF : フォント情報を格納
// HWND hWnd   : ダイアログボックスの親ウィンドウ
bool ChooseFontDialog(LOGFONT *pLF,HWND hWnd)
{
    if(pLF==NULL) return false;

    CHOOSEFONT cf;
    LOGFONT lf;

    ZeroMemory(&cf,sizeof(CHOOSEFONT));
    CopyMemory(&lf,pLF,sizeof(LOGFONT));

    cf.hwndOwner=hWnd;
    cf.lStructSize=sizeof(CHOOSEFONT);
    cf.lpLogFont=&lf;
    cf.Flags
        = CF_SCREENFONTS     // システムがサポートしているフォントのみ
        | CF_INITTOLOGFONTSTRUCT // lpLogFont で初期化
        | CF_FORCEFONTEXIST      // 存在しないフォントを
                                 // 選択できないようにする
        | CF_FIXEDPITCHONLY      // 固定幅フォントのみ
        | CF_NOSCRIPTSEL;        // 文字セットのコンボボックス無効

    if(ChooseFont(&cf)==0) return false; // ダイアログボックス作成

    CopyMemory(pLF,&lf,sizeof(LOGFONT)); // LOGFONT をコピー

    CreateCharInfo(g_ci,&lf);            // 全文字を解析

    return true;
}

CHARINFO構造体

 CHARINFO構造体は文字、文字の幅と高さ、および横と縦の射影ヒストグラムをメンバに持ちます。

// 文字解析情報
struct CHARINFO
{
    int c;      // 文字
    DWORD w,h;
    DWORD *yoko,*tate;

    CHARINFO(void);
    void SetSize(DWORD w,DWORD h);
    ~CHARINFO(void);
}

CHARINFO::CHARINFO(void)
{
    c=0;
    w=0; h=0;
    yoko=NULL; tate=NULL;
}

void CHARINFO::SetSize(DWORD w,DWORD h)
{
    this->w=w; this->h=h;
    HANDLE hHeap=GetProcessHeap();
    if(yoko) HeapFree(hHeap,0,yoko);
    if(tate) HeapFree(hHeap,0,tate);
    this->yoko=(DWORD*)HeapAlloc(
        hHeap,HEAP_ZERO_MEMORY,sizeof(DWORD)*h);
    this->tate=(DWORD*)HeapAlloc(
        hHeap,HEAP_ZERO_MEMORY,sizeof(DWORD)*w);
}

CHARINFO::~CHARINFO(void)
{
    HANDLE hHeap=GetProcessHeap();
    if(yoko) HeapFree(hHeap,0,yoko);
    if(tate) HeapFree(hHeap,0,tate);
}

全文字を解析する

 文字画像はフォントを選択した段階で作っておきます。フォント選択を含め、これらの処理はテキスト形式のアスキーアートを作る場合にも必要です。

 プログラムは次のようになります。文字を画像に変形し、文字画像の射影ヒストグラムを作ることを全文字について行っているだけです。文字画像自体は必要ないため破棄します。

// 全文字を解析
static void CreateCharInfo(CHARINFO ci[256],const LOGFONT *pLF)
{
    BmpIO charbmp;
    for(int c=0;c<256;c++)
    {
        if(!JIS_CODE(c)) continue;
        ci[c].c=c;
        AsciiToBmp(&charbmp,c,pLF);
        ci[c].SetSize(charbmp.GetWidth(),charbmp.GetHeight());
        GetCharacteristic(ci[c].yoko,ci[c].tate,&charbmp);
    }
}

文字の色を原画像の色に合わせる

 次に、アスキーアートを構成する各文字の座標に対応する色を取得する方法について解説します。問題は文字の座標です。しかし各文字の座標は分割画像の座標に等しいため、分割画像を調べれば分かります。具体的には分割画像の平均色を用います。

 プログラムは次のようになります。CreateAsciiArt関数に似ていますが、画像を最適な文字に変換するBmpToAscii関数を呼び出す代わりに、画像の平均色を取得するGetAverageColor関数を呼び出しているのが主な違いです。また、平均色の配列をカラーテーブルと呼ぶことにすると、カラーテーブルと文字列との同期をとるためにダミー情報を埋め込みます。文字列の改行に相当する場所ですね。

// 文字に対応するカラーテーブル作成
// DWORD **ppCT  : カラーテーブルを格納
// BmpIO *pColor : カラービットマップ
// LOGFONT *pLF  : フォント情報
void CreateColorTable(DWORD **ppCT, BmpIO *pColor, const LOGFONT *pLF)
{
    if(ppCT==NULL || pColor==NULL || pLF==NULL) return;

    DeleteColorTable(ppCT);

    // フォントのサイズを調べる
    BmpIO charbmp;
    AsciiToBmp(&charbmp,'a',pLF);
    int cw=(int)charbmp.GetWidth();
    int ch=(int)charbmp.GetHeight();

    // ビットマップのサイズ
    int w=(int)pColor->GetWidth();
    int h=(int)pColor->GetHeight();

    BmpIO splitbmp;

    DWORD n=0;
    HANDLE hHeap=GetProcessHeap();
    *ppCT=(DWORD*)HeapAlloc(
        hHeap,HEAP_ZERO_MEMORY,sizeof(DWORD)*(n+1));

    int x,y;
    for(y=h-1-ch;y+ch>=0;y-=ch)
    {
        for(x=0;x<w;x+=cw)
        {
            // ビットマップの一部を抽出
            SplitBmp(&splitbmp,pColor,x,y,cw,ch);
            // 平均色
            (*ppCT)[n++]=GetAverageColor(&splitbmp);
            *ppCT=(DWORD*)HeapReAlloc(
                hHeap,HEAP_ZERO_MEMORY,*ppCT,sizeof(DWORD)*(n+1));
        }
        (*ppCT)[n++]=0; // 文字列と同期をとるためのダミー情報
        (*ppCT)=(DWORD*)HeapReAlloc(
                hHeap,HEAP_ZERO_MEMORY,*ppCT,sizeof(DWORD)*(n+1));
    }
    (*ppCT)[n]=0;       // 文字列と同期をとるためのダミー情報
}

// カラーテーブル削除
// DWORD **ppCT : カラーテーブル
void DeleteColorTable(DWORD **ppCT)
{
    if(ppCT && *ppCT)
    {
        HeapFree(GetProcessHeap(),0,*ppCT);
        *ppCT=NULL;
    }
}

// ビットマップの平均色を取得
static DWORD GetAverageColor(BmpIO *pColor)
{
    if(pColor==NULL) return 0;

    BYTE *p=pColor->GetPixel();
    BYTE by=pColor->GetBitCount()/8;
    int w=(int)pColor->GetWidth();
    DWORD l=pColor->GetLength();
    int h=(int)pColor->GetHeight();

    if(p==NULL) return 0;

    DWORD r=0,g=0,b=0;

    int x,y,xb,yl;
    for(y=0;y<h;y++)
    {
        yl=y*l;
        for(x=0;x<w;x++)
        {
            xb=x*by;
            b+=p[xb  +yl];
            g+=p[xb+1+yl];
            r+=p[xb+2+yl];
        }
    }

    DWORD n=w*h;
    BYTE ar=(BYTE)((double)r/n+0.5);
    BYTE ag=(BYTE)((double)g/n+0.5);
    BYTE ab=(BYTE)((double)b/n+0.5);

    return (ar<<16)+(ag<<8)+ab;
}

HTMLファイルに保存する

 HTMLについては参考文献を参照していただくとして、HTML形式のアスキーアートはフォントを指定できるほかにも原画像と同じサイズで表示できるなど、見る側の環境によってアスキーアートが崩れることが無いのが利点です。ただ文字色については、アスキーアートの芸術性が損なわれるような気がしないでもありません。

// HTML形式で保存
// char *filename : ファイル名
// char *text     : 文字列
// LOGFONT *pLF   : フォント情報
// DWORD *pCT     : カラーテーブル
void SaveHtml(const char *filename, const char *text,
              const LOGFONT *pLF, const DWORD *pCT)
{
    if(filename==NULL || text==NULL || pLF==NULL) return;

    char title[MAX_PATH];
    if(strcmp(filename,TEMP_HTML))
    {
        char filetitle[MAX_PATH];
        GetFileTitle(filename,filetitle,MAX_PATH);
        GetFileTitleText(title,filetitle);
    }
    else strcpy(title,"AsciiArt");

    TEXTMETRIC tm;
    GetTextMetric(&tm,pLF);

    FILE *fp=fopen(filename,"w");

    fprintf(fp,"<html><head>"
        "<meta http-equiv=\"Content-type\" content=\"text/html; charset=Shift_JIS\">"
        "<title>%s</title>",title);
    fprintf(fp,"<style type=\"text/css\">"
        "<!--body,th,td{line-height: %d px;}--></style>",tm.tmHeight);
    fprintf(fp,"</head>");

    fprintf(fp,"<body bgcolor=#ffffff "
        "marginheight=0 marginwidth=0 topmargin=0 leftmargin=0>");
    fprintf(fp,"<basefont face=\"%s\">",pLF->lfFaceName);
    fprintf(fp,"<span style=\"font-size: %d px\">",tm.tmHeight);
    if(pLF->lfWeight==FW_BOLD) fprintf(fp,"<b>");
    if(pLF->lfItalic==TRUE) fprintf(fp,"<i>");
    fprintf(fp,"<pre>");

    for(int i=0;text[i];i++)
    {
        fprintf(fp,"<font color=\"#%06x\">",pCT ? pCT[i]:0);
        fprintf(fp,"%c",text[i]);
        fprintf(fp,"</font>");
    }

    fprintf(fp,"</pre>");
    if(pLF->lfWeight==FW_BOLD) fprintf(fp,"</i>");
    if(pLF->lfItalic==TRUE) fprintf(fp,"</b>");
    fprintf(fp,"</span></body></html>");

    fclose(fp);
}

// ファイル名から . と拡張子を取り除いた文字列を取得
static void GetFileTitleText(char *pTitleText,const char *pFileTitle)
{
    int i;
    for(i=0;pFileTitle[i];i++)
    {
        if(pFileTitle[i]=='.') break;
        pTitleText[i]=pFileTitle[i];
    }
    pTitleText[i]='\0';
}

 実行例を次に示します。

次のページ
プレビュー

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
特集記事連載記事一覧

もっと読む

この記事の著者

ひよこ(ヒヨコ)

職業ゲームプログラマーを志す学生です。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/345 2006/05/26 15:42

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング