การแปลงภาพเป็นศิลปะ ASCII


105

อารัมภบท

หัวข้อนี้จะปรากฏขึ้นที่นี่ใน Stack Overflow เป็นครั้งคราว แต่มักจะถูกลบออกเนื่องจากเป็นคำถามที่เขียนไม่ดี ฉันเห็นคำถามดังกล่าวมากมายและจากนั้นก็เงียบจากOP (ตัวแทนต่ำปกติ) เมื่อมีการร้องขอข้อมูลเพิ่มเติม ในบางครั้งหากข้อมูลที่ป้อนนั้นดีพอสำหรับฉันฉันตัดสินใจที่จะตอบกลับด้วยคำตอบและโดยปกติจะได้รับการโหวตเพิ่มขึ้นไม่กี่ครั้งต่อวันในขณะที่ใช้งานอยู่ แต่หลังจากนั้นไม่กี่สัปดาห์คำถามจะถูกลบ / ลบและทั้งหมดเริ่มต้นจาก จุดเริ่มต้น. ดังนั้นฉันจึงตัดสินใจเขียนคำถามและคำตอบนี้เพื่อให้สามารถอ้างอิงคำถามดังกล่าวได้โดยตรงโดยไม่ต้องเขียนคำตอบซ้ำแล้วซ้ำอีก ...

อีกเหตุผลหนึ่งคือเมตาเธรดนี้มีเป้าหมายที่ฉันดังนั้นหากคุณได้รับข้อมูลเพิ่มเติมโปรดแสดงความคิดเห็น

คำถาม

ฉันจะแปลงภาพบิตแมปเป็นASCII artโดยใช้C ++ ได้อย่างไร

ข้อ จำกัด บางประการ:

  • ภาพระดับสีเทา
  • โดยใช้แบบอักษรที่เว้นระยะห่างแบบโมโน
  • ทำให้มันง่าย (ไม่ใช้สิ่งที่สูงเกินไปสำหรับโปรแกรมเมอร์ระดับเริ่มต้น)

นี่คือหน้า Wikipedia ที่เกี่ยวข้องศิลปะ ASCII (ขอบคุณ @RogerRowland)

นี่คือเขาวงกตที่คล้ายกับ Q&A การแปลง ASCII Art


เมื่อใช้หน้าวิกินี้เป็นข้อมูลอ้างอิงคุณสามารถอธิบายได้ว่าคุณกำลังอ้างถึงศิลปะ ASCII ประเภทใด สำหรับฉันดูเหมือนว่า "การแปลงรูปภาพเป็นข้อความ" ซึ่งเป็นการค้นหาแบบ "ธรรมดา" ตั้งแต่พิกเซลสีเทาไปจนถึงอักขระข้อความที่เกี่ยวข้องดังนั้นฉันจึงสงสัยว่าคุณหมายถึงอะไรที่แตกต่างออกไป ดูเหมือนว่าคุณจะตอบตัวเองอยู่ดีว่า .....
Roger Rowland

ที่เกี่ยวข้อง: stackoverflow.com/q/26347985/2564301
usr2564301

@RogerRowland ทั้งแบบเรียบง่าย (ตามความเข้มของระดับสีเทาเท่านั้น) และขั้นสูงยิ่งขึ้นโดยคำนึงถึงรูปร่างของตัวละครด้วย (แต่ก็ยังง่ายพอ)
Spektre

1
แม้ว่าผลงานของคุณจะยอดเยี่ยม แต่ฉันก็จะขอบคุณตัวอย่างที่เลือกมากขึ้น
kmote

@TimCastelijns หากคุณอ่านอารัมภบทแล้วคุณจะเห็นว่านี่ไม่ใช่ครั้งแรกที่มีการร้องขอคำตอบประเภทนี้ (และผู้มีสิทธิเลือกตั้งส่วนใหญ่ตั้งแต่เริ่มต้นซึ่งคุ้นเคยกับคำถามก่อนหน้านี้ไม่กี่คำถามที่เหลือจึงโหวตตามนั้น) เนื่องจากนี่คือคำถาม & คำตอบไม่เพียงถามฉันไม่ได้เสียเวลากับส่วนQมากเกินไป(ซึ่งเป็นความผิดของฉันฉันยอมรับ) ได้เพิ่มข้อ จำกัด บางประการให้กับคำถามหากคุณมีคำถามที่ดีกว่าอย่าลังเลที่จะแก้ไข
Spektre

