Skip to content

Commit

Permalink
feat(issue#69): implement a compressed writer, ref issue#69 (yeqown#72)
Browse files Browse the repository at this point in the history
implement a compressed writer, ref issue#69
  • Loading branch information
yeqown authored Sep 22, 2022
1 parent f423719 commit f2f14f2
Show file tree
Hide file tree
Showing 5 changed files with 257 additions and 0 deletions.
9 changes: 9 additions & 0 deletions .issues/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module issues

go 1.19

require (
github.com/yeqown/go-qrcode/v2 v2.2.0
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
)

141 changes: 141 additions & 0 deletions .issues/issue69/issue69.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/*
* Link: https://github.com/yeqown/go-qrcode/issues/69
* Title: Feature(image-compression): PNG bit depth must be 1
* Author: stokito(https://github.com/stokito)
*/

package main

import (
"fmt"
"image"
"image/png"
"io"
"os"
"strings"

skip2 "github.com/skip2/go-qrcode"

yeqown "github.com/yeqown/go-qrcode/v2"
yeqownwstd "github.com/yeqown/go-qrcode/writer/compressed"
)

/*
See https://github.com/yeqown/go-qrcode/issues/69
Results: (unit: B)
Content length: 158 // source text length
qr-skip2-best.png: 641 // skip2 generated size
qr-yeqown-best.png: 958 // yeqown best compression level
*/
func main() {
// some json
content := "{\n \"id\": \"beb6c04733d1a2da0f87c910a384\",\n \"tmax\": 895,\n \"cur\": [\n \"USD\"\n ],\n \"imp\": [\n {\n \"id\": \"21292\",\n \"instl\": 0,\n \"secure\": 0}\n"
// strip json to alpha num just for a test
content = strings.Replace(content, "{", "$", -1)
content = strings.Replace(content, "}", "%", -1)
content = strings.Replace(content, "\n", " ", -1)
content = strings.Replace(content, "\"", "*", -1)
content = strings.Replace(content, ",", "/", -1)
content = strings.Replace(content, "[", "/", -1)
content = strings.Replace(content, "]", "/", -1)
content = strings.ToUpper(content)
fmt.Printf("Content lenght: %d\n", len(content))
//imgEncoderNone := CustomPngEncoder{png.NoCompression}
imgEncoderBest := CustomPngEncoder{png.BestCompression}

encodeWithYeqown(content, "qr-yeqown.png")
//encodeWithYeqownCompression(content, "qr-yeqown-none.png", imgEncoderNone)
encodeWithYeqownCompression(content, "qr-yeqown-best.png", imgEncoderBest)
encodeWithSkip2(content, "qr-skip2-best.png")
}

func encodeWithSkip2(content, name string) {
//err := skip2.WriteFile(content, skip2.Highest, 0, name)
//if err != nil {
// log.Fatal(err)
//}
q, err := skip2.New(content, skip2.Highest)
if err != nil {
panic(err)
}
err = q.WriteFile(0, name)
if err != nil {
panic(err)
}
stat, _ := os.Stat(name)
fmt.Printf("%s: %v\n", name, stat.Size())
}

type CustomPngEncoder struct {
CompressionLevel png.CompressionLevel
}

func (j CustomPngEncoder) Encode(w io.Writer, img image.Image) error {
pngEncoder := png.Encoder{CompressionLevel: j.CompressionLevel}
return pngEncoder.Encode(w, img)
}

func encodeWithYeqown(content, name string) {
qrc, err := yeqown.NewWith(content,
yeqown.WithEncodingMode(yeqown.EncModeAlphanumeric),
yeqown.WithErrorCorrectionLevel(yeqown.ErrorCorrectionHighest),
)
if err != nil {
panic(err)
}

option := yeqownwstd.Option{
Padding: 4,
BlockSize: 1,
}
stdw, err := yeqownwstd.New(name, &option)
if err != nil {
panic(err)
}

if err := qrc.Save(stdw); err != nil {
panic(err)
}

stat, _ := os.Stat(name)
fmt.Printf("%s: %v\n", name, stat.Size())
}

func encodeWithYeqownCompression(content, name string, imageEncoder CustomPngEncoder) {
//cfg := yeqown.DefaultConfig()
//cfg.EncMode = yeqown.EncModeAlphanumeric
////cfg.EncMode = yeqown.EncModeByte
//cfg.EcLevel = yeqown.ErrorCorrectionHighest
//
//imgOpts := yeqown.WithCustomImageEncoder(imageEncoder)
//imgOpts2 := yeqown.WithQRWidth(1)
//imgOpts3 := yeqown.WithBorderWidth(4)
//qrc, err := yeqown.NewWithConfig(content, cfg, imgOpts, imgOpts2, imgOpts3)
//if err != nil {
// panic(err)
//}

qrc, err := yeqown.NewWith(content,
yeqown.WithEncodingMode(yeqown.EncModeAlphanumeric),
yeqown.WithErrorCorrectionLevel(yeqown.ErrorCorrectionHighest),
)
if err != nil {
panic(err)
}

option := yeqownwstd.Option{
Padding: 4,
BlockSize: 1,
}
stdw, err := yeqownwstd.New(name, &option)
if err != nil {
panic(err)
}

if err := qrc.Save(stdw); err != nil {
panic(err)
}

stat, _ := os.Stat(name)
fmt.Printf("%s: %v\n", name, stat.Size())
}
9 changes: 9 additions & 0 deletions go.work
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
go 1.19

use (
.
./.issues
./writer/compressed
./writer/standard
./writer/terminal
)
7 changes: 7 additions & 0 deletions writer/compressed/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module github.com/yeqown/go-qrcode/writer/compressed

go 1.19

require (
github.com/yeqown/go-qrcode/v2 v2.2.0
)
91 changes: 91 additions & 0 deletions writer/compressed/writer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package compressed

import (
"image"
"image/color"
"image/png"
"io"
"os"

"github.com/yeqown/go-qrcode/v2"
)

type Option struct {
Padding int
BlockSize int
}

// compressedWriter implements issue#69, generating compressed images
// in some special situations, such as, network transferring.
// https://github.com/yeqown/go-qrcode/issues/69
type compressedWriter struct {
fd io.WriteCloser

option *Option
}

var (
backgroundColor = color.Gray{Y: 0xff}
foregroundColor = color.Gray{Y: 0x00}
)

func New(filename string, opt *Option) (qrcode.Writer, error) {
fd, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0644)
if err != nil {
return nil, err
}

return compressedWriter{fd: fd, option: opt}, nil
}

