APP 小票打印

208 阅读4分钟

前置知识:图片组成原理,android打印图片的图片数据格式

需求提前:打印小票需要在热熔纸上打印二维码图片

原理:利用canvas把所需打印的图片描绘出来,通过canvas api取去图像的数据,经过位图算法的出来得到位图数据,最后转化成android符合的数据格式

第一步:

<canvas style="width: 1000px;height: 1300px;" canvas-id="firstCanvas"></canvas>

新增canvas画布

第二部:

export default {
    data (){
            return{
            canvasWidth:'',
            canvasHeight:''
    }
  },
  methods:{
              renderCanvas() {
                let filePath = '../../static/img/code.jpeg'
                const firstCanvas = uni.createCanvasContext('firstCanvas', this);
                let scla = 0.2
                uni.getImageInfo({
                    src: filePath,
                    success:res=> {
                        this.canvasWidth = res.width * scla
                        this.canvasHeight = res.height * scla
                        firstCanvas.drawImage(filePath, 0, 0, this.canvasWidth, this.canvasHeight);
                        firstCanvas.draw(false, () => {
                            this.$nextTick(() => { //获取画布像素数据
                                uni.canvasGetImageData({
                                    canvasId: 'firstCanvas',
                                    x: 0,
                                    y: 0,
                                    width: this.canvasWidth,
                                    height: this.canvasHeight,
                                    success: (res) => {
                                        this.byteData = setBitmap(res)

                                    },
                                    fail: function(res) {
                                        console.log(res)
                                    }
                                })
                            })
                        });
                    },
                    fail(res) {
                        console.log(res)
                    }
                })
            }
  }
}

利用canvas画出二维码,获取图片信息。

第三步:引入print.js 文件

const _queue = [],
    command = []

function intToByte(i) {
    // 此处关键 -- android是java平台 byte数值范围是 [-128, 127]
    // 因为java平台的byte类型是有符号的 最高位表示符号,所以数值范围固定
    // 而图片计算出来的是数值是 0 -255 属于int类型
    // 所以把int 转换成byte类型 
    var b = i & 0xFF;
    var c = 0;
    if (b >= 128) {
        c = b % 128;
        c = -1 * (128 - c);
    } else {
        c = b;
    }
    return c

}

function _enqueue(cmd) {
    _queue.push(...cmd)
}
//生成位图数据
export function setBitmap(res) {
    const w = res.width
    const h = res.height

    const bitw = parseInt(String((w + 7) / 8)) * 8
    const bith = h
    const pitch = parseInt(String(bitw / 8))
    const bits = new Uint8Array(bith * pitch)
    //设置打印图片居右
    _enqueue([27]) 
    _enqueue([97]) 
    _enqueue([2])
    //设置打印图片
    _enqueue([29]) // 0x1D
    _enqueue([118]) // 0x76
    _enqueue([48]) // 0x30
    _enqueue([0]) // 0x00
    _enqueue([parseInt(String(pitch % 256))]) // 宽度的低8位
    _enqueue([parseInt(String(pitch / 256))]) // 宽度的高8位
    _enqueue([parseInt(String(bith % 256))]) // 高度的低8位
    _enqueue([parseInt(String(bith / 256))]) // 高度的高8位
    for (let y = 0; y < h; y++) {
        for (let x = 0; x < w; x++) {
            const color = res.data[(y * w + x) * 4]
            if (color < 128) {
                bits[parseInt(String(y * pitch + x / 8))] |= 0x80 >> x % 8
            }
        }
    }

    for (let i = 0; i < bits.length; i++) {
        _enqueue([bits[i]])
    }
    for (let i = 0; i < _queue.length; ++i) {
        command.push(intToByte(_queue[i]))
    }
    return command
}
生成位图原理:

1位:(黑白图像,每个像素使用1位存储,0代表黑,1代表白)
8位(灰度图像,每个像素使用8位存储,代表256种灰度)
24位(RGB彩色图像,每个像素使用24位存储,8位表示红、绿、蓝三种颜色)

在打印机上打印的是黑白图片,所以只需要取一个通道值可以了,跟取灰度的原理相似。
又因为取出来的图片数据是包含了三通道和透明度的,所以取值的时候需要每隔4个取值

灰度图像灰白两色的判断条件是获取的红色通道值(0-255)与 128 进行比较,判断该像素是接近黑色还是接近白色

如果 color < 128,表示颜色较暗,被视为黑色。
如果 color >= 128,表示颜色较亮,被视为白色。

const color = res.data[(y * w + x) * 4] 中 y * w + x 是一个常见的二维数组到一维数组的坐标转换公式,主要用于从一维数组中定位到二维图像中的某个像素

1. 二维图像的表示

假设图像的宽度为 w,高度为 h,则这个图像可以被看作一个 h 行 w 列的二维网格,其中每个像素位于一个 (x, y) 坐标位置上:

x 表示横向(列)的索引,范围是 0 <= x < w。
y 表示纵向(行)的索引,范围是 0 <= y < h。

2. 一维数组表示像素数据

通常图像的像素数据会存储在一个一维数组中,而不是直接存储在二维数组中。假设这个数组的长度是 w * h,每个元素存储一个像素值。
为了从一维数组中访问某个特定位置的像素,我们需要将二维坐标 (x, y) 转换为一维数组的索引。

设置位图数据:bits[parseInt(String(y * pitch + x / 8))] |= 0x80 >> (x % 8)

1、bits 是一个 Uint8Array 类型的数组,用于存储最终的位图数据,每个字节表示 8 个像素的黑白状态。

2、y * pitch + x / 8:计算出当前行的偏移量以及当前像素对应的字节位置(每 8 个像素存储为 1 个字节)。

  • pitch 是一行的字节数,也就是每行能存放多少个 8 像素组。
  • x / 8 表示当前像素在哪个字节内。

3、0x80 >> (x % 8):通过位移操作将当前像素位置转换成对应的位,0x80 表示一个字节的最高位,通过 >> 右移操作,将最高位移到当前像素的正确位置。

4、bits[...] |=:通过按位或运算,将这一位设置为 1,表示该像素是黑色。最终每个字节(bits[...])中的 8 位分别对应 8 个像素的黑白状态。

最后把生成的位图数据转化成android端符合的数据格式,条用intToByte方法就可以在打印机上打印了