คำตอบ:


156

มีอีกวิธีสำหรับภาพเพื่อการแปลงศิลปะ ASCII ซึ่งจะขึ้นอยู่กับการใช้ส่วนใหญ่เป็นแบบอักษรโมโนระยะห่าง เพื่อความเรียบง่ายฉันยึดติดกับพื้นฐานเท่านั้น:

ตามความเข้มของพิกเซล / พื้นที่ (การแรเงา)

วิธีนี้จะจัดการกับแต่ละพิกเซลของพื้นที่พิกเซลเป็นจุดเดียว แนวคิดคือการคำนวณความเข้มระดับสีเทาเฉลี่ยของจุดนี้แล้วแทนที่ด้วยอักขระที่มีความเข้มใกล้เคียงมากพอที่จะคำนวณได้ ด้วยเหตุนี้เราจึงต้องมีรายการอักขระที่ใช้งานได้ซึ่งแต่ละตัวมีความเข้มที่คำนวณไว้ล่วงหน้า mapขอเรียกว่าตัวละคร ในการเลือกให้เร็วขึ้นว่าตัวละครใดดีที่สุดสำหรับความเข้มมีสองวิธี:

  1. แผนที่อักขระความเข้มแบบกระจายเชิงเส้น

    ดังนั้นเราจึงใช้เฉพาะอักขระที่มีความเข้มต่างกันในขั้นตอนเดียวกัน กล่าวอีกนัยหนึ่งเมื่อเรียงลำดับจากน้อยไปมากแล้ว:

     intensity_of(map[i])=intensity_of(map[i-1])+constant;
    

    นอกจากนี้เมื่อตัวละครของเราmapเรียงลำดับแล้วเราสามารถคำนวณอักขระได้โดยตรงจากความเข้ม (ไม่จำเป็นต้องค้นหา)

     character = map[intensity_of(dot)/constant];
    
  2. แผนที่อักขระความเข้มแบบกระจายโดยพลการ

    ดังนั้นเราจึงมีอักขระที่ใช้งานได้และความเข้มข้นของมัน เราต้องหาความเข้มที่ใกล้เคียงที่สุดintensity_of(dot)ดังนั้นอีกครั้งหากเราเรียงลำดับmap[]เราสามารถใช้การค้นหาแบบไบนารีมิฉะนั้นเราต้องใช้การO(n)ค้นหาระยะทางขั้นต่ำหรือO(1)พจนานุกรม บางครั้งเพื่อความเรียบง่ายตัวละครmap[]สามารถจัดการได้แบบกระจายเชิงเส้นทำให้เกิดการบิดเบือนแกมม่าเล็กน้อยโดยปกติจะมองไม่เห็นในผลลัพธ์เว้นแต่คุณจะรู้ว่าต้องค้นหาอะไร

การแปลงตามความเข้มนั้นยอดเยี่ยมสำหรับภาพระดับสีเทา (ไม่ใช่แค่ขาวดำ) หากคุณเลือกจุดเป็นพิกเซลเดียวผลลัพธ์จะมีขนาดใหญ่ (หนึ่งพิกเซล -> อักขระเดี่ยว) ดังนั้นสำหรับภาพขนาดใหญ่จะมีการเลือกพื้นที่ (คูณขนาดตัวอักษร) แทนเพื่อรักษาอัตราส่วนและไม่ขยายมากเกินไป

ทำอย่างไร:

  1. แบ่งภาพออกเป็น (ระดับสีเทา) พิกเซลหรือ (สี่เหลี่ยม) จุด s
  2. คำนวณความเข้มของแต่ละพิกเซล / พื้นที่
  3. แทนที่ด้วยอักขระจากแผนที่อักขระที่มีความเข้มใกล้เคียงที่สุด

ในฐานะที่เป็นตัวละครmapคุณสามารถใช้อักขระใดก็ได้ แต่ผลลัพธ์จะดีขึ้นหากตัวละครมีพิกเซลกระจายอย่างเท่าเทียมกันตามพื้นที่อักขระ สำหรับการเริ่มต้นคุณสามารถใช้:

  • char map[10]=" .,:;ox%#@";

เรียงลำดับจากมากไปหาน้อยและแสร้งทำเป็นว่าเป็นการกระจายเชิงเส้น