func (w compressedWriter) Write(mat qrcode.Matrix) error {
padding := w.option.Padding
blockWidth := w.option.BlockSize
width := mat.Width()*blockWidth + 2*padding
height := width

img := image.NewPaletted(
image.Rect(0, 0, width, height),
color.Palette([]color.Color{backgroundColor, foregroundColor}),
)
bgColor := uint8(img.Palette.Index(backgroundColor))
fgColor := uint8(img.Palette.Index(foregroundColor))

rectangle := func(x1, y1 int, x2, y2 int, img *image.Paletted, color uint8) {
for x := x1; x < x2; x++ {
for y := y1; y < y2; y++ {
pos := img.PixOffset(x, y)
img.Pix[pos] = color
}
}
}

// background
rectangle(0, 0, width, height, img, bgColor)

mat.Iterate(qrcode.IterDirection_COLUMN, func(x int, y int, v qrcode.QRValue) {
sx := x*blockWidth + padding
sy := y*blockWidth + padding
es := (x+1)*blockWidth + padding
ey := (y+1)*blockWidth + padding

if v.IsSet() {
rectangle(sx, sy, es, ey, img, fgColor)
}

//switch v.IsSet() {
//case false:
// gray = backgroundColor
//default:
// gray = foregroundColor
//}

})

encoder := png.Encoder{CompressionLevel: png.BestCompression}
return encoder.Encode(w.fd, img)
}

func (w compressedWriter) Close() error {
return w.fd.Close()
}

0 comments on commit f2f14f2

Please sign in to comment.