From 3133e5f92b3ea587ca23b222d265f903e86a93cc Mon Sep 17 00:00:00 2001 From: yeqown Date: Wed, 11 Aug 2021 09:36:20 +0800 Subject: [PATCH] feat(image): support border width as output image parameters --- README.md | 8 ++++++++ image.go | 16 ++++++++++------ image_option.go | 4 ++++ image_option_api.go | 29 ++++++++++++++++++++++++++++- image_option_api_test.go | 25 +++++++++++++++++++++++++ qrcode_test.go | 16 ++++++++++++++++ 6 files changed, 91 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 657404c..acd0eb6 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,8 @@ QR code (abbreviated from Quick Response Code) is the trademark for a type of ma - [x] `WithLogoImage`, `WithLogoImageFilePNG`, `WithLogoImageFileJPEG` help you add an icon at the central of QR Code. +- [x] `WithBorderWidth` allows to specify any width of 4 sides around the qrcode. + ### Install ```sh @@ -93,6 +95,12 @@ func WithBuiltinImageEncoder(format formatTyp) ImageOption // WithCustomImageEncoder to use custom image encoder to encode image.Image into // io.Writer func WithCustomImageEncoder(encoder ImageEncoder) ImageOption + +// WithBorderWidth specify the both 4 sides' border width. Notice that +// WithBorderWidth(a) means all border width use this variable `a`, +// WithBorderWidth(a, b) mean top/bottom equal to `a`, left/right equal to `b`. +// WithBorderWidth(a, b, c, d) mean top, right, bottom, left. +func WithBorderWidth(widths ...int) ImageOption ``` use options in `New` and `NewWithConfig`. diff --git a/image.go b/image.go index 1ee340f..4a1160f 100644 --- a/image.go +++ b/image.go @@ -26,7 +26,10 @@ func drawAndSaveToFile(name string, m matrix.Matrix, opt *outputImageOptions) er if err != nil { return fmt.Errorf("could not create file: %v", err) } - defer f.Close() + + defer func(f *os.File) { + err = f.Close() + }(f) return drawAndSave(f, m, opt) } @@ -128,15 +131,16 @@ func drawAndSave(w io.Writer, m matrix.Matrix, imgOpt *outputImageOptions) (err // return rgba //} -// draw deal QRCode's matrix to be a image.Image +// draw deal QRCode's matrix to be an image.Image func draw(mat matrix.Matrix, opt *outputImageOptions) image.Image { if _debug { fmt.Printf("matrix.Width()=%d, matrix.Height()=%d\n", mat.Width(), mat.Height()) } + top, right, bottom, left := opt.borderWidths[0], opt.borderWidths[1], opt.borderWidths[2], opt.borderWidths[3] // w as image width, h as image height - w := mat.Width()*opt.qrBlockWidth() + 2*_defaultPadding - h := w + w := mat.Width()*opt.qrBlockWidth() + left + right + h := mat.Width()*opt.qrBlockWidth() + top + bottom // rgba := image.NewRGBA(image.Rect(0, 0, w, h)) dc := gg.NewContext(w, h) @@ -159,8 +163,8 @@ func draw(mat matrix.Matrix, opt *outputImageOptions) image.Image { mat.Iterate(matrix.ROW, func(x int, y int, v matrix.State) { // Draw the block ctx.upperLeft = image.Point{ - X: x*opt.qrBlockWidth() + _defaultPadding, - Y: y*opt.qrBlockWidth() + _defaultPadding, + X: x*opt.qrBlockWidth() + left, + Y: y*opt.qrBlockWidth() + top, } ctx.color = opt.stateRGBA(v) // DONE(@yeqown): make this abstract to Shapes diff --git a/image_option.go b/image_option.go index 6a8b5bf..9b1c771 100644 --- a/image_option.go +++ b/image_option.go @@ -17,6 +17,7 @@ func defaultOutputImageOption() *outputImageOptions { qrWidth: 20, // shape: _shapeRectangle, // imageEncoder: jpegEncoder{}, + borderWidths: [4]int{_defaultPadding, _defaultPadding, _defaultPadding, _defaultPadding}, } } @@ -41,6 +42,9 @@ type outputImageOptions struct { // imageEncoder specify which file format would be encoded the QR image. imageEncoder ImageEncoder + // borderWidths indicates the border width of the output image. the order is + // top, right, bottom, left same as the WithBorder + borderWidths [4]int } func (oo *outputImageOptions) backgroundColor() color.Color { diff --git a/image_option_api.go b/image_option_api.go index b761bcb..d568881 100644 --- a/image_option_api.go +++ b/image_option_api.go @@ -152,7 +152,7 @@ func WithBuiltinImageEncoder(format formatTyp) ImageOption { }) } -// WithBuiltinImageEncoder to use custom image encoder to encode image.Image into +// WithCustomImageEncoder to use custom image encoder to encode image.Image into // io.Writer func WithCustomImageEncoder(encoder ImageEncoder) ImageOption { return newFuncDialOption(func(oo *outputImageOptions) { @@ -164,3 +164,30 @@ func WithCustomImageEncoder(encoder ImageEncoder) ImageOption { }) } +// WithBorderWidth specify the both 4 sides' border width. Notice that +// WithBorderWidth(a) means all border width use this variable `a`, +// WithBorderWidth(a, b) mean top/bottom equal to `a`, left/right equal to `b`. +// WithBorderWidth(a, b, c, d) mean top, right, bottom, left. +func WithBorderWidth(widths ...int) ImageOption { + apply := func(arr *[4]int, top, right, bottom, left int) { + arr[0] = top + arr[1] = right + arr[2] = bottom + arr[3] = left + } + + return newFuncDialOption(func(oo *outputImageOptions) { + n := len(widths) + switch n { + case 0: + apply(&oo.borderWidths, _defaultPadding, _defaultPadding, _defaultPadding, _defaultPadding) + case 1: + apply(&oo.borderWidths, widths[0], widths[0], widths[0], widths[0]) + case 2, 3: + apply(&oo.borderWidths, widths[0], widths[1], widths[0], widths[1]) + default: + // 4+ + apply(&oo.borderWidths, widths[0], widths[1], widths[2], widths[3]) + } + }) +} diff --git a/image_option_api_test.go b/image_option_api_test.go index 3971c1a..eb43baa 100644 --- a/image_option_api_test.go +++ b/image_option_api_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/yeqown/go-qrcode/matrix" ) @@ -69,3 +70,27 @@ func Test_defaultOutputOption(t *testing.T) { oo2 := defaultOutputImageOption() assert.NotEqual(t, oo2.bgColor, oo.bgColor) } + +func Test_WithBorderWidth(t *testing.T) { + oo := defaultOutputImageOption() + + // zero parameter + WithBorderWidth().apply(oo) + assert.Equal(t, [4]int{_defaultPadding, _defaultPadding, _defaultPadding, _defaultPadding}, oo.borderWidths) + + // one parameter + WithBorderWidth(1).apply(oo) + assert.Equal(t, [4]int{1, 1, 1, 1}, oo.borderWidths) + + // two parameters + WithBorderWidth(1, 2).apply(oo) + assert.Equal(t, [4]int{1, 2, 1, 2}, oo.borderWidths) + + // three parameters + WithBorderWidth(1, 2, 3).apply(oo) + assert.Equal(t, [4]int{1, 2, 1, 2}, oo.borderWidths) + + // four parameters + WithBorderWidth(1, 2, 3, 4).apply(oo) + assert.Equal(t, [4]int{1, 2, 3, 4}, oo.borderWidths) +} diff --git a/qrcode_test.go b/qrcode_test.go index d533306..e201909 100644 --- a/qrcode_test.go +++ b/qrcode_test.go @@ -154,3 +154,19 @@ func Test_New_WithOutputOption_Shape(t *testing.T) { t.Fail() } } + +func Test_New_WithBorderWidth(t *testing.T) { + qrc, err := New("Test_New_WithOutputOption_Shape", + WithBorderWidth(10, 20, 30, 40), + ) + if err != nil { + t.Errorf("could not generate QRCode: %v", err) + t.Fail() + } + + // save file + if err = qrc.Save("./testdata/qrtest_border_width.jpeg"); err != nil { + t.Errorf("could not save image: %v", err) + t.Fail() + } +}