spirngboot + 佳博标签打印机踩坑记录

spirngboot + 佳博标签打印机踩坑记录

前言:本项目基于springboot项目,后端调用打印机,佳博官方提供的Javademo就可以满意整合需求

问题一:打印中文,乱码问题

  • 描述:打印中文文字,打印出来的是乱码
  • 解决方案:在每次打印前,设置这个参数
System.setProperty("jna.encoding", "GBK");

当时排查时,以为打印机没有这个楷体或者宋体等字体导致的问题,后来翻阅了大量打印机对接代码,突然看到这么一行,拿过来试一试,直接解决。

问题二:打印内容,左边边距坐标不能从(0,0)开始

  • 描述:打印内容时,把x轴的坐标设置为0,但是每次打印距离左边都有一定的距离,而且距离有时候还不一样
  • 解决方案:一样的参数,但是有时候打印出来的位置看起来有差异,因为卡纸位置没有固定住,用滚轴上的侧边东西把纸固定住;x轴不能从0开始打印,是打印机设计如此,通过佳博提供的官方电脑应用,可以测试出来,预览时,左边距大概默认是2mm;这个只能从产品设计上去规定,打印的内容左边距不能小于2mm,然后拿到参数X坐标减去2mm既可以满足需求

佳博官网提供的打印应用,通过在线客服可以得到下载地址

问题三:打印图片,只能选择PCX和BMP

  • 描述:我们产品需求是打印二维码,把二维码图片保存为pngjpg的方法都烂大街了,但是保存为PCX很少,保存为bmp的也有,但是如何把bmp通过代码转化为打印机期望的位深,以及如何发送到打印机内部,这个没研究明白。
  • 解决方案:选择了PCX,使用的第三方依赖com.github.jai-imageio仓库地址

问题四:保存为PCX图片不能打印

  • 描述:有时候我们通过代码转化出来的图片是属于RGB的,但是打印机需要的是Bitmap的格式的,在new BufferedImage()时,图片类型设置为BufferedImage.TYPE_BYTE_BINARY
BufferedImage qrCode = new BufferedImage(qrCodeWidth,qrCodeHeight, BufferedImage.TYPE_BYTE_BINARY);

问题五:保存为PCX图片,但是右边有黑色竖线,这个问题独立于打印机,属于代码问题

  • 描述:打印的二维码,有些尺寸打印出来有黑色的竖线,但是有的尺寸打印出来没有
  • 解决方案:
    • 方案一:后来发现只要打印的尺寸,是8的倍数就可以,通过补偿的方式来实现;余数小于等于4则减去余数;余数大于4则减去余数再加8,这样尺寸会有一些偏差,产品上如果能接受,也是可以的
      int size = 139;
      int remainder = size % 8;
      if (remainder <= 4) {
          size = size - remainder;
      }
      if (remainder > 4) {
          size = size - remainder + 8;
      }
  • 方案二:修改com.github.jai-imageio的源码,这个方案是在方案一之前尝试的,但是代码注释很少,写的又是很多计算逻辑,也看的不太懂,代码于2018年就停止维护了,后来尝试了一天就战略放弃了,想到了方案一。当方案一处理好之后,灵光一闪,8???好像在代码里见过;直接上PCXImageWriter的代码
public class PCXImageWriter extends ImageWriter implements PCXConstants {

    private ImageOutputStream ios;
    private Rectangle sourceRegion;
    private Rectangle destinationRegion;
    private int colorPlanes, bytesPerLine;
    private Raster inputRaster = null;
    private int scaleX, scaleY;

    public PCXImageWriter(PCXImageWriterSpi imageWriterSpi) {
        super(imageWriterSpi);
    }

    public void setOutput(Object output) {
        super.setOutput(output); // validates output
        if (output != null) {
            if (!(output instanceof ImageOutputStream))
                throw new IllegalArgumentException("output not instance of ImageOutputStream");
            ios = (ImageOutputStream) output;
            ios.setByteOrder(ByteOrder.LITTLE_ENDIAN);
        } else
            ios = null;
    }

    public IIOMetadata convertImageMetadata(IIOMetadata inData, ImageTypeSpecifier imageType, ImageWriteParam param) {
        if (inData instanceof PCXMetadata)
            return inData;
        return null;
    }

    public IIOMetadata convertStreamMetadata(IIOMetadata inData, ImageWriteParam param) {
        return null;
    }

    public IIOMetadata getDefaultImageMetadata(ImageTypeSpecifier imageType, ImageWriteParam param) {
        PCXMetadata md = new PCXMetadata();
        md.bitsPerPixel = (byte) imageType.getSampleModel().getSampleSize()[0];
        return md;
    }

    public IIOMetadata getDefaultStreamMetadata(ImageWriteParam param) {
        return null;
    }

    public void write(IIOMetadata streamMetadata, IIOImage image, ImageWriteParam param) throws IOException {
        if (ios == null) {
            throw new IllegalStateException("output stream is null");
        }

        if (image == null) {
            throw new IllegalArgumentException("image is null");
        }

        clearAbortRequest();
        processImageStarted(0);
        if (param == null)
            param = getDefaultWriteParam();

        boolean writeRaster = image.hasRaster();

        sourceRegion = param.getSourceRegion();

        SampleModel sampleModel = null;
        ColorModel colorModel = null;

        if (writeRaster) {
            inputRaster = image.getRaster();
            sampleModel = inputRaster.getSampleModel();
            colorModel = ImageUtil.createColorModel(null, sampleModel);
            if (sourceRegion == null)
                sourceRegion = inputRaster.getBounds();
            else
                sourceRegion = sourceRegion.intersection(inputRaster.getBounds());
        } else {
            RenderedImage input = image.getRenderedImage();
            inputRaster = input.getData();
            sampleModel = input.getSampleModel();
            colorModel = input.getColorModel();
            Rectangle rect = new Rectangle(input.getMinX(), input.getMinY(),
                    input.getWidth(), input.getHeight());
            if (sourceRegion == null)
                sourceRegion = rect;
            else
                sourceRegion = sourceRegion.intersection(rect);
        }

        if (sourceRegion.isEmpty())
            throw new IllegalArgumentException("source region is empty");

        IIOMetadata imageMetadata = image.getMetadata();
        PCXMetadata pcxImageMetadata = null;

        ImageTypeSpecifier imageType = new ImageTypeSpecifier(colorModel, sampleModel);
        if (imageMetadata != null) {
            // Convert metadata.
            pcxImageMetadata = (PCXMetadata) convertImageMetadata(imageMetadata, imageType, param);
        } else {
            // Use default.
            pcxImageMetadata = (PCXMetadata) getDefaultImageMetadata(imageType, param);
        }

        scaleX = param.getSourceXSubsampling();
        scaleY = param.getSourceYSubsampling();

        int xOffset = param.getSubsamplingXOffset();
        int yOffset = param.getSubsamplingYOffset();

        // cache the data type;
        int dataType = sampleModel.getDataType();

        sourceRegion.translate(xOffset, yOffset);
        sourceRegion.width -= xOffset;
        sourceRegion.height -= yOffset;

        int minX = sourceRegion.x / scaleX;
        int minY = sourceRegion.y / scaleY;
        int w = (sourceRegion.width + scaleX - 1) / scaleX;
        int h = (sourceRegion.height + scaleY - 1) / scaleY;

        xOffset = sourceRegion.x % scaleX;
        yOffset = sourceRegion.y % scaleY;

        destinationRegion = new Rectangle(minX, minY, w, h);

        boolean noTransform = destinationRegion.equals(sourceRegion);

        // Raw data can only handle bytes, everything greater must be ASCII.
        int[] sourceBands = param.getSourceBands();
        boolean noSubband = true;
        int numBands = sampleModel.getNumBands();

        if (sourceBands != null) {
            sampleModel = sampleModel.createSubsetSampleModel(sourceBands);
            colorModel = null;
            noSubband = false;
            numBands = sampleModel.getNumBands();
        } else {
            sourceBands = new int[numBands];
            for (int i = 0; i < numBands; i++)
                sourceBands[i] = i;
        }

        ios.writeByte(MANUFACTURER);
        ios.writeByte(VERSION_3_0);
        ios.writeByte(ENCODING);

        int bitsPerPixel = sampleModel.getSampleSize(0);
        ios.writeByte(bitsPerPixel);

        ios.writeShort(destinationRegion.x); // xmin
        ios.writeShort(destinationRegion.y); // ymin
        ios.writeShort(destinationRegion.x + destinationRegion.width - 1); // xmax
        ios.writeShort(destinationRegion.y + destinationRegion.height - 1); // ymax

        ios.writeShort(pcxImageMetadata.hdpi);
        ios.writeShort(pcxImageMetadata.vdpi);

        byte[] smallpalette = createSmallPalette(colorModel);
        ios.write(smallpalette);
        ios.writeByte(0); // reserved

        colorPlanes = sampleModel.getNumBands();

        ios.writeByte(colorPlanes);

        // 这里有讲究 我的理解就是把宽按照8划分,这里是8bit = 1byte吗?我也不懂,反正就是知道划分处理就对了
        bytesPerLine = destinationRegion.width * bitsPerPixel / 8;
        // 这里有按照是否是2的倍数,进行了余数补偿,感觉更有讲究了,具体为啥这样做,我理解就是对宽度进行更精准的处理
        bytesPerLine += bytesPerLine % 2;

        ios.writeShort(bytesPerLine);

        if (colorModel.getColorSpace().getType() == ColorSpace.TYPE_GRAY)
            ios.writeShort(PALETTE_GRAYSCALE);
        else
            ios.writeShort(PALETTE_COLOR);

        ios.writeShort(pcxImageMetadata.hsize);
        ios.writeShort(pcxImageMetadata.vsize);

        for (int i = 0; i < 54; i++)
            ios.writeByte(0);

        // 在这之前的ios.writeXXX 我就默认理解为图片需要设置一些头部信息之类的,就好像TCP报文有报文头和数据部分组成
        // 可能我的理解也不对

        // write image data

        if (colorPlanes == 1 && bitsPerPixel == 1) {
            write1Bit();
        } else if (colorPlanes == 1 && bitsPerPixel == 4) {
            write4Bit();
        } else {
            write8Bit();
        }

        // write 256 color palette if needed
        if (colorPlanes == 1 && bitsPerPixel == 8 &&
                colorModel.getColorSpace().getType() != ColorSpace.TYPE_GRAY) {
            ios.writeByte(12); // Magic number preceding VGA 256 Color Palette Information
            ios.write(createLargePalette(colorModel));
        }

        if (abortRequested()) {
            processWriteAborted();
        } else {
            processImageComplete();
        }
    }

