Generate an image from arbitrary strings

后端 未结 1 1022
走了就别回头了
走了就别回头了 2021-01-07 14:37

I\'ve seen plenty of image to ascii art converters, but what I need is a little more complex. Instead of a mosaic image created from ascii chars I need to be able to use a p

相关标签:
1条回答
  • 2021-01-07 14:48

    Not really suitable for Stack Overflow, but an interesting project nevertheless. So I had a go at it for a bit of fun and see how far I could get.

    I think it comes down to this:

    1. calculate a gray value for a set of ASCII characters;
    2. calculate the 'best fit' for each string;
    3. repeat 2. until done.

    I don't think "without duplicates" is feasible, unless you have very small images and lots of candidate strings. This is what I came up with, using my avatar and my current badges as list of strings. (I excluded my "c" badge; somehow my program decided that was the 'best fit' for large patches, which wasn't very attractive. Mental note: Do Not Include 1-Character Strings.)

    QuorumQuorumAutobiographerQuorumQuorumQuorumJongwareQuorumQuorumQuorumQuorumQuor
    umQuorumQuorumAutobiographerQuorumQuorumSupporterQuorumQuorumProofreaderQuorumQu
    orumQuorumAutobiographerQuorumQuorumQuorumQuorumQuorumQuorumAutobiographerQuorum
    QuorumQuorumAutobiographerQuorumQuorumQuorumQuorumQuorumQuorumAutobiographerQuor
    umQuorumQuorumAutobiographerQuorumQuorumQuorumAutobiographerQuorumMortarboardQuo
    rumQuorumAutobiographerQuorumQuorumQuorumQuorumAutobiographerQuorumMortarboardQu
    orumQuorumAutobiographerQuorumQuorumQuorumQuorumJongwareQuorumQuorumCommentatorQ
    uorumQuorumAutobiographerQuorumQuorumQuorumQuorumQuorumQuorumQuorumAutobiographe
    rQuorumQuorumAutobiographerQuorumQuorumProofreaderJongwareQuorumQuorumQuorumQuor
    umQuorumQuorumMortarboardJongwareQuorumProofreaderCommentatorSuffrageQuorumQuoru
    mQuorumQuorumJongwareQuorumAutobiographerSuffrageCommentatorCaucusCriticCleanupQ
    uorumQuorumMortarboardAutobiographerCommentatorQuorumConstituentCriticCriticQuor
    umQuorumQuorumQuorumAutobiographerJongwareCleanupSupporterInvestorCriticCleanupQ
    uorumQuorumQuorumQuorumAutobiographerFanaticCleanupFanaticInvestorCriticCleanupQ
    uorumQuorumQuorumQuorumAutobiographerCriticInformedSupporterCriticCriticInformed
    QuorumQuorumQuorumQuorumAutobiographerCriticQuorumSupporterInvestorFanaticQuorum
    QuorumQuorumQuorumQuorumAutobiographerCriticCleanupCommentatorQuorumDeputyQuorum
    QuorumQuorumQuorumQuorumAutobiographerCriticInvestorCaucusInformedDeputyQuorumQu
    orumQuorumQuorumQuorumQuorumCommentatorCriticCriticCitizen PatrolQuorumQuorumQuo
    rumQuorumQuorumQuorumQuorumAutobiographerCriticCriticCitizen PatrolQuorumQuorumQ
    uorumQuorumQuorumQuorumQuorumAutobiographerCriticCriticCitizen PatrolStewardQuor
    umQuorumQuorumQuorumQuorumAutobiographerConstituentCitizen PatrolCaucusQuorumQuo
    rumQuorumQuorumQuorumQuorumAutobiographerInvestorCriticConstituentQuorumQuorumQu
    orumQuorumQuorumQuorumQuorumCommentatorConstituentCleanupCaucusCleanupQuorumQuor
    umQuorumQuorumQuorumQuorumAutobiographerInvestorCleanupSupporterInformedQuorumQu
    orumQuorumQuorumAutobiographerCommentatorCriticInformedJongwareJongwareQuorumQuo
    rumQuorumQuorumAutobiographerCommentatorCriticInvestorJongwareJongwareQuorumQuor
    umQuorumQuorumAutobiographerFanaticInvestorCriticInformedCleanupQuorumQuorumQuor
    umQuorumQuorumQuorumDeputySupporterInvestorConstituentCaucusQuorumQuorumQuorumQu
    orumQuorumQuorumAutobiographerCriticInvestorCriticSupporterQuorumQuorumQuorumQuo
    rumQuorumQuorumQuorumAutobiographerInvestorInvestorCleanupQuorumQuorumQuorumQuor
    umQuorumQuorumQuorumQuorumCommentatorInvestorInvestorQuorumQuorumQuorumQuorumQuo
    rumQuorumQuorumQuorumQuorumCommentatorInvestorInvestorQuorumQuorumQuorumQuorumQu
    orumQuorumQuorumQuorumQuorumCommentatorInvestorCleanupStewardQuorumQuorumQuorumQ
    uorumQuorumQuorumQuorumQuorumCommentatorInvestorJongwareQuorumQuorumQuorumQuorum
    QuorumQuorumQuorumQuorumQuorumAutobiographerCleanupQuorumQuorumQuorumQuorumQuoru
    mQuorumQuorumQuorumQuorumQuorumAutobiographerSuffrageQuorumQuorumQuorumQuorumQuo
    rumQuorumQuorumQuorumQuorumQuorumAutobiographerDeputyQuorumQuorumQuorumQuorumQuo
    rumQuorumQuorumQuorumQuorumQuorumQuorumAutobiographerQuorumQuorumQuorumQuorumQuo
    rumQuorumQuorumQuorumQuorumQuorumQuorumQuorumQuorumQuorumQuorumQuorumQuorumQuoru
    

    You have to squint a bit; at a small size it looks like this:

    textvatar(tm)

    Here is how I created it.

    Step 1: Find a suitable image;

    Step 2: convert to grayscale;

    Step 3: convert gray to extremes. This step is to ensure the input range (gray values) uses the full range of 0 to 255.

    Step 4: resize the image for a best fit. I chose 80x40, and deliberately squashed the image by half. This because 'text' usually is higher than it is wide. Different fonts need different aspect ratios! The 80 is going to be the number of characters per line, the 40 is total number of lines.

    I used Photoshop for the above steps, just because I did not want to write code for it. It's not hard or something, as long as you have access to the raw image data, but it's a lot of work and not interesting.

    Intermediate step, enlarged by 400% so you can see the pixels:

    deformed grayscale image

    Step 5: Find yourself a monospaced bitmap font. I found a nice 8x8 one somewhere on the 'web. Perhaps a larger size may work better, but only very minor, because the limiting factor is your strings, not the font.

    Step 6: Calculate 'gray' values for each of the ASCII characters. This is the number of black pixels, divided by the total number of pixels. For a better spread, I divided the result for each character by the maximum found, so the lowest value is 0 (for space) and the highest is 1 (which happened to be for M, but it depends on the font). I then multiplied the values by 255, so it mimics grayscale values. Finally, as these values were the reverse of those in a grayscale image, I replaced them with 255-value.

    In between I did a lot of testing to make sure my initial idea was still sound. Here is a dump of my test image using a plain gray-to-character translation:

    0MMMMMM00000000000RDRRDDDDDDDDDDDDDDR#@RRRRR#00000RRR@RRRRR@RR@RRRRD&44&#00#0000
    MMMMMM000000000000RDRDDDDDDDDDDDDDDDR0####0R&&&&&&DR@RRR@#########RRDDD4@0000000
    MMM000#00000000000RDRRDDDDDDDDDDD&&&R00#000@&DR@####RR@#0000000##0#RD4PP&R@00000
    M000#0000000000000RDDDDDDDDDDD&D&&D&R###0000@#0000#@@##@@@@@##000000R&2FPP4R#000
    00###0000000000000RDDDDDDDDDDDDDD&&&D0000#@##@##00##@RDD&DRRRR#00000@R4FFP4PD@00
    00#000000000000000RDDDDDDDDDD&D&D&&&DRRRRDR@@@##00#R&4&44DDDRRR#0000##R&FPP4PRR@
    00##00#00000000000RDDDDDDDDDDD&&&&&&RDRRRRRRRR##@@RR&F4&RDDRR@@#0000000#&44&&4FD
    000000000000000000DDDDDDD&&&DD&&&D&&R##@RRRRRRRRRRRRD&DRD&44DD&RR@#00000RDDRR4F4
    000000000000000000RDDDDDDDRRDRDDD&DR@###@R&4&4444PP4DRRR&P22FF2FP&DRRRRRRRR@R4FD
    000000000000000000RRRRRRR@00@@R&&DRRRRRR@&P2$33*33*4RRRDP23$*33$$**333*$$2D#@4DR
    000000000000000000RRRRRRR@0000#DDRRRRRD&D4*$$%%ff3F&DRRR2$1%$$%11ff%ll1l;'IRDF&R
    000000000000000000RRR@#0#00000R4444DRRRR&3llf33$32PDRR&3Ii(i%fIlI1Il!ii/' .FF4DR
    0000000#0000000000#@@#0MM0M00#RP22*24DR4$l!!!I%*P4DDP31i===/lf1Illi((ii=;..*F4RR
    000000000000000000#@@##0M00000@F*$%*2PFflilllI1f3*2PF3fIi/=(lf$fIi/======;iF&P4R
    000000000000000000##@##00000000&3f$2F2*1i!ll1$2P4RD&4P*$$%!ii!I!ii/=/=;;;(*&R4FR
    000000000000000000#####00000000D%I3$$fflil!l1$3%3&RD2PP*$%1ll!lI1%f$3fI="1RRRRDR
    000000000000000000#####00000000Rf1f%II1IIl!l!!1%ff$*FFFF*%((lf**F4&D&PPFf2RRRRRR
    00000000000000000M#####0000000003!i11II11l!!!(ilIIl1f$f%I="=1FP4&@#R&DP22&RRR@#@
    0000000000000000000###0000000000#$i/iIIIl!!i=;"";===;""""";i12FFP4&4*4&*FRRRR@@R
    000000000000000000M000000000000000R4FF1l!i(=;"''"""""'...';/i$3$f$$33$$$4DRRRRRR
    000000000000000000000000000M00000000@21l(/ii/=;;=ilI;"'...'=;I%11f$$$ffP@@RR@R#0
    000000000000000000000000000M00000000&11!=/li(i!I%%Ii;... .";"!I!!I1ff$*R0#@#0000
    000000000000000000000000M0000M000000$lli=//=/i!lIi/l%I/""=//'=Iiiil%f$PR@#@00000
    000000000000000000000000M0M0M00000MRl!!!/=;;/l%fl!lI%3233$ff%11II%%f*2PP@##00000
    00000000000000000M000000M0MM00000003=illl!!iI%11%%f%f$32FF22$1%ff$f2DD4PR0000000
    00000000000000000M00M0#RRRRRRRRRRRPi/(lllI1I(=;=iI$2222FPFFP*$fff%FRR2322&R00000
    00000000000000000MM00#RRD&&&&&444*l/(i!ll!i!==/(il%$33*2FFF23$ff*4DDDF33*22&0000
    0000000000000000000000@R@@@@@#&f!((/i!!!lI!i=;;/!l1f32*3$$$ff$2&RR@###@##00##000
    0000000000000000#RRRR@@4F4R0R3!////(iii!!l%Ii=;"=l%f$333$ff%$4############00000#
    0000000000000000000000#@@@#0Di/ii(=/(i!!!lI%%1!(iI%$**$$fffF@0000000000000000000
    00000000000000000000000000000R3l((/((ii!lll!I%$3$f$$$$$3$f20M0000000000000000000
    000000000000000000000000000000#2l///((!!l!!i!!1%f%f$3$f%%fR000000000000000000000
    00000000000000#00000000000000000&l//(i!llllII1%%%1I1ff1I1&000000000000000000#00#
    M00000000000000000000000000000000R3l!!llII111%%%%%%f$$f%2000000000000000000RR00#
    0000#000000000000000000000000000000R*%1II111%%ffff$33fffR000000000000000000##00#
    0####000000000000000000000000000000M0RF3ff$$$fff$$33$ff&00000000000#000000##000R
    #00000000#00000000000000000000000000000R4FFFF22*****3fF00000000000000000000000@&
    00###000000000000000000000000000000000000@RRDD&&&4P*34#00000000000000000000000#R
    00000####000000000000000000000000000000000000##@RRDDR00000000000000M0000000000##
    00000000#000000000000000000000000000000000000000000000000000000000000000000000##
    

    This is similar to what your average image to ASCII art converter outputs.

    Step 7: find yourself a list of strings to use.

    Step 8: starting at the top left, test the coverage of each of your strings against the image. Print out the best fit, increase the position by this strings' length, repeat until done. (If you want no duplicates, you remove the string from your pool at this point.)

    Step 9: profit!


    For the coverage test, I used the sum of (source - dest)² for each character/pixel, divided by the length of the string: lower = better -- the smallest difference between the string and the destination.

    I did not consider pretty line endings here. I experimented with giving a negative bonus if a word filled a line exactly, but the difference in output was minor. It may still work with a larger set of strings.

    A possible improvement is to test a sequence of strings, i.e., instead of the greedy approach here, use a dynamic programming approach, much like Donald Knuth devised to decide the best breaks in word wrapping text.

    0 讨论(0)
提交回复
热议问题