Your program must take as input a line of characters, like this:


And output the characters sorted by how dark they are, like this:



  • You must use a monospaced font for darkness detection.

  • You must find out how many pixels each character takes up. You must actually draw the character and count pixels, i.e. you can't just hardcode pixel amounts.

    • As a more concrete rule: if you switched fonts, your program should still work. Furthermore, your program should be able to switch fonts by simply changing a variable or value or string in the code.
  • If you use antialiasing, you must count pixels as percentages of a fully black pixel. For example, an rgb(32, 32, 32) pixel will count as 1/8 of a full pixel. Disregard this rule if your characters are not antialiased.

  • After counting pixels, you must sort the characters by the amount of pixels, and output them in order.

  • This is , so the shortest code in bytes will win.

  • 7
    \$\begingroup\$ Strangely enough, this is also appropriate for RPG.se! \$\endgroup\$ – corsiKa Mar 7 '14 at 18:12
  • 1
    \$\begingroup\$ What, no correction for display gamma in the darkness calculation? \$\endgroup\$ – Ilmari Karonen Mar 7 '14 at 18:46
  • 6
    \$\begingroup\$ Can we use a white font? If so, I think I have this one wrapped up. \$\endgroup\$ – Paul Mar 7 '14 at 22:46

14 Answers 14


Mathematica, 112 110 108 bytes

This can still likely be golfed further. Assumes the string is in variable s.

And now uses a correct syntax to sort one list by another list.
Lucky test cases -> "Oh yeah, that works" -> Facepalm
Thanks for the sharp eyes, David Carraher.

Update: Replaced OCR A with Menlo because I realized that on OSX the OCR A font family name is actually OCR A Std. So I was sorting a default font instead of the real deal. Menlo is also monospaced with the same byte count, so no net gain or loss.

I've put up a hosted CDF export of the notebook, so you can see the code in action if you wish. I'm still figuring out how to add some interactivity to web-hosted CDFs, so for now it's just static.


Output for s = FromCharacterCode /@ Range[33, 135]; with "Courier"

enter image description here

Output for same, but with FontFamily "Monospace":

enter image description here

Note that the final results are shown in MM's internal font, not in the font being sorted. Hence, you see the differences in the font chosen reflected in the sort. The CDF link shows both, though, for the completists.

Ungolfed code:

