191 lines
4.5 KiB
Go
191 lines
4.5 KiB
Go
|
package standard
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"image"
|
||
|
"image/color"
|
||
|
|
||
|
"github.com/yeqown/go-qrcode/v2"
|
||
|
)
|
||
|
|
||
|
type ImageOption interface {
|
||
|
apply(o *outputImageOptions)
|
||
|
}
|
||
|
|
||
|
// defaultOutputImageOption default output image background color and etc options
|
||
|
func defaultOutputImageOption() *outputImageOptions {
|
||
|
return &outputImageOptions{
|
||
|
bgColor: color_WHITE, // white
|
||
|
bgTransparent: false, // not transparent
|
||
|
qrColor: color_BLACK, // black
|
||
|
logo: nil, //
|
||
|
qrWidth: 20, //
|
||
|
shape: _shapeRectangle, //
|
||
|
imageEncoder: jpegEncoder{},
|
||
|
borderWidths: [4]int{_defaultPadding, _defaultPadding, _defaultPadding, _defaultPadding},
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// outputImageOptions to output QR code image
|
||
|
type outputImageOptions struct {
|
||
|
// bgColor is the background color of the QR code image.
|
||
|
bgColor color.RGBA
|
||
|
// bgTransparent only affects on PNG_FORMAT
|
||
|
bgTransparent bool
|
||
|
|
||
|
// qrColor is the foreground color of the QR code.
|
||
|
qrColor color.RGBA
|
||
|
|
||
|
// logo this icon image would be put the center of QR Code image
|
||
|
// NOTE: logo only should have 1/5 size of QRCode image
|
||
|
logo image.Image
|
||
|
|
||
|
// qrWidth width of each qr block
|
||
|
qrWidth int
|
||
|
|
||
|
// shape means how to draw the shape of each cell.
|
||
|
shape IShape
|
||
|
|
||
|
// 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
|
||
|
|
||
|
// halftoneImg is the halftone image for the output image.
|
||
|
halftoneImg image.Image
|
||
|
}
|
||
|
|
||
|
func (oo *outputImageOptions) backgroundColor() color.RGBA {
|
||
|
if oo == nil {
|
||
|
return color_WHITE
|
||
|
}
|
||
|
|
||
|
if oo.bgTransparent {
|
||
|
(&oo.bgColor).A = 0x00
|
||
|
}
|
||
|
|
||
|
return oo.bgColor
|
||
|
}
|
||
|
|
||
|
func (oo *outputImageOptions) logoImage() image.Image {
|
||
|
if oo == nil || oo.logo == nil {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
return oo.logo
|
||
|
}
|
||
|
|
||
|
func (oo *outputImageOptions) qrBlockWidth() int {
|
||
|
if oo == nil || (oo.qrWidth <= 0 || oo.qrWidth > 255) {
|
||
|
return 20
|
||
|
}
|
||
|
|
||
|
return oo.qrWidth
|
||
|
}
|
||
|
|
||
|
func (oo *outputImageOptions) getShape() IShape {
|
||
|
if oo == nil || oo.shape == nil {
|
||
|
return _shapeRectangle
|
||
|
}
|
||
|
|
||
|
return oo.shape
|
||
|
}
|
||
|
|
||
|
// preCalculateAttribute this function must reference to draw function.
|
||
|
func (oo *outputImageOptions) preCalculateAttribute(dimension int) *Attribute {
|
||
|
if oo == nil {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
top, right, bottom, left := oo.borderWidths[0], oo.borderWidths[1], oo.borderWidths[2], oo.borderWidths[3]
|
||
|
return &Attribute{
|
||
|
W: dimension*oo.qrBlockWidth() + right + left,
|
||
|
H: dimension*oo.qrBlockWidth() + top + bottom,
|
||
|
Borders: oo.borderWidths,
|
||
|
BlockWidth: oo.qrBlockWidth(),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var (
|
||
|
color_WHITE = parseFromHex("#ffffff")
|
||
|
color_BLACK = parseFromHex("#000000")
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
// _STATE_MAPPING mapping matrix.State to color.RGBA in debug mode.
|
||
|
_STATE_MAPPING = map[qrcode.QRType]color.RGBA{
|
||
|
qrcode.QRType_INIT: parseFromHex("#ffffff"), // [bg]
|
||
|
qrcode.QRType_DATA: parseFromHex("#cdc9c3"), // [bg]
|
||
|
qrcode.QRType_VERSION: parseFromHex("#000000"), // [fg]
|
||
|
qrcode.QRType_FORMAT: parseFromHex("#444444"), // [fg]
|
||
|
qrcode.QRType_FINDER: parseFromHex("#555555"), // [fg]
|
||
|
qrcode.QRType_DARK: parseFromHex("#2BA859"), // [fg]
|
||
|
qrcode.QRType_SPLITTER: parseFromHex("#2BA859"), // [fg]
|
||
|
qrcode.QRType_TIMING: parseFromHex("#000000"), // [fg]
|
||
|
}
|
||
|
)
|
||
|
|
||
|
// translateToRGBA get color.RGBA by value State, if not found, return outputImageOptions.qrColor.
|
||
|
// NOTE: this function decides the state should use qrColor or bgColor.
|
||
|
func (oo *outputImageOptions) translateToRGBA(v qrcode.QRValue) (rgba color.RGBA) {
|
||
|
// TODO(@yeqown): use _STATE_MAPPING to replace this function while in debug mode
|
||
|
// or some special flag.
|
||
|
if v.IsSet() {
|
||
|
rgba = oo.qrColor
|
||
|
return rgba
|
||
|
}
|
||
|
|
||
|
if oo.bgTransparent {
|
||
|
(&oo.bgColor).A = 0x00
|
||
|
}
|
||
|
rgba = oo.bgColor
|
||
|
|
||
|
return rgba
|
||
|
}
|
||
|
|
||
|
// parseFromHex convert hex string into color.RGBA
|
||
|
func parseFromHex(s string) color.RGBA {
|
||
|
c := color.RGBA{
|
||
|
R: 0,
|
||
|
G: 0,
|
||
|
B: 0,
|
||
|
A: 0xff,
|
||
|
}
|
||
|
|
||
|
var err error
|
||
|
switch len(s) {
|
||
|
case 7:
|
||
|
_, err = fmt.Sscanf(s, "#%02x%02x%02x", &c.R, &c.G, &c.B)
|
||
|
case 4:
|
||
|
_, err = fmt.Sscanf(s, "#%1x%1x%1x", &c.R, &c.G, &c.B)
|
||
|
// Double the hex digits:
|
||
|
c.R *= 17
|
||
|
c.G *= 17
|
||
|
c.B *= 17
|
||
|
default:
|
||
|
err = fmt.Errorf("invalid length, must be 7 or 4")
|
||
|
}
|
||
|
if err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
|
||
|
return c
|
||
|
}
|
||
|
|
||
|
func parseFromColor(c color.Color) color.RGBA {
|
||
|
rgba, ok := c.(color.RGBA)
|
||
|
if ok {
|
||
|
return rgba
|
||
|
}
|
||
|
|
||
|
r, g, b, a := c.RGBA()
|
||
|
return color.RGBA{
|
||
|
R: uint8(r),
|
||
|
G: uint8(g),
|
||
|
B: uint8(b),
|
||
|
A: uint8(a),
|
||
|
}
|
||
|
}
|