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[]
को रैखिक रूप से वितरित के रूप में संभाला जा सकता है, जिससे थोड़ी सी गामा विरूपण होता है, आमतौर पर परिणाम में अनदेखी जब तक आप नहीं जानते कि क्या देखना है।
तीव्रता-आधारित रूपांतरण ग्रे-स्केल छवियों (न केवल काले और सफेद) के लिए भी महान है। यदि आप एक एकल पिक्सेल के रूप में डॉट का चयन करते हैं, तो परिणाम बड़े (एक पिक्सेल -> एकल चरित्र) हो जाता है, इसलिए बड़ी छवियों के लिए पहलू अनुपात को संरक्षित करने के लिए एक क्षेत्र (फ़ॉन्ट आकार का गुणा) का चयन किया जाता है और बहुत अधिक विस्तार न करें।
यह कैसे करना है:
- समान रूप से (ग्रे पैमाने पर) पिक्सल या में छवि को विभाजित (आयताकार) क्षेत्रों डॉट रों
- प्रत्येक पिक्सेल / क्षेत्र की तीव्रता की गणना करें
- इसे चरित्र मानचित्र से चरित्र द्वारा निकटतम तीव्रता से बदलें
चरित्र के रूप में 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;
जब तक आप Borland / Embarcadero वातावरण का उपयोग नहीं करते हैं, आपको VCL सामान को बदलने / अनदेखा करने की आवश्यकता है।
mm_log
वह मेमो है जहां टेक्स्ट को आउटपुट किया जाता है
bmp
इनपुट बिटमैप है
AnsiString
1 से अनुक्रमित VCL प्रकार स्ट्रिंग है, 0 से नहीं char*
!!!
यह परिणाम है: थोड़ा NSFW तीव्रता उदाहरण छवि
बाईं ओर ASCII कला आउटपुट (फ़ॉन्ट आकार 5 पिक्सेल) है, और दाईं ओर इनपुट छवि कुछ बार ज़ूम की गई है। जैसा कि आप देख सकते हैं, आउटपुट बड़ा पिक्सेल है -> चरित्र। यदि आप पिक्सेल के बजाय बड़े क्षेत्रों का उपयोग करते हैं तो ज़ूम छोटा होता है, लेकिन निश्चित रूप से आउटपुट नेत्रहीन सुखदायक होता है। यह दृष्टिकोण कोड / प्रक्रिया के लिए बहुत आसान और तेज़ है।
जब आप अधिक उन्नत चीजें जोड़ते हैं जैसे:
- स्वचालित मानचित्र संगणना
- स्वचालित पिक्सेल / क्षेत्र आकार चयन
- पहलू अनुपात सुधार
तब आप बेहतर परिणामों के साथ अधिक जटिल छवियों को संसाधित कर सकते हैं:
यहाँ 1: 1 अनुपात (वर्णों को देखने के लिए ज़ूम) में परिणाम है:
बेशक, क्षेत्र के नमूने के लिए आप छोटे विवरण खो देते हैं। यह उसी आकार की एक छवि है जिसका पहला उदाहरण क्षेत्रों के साथ नमूना है:
थोड़ा NSFW तीव्रता उन्नत उदाहरण छवि
जैसा कि आप देख सकते हैं, यह बड़ी छवियों के लिए अधिक अनुकूल है।
चरित्र फिटिंग (छायांकन और ठोस ASCII कला के बीच संकर)
यह दृष्टिकोण समान तीव्रता और आकार वाले चरित्र के साथ क्षेत्र (कोई अधिक एकल पिक्सेल डॉट्स) को बदलने की कोशिश करता है। यह बेहतर परिणाम की ओर जाता है, यहां तक कि पिछले दृष्टिकोण की तुलना में बड़े फोंट का उपयोग किया जाता है। दूसरी ओर, यह दृष्टिकोण थोड़ा धीमा है। ऐसा करने के लिए और अधिक तरीके हैं, लेकिन मुख्य विचार छवि क्षेत्र ( dot
) और गाया चरित्र के बीच अंतर (दूरी) की गणना करना है । आप पिक्सेल के बीच पूर्ण अंतर की भोली राशि के साथ शुरू कर सकते हैं, लेकिन यह बहुत अच्छे परिणाम नहीं देगा क्योंकि एक-पिक्सेल पारी भी दूरी को बड़ा कर देगी। इसके बजाय आप सहसंबंध या विभिन्न मैट्रिक्स का उपयोग कर सकते हैं। समग्र एल्गोरिथ्म पिछले दृष्टिकोण के समान ही है:
तो समान रूप से करने के लिए छवि को विभाजित (ग्रे पैमाने पर) आयताकार क्षेत्रों डॉट की
रेंडर किए गए फ़ॉन्ट वर्णों के समान ही आदर्श अनुपात (यह पहलू अनुपात को संरक्षित करेगा। यह मत भूलो कि आमतौर पर अक्षर एक्स-अक्ष पर थोड़ा ओवरलैप करते हैं)
प्रत्येक क्षेत्र की तीव्रता की गणना करें ( 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; // Character
int il, ir, iu ,id, ic; // Intensity of part: left,right,up,down,center
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) // p source image, (xs,ys) area size, (xx,yy) area position
{
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;
}
// Normalize
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) // Character sized areas
{
int i, i0, d, d0;
int xs, ys, xf, yf, x, xx, y, yy;
DWORD **p = NULL,**q = NULL; // Bitmap direct pixel access
Graphics::TBitmap *tmp; // Temporary bitmap for single character
AnsiString txt = ""; // Output ASCII art text
AnsiString eol = "\r\n"; // End of line sequence
intensity map[97]; // Character map
intensity gfx;
// Input image size
xs = bmp->Width;
ys = bmp->Height;
// Output font size
xf = font->Size; if (xf<0) xf =- xf;
yf = font->Height; if (yf<0) yf =- yf;
for (;;) // Loop to simplify the dynamic allocation error handling
{
// Allocate and initialise buffers
tmp = new Graphics::TBitmap;
if (tmp==NULL)
break;
// Allow 32 bit pixel access as DWORD/int pointer
tmp->HandleType = bmDIB; bmp->HandleType = bmDIB;
tmp->PixelFormat = pf32bit; bmp->PixelFormat = pf32bit;
// Copy target font properties to tmp
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;
// Direct pixel access to bitmaps
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];
// Create character map
for (x=0, d=32; d<128; d++, x++)
{
map[x].c = char(DWORD(d));
// Clear tmp
tmp->Canvas->FillRect(TRect(0, 0, xf, yf));
// Render tested character to tmp
tmp->Canvas->TextOutA(0, 0, map[x].c);
// Compute intensity
map[x].compute(q, xf, yf, 0, 0);
}
map[x].c = 0;
// Loop through the image by zoomed character size step
xf -= xf/3; // Characters are usually overlapping by 1/3
xs -= xs % xf;
ys -= ys % yf;
for (y=0; y<ys; y+=yf, txt += eol)
for (x=0; x<xs; x+=xf)
{
// Compute intensity
gfx.compute(p, xf, yf, x, y);
// Find the closest match in map[]
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;
}
}
// Add fitted character to output
txt += map[i0].c;
}
break;
}
// Free buffers
if (tmp) delete tmp;
if (p ) delete[] p;
return txt;
}
//---------------------------------------------------------------------------
AnsiString bmp2txt_small(Graphics::TBitmap *bmp) // pixel sized areas
{
AnsiString m = " `'.,:;i+o*%&$#@"; // Constant character map
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"
विज़ुअलाइज़ करने के लिए मेमो में भेजा जाता है।
वीसीएल के बिना उन लोगों के लिए, वीसीएल सामान को अनदेखा करें और AnsiString
आपके पास किसी भी स्ट्रिंग प्रकार के साथ बदलें , और Graphics::TBitmap
किसी भी बिटमैप या छवि वर्ग के साथ जो आपके पास पिक्सेल पहुंच क्षमता के साथ निपटान में है।
एक बहुत ही महत्वपूर्ण नोट यह है कि यह सेटिंग्स का उपयोग करता है mm_txt->Font
, इसलिए सुनिश्चित करें कि आपने सेट किया है:
Font->Pitch = fpFixed
Font->Charset = OEM_CHARSET
Font->Name = "System"
इस काम को ठीक से करने के लिए, अन्यथा फ़ॉन्ट को मोनो-स्पेस के रूप में नहीं संभाला जाएगा। माउस व्हील सिर्फ भिन्न फ़ॉन्ट आकारों पर परिणाम देखने के लिए फ़ॉन्ट आकार को ऊपर / नीचे बदलता है।
[टिप्पणियाँ]
- वर्ड पोर्ट्रेट विज़ुअलाइज़ेशन देखें
- बिटमैप / फ़ाइल एक्सेस और टेक्स्ट आउटपुट क्षमताओं वाली भाषा का उपयोग करें
- मैं दृढ़ता से पहले दृष्टिकोण के साथ शुरू करने की सलाह देता हूं क्योंकि यह बहुत सरल और सरल है, और उसके बाद ही दूसरे पर जाएं (जो कि पहले के संशोधन के रूप में किया जा सकता है, इसलिए अधिकांश कोड वैसे भी रहता है)
- उल्टे तीव्रता (काले पिक्सेल अधिकतम मूल्य है) के साथ गणना करना एक अच्छा विचार है क्योंकि मानक पाठ पूर्वावलोकन एक सफेद पृष्ठभूमि पर है, इसलिए बहुत बेहतर परिणाम देता है।
- आप उपखंड क्षेत्रों के आकार, गणना और लेआउट के साथ प्रयोग कर सकते हैं या
3x3
इसके बजाय कुछ ग्रिड का उपयोग कर सकते हैं ।
तुलना
अंत में यहाँ एक ही इनपुट पर दो दृष्टिकोणों के बीच तुलना है:
हरे रंग की बिंदी के रूप में चिह्नित छवियां # 2 के साथ की जाती हैं और लाल वाले # 1 के साथ , सभी छह-पिक्सेल फ़ॉन्ट आकार पर। जैसा कि आप प्रकाश बल्ब छवि पर देख सकते हैं, आकार-संवेदनशील दृष्टिकोण बहुत बेहतर है (भले ही # 1 2x ज़ूम की गई स्रोत छवि पर किया गया हो)।
शांत अनुप्रयोग
आज के नए प्रश्नों को पढ़ते हुए, मुझे एक अच्छे अनुप्रयोग का विचार मिला जो डेस्कटॉप के चयनित क्षेत्र को पकड़ता है और लगातार ASCIIart कनवर्टर को खिलाता है और परिणाम देखता है। एक घंटे की कोडिंग के बाद, यह हो गया है और मैं इस परिणाम से इतना संतुष्ट हूं कि मुझे इसे यहां जोड़ना होगा।
ठीक है आवेदन सिर्फ दो खिड़कियों के होते हैं। पहली मास्टर विंडो मूल रूप से छवि चयन और पूर्वावलोकन के बिना मेरी पुरानी कनवर्टर विंडो है (ऊपर सभी सामान इसमें है)। इसमें ASCII पूर्वावलोकन और रूपांतरण सेटिंग हैं। दूसरी विंडो हड़पने वाले क्षेत्र के चयन के लिए पारदर्शी के साथ एक खाली रूप है (कोई भी कार्यक्षमता नहीं है)।
अब एक टाइमर पर, मैं सिर्फ चयनित क्षेत्र को चयन प्रपत्र द्वारा हड़पता हूं , इसे रूपांतरण में पास करता हूं , और ASCIIart का पूर्वावलोकन करता हूं ।
इसलिए आप उस क्षेत्र को संलग्न करते हैं जिसे आप चयन विंडो द्वारा परिवर्तित करना चाहते हैं और मास्टर विंडो में परिणाम देखते हैं। यह एक खेल, दर्शक आदि हो सकता है, यह इस तरह दिखता है:
इसलिए अब मैं मनोरंजन के लिए ASCIIart में भी वीडियो देख सकता हूं । कुछ बहुत अच्छे हैं :)।
यदि आप इसे GLSL में लागू करने का प्रयास करना चाहते हैं, तो इस पर एक नज़र डालें: