stuffshit / thermal

Thermal printer library for Raspberry Pi
5 stars 1 forks source link

Image Support? #1

Open zettam opened 8 years ago

zettam commented 8 years ago

Very nice project. Any plans for image support?

stuffshit commented 8 years ago

Hi! Thanks for your interest Maybe in future

zettam commented 8 years ago

Looking forward to it. copied pasted this functionality from another library and it's very buggy (for some reason it randomly switches from image to gibberish text --- or sometimes some sections of images are scrambled - I believe this is all about the way things are streamed?) -- but it may give you a starting point or maybe you can figure out what's wrong. Here is the full index.js

"use strict";

var util = require('util');
var serialport = require("serialport");
var Iconv = require('iconv').Iconv;
var SerialPort = serialport.SerialPort;
var Buffer = require('buffer').Buffer;
var getPixels = require('get-pixels');
var helpers = require('./helpers');
var floydSteinberg = require('floyd-steinberg');

function Thermal(path, options) {
  serialport.SerialPort.call(this, path, options);

  var _this = this;
  this.byteTime = parseFloat(11 / this.options.baudrate);
  this.timeout = 0;

  this.UPC_A = 0; //digits only. length: 11-12 
  this.UPC_E = 1; //digits only. length: 11-12. don`t work
  this.EAN13 = 2; //digits only. length: 12-13
  this.EAN8 = 3; //digits only. length: 7-8
  this.CODE39 = 4; //[0-9 A-Z $ % + - . /]. length: 1-12
  this.I25 = 5; //digits only. length: 1-25
  this.CODEBAR = 6; //[0-9 A-D $]. length: 1-15
  this.CODE93 = 7; //all ascii table (0-127). length: 1-19
  this.CODE128 = 8; //all ascii table (0-127). length: 2-18
  this.CODE11 = 9; //digits and dash (-). length: 21
  this.MSI = 10; //digits only. length: 16

  this.on("open", function () {
    _this.init();
    _this.emit('ready');
  });
}
util.inherits(Thermal, serialport.SerialPort);

Thermal.prototype.init = function () {
  this.command(new Buffer([27, 64]));
  this.setControlParams(20, 60, 250);
};

Thermal.prototype.text = function (txt) {
  var _this = this;
  var iconv = new Iconv('UTF-8', 'CP866//TRANSLIT//IGNORE');
  var buffer = iconv.convert(txt);

  setTimeout(function () {
    _this.write(buffer);
    _this.write("\n");
    _this.timeout = buffer.length * _this.timeout;
  }, buffer.length * _this.timeout);
};

Thermal.prototype.command = function (data) {
  var _this = this;

  setTimeout(function () {
    _this.write(data);
    _this.timeout = data.length * _this.timeout;
  }, data.length * _this.timeout);
};

Thermal.prototype.print = function (txt) {
  this.text(txt);
  return this;
};

// **********************************

Thermal.prototype.printImage = function (path, cb) {
  var done = false;

  var _self = this;
  /*var iBuffer = path;
  var ditherImage = floydSteinberg(iBuffer);*/

  getPixels(path, "image/png", function (err, pixels) {
    if (!err) {
      var width = pixels.shape[0];
      var height = pixels.shape[1];

      if (width != 384 || height > 65635) {
        throw new Error('Image width must be 384px, height cannot exceed 65635px.');
      }

      // contruct an array of Uint8Array,
      // each Uint8Array contains 384/8 pixel samples, corresponding to a whole line
      var imgData = [];
      for (var y = 0; y < height; y++) {
        imgData[y] = new Uint8Array(width / 8);
        for (var x = 0; x < (width / 8); x++) {
          imgData[y][x] = 0;
          for (var n = 0; n < 8; n++) {
            var r = pixels.get(x * 8 + n, y, 0);
            var g = pixels.get(x * 8 + n, y, 1);
            var b = pixels.get(x * 8 + n, y, 2);

            var brightness = helpers.rgbToHsl(r, g, b)[2];
            // only print dark stuff
            if (brightness < 0.6) {
              imgData[y][x] += (1 << n);
            }
          }
        }
      }

      // send the commands and buffers to the printer
      _self.printImageData(width, height, imgData);
      // tell deasync getPixels is done
      done = true;
    } else {
      throw new Error(err);
    }
  });
  return this;
};