ดังนั้นหากความเข้มของพิกเซล / พื้นที่เป็นi = <0-255>อักขระที่แทนที่จะเป็น

  • map[(255-i)*10/256];

หาก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) และอักขระที่แสดงผล คุณสามารถเริ่มต้นด้วยผลรวมไร้เดียงสาของความแตกต่างสัมบูรณ์ระหว่างพิกเซล แต่นั่นจะนำไปสู่ผลลัพธ์ที่ไม่ดีนักเนื่องจากการเลื่อนเพียงหนึ่งพิกเซลจะทำให้ระยะทางมีขนาดใหญ่ คุณสามารถใช้ความสัมพันธ์หรือเมตริกอื่นแทนได้ อัลกอริทึมโดยรวมเกือบจะเหมือนกับวิธีการก่อนหน้านี้:

  1. ดังนั้นอย่างสม่ำเสมอแบ่งภาพไป (สีเทาขนาด) พื้นที่สี่เหลี่ยมdot 's

    ควรใช้อัตราส่วนภาพเดียวกับอักขระแบบอักษรที่แสดงผล (จะรักษาอัตราส่วนภาพไว้อย่าลืมว่าโดยปกติอักขระจะทับซ้อนกันเล็กน้อยบนแกน x)

  2. คำนวณความเข้มของแต่ละพื้นที่ ( dot)

  3. แทนที่ด้วยอักขระจากอักขระที่mapมีความเข้ม / รูปร่างใกล้เคียงที่สุด

เราจะคำนวณระยะห่างระหว่างอักขระกับจุดได้อย่างไร? นั่นคือส่วนที่ยากที่สุดของแนวทางนี้ ขณะทำการทดลองฉันพัฒนาความประนีประนอมระหว่างความเร็วคุณภาพและความเรียบง่าย:

  1. แบ่งพื้นที่อักขระเป็นโซน

    โซน

    • คำนวณความเข้มแยกต่างหากสำหรับโซนซ้ายขวาขึ้นลงและกึ่งกลางของอักขระแต่ละตัวจากตัวอักษรการแปลงของคุณ ( map)
    • i=(i*256)/(xs*ys)ปกติเข้มทั้งหมดเพื่อให้พวกเขามีความเป็นอิสระกับขนาดพื้นที่
  2. ประมวลผลภาพต้นฉบับในพื้นที่สี่เหลี่ยมผืนผ้า

    • (ด้วยอัตราส่วนภาพเดียวกับแบบอักษรเป้าหมาย)
    • สำหรับแต่ละพื้นที่ให้คำนวณความเข้มในลักษณะเดียวกับในสัญลักษณ์แสดงหัวข้อย่อย # 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"และส่งไปยังบันทึกช่วยจำเพื่อให้เห็นภาพ

สำหรับผู้ที่ไม่มี 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 Grabber ตัวอย่าง

ตอนนี้ฉันสามารถดูแม้แต่วิดีโอในASCIIartเพื่อความสนุกสนาน บางคนดีจริงๆ :)

มือ

หากคุณต้องการลองใช้สิ่งนี้ในGLSLดูสิ่งนี้:


30
คุณทำได้ดีมากที่นี่! ขอบคุณ! และฉันชอบการเซ็นเซอร์ ASCII!
Ander Biguri

1
ข้อเสนอแนะสำหรับการปรับปรุง: หาอนุพันธ์แบบกำหนดทิศทางไม่ใช่แค่ความเข้มข้น
Yakk - Adam Nevraumont

1
@ ยักดูแลให้ละเอียด?
tariksbl

2
@tarik ไม่เพียงจับคู่กับความเข้มเท่านั้น แต่ยังรวมถึงอนุพันธ์: หรือ band pass ช่วยเพิ่มขอบ โดยพื้นฐานแล้วความรุนแรงไม่ใช่สิ่งเดียวที่ผู้คนมองเห็นพวกเขาเห็น Gradiants และขอบ
Yakk - Adam Nevraumont

1
@ ยักการแบ่งโซนทำสิ่งนั้นทางอ้อม อาจจะดีกว่านั้นก็คือใช้ตัวจับเป็น3x3โซนและเปรียบเทียบDCT s แต่นั่นจะทำให้ประสิทธิภาพลดลงมากฉันคิดว่า
Spektre
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.