[Question]
  [
So I think we've all probably seen [this xkcd comic](http://xkcd.com/688/):
![http://imgs.xkcd.com/comics/self_description.png](https://i.stack.imgur.com/R0wuP.png "The contents of any one panel are dependent on the contents of every panel including itself. The graph of panel dependencies is complete and bidirectional, and each node has a loop. The mouseover text has two hundred and forty-two characters."):
This might either be too general or too difficult, I'm not sure. But the challenge is to create a program in any language that creates a window that has at least 2 colors and displays in English words what percentage of the screen is each color.
ex. The simplest solution would be a white background with black letters that read "Percentage of this image that is black: [x]%. Percentage of this image that is white: [y]%"
You can go as crazy or as simple as you want; plain text is a valid solution but if you make interesting images like in the xkcd comic that's even better! The winner will be the most fun and creative solution that gets the most votes. So go forth and make something fun and worthy of xkcd! :)
So, what do you think? Sound like a fun challenge? :) 
**Please include a screenshot of your program running in your answer :)**
  
[Answer]
# 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:
![enter image description here](https://i.stack.imgur.com/JYYPb.png)
```
<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>&nbsp; Fraction of<br>- this website<br>&nbsp; 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>
```
[Answer]
## [Elm](http://elm-lang.org)
Haven't seen anyone use this loophole yet: [demo](http://share-elm.com/sprout/5568959be4b06aacf0e89ff8)
```
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
```
![enter image description here](https://i.stack.imgur.com/5uLYU.png)
[Answer]
# Processing, 222 characters
![https://i.stack.imgur.com/tcj1E.png](https://i.stack.imgur.com/tcj1E.png)
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.
[Answer]
Great challenge. Here's my solution. I tried to get as close as possible to the original comic, I even used the [xkcd font](http://forums.xkcd.com/viewtopic.php?f=2&t=22741&p=1678696#p1678696).
![](https://i.stack.imgur.com/6AKne.png)
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](https://mega.co.nz/#!x0lFFSAZ!3Uv1b8oszofFbkyKhyTzZzRnfWg47GaO6pQVA2in3Rc)
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.
[Answer]
## 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:
![Pie chart showing full grayscale pixel color distribution](https://i.stack.imgur.com/36cFB.png)
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!
[Answer]
## 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:
![This image is 3.36% black pixels and 96.64% white pixels.](https://i.stack.imgur.com/0c2OX.png)
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](http://www.maa.org/mathhorizons/MH-Sep2012_XKCD.html) in which he discusses the making of cartoon #688 in some detail.
[Answer]
![enter image description here](https://i.stack.imgur.com/Tp0Fe.png)
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
```
[Answer]
# 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](http://qb64.net)):
![QBasic metagraph](https://i.stack.imgur.com/w3Vdq.png)
[Answer]
# 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:
![x.png](https://i.stack.imgur.com/HsiAT.png)
]