将简单的单声道绘图图像转换为二维文本数组
Convert a simple mono drawing image to a 2d text array
我正在尝试开发一种算法,将简单的单线图像(即迷宫)转换为文本二维数组。
比如下图,会转换成下面的文字数组。
[|------------ |]
[| | |]
[| |]
[| |------| ---- |]
[| | | |]
[| | --- |]
[|--- | | |]
[| |--- | |]
[| | | |]
[| --------------- |]
[| |]
[| -------------------|]
最后,像这样,其中 0=障碍物,1=自由通行
[0000000000000111111110]
[0111110111111111111110]
[0111111111111111111110]
[0110000000011111100000]
[0111111111011111011110]
[0111111111011111000110]
[0000111111011111111010]
[0111111111000011111010]
[0111110111111011111110]
[0111100000000000000010]
[0111111111111111111110]
[0110000000000000000000]
我正在考虑使用图像到线条艺术文本之类的算法,即
https://www.text-image.com/convert/pic2ascii.cgi
您如何看待这种做法?
Interseting 问题它的基本向量形式是 ...我设法用这个算法做到了这一点:
预处理图像
您给我们的 JPG 有损压缩意味着您的图像包含的不仅仅是 2 种颜色。所以有一些阴影和人工制品会把事情搞砸。所以首先我们必须通过阈值和重新着色来摆脱那些。所以我们可以得到 2D BW 图像(无灰度)
向量化
您的迷宫是轴对齐的,因此它只包含水平和垂直 (h
,v
) 线。因此,只需扫描图像的每一行,找到第一个开始的墙像素,然后找到它的结束像素,然后存储在某个地方……重复直到处理完整行并对所有行执行此操作。再次对图像行执行相同操作。由于您的图像具有厚壁,因此忽略比厚度阈值更细的线并删除(几乎)相同的相邻(重复)线。
从 h,v 行获取可能的网格坐标列表
简单地列出从线的起点和终点开始的所有 x 和 y(单独)坐标。然后对它们进行排序并删除太近的坐标(重复项)。
现在,最小值和最大值为您提供迷宫的 AABB,所有 coordinate-lowest coordinate
的 GCD 将为您提供网格大小。
将 h、v 线与网格对齐
简单地将所有start/end点四舍五入到最近的网格位置...
为迷宫创建文本缓冲区
AABB 连同网格大小将为您提供单元格中的 maz 分辨率,因此只需创建 2D 文本缓冲区,其中每个单元格都有 NxN
个字符。我正在使用 6x3
看起来不错的单元格(正方形并且内部有足够的 space)。
将 h,v 行转换为文本
简单地遍历所有行并渲染 -
或 |
而不是像素...如果目标位置不包含 ' ',我也使用 +
。
将二维文本数组转换成想要的文本输出
只需将行复制到单个文本中...或者如果您足够聪明,您可以在同一内存位置拥有一维和二维,并在行之间编码 eol
。
这里是我根据上面 link 中的示例制作的 C++/VCL 简单示例:
//---------------------------------------------------------------------------
#include <vcl.h>
#include <jpeg.hpp>
#pragma hdrstop
#include "win_main.h"
#include "List.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
Graphics::TBitmap *bmp=new Graphics::TBitmap;
int txt_xs=0,txt_ys=0,txt_xf=0;
//---------------------------------------------------------------------------
template <class T> void sort_asc_bubble(T *a,int n)
{
int i,e; T a0,a1;
for (e=1;e;n--) // loop until no swap occurs
for (e=0,a0=a[0],a1=a[1],i=1;i<n;a0=a1,i++,a1=a[i])// proces unsorted part of array
if (a0>a1) // condition if swap needed
{ a[i-1]=a1; a[i]=a0; a1=a0; e=1; } // swap and allow to process array again
}
//---------------------------------------------------------------------------
AnsiString bmp2lintxt(Graphics::TBitmap *bmp)
{
bool debug=false;
const int cx=6; // cell size
const int cy=3;
const int thr_bw=400; // BW threshold
const int thr_thickness=10; // wall thikness threshold
char a;
AnsiString txt="",eol="\r\n";
int x,y,x0,y0,x1,y1,xs,ys,gx,gy,nx,ny,i,i0,i1,j;
union { BYTE db[4]; DWORD dd; } c; DWORD **pyx;
List<int> h,v; // horizontal and vertical lines (x,y,size)
List<int> tx,ty;// temp lists for grid GCD computation
// [init stuff]
bmp->HandleType=bmDIB;
bmp->PixelFormat=pf32bit;
xs=bmp->Width ;
ys=bmp->Height;
if (xs<=0) return txt;
if (ys<=0) return txt;
pyx=new DWORD*[ys];
for (y=0;y<ys;y++) pyx[y]=(DWORD*)bmp->ScanLine[y];
i=xs; if (i<ys) i=ys;
// threshold bmp to B&W
x0=xs; x1=0; y0=xs; y1=0;
for (y=0;y<ys;y++)
for (x=0;x<xs;x++)
{
c.dd=pyx[y][x];
i =c.db[0];
i+=c.db[1];
i+=c.db[2];
if (i>=thr_bw) c.dd=0x00FFFFFF;
else c.dd=0x00000000;
pyx[y][x]=c.dd;
}
if (debug) bmp->SaveToFile("out0_bw.bmp");
// [vectorize]
// get horizontal lines
i0=0; i1=0; h.num=0;
for (y0=0;y0<ys;y0++)
{
for (x0=0;x0<xs;)
{
for ( ;x0<xs;x0++) if (!pyx[y0][x0]) break;
for (x1=x0;x1<xs;x1++) if ( pyx[y0][x1]){ x1--; break; }
i=x1-x0;
if (i>thr_thickness)
{
h.add(x0);
h.add(y0);
h.add(i);
}
x0=x1+1;
}
// remove duplicate lines
for (i=i0;i<i1;i+=3)
for (j=i1;j<h.num;j+=3)
if ((abs(h[i+0]-h[j+0])<thr_thickness)&&(abs(h[i+2]-h[j+2])<thr_thickness))
{
h.del(i);
h.del(i);
h.del(i);
i1-=3; i-=3; break;
}
i0=i1; i1=h.num;
}
// get vertical lines
i0=0; i1=0; v.num=0;
for (x0=0;x0<xs;x0++)
{
for (y0=0;y0<ys;)
{
for ( ;y0<ys;y0++) if (!pyx[y0][x0]) break;
for (y1=y0;y1<ys;y1++) if ( pyx[y1][x0]){ y1--; break; }
i=y1-y0;
if (i>thr_thickness)
{
v.add(x0);
v.add(y0);
v.add(i);
}
y0=y1+1;
}
// remove duplicate lines
for (i=i0;i<i1;i+=3)
for (j=i1;j<v.num;j+=3)
if ((abs(v[i+1]-v[j+1])<thr_thickness)&&(abs(v[i+2]-v[j+2])<thr_thickness))
{
v.del(i);
v.del(i);
v.del(i);
i1-=3; i-=3; break;
}
i0=i1; i1=v.num;
}
// [compute grid]
x0=xs; y0=ys; x1=0; y1=0; // AABB
gx=10; gy=10; // grid cell size
nx=0; ny=0; // grid cells
tx.num=0; ty.num=0; // clear possible x,y coordinates
for (i=0;i<h.num;i+=3)
{
x =h[i+0];
y =h[i+1];
if (x0>x) x0=x; if (x1<x) x1=x; for (j=0;j<tx.num;j++) if (tx[j]==x){ j=-1; break; } if (j>=0) tx.add(x);
if (y0>y) y0=y; if (y1<y) y1=y; for (j=0;j<ty.num;j++) if (ty[j]==y){ j=-1; break; } if (j>=0) ty.add(y);
x+=h[i+2];
if (x0>x) x0=x; if (x1<x) x1=x; for (j=0;j<tx.num;j++) if (tx[j]==x){ j=-1; break; } if (j>=0) tx.add(x);
}
for (i=0;i<v.num;i+=3)
{
x =v[i+0];
y =v[i+1];
if (x0>x) x0=x; if (x1<x) x1=x; for (j=0;j<tx.num;j++) if (tx[j]==x){ j=-1; break; } if (j>=0) tx.add(x);
if (y0>y) y0=y; if (y1<y) y1=y; for (j=0;j<ty.num;j++) if (ty[j]==y){ j=-1; break; } if (j>=0) ty.add(y);
y+=v[i+2];
if (y0>y) y0=y; if (y1<y) y1=y; for (j=0;j<ty.num;j++) if (ty[j]==y){ j=-1; break; } if (j>=0) ty.add(y);
}
// order tx,ty
sort_asc_bubble(tx.dat,tx.num);
sort_asc_bubble(ty.dat,ty.num);
// remove too close coordinates
for (i=1;i<tx.num;i++) if (tx[i]-tx[i-1]<=thr_thickness){ tx.del(i); i--; }
for (i=1;i<ty.num;i++) if (ty[i]-ty[i-1]<=thr_thickness){ ty.del(i); i--; }
// estimate gx,gy
for (gx=x1-x0,i=1;i<tx.num;i++){ x=tx[i]-tx[i-1]; if (gx>x) gx=x; } nx=(x1-x0+1)/gx; gx=(x1-x0+1)/nx; x1=x0+nx*gx;
for (gy=y1-y0,i=1;i<ty.num;i++){ y=ty[i]-ty[i-1]; if (gy>y) gy=y; } ny=(y1-y0+1)/gy; gy=(y1-y0+1)/ny; y1=y0+ny*gy;
// align x,y to grid: multiplicate nx,ny by cx,cy to form boxes and enlarge by 1 for final border lines
nx=(cx*nx)+1;
ny=(cy*ny)+1;
// align h,v lines to grid
for (i=0;i<h.num;i+=3)
{
x=h[i+0]-x0; x=((x+(gx>>1))/gx)*gx; h[i+0]=x+x0;
y=h[i+1]-y0; y=((y+(gy>>1))/gy)*gy; h[i+1]=y+y0;
j=h[i+2]; j=((j+(gx>>1))/gx)*gx; h[i+2]=j;
}
for (i=0;i<v.num;i+=3)
{
x=v[i+0]-x0; x=((x+(gx>>1))/gx)*gx; v[i+0]=x+x0;
y=v[i+1]-y0; y=((y+(gy>>1))/gy)*gy; v[i+1]=y+y0;
j=v[i+2]; j=((j+(gy>>1))/gy)*gy; v[i+2]=j;
}
// [h,v lines -> ASCII Art]
char *text=new char[nx*ny];
char **tyx=new char*[ny];
for (y=0;y<ny;y++)
for (tyx[y]=text+(nx*y),x=0;x<nx;x++)
tyx[y][x]=' ';
// h lines
for (i=0;i<h.num;i+=3)
{
x=(h[i+0]-x0)/gx;
y=(h[i+1]-y0)/gy;
j=(h[i+2] )/gx; j+=x;
x*=cx; y*=cy; j*=cx;
for (;x<=j;x++) tyx[y][x]='-';
}
// v lines
for (i=0;i<v.num;i+=3)
{
x=(v[i+0]-x0)/gx;
y=(v[i+1]-y0)/gy;
j=(v[i+2] )/gy; j+=y;
x*=cx; y*=cy; j*=cy;
for (;y<=j;y++)
if (tyx[y][x]=='-') tyx[y][x]='+';
else tyx[y][x]='|';
}
// convert char[ny][nx] to AnsiString
for (txt="",y=0;y<ny;y++,txt+=eol)
for (x=0;x<nx;x++) txt+=tyx[y][x];
txt_xs=nx; // just remember the text size for window resize
txt_ys=ny;
delete[] text;
delete[] tyx;
// [debug draw]
// grid
bmp->Canvas->Pen->Color=TColor(0x000000FF);
for (i=1,x=x0;i;x+=gx)
{
if (x>=x1){ x=x1; i=0; }
bmp->Canvas->MoveTo(x,y0);
bmp->Canvas->LineTo(x,y1);
}
for (i=1,y=y0;i;y+=gy)
{
if (y>=y1){ y=y1; i=0; }
bmp->Canvas->MoveTo(x0,y);
bmp->Canvas->LineTo(x1,y);
}
if (debug) bmp->SaveToFile("out1_grid.bmp");
// h,v lines
bmp->Canvas->Pen->Color=TColor(0x00FF0000);
bmp->Canvas->Pen->Width=2;
for (i=0;i<h.num;)
{
x=h[i]; i++;
y=h[i]; i++;
j=h[i]; i++;
bmp->Canvas->MoveTo(x,y);
bmp->Canvas->LineTo(x+j,y);
}
for (i=0;i<v.num;)
{
x=v[i]; i++;
y=v[i]; i++;
j=v[i]; i++;
bmp->Canvas->MoveTo(x,y);
bmp->Canvas->LineTo(x,y+j);
}
bmp->Canvas->Pen->Width=1;
if (debug) bmp->SaveToFile("out2_maze.bmp");
delete[] pyx;
return txt;
}
//---------------------------------------------------------------------------
void update()
{
int x0,x1,y0,y1,i,l;
x0=bmp->Width;
y0=bmp->Height;
// Font size
Form1->mm_txt->Font->Size=Form1->cb_font->ItemIndex+4;
txt_xf=abs(Form1->mm_txt->Font->Size);
// mode
Form1->mm_txt->Text=bmp2lintxt(bmp);
// output
Form1->mm_txt->Lines->SaveToFile("pic.txt");
x1=txt_xs*txt_xf;
y1=txt_ys*abs(Form1->mm_txt->Font->Height);
if (y0<y1) y0=y1;
x0+=x1+16+Form1->flb_pic->Width;
y0+=Form1->pan_top->Height;
if (x0<340) x0=340;
if (y0<128) y0=128;
Form1->ClientWidth=x0;
Form1->ClientHeight=y0;
Form1->Caption=AnsiString().sprintf("Picture -> Text ( Font %ix%i )",abs(Form1->mm_txt->Font->Size),abs(Form1->mm_txt->Font->Height));
}
//---------------------------------------------------------------------------
void draw()
{
Form1->ptb_gfx->Canvas->Draw(0,0,bmp);
}
//---------------------------------------------------------------------------
void load(AnsiString name)
{
if (name=="") return;
AnsiString ext=ExtractFileExt(name).LowerCase();
if (ext==".bmp")
{
bmp->LoadFromFile(name);
}
if (ext==".jpg")
{
TJPEGImage *jpg=new TJPEGImage;
jpg->LoadFromFile(name);
bmp->Assign(jpg);
delete jpg;
}
bmp->HandleType=bmDIB;
bmp->PixelFormat=pf32bit;
Form1->ptb_gfx->Width=bmp->Width;
Form1->ClientHeight=bmp->Height;
Form1->ClientWidth=(bmp->Width<<1)+32;
}
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner):TForm(Owner)
{
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormDestroy(TObject *Sender)
{
delete bmp;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
draw();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::flb_picChange(TObject *Sender)
{
load(flb_pic->FileName);
update();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormActivate(TObject *Sender)
{
flb_pic->SetFocus();
flb_pic->Update();
if (flb_pic->ItemIndex==-1)
if (flb_pic->Items->Count>0)
{
flb_pic->ItemIndex=0;
flb_picChange(this);
}
}
//---------------------------------------------------------------------------
只需忽略 VCL 内容并将生成的文本转换为您可以使用的任何内容。我也使用我的动态列表模板:
List<double> xxx;
等同于 double xxx[];
xxx.add(5);
将 5
添加到列表末尾
xxx[7]
访问数组元素(安全)
xxx.dat[7]
访问数组元素(不安全但直接访问速度快)
xxx.num
是数组实际使用的大小
xxx.reset()
清空数组并设置xxx.num=0
xxx.allocate(100)
为 100
项预分配 space
所以使用你得到的任何列表或重新编码或使用 std::vector
代替...
我从你的图片中删除了文字:
这是使用它作为输入的结果:
+-----------+------------------ |
| | |
| | |
| | | | +-----------+
| | | | |
| | | | |
| +-----------+ +-----+ |
| | | |
| | | |
+------ | +-----+ | |
| | | |
| | | |
| ------+-----------+------ |
| |
| |
| ------------------------------+
这里是保存的调试位图(从左到右:BW、Grid、Maze):
代码中唯一重要的东西是函数:
AnsiString bmp2lintxt(Graphics::TBitmap *bmp);
来自 VCL(基于 GDI)位图的 returns 文本。
我正在尝试开发一种算法,将简单的单线图像(即迷宫)转换为文本二维数组。
比如下图,会转换成下面的文字数组。
[|------------ |]
[| | |]
[| |]
[| |------| ---- |]
[| | | |]
[| | --- |]
[|--- | | |]
[| |--- | |]
[| | | |]
[| --------------- |]
[| |]
[| -------------------|]
最后,像这样,其中 0=障碍物,1=自由通行
[0000000000000111111110]
[0111110111111111111110]
[0111111111111111111110]
[0110000000011111100000]
[0111111111011111011110]
[0111111111011111000110]
[0000111111011111111010]
[0111111111000011111010]
[0111110111111011111110]
[0111100000000000000010]
[0111111111111111111110]
[0110000000000000000000]
我正在考虑使用图像到线条艺术文本之类的算法,即 https://www.text-image.com/convert/pic2ascii.cgi
您如何看待这种做法?
Interseting 问题它的基本向量形式是
预处理图像
您给我们的 JPG 有损压缩意味着您的图像包含的不仅仅是 2 种颜色。所以有一些阴影和人工制品会把事情搞砸。所以首先我们必须通过阈值和重新着色来摆脱那些。所以我们可以得到 2D BW 图像(无灰度)
向量化
您的迷宫是轴对齐的,因此它只包含水平和垂直 (
h
,v
) 线。因此,只需扫描图像的每一行,找到第一个开始的墙像素,然后找到它的结束像素,然后存储在某个地方……重复直到处理完整行并对所有行执行此操作。再次对图像行执行相同操作。由于您的图像具有厚壁,因此忽略比厚度阈值更细的线并删除(几乎)相同的相邻(重复)线。从 h,v 行获取可能的网格坐标列表
简单地列出从线的起点和终点开始的所有 x 和 y(单独)坐标。然后对它们进行排序并删除太近的坐标(重复项)。
现在,最小值和最大值为您提供迷宫的 AABB,所有
coordinate-lowest coordinate
的 GCD 将为您提供网格大小。将 h、v 线与网格对齐
简单地将所有start/end点四舍五入到最近的网格位置...
为迷宫创建文本缓冲区
AABB 连同网格大小将为您提供单元格中的 maz 分辨率,因此只需创建 2D 文本缓冲区,其中每个单元格都有
NxN
个字符。我正在使用6x3
看起来不错的单元格(正方形并且内部有足够的 space)。将 h,v 行转换为文本
简单地遍历所有行并渲染
-
或|
而不是像素...如果目标位置不包含 ' ',我也使用+
。将二维文本数组转换成想要的文本输出
只需将行复制到单个文本中...或者如果您足够聪明,您可以在同一内存位置拥有一维和二维,并在行之间编码
eol
。
这里是我根据上面 link 中的示例制作的 C++/VCL 简单示例:
//---------------------------------------------------------------------------
#include <vcl.h>
#include <jpeg.hpp>
#pragma hdrstop
#include "win_main.h"
#include "List.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
Graphics::TBitmap *bmp=new Graphics::TBitmap;
int txt_xs=0,txt_ys=0,txt_xf=0;
//---------------------------------------------------------------------------
template <class T> void sort_asc_bubble(T *a,int n)
{
int i,e; T a0,a1;
for (e=1;e;n--) // loop until no swap occurs
for (e=0,a0=a[0],a1=a[1],i=1;i<n;a0=a1,i++,a1=a[i])// proces unsorted part of array
if (a0>a1) // condition if swap needed
{ a[i-1]=a1; a[i]=a0; a1=a0; e=1; } // swap and allow to process array again
}
//---------------------------------------------------------------------------
AnsiString bmp2lintxt(Graphics::TBitmap *bmp)
{
bool debug=false;
const int cx=6; // cell size
const int cy=3;
const int thr_bw=400; // BW threshold
const int thr_thickness=10; // wall thikness threshold
char a;
AnsiString txt="",eol="\r\n";
int x,y,x0,y0,x1,y1,xs,ys,gx,gy,nx,ny,i,i0,i1,j;
union { BYTE db[4]; DWORD dd; } c; DWORD **pyx;
List<int> h,v; // horizontal and vertical lines (x,y,size)
List<int> tx,ty;// temp lists for grid GCD computation
// [init stuff]
bmp->HandleType=bmDIB;
bmp->PixelFormat=pf32bit;
xs=bmp->Width ;
ys=bmp->Height;
if (xs<=0) return txt;
if (ys<=0) return txt;
pyx=new DWORD*[ys];
for (y=0;y<ys;y++) pyx[y]=(DWORD*)bmp->ScanLine[y];
i=xs; if (i<ys) i=ys;
// threshold bmp to B&W
x0=xs; x1=0; y0=xs; y1=0;
for (y=0;y<ys;y++)
for (x=0;x<xs;x++)
{
c.dd=pyx[y][x];
i =c.db[0];
i+=c.db[1];
i+=c.db[2];
if (i>=thr_bw) c.dd=0x00FFFFFF;
else c.dd=0x00000000;
pyx[y][x]=c.dd;
}
if (debug) bmp->SaveToFile("out0_bw.bmp");
// [vectorize]
// get horizontal lines
i0=0; i1=0; h.num=0;
for (y0=0;y0<ys;y0++)
{
for (x0=0;x0<xs;)
{
for ( ;x0<xs;x0++) if (!pyx[y0][x0]) break;
for (x1=x0;x1<xs;x1++) if ( pyx[y0][x1]){ x1--; break; }
i=x1-x0;
if (i>thr_thickness)
{
h.add(x0);
h.add(y0);
h.add(i);
}
x0=x1+1;
}
// remove duplicate lines
for (i=i0;i<i1;i+=3)
for (j=i1;j<h.num;j+=3)
if ((abs(h[i+0]-h[j+0])<thr_thickness)&&(abs(h[i+2]-h[j+2])<thr_thickness))
{
h.del(i);
h.del(i);
h.del(i);
i1-=3; i-=3; break;
}
i0=i1; i1=h.num;
}
// get vertical lines
i0=0; i1=0; v.num=0;
for (x0=0;x0<xs;x0++)
{
for (y0=0;y0<ys;)
{
for ( ;y0<ys;y0++) if (!pyx[y0][x0]) break;
for (y1=y0;y1<ys;y1++) if ( pyx[y1][x0]){ y1--; break; }
i=y1-y0;
if (i>thr_thickness)
{
v.add(x0);
v.add(y0);
v.add(i);
}
y0=y1+1;
}
// remove duplicate lines
for (i=i0;i<i1;i+=3)
for (j=i1;j<v.num;j+=3)
if ((abs(v[i+1]-v[j+1])<thr_thickness)&&(abs(v[i+2]-v[j+2])<thr_thickness))
{
v.del(i);
v.del(i);
v.del(i);
i1-=3; i-=3; break;
}
i0=i1; i1=v.num;
}
// [compute grid]
x0=xs; y0=ys; x1=0; y1=0; // AABB
gx=10; gy=10; // grid cell size
nx=0; ny=0; // grid cells
tx.num=0; ty.num=0; // clear possible x,y coordinates
for (i=0;i<h.num;i+=3)
{
x =h[i+0];
y =h[i+1];
if (x0>x) x0=x; if (x1<x) x1=x; for (j=0;j<tx.num;j++) if (tx[j]==x){ j=-1; break; } if (j>=0) tx.add(x);
if (y0>y) y0=y; if (y1<y) y1=y; for (j=0;j<ty.num;j++) if (ty[j]==y){ j=-1; break; } if (j>=0) ty.add(y);
x+=h[i+2];
if (x0>x) x0=x; if (x1<x) x1=x; for (j=0;j<tx.num;j++) if (tx[j]==x){ j=-1; break; } if (j>=0) tx.add(x);
}
for (i=0;i<v.num;i+=3)
{
x =v[i+0];
y =v[i+1];
if (x0>x) x0=x; if (x1<x) x1=x; for (j=0;j<tx.num;j++) if (tx[j]==x){ j=-1; break; } if (j>=0) tx.add(x);
if (y0>y) y0=y; if (y1<y) y1=y; for (j=0;j<ty.num;j++) if (ty[j]==y){ j=-1; break; } if (j>=0) ty.add(y);
y+=v[i+2];
if (y0>y) y0=y; if (y1<y) y1=y; for (j=0;j<ty.num;j++) if (ty[j]==y){ j=-1; break; } if (j>=0) ty.add(y);
}
// order tx,ty
sort_asc_bubble(tx.dat,tx.num);
sort_asc_bubble(ty.dat,ty.num);
// remove too close coordinates
for (i=1;i<tx.num;i++) if (tx[i]-tx[i-1]<=thr_thickness){ tx.del(i); i--; }
for (i=1;i<ty.num;i++) if (ty[i]-ty[i-1]<=thr_thickness){ ty.del(i); i--; }
// estimate gx,gy
for (gx=x1-x0,i=1;i<tx.num;i++){ x=tx[i]-tx[i-1]; if (gx>x) gx=x; } nx=(x1-x0+1)/gx; gx=(x1-x0+1)/nx; x1=x0+nx*gx;
for (gy=y1-y0,i=1;i<ty.num;i++){ y=ty[i]-ty[i-1]; if (gy>y) gy=y; } ny=(y1-y0+1)/gy; gy=(y1-y0+1)/ny; y1=y0+ny*gy;
// align x,y to grid: multiplicate nx,ny by cx,cy to form boxes and enlarge by 1 for final border lines
nx=(cx*nx)+1;
ny=(cy*ny)+1;
// align h,v lines to grid
for (i=0;i<h.num;i+=3)
{
x=h[i+0]-x0; x=((x+(gx>>1))/gx)*gx; h[i+0]=x+x0;
y=h[i+1]-y0; y=((y+(gy>>1))/gy)*gy; h[i+1]=y+y0;
j=h[i+2]; j=((j+(gx>>1))/gx)*gx; h[i+2]=j;
}
for (i=0;i<v.num;i+=3)
{
x=v[i+0]-x0; x=((x+(gx>>1))/gx)*gx; v[i+0]=x+x0;
y=v[i+1]-y0; y=((y+(gy>>1))/gy)*gy; v[i+1]=y+y0;
j=v[i+2]; j=((j+(gy>>1))/gy)*gy; v[i+2]=j;
}
// [h,v lines -> ASCII Art]
char *text=new char[nx*ny];
char **tyx=new char*[ny];
for (y=0;y<ny;y++)
for (tyx[y]=text+(nx*y),x=0;x<nx;x++)
tyx[y][x]=' ';
// h lines
for (i=0;i<h.num;i+=3)
{
x=(h[i+0]-x0)/gx;
y=(h[i+1]-y0)/gy;
j=(h[i+2] )/gx; j+=x;
x*=cx; y*=cy; j*=cx;
for (;x<=j;x++) tyx[y][x]='-';
}
// v lines
for (i=0;i<v.num;i+=3)
{
x=(v[i+0]-x0)/gx;
y=(v[i+1]-y0)/gy;
j=(v[i+2] )/gy; j+=y;
x*=cx; y*=cy; j*=cy;
for (;y<=j;y++)
if (tyx[y][x]=='-') tyx[y][x]='+';
else tyx[y][x]='|';
}
// convert char[ny][nx] to AnsiString
for (txt="",y=0;y<ny;y++,txt+=eol)
for (x=0;x<nx;x++) txt+=tyx[y][x];
txt_xs=nx; // just remember the text size for window resize
txt_ys=ny;
delete[] text;
delete[] tyx;
// [debug draw]
// grid
bmp->Canvas->Pen->Color=TColor(0x000000FF);
for (i=1,x=x0;i;x+=gx)
{
if (x>=x1){ x=x1; i=0; }
bmp->Canvas->MoveTo(x,y0);
bmp->Canvas->LineTo(x,y1);
}
for (i=1,y=y0;i;y+=gy)
{
if (y>=y1){ y=y1; i=0; }
bmp->Canvas->MoveTo(x0,y);
bmp->Canvas->LineTo(x1,y);
}
if (debug) bmp->SaveToFile("out1_grid.bmp");
// h,v lines
bmp->Canvas->Pen->Color=TColor(0x00FF0000);
bmp->Canvas->Pen->Width=2;
for (i=0;i<h.num;)
{
x=h[i]; i++;
y=h[i]; i++;
j=h[i]; i++;
bmp->Canvas->MoveTo(x,y);
bmp->Canvas->LineTo(x+j,y);
}
for (i=0;i<v.num;)
{
x=v[i]; i++;
y=v[i]; i++;
j=v[i]; i++;
bmp->Canvas->MoveTo(x,y);
bmp->Canvas->LineTo(x,y+j);
}
bmp->Canvas->Pen->Width=1;
if (debug) bmp->SaveToFile("out2_maze.bmp");
delete[] pyx;
return txt;
}
//---------------------------------------------------------------------------
void update()
{
int x0,x1,y0,y1,i,l;
x0=bmp->Width;
y0=bmp->Height;
// Font size
Form1->mm_txt->Font->Size=Form1->cb_font->ItemIndex+4;
txt_xf=abs(Form1->mm_txt->Font->Size);
// mode
Form1->mm_txt->Text=bmp2lintxt(bmp);
// output
Form1->mm_txt->Lines->SaveToFile("pic.txt");
x1=txt_xs*txt_xf;
y1=txt_ys*abs(Form1->mm_txt->Font->Height);
if (y0<y1) y0=y1;
x0+=x1+16+Form1->flb_pic->Width;
y0+=Form1->pan_top->Height;
if (x0<340) x0=340;
if (y0<128) y0=128;
Form1->ClientWidth=x0;
Form1->ClientHeight=y0;
Form1->Caption=AnsiString().sprintf("Picture -> Text ( Font %ix%i )",abs(Form1->mm_txt->Font->Size),abs(Form1->mm_txt->Font->Height));
}
//---------------------------------------------------------------------------
void draw()
{
Form1->ptb_gfx->Canvas->Draw(0,0,bmp);
}
//---------------------------------------------------------------------------
void load(AnsiString name)
{
if (name=="") return;
AnsiString ext=ExtractFileExt(name).LowerCase();
if (ext==".bmp")
{
bmp->LoadFromFile(name);
}
if (ext==".jpg")
{
TJPEGImage *jpg=new TJPEGImage;
jpg->LoadFromFile(name);
bmp->Assign(jpg);
delete jpg;
}
bmp->HandleType=bmDIB;
bmp->PixelFormat=pf32bit;
Form1->ptb_gfx->Width=bmp->Width;
Form1->ClientHeight=bmp->Height;
Form1->ClientWidth=(bmp->Width<<1)+32;
}
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner):TForm(Owner)
{
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormDestroy(TObject *Sender)
{
delete bmp;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
draw();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::flb_picChange(TObject *Sender)
{
load(flb_pic->FileName);
update();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormActivate(TObject *Sender)
{
flb_pic->SetFocus();
flb_pic->Update();
if (flb_pic->ItemIndex==-1)
if (flb_pic->Items->Count>0)
{
flb_pic->ItemIndex=0;
flb_picChange(this);
}
}
//---------------------------------------------------------------------------
只需忽略 VCL 内容并将生成的文本转换为您可以使用的任何内容。我也使用我的动态列表模板:
List<double> xxx;
等同于 double xxx[];
xxx.add(5);
将 5
添加到列表末尾
xxx[7]
访问数组元素(安全)
xxx.dat[7]
访问数组元素(不安全但直接访问速度快)
xxx.num
是数组实际使用的大小
xxx.reset()
清空数组并设置xxx.num=0
xxx.allocate(100)
为 100
项预分配 space
所以使用你得到的任何列表或重新编码或使用 std::vector
代替...
我从你的图片中删除了文字:
这是使用它作为输入的结果:
+-----------+------------------ |
| | |
| | |
| | | | +-----------+
| | | | |
| | | | |
| +-----------+ +-----+ |
| | | |
| | | |
+------ | +-----+ | |
| | | |
| | | |
| ------+-----------+------ |
| |
| |
| ------------------------------+
这里是保存的调试位图(从左到右:BW、Grid、Maze):
代码中唯一重要的东西是函数:
AnsiString bmp2lintxt(Graphics::TBitmap *bmp);
来自 VCL(基于 GDI)位图的 returns 文本。