This repository has been archived by the owner on Apr 21, 2023. It is now read-only.
forked from SimonWaldherr/zplgfa
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathzplgfa.go
286 lines (255 loc) · 7.58 KB
/
zplgfa.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
package zplgfa
import (
"bytes"
"encoding/hex"
"fmt"
"image"
"image/color"
"io"
"math"
"strings"
)
// GraphicType is a type to select the graphic format
type GraphicType int
func (gt GraphicType) String() string {
if gt == Binary {
return "B"
}
return "A"
}
const (
// ASCII graphic type using only hex characters (0-9A-F)
ASCII GraphicType = iota
// Binary saving the same data as binary
Binary
// CompressedASCII compresses the hex data via RLE
CompressedASCII
)
// ConvertToZPL is just a wrapper for ConvertToGraphicField which also includes the ZPL
// starting code ^XA and ending code ^XZ, as well as a Field Separator and Field Origin.
func ConvertToZPL(img image.Image, graphicType GraphicType) string {
return fmt.Sprintf("^XA,^FS\n^FO0,0\n%s^FS,^XZ\n", ConvertToGraphicField(img, graphicType))
}
var (
shortWhite = flatten(rgbaFromColor(color.White))
shortBlack = flatten(rgbaFromColor(color.Black))
)
func whiteish(v uint32) bool {
// colors represented in u32 are in range [0, 0xffff]
// see type image.Color interface docs
return v > 0xff00
}
func blackish(v uint32) bool {
// colors represented in u32 are in range [0, 0xffff]
// see type image.Color interface docs
return v < 0x00ff
}
type rgba struct{ r, g, b, a uint32 }
func (c rgba) RGBA() (r, g, b, a uint32) {
return c.r, c.g, c.b, c.a
}
func rgbaFromColor(c color.Color) rgba {
r, g, b, a := c.RGBA()
return rgba{r, g, b, a}
}
func shortcircuit(input rgba) (color.Gray16, bool) {
r, g, b, a := input.RGBA()
if whiteish(r) && whiteish(g) && whiteish(b) && whiteish(a) {
return shortWhite, true
}
if blackish(r) && blackish(g) && blackish(b) && blackish(a) {
return shortBlack, true
}
return color.Gray16{}, false
}
// FlattenImage optimizes an image for the converting process
// Not really needed as ConvertToGraphicField already does this internally
// to avoid looping through image (and doing image.At calls) twice
func FlattenImage(source image.Image) *image.Gray16 {
size := source.Bounds().Size()
target := image.NewGray16(source.Bounds())
for y := 0; y < size.Y; y++ {
for x := 0; x < size.X; x++ {
p := source.At(x, y)
rgba := rgbaFromColor(p)
flat, ok := shortcircuit(rgba)
if !ok {
flat = flatten(rgba)
}
target.SetGray16(x, y, flat)
}
}
return target
}
// adapted from color.Gray16Model.Convert
func gray16Model(r, g, b uint32) color.Gray16 {
// These coefficients (the fractions 0.299, 0.587 and 0.114) are the same
// as those given by the JFIF specification and used by func RGBToYCbCr in
// ycbcr.go.
//
// Note that 19595 + 38470 + 7471 equals 65536.
y := (19595*r + 38470*g + 7471*b + 1<<15) >> 16
return color.Gray16{uint16(y)}
}
func flatten(input rgba) color.Gray16 {
r, g, b, a := input.RGBA()
alpha := float32(a) / 0xffff
val := 0xffff - uint32((float32(color.White.Y) * alpha))
conv := func(c uint32) uint32 {
return val | uint32(float32(c)*alpha)
}
return gray16Model(conv(r), conv(g), conv(b))
}
func writeRepeatCode(dst io.Writer, repeatCount int, char rune) int {
n := 0
if repeatCount > 419 {
repeatCount -= 419
n += writeRepeatCode(dst, repeatCount, char)
repeatCount = 419
}
high := repeatCount / 20
low := repeatCount % 20
const lowString = " GHIJKLMNOPQRSTUVWXY"
const highString = " ghijklmnopqrstuvwxyz"
if high > 0 {
n += mustWrite(dst, []byte{highString[high]})
}
if low > 0 {
n += mustWrite(dst, []byte{lowString[low]})
}
n += mustWrite(dst, []byte(string(char)))
return n
}
// CompressASCII compresses the ASCII data of a ZPL Graphic Field using RLE
func CompressASCII(dst io.Writer, in string) {
var lastChar rune
var lastCharSince int
haveWritten := false
update := func(i int) {
if i-lastCharSince > 4 {
if n := writeRepeatCode(dst, i-lastCharSince, lastChar); n > 0 {
haveWritten = true
}
return
}
for j := 0; j < i-lastCharSince; j++ {
if n := mustWrite(dst, []byte(string(lastChar))); n > 0 {
haveWritten = true
}
}
}
for i, curChar := range in {
if lastChar == curChar {
continue
}
update(i)
lastChar = curChar
lastCharSince = i
}
if lastCharSince == 0 {
switch lastChar {
case '0':
mustWrite(dst, []byte(","))
return
case 'F':
mustWrite(dst, []byte("!"))
return
}
}
update(len(in))
if !haveWritten {
writeRepeatCode(dst, len(in), lastChar)
}
}
func mustWrite(dst io.Writer, s []byte) int {
n, err := dst.Write(s)
if err != nil {
panic(err)
}
return n
}
// ConvertToGraphicField converts an image.Image picture to a ZPL compatible Graphic Field.
// The ZPL ^GF (Graphic Field) supports various data formats, this package supports the
// normal ASCII encoded, as well as a RLE compressed ASCII format. It also supports the
// Binary Graphic Field format. The encoding can be chosen by the second argument.
func ConvertToGraphicField(source image.Image, graphicType GraphicType) string {
size := source.Bounds().Size()
width := size.X / 8
height := size.Y
if size.Y%8 != 0 {
width = width + 1
}
dst := bytes.NewBuffer(make([]byte, 0, 8*1024))
var compressionBuf *bytes.Buffer
readyCompressionBuf := func(hexstr string) {
if compressionBuf == nil {
compressionBuf = bytes.NewBuffer(make([]byte, 0, len(hexstr)))
return
}
compressionBuf.Reset()
if len(hexstr) > compressionBuf.Cap() {
compressionBuf.Grow(len(hexstr) - compressionBuf.Cap())
}
}
// adapted from: https://go-review.googlesource.com/c/go/+/72370
pxRGBA := func(x, y int) (r, g, b, a uint32) { return source.At(x, y).RGBA() }
// Fast paths for special cases to avoid excessive use of the color.Color
// interface which escapes to the heap but need to be discovered for
// each pixel on r. See also https://golang.org/issues/15759.
switch src0 := source.(type) {
case *image.RGBA:
pxRGBA = func(x, y int) (r, g, b, a uint32) { return src0.RGBAAt(x, y).RGBA() }
case *image.NRGBA:
pxRGBA = func(x, y int) (r, g, b, a uint32) { return src0.NRGBAAt(x, y).RGBA() }
case *image.RGBA64:
pxRGBA = func(x, y int) (r, g, b, a uint32) { return src0.RGBA64At(x, y).RGBA() }
case *image.NRGBA64:
pxRGBA = func(x, y int) (r, g, b, a uint32) { return src0.NRGBA64At(x, y).RGBA() }
case *image.YCbCr:
pxRGBA = func(x, y int) (r, g, b, a uint32) { return src0.YCbCrAt(x, y).RGBA() }
}
var lastLine string
for y := 0; y < size.Y; y++ {
line := make([]uint8, width)
lineIndex := 0
index := uint8(0)
currentByte := line[lineIndex]
for x := 0; x < size.X; x++ {
index = index + 1
r, g, b, a := pxRGBA(x, y)
rgba := rgba{r, g, b, a}
lum, ok := shortcircuit(rgba)
if !ok {
lum = flatten(rgba)
}
if lum.Y < math.MaxUint16/2 {
currentByte = currentByte | (1 << (8 - index))
}
if index >= 8 {
line[lineIndex] = currentByte
lineIndex++
if lineIndex < len(line) {
currentByte = line[lineIndex]
}
index = 0
}
}
hexstr := strings.ToUpper(hex.EncodeToString(line))
switch graphicType {
case ASCII:
mustWrite(dst, []byte(hexstr+"\n"))
case CompressedASCII:
readyCompressionBuf(hexstr)
CompressASCII(compressionBuf, hexstr)
if lastLine == compressionBuf.String() {
mustWrite(dst, []byte(":"))
} else {
mustWrite(dst, compressionBuf.Bytes())
}
lastLine = compressionBuf.String()
case Binary:
mustWrite(dst, []byte(line))
}
}
return fmt.Sprintf("^GF%s,%d,%d,%d,\n", graphicType.String(), dst.Len(), width*height, width) + dst.String()
}