    private void write4Bit() throws IOException {
        int[] unpacked = new int[sourceRegion.width];
        int[] samples = new int[bytesPerLine];

        for (int line = 0; line < sourceRegion.height; line += scaleY) {
            inputRaster.getSamples(sourceRegion.x, line + sourceRegion.y, sourceRegion.width, 1, 0, unpacked);

            int val = 0, dst = 0;
            for (int x = 0, nibble = 0; x < sourceRegion.width; x += scaleX) {
                val = val | (unpacked[x] & 0x0F);
                if (nibble == 1) {
                    samples[dst++] = val;
                    nibble = 0;
                    val = 0;
                } else {
                    nibble = 1;
                    val = val << 4;
                }
            }

            int last = samples[0];
            int count = 0;

            for (int x = 0; x < bytesPerLine; x += scaleX) {
                int sample = samples[x];
                if (sample != last || count == 63) {
                    writeRLE(last, count);
                    count = 1;
                    last = sample;
                } else
                    count++;
            }
            if (count >= 1) {
                writeRLE(last, count);
            }

            processImageProgress(100.0F * line / sourceRegion.height);
        }
    }

    private void write1Bit() throws IOException {
        int[] unpacked = new int[sourceRegion.width];
        int[] samples = new int[bytesPerLine];

        for (int line = 0; line < sourceRegion.height; line += scaleY) {
            inputRaster.getSamples(sourceRegion.x, line + sourceRegion.y, sourceRegion.width, 1, 0, unpacked);

            int val = 0, dst = 0;
            for (int x = 0, bit = 1 << 7; x < sourceRegion.width; x += scaleX) {
                if (unpacked[x] > 0)
                    val = val | bit;
                if (bit == 1) {
                    samples[dst++] = val;
                    bit = 1 << 7;
                    val = 0;
                } else {
                    bit = bit >> 1;
                }
            }

            int last = samples[0];
            int count = 0;

            // 上文的根据宽度计算的值,在这里进行了使用处理
            for (int x = 0; x < bytesPerLine; x += scaleX) {
                int sample = samples[x];
                if (sample != last || count == 63) {
                    writeRLE(last, count);
                    count = 1;
                    last = sample;
                } else
                    count++;
            }
            // 关键的地方在这了,这里进行了for循环之后的单独处理
            // 像极了生成图片右边的那个竖线逻辑
            if (count >= 1) {
                // 原来的
                // writeRLE(last, count);
                // 修改之后的,盲目修改,也不知道修改的对不对,反正生成图片没毛病
                writeRLE(samples[0], count);
            }

            processImageProgress(100.0F * line / sourceRegion.height);
        }
    }