Thermal.prototype.printImageData = function (width, height, imgData) {
  if (width != 384 || height > 65635) {
    throw new Error('Image width must be 384px, height cannot exceed 65635px.');
  }

  // send the commands and buffers to the printer
  var commands = [18, 118, height & 255, height >> 8];
  for (var y = 0; y < imgData.length; y++) {
    var buf = helpers.uint8ArrayToBuffer(imgData[y]);
    commands.push.apply(commands, buf);
  }
  this.command(commands);
  return this;
};

// **********************************

Thermal.prototype.feed = function (n) {
  for (var i = 0; i < n; i++)
    this.command("\n");
  return this;
};

Thermal.prototype.inverseOn = function () {
  this.command(new Buffer([29, 66, 1]));
  //this.command(new Buffer('1D4201','hex'));
  return this;
};

Thermal.prototype.inverseOff = function () {
  this.command(new Buffer([29, 66, 0]));
  return this;
};

Thermal.prototype.justify = function (n) {
  var c = '';

  if (n)
    c = n.toUpperCase();

  switch (c) {
  case 'C':
    var pos = 1;
    break;
  case 'R':
    var pos = 2;
    break;
  default:
    var pos = 0;
    break;
  }

  this.command(new Buffer([27, 97, pos]));
  return this;
};

Thermal.prototype.boldOn = function () {
  this.command(new Buffer([27, 69, 1]));
  return this;
};

Thermal.prototype.boldOff = function () {
  this.command(new Buffer([27, 69, 0]));
  return this;
};

Thermal.prototype.underlineOn = function (n) {
  n = parseInt(n) || 1;

  this.command(new Buffer([27, 45, n]));
  return this;
};

Thermal.prototype.underlineOff = function () {
  this.command(new Buffer([27, 45, 0]));
  return this;
};

Thermal.prototype.setSize = function (n) {
  var c = '';

  if (n)
    c = n.toUpperCase();

  switch (c) {
  case 'L':
    var size = 17;
    break;
  case 'M':
    var size = 1;
    break;
  default:
    var size = 0;
    break;
  }
  this.command(new Buffer([29, 33, size]));
  return this;
};

Thermal.prototype.setLineHeight = function (n) {
  if (typeof (n) === 'undefined' || n === 0) var n = 32;
  if (n < 24) n = 24;
  if (n > 255) n = 255;

  this.command(new Buffer([27, 51, n]));
  return this;
};

Thermal.prototype.doubleHeightOn = function () {
  this.command(new Buffer([27, 33, 16]));
  return this;
};

Thermal.prototype.doubleHeightOff = function () {
  this.command(new Buffer([27, 33, 0]));
  return this;
};

Thermal.prototype.doubleWidthOn = function () {
  this.command(new Buffer([27, 14]));
  return this;
};

Thermal.prototype.doubleWidthOff = function () {
  this.command(new Buffer([27, 20]));
  return this;
};

Thermal.prototype.upsideDownOn = function () {
  this.command(new Buffer([27, 123, 1]));
  return this;
};

Thermal.prototype.upsideDownOff = function () {
  this.command(new Buffer([27, 123, 0]));
  return this;
};

Thermal.prototype.online = function () {
  this.command(new Buffer([27, 61, 1]));
  return this;
};

Thermal.prototype.offline = function () {
  this.command(new Buffer([27, 61, 0]));
  return this;
};

Thermal.prototype.selftest = function () {
  this.command(new Buffer([18, 84]));
  return this;
};

Thermal.prototype.printBarcode = function (text, type) {
  this.command(new Buffer([29, 72, 2])); //text pos. 0-3. 0 - don`t display, 1 - on top, 2 - on bottom
  this.command(new Buffer([29, 119, 3])); //barcode width. 2-3. default is 3
  this.command(new Buffer([29, 107, type])); //set barcode type
  this.command(text);
  return this;
};

Thermal.prototype.setBarcodeHeight = function (n) {
  this.command(new Buffer([29, 104, n]));
  return this;
};

