| Bytes | Lang | Time | Link |
|---|---|---|---|
| 003 | Python | 240820T122901Z | Jitse |
| 222 | Processing | 130711T220239Z | daniero |
| nan | 150529T073412Z | DLosc | |
| nan | 140321T150208Z | Alex Shr | |
| nan | Great challenge. Here's my solution. I tried to get as close as possible to the original comic | 140614T010906Z | K893824 |
| nan | 140322T054401Z | user1921 | |
| nan | 140304T192510Z | Jack M | |
| nan | 130712T143926Z | copy | |
| nan | 130713T050411Z | breadbox | |
| nan | 130712T023838Z | breadbox |
Python 3
A quick-and-dirty implementation of the three-panel xkcd in matplotlib. Converges in ~10 iterations.
Code:
import numpy as np
from matplotlib import pyplot as plt
def generate(first=(1,0), second=(0,0,0)):
# Initialize
lw, fs, fw = 2.5, 12, 'heavy'
plt.close('all')
fig, (ax0, ax1, ax2) = plt.subplots(1,3,figsize=(12,3))
for ax in fig.axes:
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)
plt.tight_layout()
# First panel
bx0 = fig.add_axes(ax0.get_position())
bx0.pie(first, labels=(' FRACTION\n WHITE', 'FRACTION \nBLACK '),
colors=('white', 'black'), startangle=250,
wedgeprops={'edgecolor':'k', 'linewidth':lw},
textprops={'fontsize': fs, 'weight':fw})
# Second panel
(x0, y0), (x1, y1) = ax1.get_position().get_points()
bx1 = fig.add_axes((x0+0.04, y0+0.15, x1-x0-0.08, y1-y0-0.3))
bx1.bar([1,2,3], second, color='black', width=0.5)
bx1.set_title('BLACK INK BY PANEL', fontsize=fs, weight=fw)
bx1.set_xticks([1,2,3])
bx1.set_xticklabels([1,2,3], fontsize=fs, weight=fw)
bx1.tick_params(top=False,
bottom=False,
left=False,
right=False,
labelleft=False,
labelbottom=True)
bx1.spines[['right', 'top']].set_visible(False)
# Third panel
(x0, y0), (x1, y1) = ax2.get_position().get_points()
bx2 = fig.add_axes((x0+0.025, y0+0.1, x1-x0-0.05, y1-y0-0.2))
bx2.set_title('BLACK INK LOCATION', fontsize=fs, weight=fw)
bx2.set_xticks([0])
bx2.set_xticklabels([0], fontsize=fs, weight=fw)
bx2.set_yticks([0])
bx2.set_yticklabels([0], fontsize=fs, weight=fw)
bx2.spines[['right', 'top']].set_visible(False)
# Set linewidths
for ax in fig.axes:
ax.xaxis.set_tick_params(width=lw)
ax.yaxis.set_tick_params(width=lw)
for edge in ['top','bottom','left','right']:
ax.spines[edge].set_linewidth(lw)
# Apply Droste effect
for _ in range(10):
fig.canvas.draw()
w,h = fig.canvas.get_width_height()
arr = np.frombuffer(fig.canvas.tostring_rgb(), dtype='uint8')
arr = (arr.reshape(h,w,3).sum(axis=2) > 0)[::-1]
bx2.imshow(arr, cmap='gray', origin='lower')
# Get stats
white = arr.sum()
black = arr.size - white
panels = ((~arr)[...,:w//3].sum(),
(~arr)[...,w//3:w//3*2].sum(),
(~arr)[...,w//3*2:].sum())
return (white, black), panels
if __name__ == '__main__':
prev_stats = (1,0), (0,0,0)
curr_stats = generate(*prev_stats)
while curr_stats != prev_stats:
prev_stats = curr_stats
curr_stats = generate(*prev_stats)
print(curr_stats)
plt.show()
Processing, 222 characters

I've always wanted to make my own version of that comic strip! The simplest (only?) way I could think of doing this was trial and error - draw something, count, draw again...
This program settles for an accurate percentage after a few seconds. It's not very pretty, but it's interactive; you can resize the window and it will start to recalculate.
Added some newlines for readability:
float s,S,n;
int i;
void draw(){
frame.setResizable(true);
background(255);
fill(s=i=0);
text(String.format("%.2f%% of this is white",S/++n*100),10,10);
loadPixels();
while(i<width*height)if(pixels[i++]==-1)s++;
S+=s/height/width;
}
It only shows percentage of white pixels; Because of antialiasing of the text, non-white pixels are not necessarily black. The longer it is running the more time it will need to update itself on a resize.
Edit:
So, it's a code-challenge; I sort of golfed it anyways. Maybe I could add some sort of graphs later, but the general principle would remain the same. The interactiveness is the neat part I think.
QBasic
Because nostalgia.
And because I don't really know any image libraries in modern languages.
SCREEN 9
CONST screenWidth = 640
CONST screenHeight = 350
CONST totalPixels# = screenWidth * screenHeight
accuracy = 6
newWhite# = 0
newGreen# = 0
newBlack# = totalPixels#
DO
CLS
white# = newWhite#
green# = newGreen#
black# = newBlack#
' Change the precision of the percentages every once in a while
' This helps in finding values that converge
IF RND < .1 THEN accuracy = INT(RND * 4) + 2
format$ = "###." + LEFT$("######", accuracy) + "%"
' Display text
LOCATE 1
PRINT "Percentage of the screen which is white:";
PRINT USING format$; pct(white#)
LOCATE 4
PRINT white#; "/"; totalPixels#; "pixels"
LOCATE 7
PRINT "Percentage of the screen which is black:";
PRINT USING format$; pct(black#)
LOCATE 10
PRINT black#; "/"; totalPixels#; "pixels"
LOCATE 13
PRINT "Percentage of the screen which is green:";
PRINT USING format$; pct(green#)
LOCATE 16
PRINT green#; "/"; totalPixels#; "pixels"
' Display bar graphs
LINE (0, 16)-(pct(white#) / 100 * screenWidth, 36), 2, BF
LINE (0, 100)-(pct(black#) / 100 * screenWidth, 120), 2, BF
LINE (0, 184)-(pct(green#) / 100 * screenWidth, 204), 2, BF
newBlack# = pixels#(0)
newGreen# = pixels#(2)
newWhite# = pixels#(15)
LOOP UNTIL black# = newBlack# AND white# = newWhite# AND green# = newGreen#
' Wait for user keypress before ending program: otherwise the "Press any
' key to continue" message would instantly make the results incorrect!
x$ = INPUT$(1)
FUNCTION pixels# (colr)
' Counts how many pixels of the given color are on the screen
pixels# = 0
FOR i = 0 TO screenWidth - 1
FOR j = 0 TO screenHeight - 1
IF POINT(i, j) = colr THEN pixels# = pixels# + 1
NEXT j
NEXT i
END FUNCTION
FUNCTION pct (numPixels#)
' Returns percentage, given a number of pixels
pct = numPixels# / totalPixels# * 100
END FUNCTION
Pretty straightforward output-count-repeat method. The main "interesting" thing is that the program randomly tries different precisions for the percentages--I found that it didn't always converge otherwise.
And the output (tested on QB64):

Elm
Haven't seen anyone use this loophole yet: demo
import Color exposing (hsl)
import Graphics.Element exposing (..)
import Mouse
import Text
import Window
msg a = centered <| Text.color a (Text.fromString "half the screen is this color")
type Pos = Upper | Lower
screen (w,h) (x,y) =
let (dx,dy) = (toFloat x - toFloat w / 2, toFloat h / 2 - toFloat y)
ang = hsl (atan2 dy dx) 0.7 0.5
ang' = hsl (atan2 dx dy) 0.7 0.5
box c = case c of
Upper -> container w (h // 2) middle (msg ang) |> color ang'
Lower -> container w (h // 2) middle (msg ang') |> color ang
in flow down [box Upper, box Lower]
main = Signal.map2 screen Window.dimensions Mouse.position

Great challenge. Here's my solution. I tried to get as close as possible to the original comic, I even used the xkcd font.

It's a WPF application, but I used System.Drawing to do the drawing parts because I'm lazy.
Basic concept: In WPF, windows are Visuals, which means they can be rendered. I render the entire Window instance onto a bitmap, count up the black and total black or white (ignoring the grays in the font smoothing and stuff) and also count these up for each 3rd of the image (for each panel). Then I do it again on a timer. It reaches equilibrium within a second or two.
Download:
MEGA Always check files you download for viruses, etc, etc.
You'll need to install the font above to your system if you want to see it, otherwise it's the WPF default one.
XAML:
<Window
x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="xkcd: 688" Height="300" Width="1000" WindowStyle="ToolWindow">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.3*"/>
<ColumnDefinition Width="0.3*"/>
<ColumnDefinition Width="0.3*"/>
</Grid.ColumnDefinitions>
<Border BorderBrush="Black" x:Name="bFirstPanel" BorderThickness="3" Padding="10px" Margin="0 0 10px 0">
<Grid>
<Label FontSize="18" FontFamily="xkcd" VerticalAlignment="Top">Fraction of this window that is white</Label>
<Label FontSize="18" FontFamily="xkcd" VerticalAlignment="Bottom">Fraction of this window that is black</Label>
<Image x:Name="imgFirstPanel"></Image>
</Grid>
</Border>
<Border Grid.Column="1" x:Name="bSecondPanel" BorderBrush="Black" BorderThickness="3" Padding="10px" Margin="10px 0">
<Grid>
<TextBlock FontSize="18" FontFamily="xkcd" VerticalAlignment="Top" HorizontalAlignment="Left">Amount of <LineBreak></LineBreak>black ink <LineBreak></LineBreak>by panel:</TextBlock>
<Image x:Name="imgSecondPanel"></Image>
</Grid>
</Border>
<Border Grid.Column="2" x:Name="bThirdPanel" BorderBrush="Black" BorderThickness="3" Padding="10px" Margin="10px 0 0 0">
<Grid>
<TextBlock FontSize="18" FontFamily="xkcd" VerticalAlignment="Top" HorizontalAlignment="Left">Location of <LineBreak></LineBreak>black ink <LineBreak></LineBreak>in this window:</TextBlock>
<Image x:Name="imgThirdPanel"></Image>
</Grid>
</Border>
</Grid>
</Window>
Code:
using System;
using System.Drawing;
using System.Timers;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Brushes = System.Drawing.Brushes;
namespace WpfApplication1
{
public partial class MainWindow : Window
{
private Timer mainTimer = new Timer();
public MainWindow()
{
InitializeComponent();
Loaded += (o1,e1) =>
{
mainTimer = new Timer(1000/10);
mainTimer.Elapsed += (o, e) => {
try
{
Dispatcher.Invoke(Refresh);
} catch(Exception ex)
{
// Nope
}
};
mainTimer.Start();
};
}
private void Refresh()
{
var actualh = this.RenderSize.Height;
var actualw = this.RenderSize.Width;
var renderTarget = new RenderTargetBitmap((int) actualw, (int) actualh, 96, 96, PixelFormats.Pbgra32);
var sourceBrush = new VisualBrush(this);
var visual = new DrawingVisual();
var context = visual.RenderOpen();
// Render the window onto the target bitmap
using (context)
{
context.DrawRectangle(sourceBrush, null, new Rect(0,0, actualw, actualh));
}
renderTarget.Render(visual);
// Create an array with all of the pixel data
var stride = (int) actualw*4;
var data = new byte[stride * (int)actualh];
renderTarget.CopyPixels(data, stride, 0);
var blackness = 0f;
var total = 0f;
var blacknessFirstPanel = 0f;
var blacknessSecondPanel = 0f;
var blacknessThirdPanel = 0f;
var totalFirstPanel = 0f;
var totalSecondPanel = 0f;
var totalThirdPanel = 0f;
// Count all of the things
for (var i = 0; i < data.Length; i += 4)
{
var b = data[i];
var g = data[i + 1];
var r = data[i + 2];
if (r == 0 && r == g && g == b)
{
blackness += 1;
total += 1;
var x = i%(actualw*4) / 4;
if(x < actualw / 3f)
{
blacknessFirstPanel += 1;
totalFirstPanel += 1;
} else if (x < actualw * (2f / 3f))
{
blacknessSecondPanel += 1;
totalSecondPanel += 1;
}
else if (x < actualw)
{
blacknessThirdPanel += 1;
totalThirdPanel += 1;
}
} else if (r == 255 && r == g && g == b)
{
total += 1;
var x = i % (actualw * 4) / 4;
if (x < actualw / 3f)
{
totalFirstPanel += 1;
}
else if (x < actualw * (2f / 3f))
{
totalSecondPanel += 1;
}
else if (x < actualw)
{
totalThirdPanel += 1;
}
}
}
var black = blackness/total;
Redraw(black, blacknessFirstPanel, blacknessSecondPanel, blacknessThirdPanel, blackness, renderTarget);
}
private void Redraw(double black, double firstpanel, double secondpanel, double thirdpanel, double totalpanels, ImageSource window)
{
DrawPieChart(black);
DrawBarChart(firstpanel, secondpanel, thirdpanel, totalpanels);
DrawImage(window);
}
void DrawPieChart(double black)
{
var w = (float)bFirstPanel.ActualWidth;
var h = (float)bFirstPanel.ActualHeight;
var padding = 0.1f;
var b = new Bitmap((int)w, (int)h);
var g = Graphics.FromImage(b);
var px = padding*w;
var py = padding*h;
var pw = w - (2*px);
var ph = h - (2*py);
g.DrawEllipse(Pens.Black, px,py,pw,ph);
g.FillPie(Brushes.Black, px, py, pw, ph, 120, (float)black * 360);
g.DrawLine(Pens.Black, 30f, h * 0.1f, w / 2 + w * 0.1f, h / 2 - h * 0.1f);
g.DrawLine(Pens.Black, 30f, h - h * 0.1f, w / 2 - w * 0.2f, h / 2 + h * 0.2f);
imgFirstPanel.Source = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(b.GetHbitmap(), IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromWidthAndHeight(b.Width, b.Height));
}
void DrawBarChart(double b1, double b2, double b3, double btotal)
{
var w = (float)bFirstPanel.ActualWidth;
var h = (float)bFirstPanel.ActualHeight;
var padding = 0.1f;
var b = new Bitmap((int)w, (int)h);
var g = Graphics.FromImage(b);
var px = padding * w;
var py = padding * h;
var pw = w - (2 * px);
var ph = h - (2 * py);
g.DrawLine(Pens.Black, px, py, px, ph+py);
g.DrawLine(Pens.Black, px, py + ph, px+pw, py+ph);
var fdrawbar = new Action<int, double>((number, value) =>
{
var height = ph*(float) value/(float) btotal;
var width = pw/3f - 4f;
var x = px + (pw/3f)*(number-1);
var y = py + (ph - height);
g.FillRectangle(Brushes.Black, x, y, width, height);
});
fdrawbar(1, b1);
fdrawbar(2, b2);
fdrawbar(3, b3);
imgSecondPanel.Source = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(b.GetHbitmap(), IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromWidthAndHeight(b.Width, b.Height));
}
void DrawImage(ImageSource window)
{
imgThirdPanel.Source = window;
}
}
}
The code isn't cleaned up, but it should be somewhat readable, sorry.
AWK
... with netpbm and other helpers
The 'x' file:
BEGIN {
FS=""
n++
while(n!=m) {
c="printf '%s\n' '"m"% black pixels'"
c=c" '"100-m"% white pixels'"
c=c" | pbmtext -space 1 -lspace 1 | pnmtoplainpnm | tee x.pbm"
n=m
delete P
nr=0
while(c|getline==1) if(++nr>2) for(i=1;i<=NF;i++) P[$i]++
close(c)
m=100*P[1]/(P[0]+P[1])
print m"%"
}
}
The run:
$ awk -f x
4.44242%
5.2424%
5.04953%
5.42649%
5.27746%
5.1635%
5.15473%
5.20733%
5.20733%
The picture is written as 'x.pbm', I converted it to png for uploading:


The image is 100x100 and the numbers are exact, and I do mean exact - I chose a 10000 pixel image so that the percentages could be expressed with two decimal places. The method was a bit of math, a bit of guessing, and some number crunching in Python.
Seeing as I knew in advance that the percentages could be expressed in 4 digits, I counted how many black pixels were in each of the digits 0 through 9, in 8 pixel high Arial, which is what the text is written in. I wrote a quick function weight which tells you how many pixels are needed to write a given number, left padded with zeros to have 4 digits:
def weight(x):
total = 4 * px[0]
while x > 0:
total = total - px[0] + px[x % 10]
x = x / 10
return total
px is an array mapping digits to number of required pixels. If B is the number of black pixels, and W is the number of white pixels, we have B + W = 10000, and we need:
B = 423 + weight(B) + weight(W)
W = 9577 - weight(B) - weight(W)
Where did the constants come from? 423 is the "initial" number of black pixels, the number of black pixels in the text without the numbers. 9577 is the number of initial white pixels. I had to adjust the amount of initial black pixels several times before I managed to get constants such that the above system even has a solution. This was done by guessing and crossing my fingers.
The above system is horribly non-linear, so obviously you can forget about solving it symbolically, but what you can do is just loop through every value of B, set W = 10000 - B, and check the equations explicitly.
>>> for b in range(10000 + 1):
... if b == weight(b) + weight(10000 - b)+423: print b;
...
562
564
JavaScript with HTML
I tried to reproduce the original comic more precisely. A screenshot is taken using the html2canvas library. The numbers are calculated repeatedly, so you can resize the window or even add something to page in real time.
Try it online: http://copy.sh/xkcd-688.html
Here's a screenshot:

<html contenteditable>
<script src=http://html2canvas.hertzen.com/build/html2canvas.js></script>
<script>
onload = function() {
setInterval(k, 750);
k();
}
function k() {
html2canvas(document.body, { onrendered: t });
}
function t(c) {
z.getContext("2d").drawImage(c, 0, 0, 300, 150);
c = c.getContext("2d").getImageData(0, 0, c.width, c.height).data;
for(i = y = 0; i < c.length;)
y += c[i++];
y /= c.length * 255;
x.textContent = (y * 100).toFixed(6) + "% of this website is white";
q = g.getContext("2d");
q.fillStyle = "#eee";
q.beginPath();
q.moveTo(75, 75);
q.arc(75,75,75,0,7,false);
q.lineTo(75,75);
q.fill();
q.fillStyle = "#000";
q.beginPath();
q.moveTo(75, 75);
q.arc(75,75,75,0,6.28319*(1-y),false);
q.lineTo(75,75);
q.fill();
}
</script>
<center>
<h2 id=x></h2>
<hr>
<table><tr>
<td>Fraction of<br>this website<br>which is white _/
<td><canvas width=150 id=g></canvas>
<td> Fraction of<br>- this website<br> which is black
</table>
<hr>
0
<canvas style="border-width: 0 0 1px 1px; border-style: solid" id=z></canvas>
<h4>Location of coloured pixels in this website</h4>
C (with SDL and SDL_ttf): Grayscale solution
Here's a solution that takes advantage of the pie chart form to capture the complete spectrum of grayscale pixel colors, clocking in at just under 100 lines.
#include <stdio.h>
#include <string.h>
#include <math.h>
#include "SDL.h"
#include "SDL_ttf.h"
int main(void)
{
SDL_Surface *screen, *buffer, *caption;
SDL_Color pal[256];
SDL_Rect rect;
SDL_Event event;
TTF_Font *font;
int levels[256], plev[256];
Uint8 *p;
float g;
int cr, redraw, hoffset, h, n, v, w, x, y;
SDL_Init(SDL_INIT_VIDEO);
TTF_Init();
screen = SDL_SetVideoMode(640, 480, 0, SDL_ANYFORMAT | SDL_RESIZABLE);
font = TTF_OpenFont(FONTPATH, 24);
buffer = 0;
for (;;) {
if (!buffer) {
buffer = SDL_CreateRGBSurface(SDL_SWSURFACE, screen->w, screen->h,
8, 0, 0, 0, 0);
for (n = 0 ; n < 256 ; ++n)
pal[n].r = pal[n].g = pal[n].b = n;
SDL_SetColors(buffer, pal, 0, 256);
}
memcpy(plev, levels, sizeof levels);
memset(levels, 0, sizeof levels);
SDL_LockSurface(buffer);
p = buffer->pixels;
for (h = 0 ; h < buffer->h ; ++h) {
for (w = 0 ; w < buffer->w ; ++w)
++levels[p[w]];
p += buffer->pitch;
}
for (n = 1 ; n < 256 ; ++n)
levels[n] += levels[n - 1];
redraw = memcmp(levels, plev, sizeof levels);
if (redraw) {
SDL_UnlockSurface(buffer);
SDL_FillRect(buffer, NULL, 255);
caption = TTF_RenderText_Shaded(font,
"Distribution of pixel color in this image",
pal[0], pal[255]);
rect.x = (buffer->w - caption->w) / 2;
rect.y = 4;
hoffset = caption->h + 4;
SDL_BlitSurface(caption, NULL, buffer, &rect);
SDL_FreeSurface(caption);
SDL_LockSurface(buffer);
cr = buffer->h - hoffset;
cr = (cr < buffer->w ? cr : buffer->w) / 2 - 4;
p = buffer->pixels;
for (h = 0 ; h < buffer->h ; ++h) {
y = h - (screen->h + hoffset) / 2;
for (w = 0 ; w < buffer->w ; ++w) {
x = w - buffer->w / 2;
g = sqrtf(x * x + y * y);
if (g < cr - 1) {
g = atanf((float)y / (x + g));
v = levels[255] * (g / M_PI + 0.5);
for (n = 0 ; n < 255 && levels[n] < v ; ++n) ;
p[w] = n;
} else if (g < cr + 1) {
p[w] = (int)(128.0 * fabs(g - cr));
}
}
p += buffer->pitch;
}
}
SDL_UnlockSurface(buffer);
SDL_BlitSurface(buffer, NULL, screen, NULL);
SDL_UpdateRect(screen, 0, 0, 0, 0);
if (redraw ? SDL_PollEvent(&event) : SDL_WaitEvent(&event)) {
if (event.type == SDL_QUIT)
break;
if (event.type == SDL_VIDEORESIZE) {
SDL_SetVideoMode(event.resize.w, event.resize.h, 0,
SDL_ANYFORMAT | SDL_RESIZABLE);
SDL_FreeSurface(buffer);
buffer = 0;
}
}
}
SDL_Quit();
TTF_Quit();
return 0;
}
As with my previous solution, the path to the font file needs to be either hardcoded in the source or added to the build command, e.g.:
gcc -Wall -o xkcdgolf `sdl-config --cflags`
-DFONTPATH=`fc-match --format='"%{file}"' :bold`
xkcdgolf.c -lSDL_ttf `sdl-config --libs` -lm
The output of the program looks like this:

This one is fun to watch, because all the math slows down the redraws to where you can see the program zero in on the stable solution. The first estimate is wildly off (since the surface starts out all-black), and then shrinks down to the final size after about a dozen or so iterations.
The code works by taking a population count of each pixel color in the current image. If this population count doesn't match the last one, then it redraws the image. The code iterates over every pixel, but it transforms the x,y coordinates into polar coordinates, computing first the radius (using the center of the image as the origin). If the radius is within the pie chart area, it then computes the theta. The theta is easily scaled to the population counts, which determines the pixel color. On the other hand, if the radius is right on the border of the pie chart, then an anti-aliased value is computed to draw the circle around the outside of the chart. Polar coordinates make everything easy!
C (with SDL and SDL_ttf)
Here's a very simple implementation, in about 60 lines of C code:
#include <stdio.h>
#include "SDL.h"
#include "SDL_ttf.h"
int main(void)
{
char buf[64];
SDL_Surface *screen, *text;
SDL_Rect rect;
SDL_Color black;
SDL_Event event;
TTF_Font *font;
Uint32 blackval, *p;
int size, b, prevb, h, i;
SDL_Init(SDL_INIT_VIDEO);
TTF_Init();
screen = SDL_SetVideoMode(640, 480, 32, SDL_ANYFORMAT | SDL_RESIZABLE);
font = TTF_OpenFont(FONTPATH, 32);
black.r = black.g = black.b = 0;
blackval = SDL_MapRGB(screen->format, 0, 0, 0);
b = -1;
for (;;) {
prevb = b;
b = 0;
SDL_LockSurface(screen);
p = screen->pixels;
for (h = screen->h ; h ; --h) {
for (i = 0 ; i < screen->w ; ++i)
b += p[i] == blackval;
p = (Uint32*)((Uint8*)p + screen->pitch);
}
SDL_UnlockSurface(screen);
size = screen->w * screen->h;
SDL_FillRect(screen, NULL, SDL_MapRGB(screen->format, 255, 255, 255));
sprintf(buf, "This image is %.2f%% black pixels", (100.0 * b) / size);
text = TTF_RenderText_Solid(font, buf, black);
rect.x = (screen->w - text->w) / 2;
rect.y = screen->h / 2 - text->h;
SDL_BlitSurface(text, NULL, screen, &rect);
SDL_FreeSurface(text);
sprintf(buf, "and %.2f%% white pixels.", (100.0 * (size - b)) / size);
text = TTF_RenderText_Solid(font, buf, black);
rect.x = (screen->w - text->w) / 2;
rect.y = screen->h / 2;
SDL_BlitSurface(text, NULL, screen, &rect);
SDL_FreeSurface(text);
SDL_UpdateRect(screen, 0, 0, 0, 0);
if (b == prevb ? SDL_WaitEvent(&event) : SDL_PollEvent(&event)) {
if (event.type == SDL_QUIT)
break;
if (event.type == SDL_VIDEORESIZE)
SDL_SetVideoMode(event.resize.w, event.resize.h, 32,
SDL_ANYFORMAT | SDL_RESIZABLE);
}
}
TTF_Quit();
SDL_Quit();
return 0;
}
To compile this, you need to define FONTPATH to point to a .ttf file of the font to use:
gcc -Wall -o xkcdgolf `sdl-config --cflags`
-DFONTPATH='"/usr/share/fonts/truetype/freefont/FreeSansBold.ttf"'
xkcdgolf.c -lSDL_ttf `sdl-config --libs`
On most modern Linux machines you can use the fc-match utility to look up font locations, so the compile command becomes:
gcc -Wall -o xkcdgolf `sdl-config --cflags`
-DFONTPATH=`fc-match --format='"%{file}"' :bold`
xkcdgolf.c -lSDL_ttf `sdl-config --libs`
(Of course you can replace the requested font with your personal favorite.)
The code specifically requests no anti-aliasing, so that the window contains only black and white pixels.
Finally, I was inspired by @daniero's elegant solution to permit window resizing. You'll see that sometimes the program oscillates between counts, stuck in an orbit around an attractor it can never reach. When that happens, just resize the window a bit until it stops.
And, per request, here's what it looks like when I run it on my system:

Finally, I feel that I should point out, in case anyone here hasn't already seen it, that the MAA published an interview with Randall Munroe in which he discusses the making of cartoon #688 in some detail.