    private void write8Bit() throws IOException {
        int[][] samples = new int[colorPlanes][bytesPerLine];

        for (int line = 0; line < sourceRegion.height; line += scaleY) {
            for (int band = 0; band < colorPlanes; band++) {
                inputRaster.getSamples(sourceRegion.x, line + sourceRegion.y, sourceRegion.width, 1, band, samples[band]);
            }

            int last = samples[0][0];
            int count = 0;

            for (int band = 0; band < colorPlanes; band++) {
                for (int x = 0; x < bytesPerLine; x += scaleX) {
                    int sample = samples[band][x];
                    if (sample != last || count == 63) {
                        writeRLE(last, count);
                        count = 1;
                        last = sample;
                    } else
                        count++;
                }
            }
            if (count >= 1) {
                writeRLE(last, count);
            }

            processImageProgress(100.0F * line / sourceRegion.height);
        }
    }

    private void writeRLE(int val, int count) throws IOException {
        if (count == 1 && (val & 0xC0) != 0xC0) {
            ios.writeByte(val);
        } else {
            ios.writeByte(0xC0 | count);
            ios.writeByte(val);
        }
    }

    private byte[] createSmallPalette(ColorModel cm) {
        byte[] palette = new byte[16 * 3];

        if (!(cm instanceof IndexColorModel))
            return palette;

        IndexColorModel icm = (IndexColorModel) cm;
        if (icm.getMapSize() > 16)
            return palette;

        for (int i = 0, offset = 0; i < icm.getMapSize(); i++) {
            palette[offset++] = (byte) icm.getRed(i);
            palette[offset++] = (byte) icm.getGreen(i);
            palette[offset++] = (byte) icm.getBlue(i);
        }

        return palette;
    }

    private byte[] createLargePalette(ColorModel cm) {
        byte[] palette = new byte[256 * 3];

        if (!(cm instanceof IndexColorModel))
            return palette;

        IndexColorModel icm = (IndexColorModel) cm;

        for (int i = 0, offset = 0; i < icm.getMapSize(); i++) {
            palette[offset++] = (byte) icm.getRed(i);
            palette[offset++] = (byte) icm.getGreen(i);
            palette[offset++] = (byte) icm.getBlue(i);
        }

        return palette;
    }
}

问题六:修改源码后,如何整合到自己的项目中

  • 描述:修改完后,但是如何整合到项目中呢?我本来想是直接把源码重新打包成jar,放在lib目录下,然后在maven目录下引用,但是感觉不够优雅
  • 解决方案:
    • 看到源码里是通过插件方式进行拓展JDKImageIO,那么我是不是也可以的呢?说干就干,直接copy源码的PCX文件到项目中,所有文件加上前缀Custom,然后resources目录下,加上文件如下图,内容是类的全路径:com.xxxxx.xxxxx.imageio.pcx.CustomPCXImageWriterSpi;这里的实现原理建议参考本人另外一篇拙作设计模式之职责链模式——百看不如一练
      在这里插入图片描述
      在这里插入图片描述

    • 然后注意修改文件类型,避免与源码冲突

      在这里插入图片描述

    • 使用如下

      ImageIO.write(bufferedImage, "cpcx", new File("D:\\xxxxx\\xxxx\\" + "im_141_png_new" + ".pcx"));
      
  • 15
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值