Thermal.prototype.setControlParams = function (dots, time, interval) {
  /*
Set “max heating dots”,”heating time”, “heating interval”
n1 = 0-255 Max printing dots,Unit(8dots),Default:7(64 dots)
n2 = 3-255 Heating time,Unit(10us),Default:80(800us)
n3 = 0-255 Heating interval,Unit(10us),Default:2(20us)
The more max heting dots, the more peak current will cost
whenprinting, the faster printing speed. The max heating dots is
8*(n1+1)
The more heating time, the more density , but the slower printing speed. If heating time is too short, blank page may occur.
The more heating interval, the more clear, but the slower printingspeed.
*/
  this.command(new Buffer([27, 55, dots, time, interval]));
  return this;
};

Thermal.prototype.setCodeTable = function (n) {
  //n: 0-47
  /*
0 - CP437 [USA, european standard]
1 - KataKana [Katakana]
2 - CP850 [Multi-lang]
3 - CP860 [Portuguese]
4 - CP863 [Canada - french]
5 - CP865 [Nordic]
6 - WCP1251 [Cyrillic]
7 - CP866 Slavic 2
8 - МИК [Slavic / Bolgarian]
9 - CP755 [Eastern Europe, Latvia 2]
10 - [Iran, Persian]
11 - reserved
12 - reserved
13 - reserved
14 - reserved
15 - CP862 [Hebrew]
16 - WCP1252 [Latin 1]
17 - WCP1253 [Greeсу]
18 - CP852 [Latin 2]
19 - CP858 [1 + european languages, latin symbols]
20 - Иран Ⅱ [Persian]
21 - Latvia
22 - CP864 [Arabic]
23 - ISO-8859- 1 [Western Europe]
24 - CP737 [Greece]
25 - WCP1257 [Baltic]
26 - Thai
27 - CP720 [Arabic]
28 - CP855
29 - CP857 [Turkish]
30 - WCP1250 [Central Europe]
31 - CP775
32 - WCP1254 [Turkish]
33 - WCP1255 [Arabic]
34 - WCP1256 [Arabic]
35 - WCP1258 [Vietnamese]
36 - ISO-8859- 2 [Latin 2]
37 - ISO-8859- 3 [Latin 3]
38 - ISO-8859- 4 [Baltic]
39 - ISO-8859- 5 [Cyrillic]
40 - ISO-8859- 6 [Arabic]
41 - ISO-8859- 7 [Greece]
42 - ISO-8859- 8 [Arabic]
43 - ISO-8859- 9 [Turkish]
44 - ISO-8859- 15 [Latin 9]
45 - [Thai 2]
46 - CP856
47 - CP874
*/
  this.command(new Buffer([27, 116, n]));
  return this;
};

/*
Prints all buffered data to the print region collectively, then recovers to the standard mode.
-All buffer data is deleted after printing.
-The print area set by ESC W (Set print region in page mode) is reset to the default setting. ï No paper cut is executed.
-Sets the print position to the beginning of the next line after execution.
-This command is enabled only in page mode.
*/
Thermal.prototype.flush = function () {
  this.command(new Buffer([12]));
  return this;
};

module.exports = Thermal;

and here is the helpers.js:

'use strict';
module.exports.rgbToHsl = function(r, g, b) {
    // bound values from 0 to 1
    r /= 255;
    g /= 255;
    b /= 255;

    var max = Math.max(r, g, b),
        min = Math.min(r, g, b);

    var h, s, l = (max+min)/2;

    if (max == min) {
        h = s = 0; // achromatic
    }
    else {
        var d = max-min;
        s = l > 0.5 ? d/(2-max-min) : d/(max+min);
        switch (max) {
            case r:
                h = (g-b)/d + (g < b ? 6 : 0);
                break;
            case g:
                h = (b-r)/d + 2;
                break;
            case b:
                h = (r-g)/d + 4;
                break;
        }
        h /= 6;
    }
    return [h, s, l];
};

module.exports.uint8ArrayToBuffer = function (array) {
    var buf = new Buffer(array.byteLength);
    var view = new Uint8Array(array);
    for (var i = 0; i < buf.length; ++i) {
        buf[i] = view[i];
    }
    return buf;
};