Gran reto. Aquí está mi solución. Traté de acercarme lo más posible al cómic original, incluso usé la fuente xkcd .
Es una aplicación WPF, pero solía System.Drawing
hacer las partes del dibujo porque soy vago.
Concepto básico: en WPF, las ventanas son Visuals
, lo que significa que se pueden representar. Represento toda la instancia de Windows en un mapa de bits, cuento el negro y el blanco o negro total (ignorando los grises en el suavizado de fuente y demás) y también cuento estos para cada 3º de la imagen (para cada panel). Luego lo hago nuevamente en un temporizador. Alcanza el equilibrio en un segundo o dos.
Descargar:
MEGA
Siempre revise los archivos que descarga en busca de virus, etc., etc.
Tendrá que instalar la fuente anterior en su sistema si desea verla; de lo contrario, es la predeterminada de WPF.
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>
Código:
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;
}
}
}
El código no está limpio, pero debería ser algo legible, lo siento.