Winarkeo POSPicPrinter is the outcome of the below project, and will allow you to thermal print pictures the most comfortable way!

Download

pospicprint_setup.exe

Source included in setup dir

Happy printing!


We've seen in a previous article how to get most of a thermal printer, and how this one could be a good economic solution to everyday printing (drafts, grocery lists, mails addresses...). However, the D10 model was only limited to text (although we pushed a bit more the experiment), and, after purchase of a more modern thermal printer, which can print graphics at a speed of 90mm per second, it's time to level up.

For the hardcore photograph or illustrator (or casual one), the ability to print one's creations infinitely, without worrying about the printing delay and cost is an advantage. Indeed, the prints will be limited to 58 or 79mm wide (which shouldn't bother the Instax generation), indeed the output will only be black and white (for colour lovers), and thermal paper life is pretty variable. But printing without waiting, making physical albums quickly, creating material for street artists and scarpbookers is a must. Having to pay a certain amount for a colored zoom on the mac-n-cheese of the latest holiday is maybe a waste. Keeping for classical printing what really matters, while letting less interesting shots to recycled IKEA receipts (or even better, the receipt of the mac-n-cheese) can be an interesting approach regarding the way we handle our memories.

This is why we're gonna try to take the best advantage of the printer's graphical abilities.

The printer

The thermal printer is from the brand KKmoon, compatible with 58mm wide paper rolls.

The project

We're gonna code a Windows Console Application thanks to SharpDevelop (C#) and the .NET Framework 4.0. Dropping a picture file on the program will automatically launch the printing, after specifying some parameters.

In fact, the Windows Printing Assistant, Word and most of the softwares aren't designed to work with thermal printers. As the paper's length is almost infinite, this leads to ludicrous margins, and a never-lasting print: the paper keeps feeding even after the last pixels heated. What we want is a nice and clean output.

Application logic

I'm not going to detail the whole code, but here's a peek at the logic:

We're gonna use the PrintDocument class of the System.Drawing namespace in order to send the picture for printing.

PrintDocument pd = new PrintDocument();
pd.DefaultPageSettings.Landscape = false; //en portrait, s'il vous plait
pd.PrinterSettings.PrinterName = "POS-58"; //nom de l'imprimante (Panneau de Config > Périph. et Imprimantes)
//pd.DefaultPageSettings.Margins = new Margins(0,0,0,0); les marges de l'imprimantes ne peuvent être réduites plus
pd.PrintPage += (o, e) =>
{
    Image img = Image.FromFile(args[0]); //on prend l'image glissée
    if(img.Width > img.Height) img.RotateFlip(RotateFlipType.Rotate90FlipNone); //si en paysage, on pivote en portrait 

    e.Graphics.DrawImage(img, resizeKeepAspect(img, (int)pd.DefaultPageSettings.PrintableArea.Width, img.Height));
};          
pd.Print();

If the image is landscape, it will be oriented in portrait, for its length to benefit the whole thermal paper surface. Then we're gonna proportionaly resize the picture in order to fill the paper, using an edited version of the useful ResizeKeepAspect function proposed in this Stackoverflow thread. As DrawImage takes some Rectangle and not some Size as second parameter, we're going to change its return type:

public static Size ResizeKeepAspect(Size src, int maxWidth, int maxHeight)
{
    decimal rnd = Math.Min(maxWidth / (decimal)src.Width, maxHeight / (decimal)src.Height);
    return new Size((int)Math.Round(src.Width * rnd), (int)Math.Round(src.Height * rnd));
}

becomes

public static Rectangle resizeKeepAspect(Image src, int maxWidth, int maxHeight)
{
    decimal rnd = Math.Min(maxWidth / (decimal)src.Width, maxHeight / (decimal)src.Height);
    return new Rectangle(0, 0, (int)Math.Round(src.Width * rnd), (int)Math.Round(src.Height * rnd));
}

Note that we use

pd.DefaultPageSettings.PrintableArea.Width

to obtain the maximum length of the paper strip (printable area, without margins), in order to fill efficiently the surface.

Here's enough to make great prints! However, there is a problem. When there's too much dark tones the printer can't manage to cover the whole surface with pure black. In short, it can't maintain itself at some higher temperature. We have to find a black value which can be printed equally, without variations altering the picture's integrity. To do so we're going to modify the alpha of the picture. This way the absolute black of value 0 (on 255) will be brightened, let's say at 64 (25%). To achieve this, we can change the luminance of each pixel (by RGB value edit or by adding an alpha value), that is, even with Bitmap.LockBits is not really ergonomical. Or we can choose the trick to change the picture opacity, while alpha blending it on a white canvas. The brightness would be edited in a much simpler way. We're going to take this Stackoverflow function as a start, with a slightly edit, replacing

//creating an empty background
var output = new Bitmap(image.Width, image.Height);

with

//creating a white background
var output = new Bitmap(image.Width, image.Height);
using (Graphics graph = Graphics.FromImage(output))
{
    Rectangle ImageSize = new Rectangle(0,0,image.Width, image.Height);
    graph.FillRectangle(Brushes.White, ImageSize);
}

We can now print the same picture, from 100% opacity (the darkest tone, 0), to 75% (the maximum black value increasing to 64), 50% (128 128 128), passing by the 96 96 96 depth. We notice the deepest and more constant black that we can obtain is the 128 one, so 50%. A pretty good grey.

As each picture got its own tone, we let the user specify the opacity percentage he wants to apply to the print, using Console.ReadLine().

Previous Post Next Post