commit 07c1abe3d3caf693101fbb2bc88953623df6c2d6 Author: Simon Waldherr Date: Fri Oct 19 19:52:47 2018 +0200 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dba14d9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.zpl +*.png +cmd/zplgfa/zplgfa diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..152b904 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2018 Simon Waldherr + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..7b70ab6 --- /dev/null +++ b/README.md @@ -0,0 +1,62 @@ +# ZPLGFA Golang Package + +The ZPLGFA Golang package implements some functions to convert PNG, JPEG and GIF files to ZPL compatible ^GF-elements ([Graphic Fields](https://www.zebra.com/us/en/support-downloads/knowledge-articles/gf-graphic-field-zpl-command.html)). + +## install + +1. [install Golang](https://golang.org/doc/install) +1. `go get simonwaldherr.de/go/zplgfa` + +## example + +```go +package main + +import ( + "simonwaldherr.de/go/zplgfa" + "fmt" + "image" + _ "image/gif" + _ "image/jpeg" + _ "image/png" + "log" + "os" +) + +func main() { + // open file + file, err := os.Open("label.png") + if err != nil { + log.Printf("Warning: could not open the file: %s\n", err) + return + } + + defer file.Close() + + // load image head information + config, format, err := image.DecodeConfig(file) + if err != nil { + log.Printf("Warning: image not compatible, format: %s, config: %v, error: %s\n", format, config, err) + } + + // reset file pointer to the beginning of the file + file.Seek(0, 0) + + // load and decode image + img, _, err := image.Decode(file) + if err != nil { + log.Printf("Warning: could not decode the file, %s\n", err) + return + } + + // flatten image + flat := zplgfa.FlattenImage(img) + + // convert image to zpl compatible type + gfimg := zplgfa.ConvertToZPL(flat, zplgfa.CompressedASCII) + + // output zpl with graphic field date to stdout + fmt.Println(gfimg) +} + +``` \ No newline at end of file diff --git a/cmd/zplgfa/README.md b/cmd/zplgfa/README.md new file mode 100644 index 0000000..6428a5c --- /dev/null +++ b/cmd/zplgfa/README.md @@ -0,0 +1,31 @@ +# ZPLGFA CLI Tool + +The ZPLGFA cli tool converts PNG, JPEG and GIF images to [ZPL](https://www.zebra.com/content/dam/zebra/manuals/printers/common/programming/zpl-zbi2-pm-en.pdf) strings. +So if you need to print labels on a [ZPL](https://en.wikipedia.org/wiki/Zebra_(programming_language) compatible printer +(like the amazing [ZEBRA ZM400](https://amzn.to/2OD5S4n)), but don't have ZPL-templates, you can use this free tool. + +## install + +1. [install Golang](https://golang.org/doc/install) +1. `go install simonwaldherr.de/go/zplgfa/cmd/zplgfa` + +## usage + +So if your image file is `label.png` and the IP of your printer is `192.168.178.42` you can print via this command: + +```sh +./zplgfa -file label.png | nc 192.168.178.42 9100 +``` + +You can also use some effects, e.g. blur: + +```sh +./zplgfa -file label.png -edit blur | nc 192.168.178.42 9100 +``` + +The ZPLGFA is actually just a demo application for the ZPLGFA package, +if you need something for productive work, look at the source and build something, depending on your needs + +## test + +You have your ZPLs, but no ZEBRA printer? You can test almost all ZPL functionality at [labelary.com](http://labelary.com/viewer.html). \ No newline at end of file diff --git a/cmd/zplgfa/main.go b/cmd/zplgfa/main.go new file mode 100644 index 0000000..acadef3 --- /dev/null +++ b/cmd/zplgfa/main.go @@ -0,0 +1,146 @@ +package main + +import ( + "simonwaldherr.de/go/zplgfa" + "flag" + "fmt" + "github.com/anthonynsimon/bild/blur" + "github.com/anthonynsimon/bild/effect" + "github.com/anthonynsimon/bild/segment" + "github.com/nfnt/resize" + "image" + "image/color" + _ "image/gif" + _ "image/jpeg" + _ "image/png" + "log" + "math" + "os" + "strings" +) + +func main() { + var filenameFlag string + var graphicTypeFlag string + var imageEditFlag string + var imageResizeFlag float64 + var graphicType zplgfa.GraphicType + + flag.StringVar(&filenameFlag, "file", "", "filename to convert to zpl") + flag.StringVar(&graphicTypeFlag, "type", "CompressedASCII", "type of graphic field encoding") + flag.StringVar(&imageEditFlag, "edit", "", "manipulate the image [invert,monochrome]") + flag.Float64Var(&imageResizeFlag, "resize", 1.0, "zoom/resize the image") + + // load flag input arguments + flag.Parse() + + // open file + file, err := os.Open(filenameFlag) + if err != nil { + log.Printf("Warning: could not open the file \"%s\": %s\n", filenameFlag, err) + return + } + + defer file.Close() + + // load image head information + config, format, err := image.DecodeConfig(file) + if err != nil { + log.Printf("Warning: image not compatible, format: %s, config: %v, error: %s\n", format, config, err) + } + + // reset file pointer to the beginning of the file + file.Seek(0, 0) + + // load and decode image + img, _, err := image.Decode(file) + if err != nil { + log.Printf("Warning: could not decode the file, %s\n", err) + return + } + + // select graphic field type + switch graphicTypeFlag { + case "ASCII": + graphicType = zplgfa.ASCII + case "Binary": + graphicType = zplgfa.Binary + case "CompressedASCII": + graphicType = zplgfa.CompressedASCII + default: + graphicType = zplgfa.CompressedASCII + } + + // apply image manipulation functions + if strings.Contains(imageEditFlag, "monochrome") { + img = editImageMonochrome(img) + } + if strings.Contains(imageEditFlag, "blur") { + img = blur.Gaussian(img, float64(config.Width)/300) + } + if strings.Contains(imageEditFlag, "edge") { + img = effect.Sobel(img) + } + if strings.Contains(imageEditFlag, "segment") { + img = segment.Threshold(img, 128) + } + if strings.Contains(imageEditFlag, "invert") { + img = editImageInvert(img) + } + + // resize image + if imageResizeFlag != 1.0 { + img = resize.Resize(uint(float64(config.Width)*imageResizeFlag), uint(float64(config.Height)*imageResizeFlag), img, resize.MitchellNetravali) + } + + // flatten image + flat := zplgfa.FlattenImage(img) + + // convert image to zpl compatible type + gfimg := zplgfa.ConvertToZPL(flat, graphicType) + + // output zpl with graphic field date to stdout + fmt.Println(gfimg) +} + +type ImageSet interface { + Set(x, y int, c color.Color) +} + +func editImageInvert(img image.Image) image.Image { + b := img.Bounds() + + imgSet := img.(ImageSet) + for y := b.Min.Y; y < b.Max.Y; y++ { + for x := b.Min.X; x < b.Max.X; x++ { + oldPixel := img.At(x, y) + r, g, b, a := oldPixel.RGBA() + r = 65535 - r + g = 65535 - g + b = 65535 - b + pixel := color.RGBA{uint8(r), uint8(g), uint8(b), uint8(a)} + imgSet.Set(x, y, pixel) + } + } + return img +} + +func editImageMonochrome(img image.Image) image.Image { + b := img.Bounds() + + imgSet := img.(ImageSet) + for y := b.Min.Y; y < b.Max.Y; y++ { + for x := b.Min.X; x < b.Max.X; x++ { + oldPixel := img.At(x, y) + r, g, b, a := oldPixel.RGBA() + if r > math.MaxUint16/2 || g > math.MaxUint16/2 || b > math.MaxUint16/2 { + r, g, b = 65535, 65535, 65535 + } else { + r, g, b = 0, 0, 0 + } + pixel := color.RGBA{uint8(r), uint8(g), uint8(b), uint8(a)} + imgSet.Set(x, y, pixel) + } + } + return img +} diff --git a/zplgfa.go b/zplgfa.go new file mode 100644 index 0000000..3135eb1 --- /dev/null +++ b/zplgfa.go @@ -0,0 +1,195 @@ +package zplgfa + +import ( + "encoding/hex" + "fmt" + "image" + "image/color" + "math" + "strings" +) + +type GraphicType int + +const ( + ASCII GraphicType = iota + Binary + CompressedASCII +) + +// ConvertToZPL is just a wrapper for ConvertToGraphicField which also includes the ZPL +// starting code ^XA and ending code ^XZ, as wall 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)) +} + +func FlattenImage(source image.Image) *image.NRGBA { + size := source.Bounds().Size() + background := color.White + target := image.NewNRGBA(source.Bounds()) + for y := 0; y < size.Y; y++ { + for x := 0; x < size.X; x++ { + p := source.At(x, y) + flat := flatten(p, background) + target.Set(x, y, flat) + } + } + return target +} + +func flatten(input color.Color, background color.Color) color.Color { + source := color.NRGBA64Model.Convert(input).(color.NRGBA64) + r, g, b, a := source.RGBA() + bg_r, bg_g, bg_b, _ := background.RGBA() + alpha := float32(a) / 0xffff + conv := func(c uint32, bg uint32) uint8 { + val := 0xffff - uint32((float32(bg) * alpha)) + val = val | uint32(float32(c)*alpha) + return uint8(val >> 8) + } + c := color.NRGBA{ + conv(r, bg_r), + conv(g, bg_g), + conv(b, bg_b), + uint8(0xff), + } + return c +} + +func getRepeatCode(repeatCount int, char string) string { + repeatStr := "" + if repeatCount > 419 { + repeatCount -= 419 + repeatStr += getRepeatCode(repeatCount, char) + repeatCount = 419 + } + + high := repeatCount / 20 + low := repeatCount % 20 + + lowString := " GHIJKLMNOPQRSTUVWXY" + highString := " ghijklmnopqrstuvwxyz" + + if high > 0 { + repeatStr += string(highString[high]) + } + if low > 0 { + repeatStr += string(lowString[low]) + } + + repeatStr += char + + return repeatStr +} + +func compressASCII(in string) string { + in = strings.ToUpper(in) + var curChar string = "" + var lastChar string = "" + var lastCharSince int = 0 + var output string = "" + var repCode string + + for i := 0; i < len(in)+1; i++ { + if i == len(in) { + curChar = "" + if lastCharSince == 0 { + switch lastChar { + case "0": + output = "," + return output + case "F": + output = "!" + return output + } + } + } else { + curChar = string(in[i]) + } + if lastChar != curChar { + if i-lastCharSince > 8 { + repCode = getRepeatCode(i-lastCharSince, lastChar) + output += repCode + } else { + for j := 0; j < i-lastCharSince; j++ { + output += lastChar + } + } + + lastChar = curChar + lastCharSince = i + } + } + + if output == "" { + output += getRepeatCode(len(in), lastChar) + } + + return output +} + +// 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 choosen by the second argument. +func ConvertToGraphicField(source image.Image, graphicType GraphicType) string { + var gfType string + var lastLine string + size := source.Bounds().Size() + width := size.X / 8 + height := size.Y + if size.Y%8 != 0 { + width = width + 1 + } + + var GraphicFieldData 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 + p := source.At(x, y) + lum := color.Gray16Model.Convert(p).(color.Gray16) + 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 := hex.EncodeToString(line) + switch graphicType { + case ASCII: + GraphicFieldData += fmt.Sprintln(hexstr) + case CompressedASCII: + curLine := compressASCII(hexstr) + if lastLine == curLine { + GraphicFieldData += ":" + } else { + GraphicFieldData += curLine + } + lastLine = curLine + case Binary: + GraphicFieldData += fmt.Sprintf("%s", line) + default: + graphicType = CompressedASCII + GraphicFieldData += fmt.Sprintln(compressASCII(hexstr)) + } + } + + if graphicType == ASCII || graphicType == CompressedASCII { + gfType = "A" + } else if graphicType == Binary { + gfType = "B" + } + + return fmt.Sprintf("^GF%s,%d,%d,%d,\n%s", gfType, len(GraphicFieldData), width*height, width, GraphicFieldData) +}