s = FromCharacterCode /@ Range[33, 135];
c = Characters@s;
Last /@ Sort[
    Transpose@{Total[1 - # & /@ 
        ImageData@Rasterize@Style[#, FontFamily -> "Menlo"], 3] & /@ c, c}]
  • 1
    \$\begingroup\$ Examine for this input: "" <> (FromCharacterCode /@ Range[33, 135]) \$\endgroup\$ – DavidC Mar 7 '14 at 14:56
  • \$\begingroup\$ Default family or not, the specs required "your program should be able to switch fonts by simply changing a variable". Because of anti-aliasing values, it is possible to get some sorts that look wrong, but I'll have another look at the full range and see if anything is wrong. \$\endgroup\$ – Jonathan Van Matre Mar 7 '14 at 15:23
  • 1
    \$\begingroup\$ what a terrific language! just found out about the new programming language effort by Wolfram the other day, looking forward to that. \$\endgroup\$ – Randy L Mar 7 '14 at 17:15
  • 1
    \$\begingroup\$ You can save two bytes by changing Characters[] to Characters@ and Reverse[] to Reverse@. \$\endgroup\$ – Michael Stern Mar 7 '14 at 17:41
  • 2
    \$\begingroup\$ I don't want to post a similar answer, but I tried myself and it came up shorter StringJoin@SortBy[Characters@"@+.0",ImageData@Binarize@Rasterize@Style[#,FontFamily->"Monospace"]~Total~2&] \$\endgroup\$ – swish Mar 7 '14 at 19:57

Bash + ImageMagick: 164 147 148 characters

while read -n1 c
o=`convert -size 20x15 xc: +antialias -font cour.ttf -draw "text 0,10 '$c'" xpm:-`
o=${o//[^ ]}
echo "${a[@]}"

Sample run:

bash-4.1$ echo -n '@+.0' | bash graysort.sh
. + 0 @

Separators are inserted between grayness groups. Characters with identical grayness level are not separated:

bash-4.1$ echo -n 'abcdefghijklmnopqrstuvwxyz' | bash graysort.sh
i cl jortz esv ax u df bgnpq y hk w m
  • \$\begingroup\$ +1 for sorting using bash array indices instead of sort \$\endgroup\$ – Digital Trauma Mar 7 '14 at 16:16

QBasic, 259 bytes

DIM a(255)
FOR i = 32 TO 255
    PRINT CHR$(i);
    FOR p = 0 TO 64
        a(i) = a(i) + POINT(p MOD 8, p \ 8)
    NEXT p
FOR p = 0 TO 96
    FOR i = 32 TO 255
        IF a(i) = p THEN PRINT CHR$(i);
    NEXT i

I did this for fun, so it's technically non-compliant to the rules in one way. It doesn't take a list of characters, but instead prints all characters from 32-255 and uses that instead. If you really want to see a version which complies with this rule, please tell me.

It also fails another technicality: "Furthermore, your program should be able to switch fonts by simply changing a variable or value or string in the code." There is no easy way to do this from within QBasic. However, the program will of course work fine with any codepage of your choosing.

Lastly, I could squeeze away a few characters (mostly whitespace that the QBasic IDE helpfully inserts,) but it's probably not worth it since this answer stands no chance of winning anyway.

QBasic sort characters by darkness

  • \$\begingroup\$ Plus one for doing it for fun! \$\endgroup\$ – Jonathan Van Matre Mar 7 '14 at 23:56

Javascript + Canvas and Browser DOM (280 237 235 bytes)

Updated version with suggetions from Fors and toothbrush in comments:

function m(x){a=document.createElement('canvas').getContext('2d');a.font='9px Monaco';a.fillText(x,y=i=0,20);for(;i<3600;)y+=a.getImageData(0,0,30,30).data[i++];return y}alert(s.split('').sort(function(a,b){return m(a)-m(b)}).join(''))

More readable version:

// Scoring function - Calculates darkness for single character
function m(x) {
    a = document.createElement('canvas').getContext('2d');
    a.font = '9px Monaco';
    a.fillText(x, y = i = 0, 20);
    for (; i < 3600;) y += a.getImageData(0, 0, 30, 30).data[i++];
    return y
// Assume input is in variable s and alert as output. Comparison function now expression.
alert(s.split('').sort(function (a, b) {
    return m(a) - m(b)

Can maybe be golfed more.

I'm new to this site, so I am unsure how input is normally read for Javascript answers. I assume input is contained in a variable named s. If this is not OK, I will update the answer and the char count.

JSFiddle of updated version.

JSFiddle of first version.

  • \$\begingroup\$ I really like it, however there are many possible improvements here. The variables c and s are unnecessary (a=document.createElement('canvas').getContext('2d') and y+=a.getImageData(0,0,30,30).data[i]), the element doesn't need to be appended, the zero initialisation of y can be combined with that of i (i=y=0), the post-increment of i can be combined with the adding of y (for(...;y+=...[i++]);), and the typeface Monaco is mono-spaced and has a shorter name than Courier. \$\endgroup\$ – Fors Mar 7 '14 at 19:04
  • \$\begingroup\$ Thanks! I'm not a seasoned golfer yet, so your comments help a lot. I have incorporated them into the code now. \$\endgroup\$ – waxwing Mar 8 '14 at 7:41
  • \$\begingroup\$ Javascript answers here normally read input through prompt(); but this is also fine. \$\endgroup\$ – Kartik Mar 8 '14 at 12:48
  • \$\begingroup\$ You can remove y=i=0; and change a.fillText(x,0,20) to a.fillText(x,y=i=0,20). \$\endgroup\$ – Toothbrush Mar 9 '14 at 1:06
  • \$\begingroup\$ Thanks, added that as well! Two characters are two characters! \$\endgroup\$ – waxwing Mar 13 '14 at 20:10

PHP, 298 characters

I've added a few line breaks so you can see it in all its hideousness:

$a[]=ord($s[$i])+($n<<8);}sort($a);foreach($a as $v)echo chr($v);

This code uses the GD fonts that come built-in with PHP. The second argument of imagechar() selects the font (numbers from 1 to 5 are valid).


Input:  !@#$%^&*-=WEIX,./'
Output: '-.,^=!/*IE%X#$&@W

If you insert the following on top of the code shown above, then you'll be able to supply the list of characters in your web browser.

if(@$_SERVER['PATH_INFO']=='/a.png') {
  $s = $_GET['s'];
  $im = imagecreate(strlen($s)*(FONT_SIZE+4)+4,FONT_SIZE+12);
  $c = imagecolorallocate($im,0,0,0);
  header("Content-Type: image/png");
$me = $_SERVER['PHP_SELF'];
$t1 = $img = "";
if ($t1=htmlspecialchars(@$_GET['s'])) {
  $img="<p><img src=\"$me/a.png?s=$t2\" /></p>";
echo <<<END_HTML
<form action="$me" method="get">
<input type="text" name="s" size="40" value="$t1" />
<input type="submit" value="Go" />
if(!isset($_GET['s'])) exit();
  • 1
    \$\begingroup\$ if you use imagecreatetruecolor you can drop the first allocate and use the function name directly on the second, for -11. [] instead of array(). and foreach($a as$v) works as well \$\endgroup\$ – Einacio Mar 7 '14 at 16:38


This has code to be the second hardest code I've ever written for a calculator. No hard-coded pixel values, it actually draws the text on a graph and loops to count each pixel.





  • \$\begingroup\$ If this works, it is the best answer so far. Whatever is GTB? \$\endgroup\$ – slater Mar 8 '14 at 17:52
  • \$\begingroup\$ @slater Why don't you click on the link and find out? \$\endgroup\$ – Timtech Mar 8 '14 at 18:00
  • \$\begingroup\$ Thank you for pointing out that your post title is indeed a link. \$\endgroup\$ – slater Mar 8 '14 at 18:07
  • 2
    \$\begingroup\$ The domain to get the software necessary to decrypt your compiler is dead. Dead here too. I translated the code by hand for the fun of testing it out, but you seem to have opened 10 parens and closed only one, so I am unsure how to resolve that. The compiler in my head says: "Error::unmatched identifier" :) \$\endgroup\$ – Jonathan Van Matre Mar 9 '14 at 3:06
  • 1
    \$\begingroup\$ @JonathanVanMatre Don't worry; TI-84 automatically closes them for you. \$\endgroup\$ – Timtech Mar 13 '14 at 21:00

Java - 468 450 444

public static void main(String[]a){class c implements Comparable<c>{char d;c(char e){d=e;}public int compareTo(c o){return e(d)>e(o.d)?1:-1;}int e(char f){int a=0,x,y;BufferedImage img=new BufferedImage(99,99,1);img.getGraphics().drawString(""+f,9,80);for(y=0;y<99;y++)for(x=0;x<99;x++)a+=img.getRGB(x,y);return a;}}c[]s=new c[a[0].length()];int i=0;for(char d:a[0].toCharArray())s[i++]=new c(d);Arrays.sort(s);for(c d:s)System.out.print(d.d);}

@+.0abcdefghijklmnopqrstuvwxyz -> .irl+jcvtfxyzsuonkheaqpdb0wgm@


    public static void main(String[] a) {
    a = new String[]{"@+.0abcdefghijklmnopqrstuvwxyz"};
    class c implements Comparable<c> {
        char    d;

        c(char e) {
            d = e;

        public int compareTo(c o) {
            return e(d) > e(o.d)? 1 : -1;

        int e(char f) {
            int a = 0, x, y;
            BufferedImage img = new BufferedImage(99, 99, 1);
            img.getGraphics().drawString("" + f, 9, 80);
            for (y = 0; y < 99; y++)
                for (x = 0; x < 99; x++)
                    a += img.getRGB(x, y);
            return a;
    c[] s = new c[a[0].length()];
    int i = 0;
    for (char d : a[0].toCharArray())
        s[i++] = new c(d);
    for (c d : s)
  • \$\begingroup\$ Tip: avoid public or private modifiers as far as possible; that saves unnecessary bytes \$\endgroup\$ – masterX244 Mar 8 '14 at 20:41
  • \$\begingroup\$ forgot all about that \$\endgroup\$ – Mark Jeronimus Mar 9 '14 at 11:49
  • \$\begingroup\$ @MarkJeronimus this was the way I was going t otackle it, but I wanted to try using FontRenderingContext. \$\endgroup\$ – Magic Octopus Urn Jan 24 '17 at 20:44

Postscript, 381

Here's something completely different, just for fun. As most fonts are vector anyway, 'counting pixels' is a little odd, isn't it. Calculating glyph shape area, while being correct way, is not that easy. An alternative can be scanning a rectangle and counting 'hits' when a point is inside a glyph shape, and Postscript has operators for this kind of checks. Though, true, scanning and insideness-testing is just a weird way of counting pixels.

(%stdin)(r)file token pop/Courier 99 selectfont[1 index length{0}repeat]0 1 99{0 1 99{0 1 5 index length 1 sub{newpath 9 19 moveto 3 copy 7 index exch 1 getinterval false charpath infill{3 index exch 2 copy get 1 add put}{pop}ifelse}for pop}for pop}for 0 1 99 dup mul{0 1 3 index length 1 sub{dup 3 index exch get 2 index eq{3 index exch 1 getinterval print}{pop}ifelse}for pop}for


(%stdin) (r) file token pop
/Courier 99 selectfont
%/DejaVuSansMono 99 selectfont
%/UbuntuMono-Regular 99 selectfont
[ 1 index length {0} repeat ]   % str []
0 1 99 {
    0 1 99 {
        0 1 5 index length 1 sub {
            9 19 moveto
            3 copy              % str [] n m i n m i
            7 index exch        % str [] n m i n m str i
            1 getinterval       % str [] n m i n m s
            false charpath      % str [] n m i n m
            infill              % str [] n m i bool
            {3 index exch 2 copy get 1 add put} {pop} ifelse
        } for
    } for
} for
% un-comment next line to print number of 'hits' for each glyph
% dup {=} forall
% next is 'lazy sort'
0 1 99 dup mul {                % str [] i
    0 1 3 index length 1 sub {  % str [] i j
        dup 3 index exch        % str [] i j [] j
        get 2 index eq          % str [] i j bool
        {3 index exch 1 getinterval print} {pop} ifelse
    } for
} for

And here are results for 3 different fonts (selection of which can be un-commented, above):

$ echo '(.-?@AByz01)' | gs -q -dBATCH d.ps
$ echo '(.-?@AByz01)' | gs -q -dBATCH d.ps
$ echo '(.-?@AByz01)' | gs -q -dBATCH d.ps
  • \$\begingroup\$ Counting pixels...counting pixel intersections...tomato....tomahto.... \$\endgroup\$ – Jonathan Van Matre Mar 10 '14 at 14:13

Perl (with GD) (159)

use GD;sub i{$i=new GD'Image 5,8;$B=colorExact$i 9,9,9;colorExact$i 0,0,0;char$i gdTinyFont,0,0,@_,1;$_=unpack"B*",wbmp$i 0;y/0//c}print+sort{i($a)-i($b)}@ARGV


> perl dark.pl 1 2 3 @ # . , : ~ $ M i I s S

edit: shortened to 159 chars


Java, 584

Wow... This was not a good language to do this in.

import java.awt.geom.*;import java.util.*;class F{static void n(final String f,List<Character> s){Collections.sort(s,new Comparator<Character>(){public int compare(Character a,Character b){return d(f,""+a) - d(f,""+b);}});}static int d(String f,String s){int i=0;PathIterator p=new java.awt.Font(f,0,12).createGlyphVector(((java.awt.Graphics2D)new java.awt.image.BufferedImage(8,8,2).getGraphics()).getFontRenderContext(),s).getGlyphOutline(0).getPathIterator(AffineTransform.getRotateInstance(0.0, 0.0));while(!p.isDone()){i+=p.currentSegment(new double[99])/2;p.next();}return i;}}


import java.awt.geom.*;
import java.util.*;
public class F {
    public static void main(String[]args){
        List<Character> s = new ArrayList<Character>(0);
        n("Calibri", s);
    static void n(final String f,List<Character> s){
        Collections.sort(s,new Comparator<Character>(){
            public int compare(Character a,Character b){
                return d(f,""+a) - d(f,""+b);
    static int d(String f,String s){
        int i=0;
        PathIterator p=new java.awt.Font(f,0,12).createGlyphVector(((java.awt.Graphics2D)new java.awt.image.BufferedImage(8,8,2).getGraphics()).getFontRenderContext(),s).getGlyphOutline(0).getPathIterator(AffineTransform.getRotateInstance(0.0, 0.0));
            i+=p.currentSegment(new double[99])/2;
        return i;

This setup results in:

[., +, 0, @]

The only line here that needs explanation:

PathIterator p=new java.awt.Font(f,0,12).createGlyphVector(((java.awt.Graphics2D)new java.awt.image.BufferedImage(8,8,2).getGraphics()).getFontRenderContext(),s).getGlyphOutline(0).getPathIterator(AffineTransform.getRotateInstance(0.0, 0.0));
  • Initialize the 12pt font object with the passed font.
  • Create a new BufferedImage obejct to create a Graphics2D object linked to a GraphicsContext.
  • Get the font rendering context of the 2D graphics context for the string s.
  • Get the first glyph (only glyph) in the string.
  • Get the path iterator (list of points).

Then this final piece brings it together...

    i+=p.currentSegment(new double[99])/2;

By iterating through all points and summing count of points. This density information is passed back up to the comparator and is used for sorting.


R, 195 characters


Indented with comments:

A=strsplit(scan(,""),"")[[1]] #Take characters as strings and split into single chars
cat(A[order(sapply(A,function(x){ #Apply the following function to each char and order accordingly
                 png('a',a='none',fa='monospace'); #Open empty png without antialiasing and with monospace font
                 frame(); #create empty plot
                 text(0,0,x); #add the char as text to the plot
                 dev.off(); #close png device
                 sum(apply(png::readPNG('a'), #read it back as rbga 3d matrix
                           c(1,2), #check every layer (R, G, B, A)
                           function(x)any(x!=1))) #if any are not 1, send TRUE
                 }))], #Sum all TRUEs
    sep="") #Prints to output


> A=strsplit(scan(,""),"")[[1]];cat(A[order(sapply(A,function(x){png('a',a='none',fa='monospace');frame();text(0,0,x);dev.off();sum(apply(png::readPNG('a'),c(1,2),function(x)any(x!=1)))}))],sep="")
1: @+.0
Read 1 item
> A=strsplit(scan(,""),"")[[1]];cat(A[order(sapply(A,function(x){png('a',a='none',fa='monospace');frame();text(0,0,x);dev.off();sum(apply(png::readPNG('a'),c(1,2),function(x)any(x!=1)))}))],sep="")
1: 1234567890
Read 1 item

The gestion of fonts in R plots being platform-dependent, I cannot guarantee that it works on PC, but it does on a Mac (OS X 10.7.5, R 2.14.2).


SmileBASIC, 179 176 173 bytes

GSAVE.,0,8,8,A,0S=0FOR I=0TO 63S=S+A[I]NEXT

Uses the currently loaded font. Fonts can be loaded with LOAD"GRPF:filename".

More readable code:

 FOR I=0 TO 63

PHP - 485


$ php pcg-23362.php "@+.0"


<?php $f='x.ttf';$d=array();foreach(str_split($argv[1]) as$_){$B=imagettfbbox(50,0,$f,$_);$w=abs($B[4]-$B[0]);$h=abs($B[5]-$B[1]);$im=imagecreate($w,$h);imagecolorallocate($im,255,255,255);imagettftext($im,50,0,0,$h-$B[1],imagecolorallocate($im,0,0,0),$f,$_);$b=$w*$h;for($x=0;$x<$w;$x++)for($y=0;$y<$h;$y++){$z=imagecolorsforindex($im,imagecolorat($im,$x,$y));$color=$z['red']*$z['green']*$z['blue'];$b-=$color/0x1000000;}$d[$_]=$b / ($w * $h);}asort($d);echo implode(array_keys($d));
  • \$\begingroup\$ Hey, send me a copy of X.TTF...the shortest font I have is OCR A. ;-D \$\endgroup\$ – Jonathan Van Matre Mar 9 '14 at 3:08

Python + freetype-py: 147

import sys,freetype as F;f=F.Face('m.ttf');f.set_char_size(99);print(sorted([(f.load_char(c)or sum(f.glyph.bitmap.buffer),c)for c in raw_input()]))

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.