clockworkpi / DevTerm

This code repository offers downloads for the latest images of various DevTerm models, as well as kernel patches, keyboard firmware, the source code for screen and printer drivers, hardware schematics, assembly instructions, and essential technical documents.
GNU Lesser General Public License v2.1
384 stars 68 forks source link

Add thermal_printer command to print images using 4 colors. #52

Closed msizov closed 1 year ago

msizov commented 1 year ago

Hi!

I've implemented a function for printing grayscale images, that works in a similar way as a monochrome print, but with a few differences:

  1. Paper will stay in place while printing all 3 non-white 'colors' sequentially, it will be moved after printing the third color. Colors are calculated from cfg: white color, density/4, density/2, density.
  2. User now needs to send 3 rows of color bitstrings for each image row. Colors are mutually exclusive. Each row follows monochrome image format.
  3. Header's height field now accounts data row count, not pixel rows, i.e. it needs to be multiplied by 3.
  4. Command id is different: cmd[0] == ASCII_GS && cmd[1] == 118 && cmd[2] == 49

E.g. for image 200x200, user needs to send header with width 200, height 600. Image data is arranged the following way (fourth color is blank, i.e. zero bits): 25 bytes (200bits) of 1st color for first row 25 bytes (200bits) of 2nd color for first row 25 bytes (200bits) of 3rd color for first row 25 bytes (200bits) of 1st color for second row 25 bytes (200bits) of 2nd color for second row ...etc... 25 3 200 bytes total.

Example 4 color image with comparison to monochrome print: shades

P.S. There is one minor change, that touches previous print function - I got better results when in print_dots_8bit I repeated heat cycles cfg->density times instead of hardcoded 10 times.

dangpzanco commented 1 year ago

How does your solution compare to @korneliuszo's approach at grayscale here at their repo?

Please checkout their kernel module and the python scripts grayprint.py and grayhitherprint.py for an alternative implementation of grayscale, I think it has 3 levels: white, gray and black. If you use Manjaro ARM or Arch Linux ARM, you can install the driver, the python scripts and a CUPS plugin via this repo (just run makepkg -si inside each folder).

Also, I think your code would benefit from having a user-friendly script like the python code I mentioned: you just run grayprint.py image.png and it prints in grayscale for you, no need for the user to rearrange the data manually.

That being said, I'll definitely test your approach when I have some time. I was trying to come up with a better gray-scale solution by experimenting with @korneliuszo's python code, but I never got something satisfactory (with an arbitrary/larger number of colors).

msizov commented 1 year ago

Thanks, I've checked that repo, foundation looks the same - grayscale colors have different exposure times. @korneliuszo's implementation indeed has 3 base colors and also an option to use dithering to simulate more shades of gray.

Actual purpose of proposed code is to serve as an emulator of Game Boy Printer, hence palette of 4 colors.

Here is the sample script to print images: https://gist.github.com/msizov/f693a17586dce35facf96c5edd485be3

korneliuszo commented 1 year ago

Exposure time is SW generated in both drivers, but in mine it's in kernelspace.

From my experience - different shades of gray bleakout in different rates.

msizov commented 1 year ago

Do you happen to know relation between printed color and exposure time? I've noticed that in clockworkpi's implementation time depends linearly on density: A * denstity + B, but I doubt it's linear in nature. I've picked density values through trial and error, it would be great if there is an empirical formula somewhere.

dangpzanco commented 1 year ago

@msizov I think the best way to implement the relation between color and exposure would be as follows:

  1. Print a "test page" with many distinct values of burn_count (number of burns per dot) and burn_time (time per burn): I remember there's a trade-off between tone accuracy and time when choosing the value of burn_count, but maybe only burn_time is important (?). Example test page: make a 2D grid of squares with a clear blank separation between them (maybe a black contour too?).
  2. Scan the page. This might be more difficult if you don't have a scanner, but a simple photo could also work well.
  3. Identify the different "squares" and compute their relative light intensities (average over all pixels inside the square: bigger squares provide better estimations).
  4. Now you have all the data you need to perform a (linear) regression. You might want to plot the data first and see if the best model is linear or log/exponential, polynomial regression might be useful as well. See scikit-learn's guide for more information.
  5. It might be need to perform this "calibration" for each type/brand of thermal paper, because they might have different exposure times. But hopefully this can be fully automated, just like one of those scanner alignment pages. I think even a crude estimation would be good enough for other paper types.

If you intend to do the calibration and need any help with the machine learning aspect of the process (3. and 4.), feel free to contact me privately.

PS: I forgot to show my results using grayprint.py before, here's an example print.