มีอีกวิธีสำหรับภาพเพื่อการแปลงศิลปะ ASCII ซึ่งจะขึ้นอยู่กับการใช้ส่วนใหญ่เป็นแบบอักษรโมโนระยะห่าง เพื่อความเรียบง่ายฉันยึดติดกับพื้นฐานเท่านั้น:
ตามความเข้มของพิกเซล / พื้นที่ (การแรเงา)
วิธีนี้จะจัดการกับแต่ละพิกเซลของพื้นที่พิกเซลเป็นจุดเดียว แนวคิดคือการคำนวณความเข้มระดับสีเทาเฉลี่ยของจุดนี้แล้วแทนที่ด้วยอักขระที่มีความเข้มใกล้เคียงมากพอที่จะคำนวณได้ ด้วยเหตุนี้เราจึงต้องมีรายการอักขระที่ใช้งานได้ซึ่งแต่ละตัวมีความเข้มที่คำนวณไว้ล่วงหน้า map
ขอเรียกว่าตัวละคร ในการเลือกให้เร็วขึ้นว่าตัวละครใดดีที่สุดสำหรับความเข้มมีสองวิธี:
แผนที่อักขระความเข้มแบบกระจายเชิงเส้น
ดังนั้นเราจึงใช้เฉพาะอักขระที่มีความเข้มต่างกันในขั้นตอนเดียวกัน กล่าวอีกนัยหนึ่งเมื่อเรียงลำดับจากน้อยไปมากแล้ว:
intensity_of(map[i])=intensity_of(map[i-1])+constant;
นอกจากนี้เมื่อตัวละครของเราmap
เรียงลำดับแล้วเราสามารถคำนวณอักขระได้โดยตรงจากความเข้ม (ไม่จำเป็นต้องค้นหา)
character = map[intensity_of(dot)/constant];
แผนที่อักขระความเข้มแบบกระจายโดยพลการ
ดังนั้นเราจึงมีอักขระที่ใช้งานได้และความเข้มข้นของมัน เราต้องหาความเข้มที่ใกล้เคียงที่สุดintensity_of(dot)
ดังนั้นอีกครั้งหากเราเรียงลำดับmap[]
เราสามารถใช้การค้นหาแบบไบนารีมิฉะนั้นเราต้องใช้การO(n)
ค้นหาระยะทางขั้นต่ำหรือO(1)
พจนานุกรม บางครั้งเพื่อความเรียบง่ายตัวละครmap[]
สามารถจัดการได้แบบกระจายเชิงเส้นทำให้เกิดการบิดเบือนแกมม่าเล็กน้อยโดยปกติจะมองไม่เห็นในผลลัพธ์เว้นแต่คุณจะรู้ว่าต้องค้นหาอะไร
การแปลงตามความเข้มนั้นยอดเยี่ยมสำหรับภาพระดับสีเทา (ไม่ใช่แค่ขาวดำ) หากคุณเลือกจุดเป็นพิกเซลเดียวผลลัพธ์จะมีขนาดใหญ่ (หนึ่งพิกเซล -> อักขระเดี่ยว) ดังนั้นสำหรับภาพขนาดใหญ่จะมีการเลือกพื้นที่ (คูณขนาดตัวอักษร) แทนเพื่อรักษาอัตราส่วนและไม่ขยายมากเกินไป
ทำอย่างไร:
- แบ่งภาพออกเป็น (ระดับสีเทา) พิกเซลหรือ (สี่เหลี่ยม) จุด s
- คำนวณความเข้มของแต่ละพิกเซล / พื้นที่
- แทนที่ด้วยอักขระจากแผนที่อักขระที่มีความเข้มใกล้เคียงที่สุด
ในฐานะที่เป็นตัวละครmap
คุณสามารถใช้อักขระใดก็ได้ แต่ผลลัพธ์จะดีขึ้นหากตัวละครมีพิกเซลกระจายอย่างเท่าเทียมกันตามพื้นที่อักขระ สำหรับการเริ่มต้นคุณสามารถใช้:
char map[10]=" .,:;ox%#@";
เรียงลำดับจากมากไปหาน้อยและแสร้งทำเป็นว่าเป็นการกระจายเชิงเส้น
ดังนั้นหากความเข้มของพิกเซล / พื้นที่เป็นi = <0-255>
อักขระที่แทนที่จะเป็น
หากi==0
พิกเซล / พื้นที่เป็นสีดำหากi==127
พิกเซล / พื้นที่เป็นสีเทาและหากi==255
พิกเซล / พื้นที่เป็นสีขาว คุณสามารถทดลองกับตัวละครต่างๆภายในmap[]
...
นี่คือตัวอย่างโบราณของฉันใน C ++ และ VCL:
AnsiString m = " .,:;ox%#@";
Graphics::TBitmap *bmp = new Graphics::TBitmap;
bmp->LoadFromFile("pic.bmp");
bmp->HandleType = bmDIB;
bmp->PixelFormat = pf24bit;
int x, y, i, c, l;
BYTE *p;
AnsiString s, endl;
endl = char(13); endl += char(10);
l = m.Length();
s ="";
for (y=0; y<bmp->Height; y++)
{
p = (BYTE*)bmp->ScanLine[y];
for (x=0; x<bmp->Width; x++)
{
i = p[x+x+x+0];
i += p[x+x+x+1];
i += p[x+x+x+2];
i = (i*l)/768;
s += m[l-i];
}
s += endl;
}
mm_log->Lines->Text = s;
mm_log->Lines->SaveToFile("pic.txt");
delete bmp;
คุณต้องเปลี่ยน / ละเว้นสิ่ง VCL เว้นแต่คุณจะใช้สภาพแวดล้อมBorland / Embarcadero
mm_log
คือบันทึกที่มีการส่งข้อความออกมา
bmp
คือบิตแมปอินพุต
AnsiString
เป็นสตริงประเภท VCL ที่สร้างดัชนีจาก 1 ไม่ใช่จาก 0 เป็นchar*
!!!
นี่คือผลลัพธ์: ภาพตัวอย่างความเข้ม NSFW เล็กน้อย
ทางด้านซ้ายคือเอาต์พุตศิลปะ ASCII (ขนาดตัวอักษร 5 พิกเซล) และภาพอินพุตด้านขวาจะซูมสองสามครั้ง อย่างที่คุณเห็นผลลัพธ์มีขนาดใหญ่กว่าพิกเซล -> อักขระ หากคุณใช้พื้นที่ขนาดใหญ่แทนพิกเซลการซูมก็จะเล็กลง แต่แน่นอนว่าผลลัพธ์ที่ได้จะไม่ค่อยน่าพึงพอใจนัก แนวทางนี้ง่ายและรวดเร็วในการเขียนโค้ด / ประมวลผล
เมื่อคุณเพิ่มสิ่งขั้นสูงเพิ่มเติมเช่น:
- การคำนวณแผนที่อัตโนมัติ
- การเลือกขนาดพิกเซล / พื้นที่อัตโนมัติ
- การแก้ไขอัตราส่วน
จากนั้นคุณสามารถประมวลผลภาพที่ซับซ้อนมากขึ้นพร้อมผลลัพธ์ที่ดีกว่า:
นี่คือผลลัพธ์ในอัตราส่วน 1: 1 (ซูมเพื่อดูตัวอักษร):
แน่นอนว่าสำหรับการสุ่มตัวอย่างพื้นที่คุณจะสูญเสียรายละเอียดเล็ก ๆ น้อย ๆ ไป นี่คือภาพขนาดเดียวกับตัวอย่างแรกที่มีพื้นที่:
ภาพตัวอย่างขั้นสูงความเข้ม NSFW เล็กน้อย
อย่างที่คุณเห็นมันเหมาะสำหรับภาพขนาดใหญ่มากกว่า
การปรับตัวอักษร (ลูกผสมระหว่างการแรเงาและศิลปะ ASCII แบบทึบ)
วิธีนี้พยายามแทนที่พื้นที่ (ไม่มีจุดพิกเซลเดียวอีกต่อไป) ด้วยอักขระที่มีความเข้มและรูปร่างใกล้เคียงกัน สิ่งนี้นำไปสู่ผลลัพธ์ที่ดีขึ้นแม้จะใช้แบบอักษรที่ใหญ่กว่าเมื่อเปรียบเทียบกับวิธีการก่อนหน้านี้ ในทางกลับกันแนวทางนี้ค่อนข้างช้ากว่าแน่นอน มีหลายวิธีในการดำเนินการนี้ แต่แนวคิดหลักคือการคำนวณความแตกต่าง (ระยะทาง) ระหว่างพื้นที่ภาพ ( dot
) และอักขระที่แสดงผล คุณสามารถเริ่มต้นด้วยผลรวมไร้เดียงสาของความแตกต่างสัมบูรณ์ระหว่างพิกเซล แต่นั่นจะนำไปสู่ผลลัพธ์ที่ไม่ดีนักเนื่องจากการเลื่อนเพียงหนึ่งพิกเซลจะทำให้ระยะทางมีขนาดใหญ่ คุณสามารถใช้ความสัมพันธ์หรือเมตริกอื่นแทนได้ อัลกอริทึมโดยรวมเกือบจะเหมือนกับวิธีการก่อนหน้านี้:
ดังนั้นอย่างสม่ำเสมอแบ่งภาพไป (สีเทาขนาด) พื้นที่สี่เหลี่ยมdot 's
ควรใช้อัตราส่วนภาพเดียวกับอักขระแบบอักษรที่แสดงผล (จะรักษาอัตราส่วนภาพไว้อย่าลืมว่าโดยปกติอักขระจะทับซ้อนกันเล็กน้อยบนแกน x)
คำนวณความเข้มของแต่ละพื้นที่ ( dot
)
แทนที่ด้วยอักขระจากอักขระที่map
มีความเข้ม / รูปร่างใกล้เคียงที่สุด
เราจะคำนวณระยะห่างระหว่างอักขระกับจุดได้อย่างไร? นั่นคือส่วนที่ยากที่สุดของแนวทางนี้ ขณะทำการทดลองฉันพัฒนาความประนีประนอมระหว่างความเร็วคุณภาพและความเรียบง่าย:
แบ่งพื้นที่อักขระเป็นโซน
- คำนวณความเข้มแยกต่างหากสำหรับโซนซ้ายขวาขึ้นลงและกึ่งกลางของอักขระแต่ละตัวจากตัวอักษรการแปลงของคุณ (
map
)
i=(i*256)/(xs*ys)
ปกติเข้มทั้งหมดเพื่อให้พวกเขามีความเป็นอิสระกับขนาดพื้นที่
ประมวลผลภาพต้นฉบับในพื้นที่สี่เหลี่ยมผืนผ้า
- (ด้วยอัตราส่วนภาพเดียวกับแบบอักษรเป้าหมาย)
- สำหรับแต่ละพื้นที่ให้คำนวณความเข้มในลักษณะเดียวกับในสัญลักษณ์แสดงหัวข้อย่อย # 1
- ค้นหาการจับคู่ที่ใกล้เคียงที่สุดจากความเข้มในตัวอักษรการแปลง
- เอาต์พุตอักขระที่ติดตั้ง
นี่คือผลลัพธ์สำหรับขนาดตัวอักษร = 7 พิกเซล
อย่างที่คุณเห็นผลลัพธ์เป็นที่น่าพอใจแม้จะใช้ขนาดตัวอักษรที่ใหญ่กว่า (ตัวอย่างวิธีการก่อนหน้านี้คือขนาดตัวอักษร 5 พิกเซล) เอาต์พุตมีขนาดใกล้เคียงกับภาพอินพุต (ไม่มีการซูม) ผลลัพธ์ที่ดีกว่าเกิดขึ้นได้เนื่องจากตัวอักษรมีความใกล้เคียงกับภาพต้นฉบับมากขึ้นไม่เพียง แต่ด้วยความเข้มเท่านั้น แต่ยังรวมถึงรูปร่างโดยรวมด้วยดังนั้นคุณจึงสามารถใช้ฟอนต์ที่ใหญ่ขึ้นและยังคงรักษารายละเอียดไว้ได้ (ถึงจุดหนึ่ง)
นี่คือรหัสที่สมบูรณ์สำหรับแอปพลิเคชันการแปลงตาม VCL:
#include <vcl.h>
#pragma hdrstop
#include "win_main.h"
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
Graphics::TBitmap *bmp=new Graphics::TBitmap;
class intensity
{
public:
char c;
int il, ir, iu ,id, ic;
intensity() { c=0; reset(); }
void reset() { il=0; ir=0; iu=0; id=0; ic=0; }
void compute(DWORD **p,int xs,int ys,int xx,int yy)
{
int x0 = xs>>2, y0 = ys>>2;
int x1 = xs-x0, y1 = ys-y0;
int x, y, i;
reset();
for (y=0; y<ys; y++)
for (x=0; x<xs; x++)
{
i = (p[yy+y][xx+x] & 255);
if (x<=x0) il+=i;
if (x>=x1) ir+=i;
if (y<=x0) iu+=i;
if (y>=x1) id+=i;
if ((x>=x0) && (x<=x1) &&
(y>=y0) && (y<=y1))
ic+=i;
}
i = xs*ys;
il = (il << 8)/i;
ir = (ir << 8)/i;
iu = (iu << 8)/i;
id = (id << 8)/i;
ic = (ic << 8)/i;
}
};
AnsiString bmp2txt_big(Graphics::TBitmap *bmp,TFont *font)
{
int i, i0, d, d0;
int xs, ys, xf, yf, x, xx, y, yy;
DWORD **p = NULL,**q = NULL;
Graphics::TBitmap *tmp;
AnsiString txt = "";
AnsiString eol = "\r\n";
intensity map[97];
intensity gfx;
xs = bmp->Width;
ys = bmp->Height;
xf = font->Size; if (xf<0) xf =- xf;
yf = font->Height; if (yf<0) yf =- yf;
for (;;)
{
tmp = new Graphics::TBitmap;
if (tmp==NULL)
break;
tmp->HandleType = bmDIB; bmp->HandleType = bmDIB;
tmp->PixelFormat = pf32bit; bmp->PixelFormat = pf32bit;
tmp->Canvas->Font->Assign(font);
tmp->SetSize(xf, yf);
tmp->Canvas->Font ->Color = clBlack;
tmp->Canvas->Pen ->Color = clWhite;
tmp->Canvas->Brush->Color = clWhite;
xf = tmp->Width;
yf = tmp->Height;
p = new DWORD*[ys];
if (p == NULL) break;
for (y=0; y<ys; y++)
p[y] = (DWORD*)bmp->ScanLine[y];
q = new DWORD*[yf];
if (q == NULL) break;
for (y=0; y<yf; y++)
q[y] = (DWORD*)tmp->ScanLine[y];
for (x=0, d=32; d<128; d++, x++)
{
map[x].c = char(DWORD(d));
tmp->Canvas->FillRect(TRect(0, 0, xf, yf));
tmp->Canvas->TextOutA(0, 0, map[x].c);
map[x].compute(q, xf, yf, 0, 0);
}
map[x].c = 0;
xf -= xf/3;
xs -= xs % xf;
ys -= ys % yf;
for (y=0; y<ys; y+=yf, txt += eol)
for (x=0; x<xs; x+=xf)
{
gfx.compute(p, xf, yf, x, y);
i0 = 0; d0 = -1;
for (i=0; map[i].c; i++)
{
d = abs(map[i].il-gfx.il) +
abs(map[i].ir-gfx.ir) +
abs(map[i].iu-gfx.iu) +
abs(map[i].id-gfx.id) +
abs(map[i].ic-gfx.ic);
if ((d0<0)||(d0>d)) {
d0=d; i0=i;
}
}
txt += map[i0].c;
}
break;
}
if (tmp) delete tmp;
if (p ) delete[] p;
return txt;
}
AnsiString bmp2txt_small(Graphics::TBitmap *bmp)
{
AnsiString m = " `'.,:;i+o*%&$#@";
int x, y, i, c, l;
BYTE *p;
AnsiString txt = "", eol = "\r\n";
l = m.Length();
bmp->HandleType = bmDIB;
bmp->PixelFormat = pf32bit;
for (y=0; y<bmp->Height; y++)
{
p = (BYTE*)bmp->ScanLine[y];
for (x=0; x<bmp->Width; x++)
{
i = p[(x<<2)+0];
i += p[(x<<2)+1];
i += p[(x<<2)+2];
i = (i*l)/768;
txt += m[l-i];
}
txt += eol;
}
return txt;
}
void update()
{
int x0, x1, y0, y1, i, l;
x0 = bmp->Width;
y0 = bmp->Height;
if ((x0<64)||(y0<64)) Form1->mm_txt->Text = bmp2txt_small(bmp);
else Form1->mm_txt->Text = bmp2txt_big (bmp, Form1->mm_txt->Font);
Form1->mm_txt->Lines->SaveToFile("pic.txt");
for (x1 = 0, i = 1, l = Form1->mm_txt->Text.Length();i<=l;i++) if (Form1->mm_txt->Text[i] == 13) { x1 = i-1; break; }
for (y1=0, i=1, l=Form1->mm_txt->Text.Length();i <= l; i++) if (Form1->mm_txt->Text[i] == 13) y1++;
x1 *= abs(Form1->mm_txt->Font->Size);
y1 *= abs(Form1->mm_txt->Font->Height);
if (y0<y1) y0 = y1; x0 += x1 + 48;
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)
{
bmp->LoadFromFile(name);
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)
{
load("pic.bmp");
update();
}
void __fastcall TForm1::FormDestroy(TObject *Sender)
{
delete bmp;
}
void __fastcall TForm1::FormPaint(TObject *Sender)
{
draw();
}
void __fastcall TForm1::FormMouseWheel(TObject *Sender, TShiftState Shift, int WheelDelta, TPoint &MousePos, bool &Handled)
{
int s = abs(mm_txt->Font->Size);
if (WheelDelta<0) s--;
if (WheelDelta>0) s++;
mm_txt->Font->Size = s;
update();
}
มันเป็นแอปพลิเคชันแบบฟอร์ม ( Form1
) ที่เรียบง่ายโดยมีตัวเดียวTMemo mm_txt
อยู่ในนั้น มันโหลดภาพ"pic.bmp"
จากนั้นตามความละเอียดให้เลือกวิธีที่จะใช้ในการแปลงเป็นข้อความที่บันทึก"pic.txt"
และส่งไปยังบันทึกช่วยจำเพื่อให้เห็นภาพ
สำหรับผู้ที่ไม่มี VCL ให้ละเว้นสิ่ง VCL และแทนที่AnsiString
ด้วยประเภทสตริงใด ๆ ที่คุณมีและGraphics::TBitmap
ด้วยบิตแมปหรือคลาสรูปภาพที่คุณมีพร้อมความสามารถในการเข้าถึงพิกเซล
หมายเหตุที่สำคัญมากคือสิ่งนี้ใช้การตั้งค่าของmm_txt->Font
ดังนั้นโปรดตรวจสอบว่าคุณได้ตั้งค่า:
Font->Pitch = fpFixed
Font->Charset = OEM_CHARSET
Font->Name = "System"
เพื่อให้ทำงานได้อย่างถูกต้องมิฉะนั้นแบบอักษรจะไม่ถูกจัดการเป็นแบบโมโนเว้นวรรค วงล้อของเมาส์จะเปลี่ยนขนาดตัวอักษรขึ้น / ลงเพื่อดูผลลัพธ์ของขนาดตัวอักษรต่างๆ
[หมายเหตุ]
- ดูการแสดงภาพ Word Portraits
- ใช้ภาษาที่มีความสามารถในการเข้าถึงบิตแมป / ไฟล์และเอาต์พุตข้อความ
- ฉันขอแนะนำอย่างยิ่งให้เริ่มต้นด้วยแนวทางแรกเนื่องจากง่ายมากตรงไปตรงมาและเรียบง่ายจากนั้นย้ายไปที่สอง (ซึ่งสามารถทำได้เป็นการแก้ไขข้อแรกดังนั้นโค้ดส่วนใหญ่จะยังคงอยู่เหมือนเดิม)
- เป็นความคิดที่ดีที่จะคำนวณด้วยความเข้มกลับหัว (พิกเซลสีดำคือค่าสูงสุด) เนื่องจากการแสดงตัวอย่างข้อความมาตรฐานอยู่บนพื้นหลังสีขาวจึงทำให้ได้ผลลัพธ์ที่ดีกว่ามาก
- คุณสามารถทดลองกับขนาดจำนวนและรูปแบบของโซนการแบ่งย่อยหรือใช้ตารางบางส่วนเช่น
3x3
แทน
การเปรียบเทียบ
ในที่สุดนี่คือการเปรียบเทียบระหว่างสองวิธีในอินพุตเดียวกัน:
ภาพที่ทำเครื่องหมายจุดสีเขียวจะใช้แนวทาง# 2และจุดสีแดงที่มี# 1ทั้งหมดมีขนาดตัวอักษรหกพิกเซล ดังที่คุณเห็นในภาพหลอดไฟวิธีการที่ไวต่อรูปร่างจะดีกว่ามาก (แม้ว่า# 1จะทำในภาพต้นฉบับที่ซูม 2 เท่าก็ตาม)
แอปพลิเคชั่นสุดเจ๋ง
ในขณะที่อ่านคำถามใหม่ของวันนี้ฉันได้ความคิดเกี่ยวกับแอปพลิเคชันที่ยอดเยี่ยมที่คว้าพื้นที่ที่เลือกของเดสก์ท็อปและป้อนข้อมูลไปยังตัวแปลงASCIIartอย่างต่อเนื่องและดูผลลัพธ์ หลังจากเขียนโค้ดไปหนึ่งชั่วโมงก็เสร็จแล้วและฉันก็พอใจกับผลลัพธ์มากจนต้องเพิ่มที่นี่
ตกลงแอปพลิเคชันประกอบด้วยหน้าต่างเพียงสองหน้าต่าง หน้าต่างหลักแรกเป็นหน้าต่างตัวแปลงเก่าของฉันโดยไม่ต้องเลือกภาพและดูตัวอย่าง (ทุกสิ่งข้างต้นอยู่ในนั้น) มีเพียงการแสดงตัวอย่าง ASCII และการตั้งค่าการแปลง หน้าต่างที่สองเป็นรูปแบบว่างเปล่าที่มีด้านในโปร่งใสสำหรับการเลือกพื้นที่จับ (ไม่มีฟังก์ชันใด ๆ )
ตอนนี้ในเวลาที่ฉันเพิ่งคว้าพื้นที่ที่เลือกตามรูปแบบการเลือกผ่านไปแปลงและดูตัวอย่างASCIIart
ดังนั้นคุณจึงใส่พื้นที่ที่คุณต้องการแปลงโดยหน้าต่างการเลือกและดูผลลัพธ์ในหน้าต่างหลัก อาจเป็นเกมโปรแกรมดู ฯลฯ ซึ่งมีลักษณะดังนี้:
ตอนนี้ฉันสามารถดูแม้แต่วิดีโอในASCIIartเพื่อความสนุกสนาน บางคนดีจริงๆ :)
หากคุณต้องการลองใช้สิ่งนี้ในGLSLดูสิ่งนี้: