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'; }
実行例を次に示します。