This is something of a conundrum for me… It’s getting annoying to work out how many days have passed since I quit smoking, but I also want to share it on Facebook every day, as I have been. The conundrum is: I’m lazy, but to avoid having to look at a calendar every day, and this is only going to get worse, I must do some work.
So here it is, a silly little application to work it out and allow me to copy an image to the clipboard before sharing. It looks like this:
It’s quite crude, and calculates the size of the font to be used by dynamically measuring text programmatically in a loop before doing the actual drawing, onto an image I created by drawing a gradient. So an obvious limitation is that the first line won’t be cantered… because I measure the text using a rectangle, and when I have tried centering the text it fucks it up completely, and I am after all lazy.
I could probably also allow choosing the gradient start and end colours, or maybe allow the user to load an image instead.
But still, I like it. The shadow is achieved simply by drawing twice… i.e. calculate where to draw, and then draw once with x+5, y+5, in black, and then draw again at the proper X and Y coordinate, in white… I draw the shadow first because I’m drawing directly onto the bitmap, so drawing the white first would land up with the black drawing over it.
For anyone interested, the source code is pretty elementary… main form is this:
using System;
using System.Collections.Generic;
using System.Windows.Forms;
namespace SmokeFree
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void btnCopy_Click(object sender, EventArgs e)
{
Clipboard.SetImage(pictureBox1.Image);
}
private void btnRefresh_Click(object sender, EventArgs e)
{
RefreshText();
}
private void dtStart_ValueChanged(object sender, EventArgs e)
{
Properties.Settings.Default.StartDate = dtStart.Value;
}
private void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
Properties.Settings.Default.Save();
}
private void Form1_Load(object sender, EventArgs e)
{
dtStart.Value = Properties.Settings.Default.StartDate;
var lines = new List<string>();
foreach (var line in Properties.Settings.Default.Template)
{
lines.Add(line);
}
richTextBox1.Lines = lines.ToArray();
RefreshText();
}
private void RefreshText()
{
var days = DateTime.Today.Subtract(dtStart.Value).Days.ToString();
pictureBox1.Image = ImageUtilities.ModifyImage(ImageUtilities.OutputGradientImage(), richTextBox1.Text.Replace("[N]", days));
}
private void richTextBox1_TextChanged(object sender, EventArgs e)
{
var lines = new System.Collections.Specialized.StringCollection();
lines.AddRange(richTextBox1.Lines);
Properties.Settings.Default.Template = lines;
}
}
}
And the code that plays with the images is this:
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
namespace SmokeFree
{
internal class ImageUtilities
{
public static Image ModifyImage(Image image, string text)
{
bool indexPixelFormat = image.PixelFormat == PixelFormat.Format8bppIndexed;
// if indexPixelFormat, this must be explicitly disposed.
Image tempImage = indexPixelFormat ? new Bitmap(image.Width, image.Height) : image;
try
{
using (Graphics graphics = Graphics.FromImage(tempImage))
{
if (indexPixelFormat)
graphics.DrawImage(image, 0, 0);
graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
float fontSize = 100;
Font font = new Font("Segoe UI Emoji", fontSize, FontStyle.Bold);
try
{
// Measure string to figure out the width needed, starting with font size 100 loop down until it will fit.
SizeF stringSize = graphics.MeasureString(text, font);
while (stringSize.Width > image.Width || stringSize.Height > image.Height)
{
font.Dispose();
fontSize -= 2;
font = new Font("Segoe UI Emoji", fontSize, FontStyle.Bold);
stringSize = graphics.MeasureString(text, font);
}
/* Draw twice, first in transparent black and then
* transparent white, so we have a shadow effect. */
using (SolidBrush shadowBrush = new SolidBrush(Color.FromArgb(255, 0, 0, 0)),
textBrush = new SolidBrush(Color.FromArgb(255, 255, 255, 255)))
{
float x = (image.Width - stringSize.Width) / 2F;
float y = (image.Height - stringSize.Height) / 2F;
graphics.DrawString(text, font, shadowBrush, new PointF(x + 5, y + 5));
graphics.DrawString(text, font, textBrush, new PointF(x, y));
}
}
finally
{
font.Dispose();
}
}
}
finally
{
if (indexPixelFormat)
tempImage.Dispose();
}
return image;
}
public static Bitmap OutputGradientImage()
{
Bitmap bitmap = new Bitmap(640, 480);
using (Graphics graphics = Graphics.FromImage(bitmap))
using (LinearGradientBrush brush = new LinearGradientBrush(new Rectangle(0, 0, 640, 480), Color.Blue, Color.Red, LinearGradientMode.Vertical))
{
graphics.FillRectangle(brush, new Rectangle(0, 0, 640, 480));
return bitmap;
}
}
}
}