qrkit

package module
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 20, 2026 License: MIT Imports: 15 Imported by: 0

README

qrkit

qrkit

Go Reference

A zero-dependency QR code generator for Go with rich styling: custom module shapes, colours, transparent backgrounds, styled finder eyes and centre logos. Output as PNG (image.Image) or resolution-independent SVG.

Everything, including the Reed-Solomon error correction, matrix construction and rasteriser, is implemented from scratch on top of the Go standard library. The go.mod has no require lines.

Rounded style Dots with circular eyes Dots with a circular logo Transparent code on a gradient

Features

  • Standards-compliant QR Code Model 2 encoder (ISO/IEC 18004): versions 1-40, error-correction levels L / M / Q / H, automatic numeric / alphanumeric / byte mode selection, automatic mask selection with the standard's penalty rules.
  • Shapes: square, rounded (merging "liquid" corners), circle, diamond, vertical bars, horizontal bars, plus a module scale for gapped styles.
  • Finder ("eye") styling: square, rounded, circle or module-by-module, with independent outer/inner colours.
  • Colours: any foreground and background color.Color, including semi-transparent.
  • Transparent background support in both PNG and SVG.
  • Logo in the centre: any image.Image, with square / rounded / circle clipping, an optional backing plate, and a safety check that guarantees the logo stays within the code's error-correction budget (bumping the version when needed).
  • PNG and SVG output, anti-aliased, with exact pixel sizing.
  • Library-friendly: small functional-options API, sentinel errors for errors.Is, immutable and goroutine-safe results, no global state, no init side effects.
  • A handy CLI (cmd/qrkit).

Install

go get github.com/mohamedation/qrkit

Requires Go 1.21 or newer.

Quick start

package main

import (
	"log"

	"github.com/mohamedation/qrkit"
)

func main() {
	qr, err := qrkit.New("https://q.mohamedation.com")
	if err != nil {
		log.Fatal(err)
	}
	if err := qr.Save("code.png"); err != nil { // ".svg" works too
		log.Fatal(err)
	}
}

Other ways to get the result:

img := qr.Image()          // image.Image (*image.NRGBA) - draw it, resize it, embed it
data, err := qr.PNG()      // []byte
err = qr.WritePNG(w)       // any io.Writer, e.g. an http.ResponseWriter
svg, err := qr.SVG()       // string
err = qr.WriteSVG(w)
grid := qr.Matrix()        // [][]bool, render it however you like

Serving a code over HTTP:

http.HandleFunc("/qr", func(w http.ResponseWriter, r *http.Request) {
	qr, err := qrkit.New(r.URL.Query().Get("text"), qrkit.WithSize(300))
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}
	w.Header().Set("Content-Type", "image/png")
	_ = qr.WritePNG(w)
})

Styling

qr, err := qrkit.New("https://q.mohamedation.com",
	qrkit.WithSize(600),
	qrkit.WithForeground(color.NRGBA{0x1b, 0x2a, 0x49, 0xff}),
	qrkit.WithTransparentBackground(),
	qrkit.WithModuleShape(qrkit.ShapeRounded),
	qrkit.WithFinderStyle(qrkit.FinderStyle{
		Shape:      qrkit.FinderRounded,
		OuterColor: color.NRGBA{0xe8, 0x4a, 0x5f, 0xff},
		InnerColor: color.NRGBA{0x1b, 0x2a, 0x49, 0xff},
	}),
)
Shape option Result
ShapeSquare (default)
ShapeRounded
ShapeCircle + WithModuleScale(0.9)
ShapeDiamond
ShapeVerticalBars
ShapeHorizontalBars
ShapeRounded + WithModuleScale(0.8)
Transparent backgrounds

WithTransparentBackground() (or any colour with alpha < 255 via WithBackground) produces a PNG with real alpha and an SVG without a background rectangle, so the code can be laid over photos, gradients or coloured packaging:

Remember that scanners need contrast: dark modules on a light area, or, with most modern scanners, light modules on a dark area.

Logos
f, _ := os.Open("logo.png")
logo, _, _ := image.Decode(f) // import _ "image/png" (and/or image/jpeg)

qr, err := qrkit.New("https://q.mohamedation.com",
	qrkit.WithLogo(logo,
		qrkit.LogoScale(0.22),               // logo's longer side, as a fraction of the code width
		qrkit.LogoPadding(1),                // empty margin around it, in modules
		qrkit.LogoClip(qrkit.LogoCircle),    // LogoSquare | LogoRounded | LogoCircle
		qrkit.LogoPlate(color.White),        // optional solid plate behind the logo
	),
)
if errors.Is(err, qrkit.ErrLogoTooLarge) {
	// reduce LogoScale, or shorten the content
}

How logo safety works. The modules under the logo are removed, and the code relies on error correction to recover the missing data. qrkit therefore

  1. defaults to recovery level H when a logo is set (override with WithRecoveryLevel),
  2. works out exactly which data codewords the logo destroys and checks, for every Reed-Solomon block, that the loss stays within 60% of that block's correction capacity,
  3. refuses (returns ErrLogoTooLarge) if the logo would touch a finder, timing or format pattern, and
  4. automatically picks a larger version if the smallest one cannot hold your logo safely. Short content with a logo therefore yields a bigger, denser code.

LogoAllowUnsafe() disables the budget check (never the structural check) if you insist on a bigger logo. Always test such a code with real devices.

Tips: keep logos simple and high-contrast, at LogoScale ≈ 0.15-0.25, and prefer shorter content: fewer bytes means a lower version, larger modules, and more room for the logo.

Options reference

Option Default Description
WithRecoveryLevel(l) LevelMedium (LevelHigh with a logo) LevelLow ≈7%, LevelMedium ≈15%, LevelQuartile ≈25%, LevelHigh ≈30%
WithVersion(v) / WithVersionRange(min,max) 1-40 Force or bound the symbol version
WithMask(m) auto (-1) Force mask pattern 0-7
WithQuietZone(n) 4 Border in modules (the standard requires 4)
WithSize(px) 512 Image is exactly px×px; modules get the largest whole pixel size that fits and the remainder widens the quiet zone
WithModuleSize(px) Pixels per module instead of a total size (last of the two wins)
WithForeground(c) black Dark module colour
WithBackground(c) white Background; may be translucent
WithTransparentBackground() Fully transparent background
WithModuleShape(s) ShapeSquare See shapes above
WithModuleScale(f) 1 Module size inside its cell, in (0, 1]
WithCornerRadius(f) 0.4 Corner radius for ShapeRounded, in [0, 0.5]
WithFinderStyle(s) square, foreground Eye shape (FinderSquare/Rounded/Circle/Modules) and colours
WithLogo(img, ...) none Centre logo; see LogoScale, LogoPadding, LogoClip, LogoPlate, LogoAllowUnsafe

Pixel sizes are always whole pixels per module, so square edges are perfectly crisp with no anti-aliasing seams. Full API documentation lives on pkg.go.dev.

Errors

All errors can be tested with errors.Is:

Error Meaning
ErrEmptyData Nothing to encode
ErrDataTooLong Content does not fit the permitted versions at the chosen level
ErrInvalidOption An option value is out of range (message says which)
ErrLogoTooLarge The logo cannot be placed safely

Command-line tool

go install github.com/mohamedation/qrkit/cmd/qrkit@latest

qrkit -o code.png "https://q.mohamedation.com"
qrkit -o code.svg -shape rounded -finder rounded -fg "#1b2a49" -bg transparent "hello"
qrkit -o logo.png -logo logo.png -logo-shape circle -logo-scale 0.22 "https://q.mohamedation.com"
echo -n "text from a pipe" | qrkit -terminal -

Run qrkit -h for all flags.

Design notes

  • Encoding pipeline: mode selection → smallest fitting version → bit stream with terminator/padding → Reed-Solomon over GF(2⁸) per block → interleaving → module placement → evaluate all 8 masks with the four ISO penalty rules → format and version information.
  • One intermediate representation: the symbol is turned into a list of geometric shapes (rounded rectangles / diamonds with optional holes). The PNG renderer rasterises them with signed-distance-field anti-aliasing; the SVG renderer serialises them as paths (one merged path per colour, so there are no hairline seams between modules). Both outputs therefore look the same.
  • Not supported: Kanji mode, ECI, structured append, FNC1, Micro QR / rMQR. Content is encoded as one segment in a single mode (the most compact one that can represent it); text is UTF-8.

Testing

go test -race -cover ./...
go test -bench . -run xxx

The suite checks the standard's format/version information tables, capacity tables and alignment positions, a worked Reed-Solomon example, matrix structure, golden output, option validation, the logo safety logic, SVG well-formedness and concurrency. During development every version × level combination (160 symbols filled to capacity), all shape/colour/logo styles and the SVG output were additionally decoded successfully with an independent decoder (zxing-cpp).

License

MIT

Documentation

Overview

Package qrkit generates QR codes (ISO/IEC 18004 Model 2) as images or SVG, with rich styling, using only the Go standard library.

Quick start

qr, err := qrkit.New("https://q.mohamedation.com")
if err != nil {
	log.Fatal(err)
}
if err := qr.Save("code.png"); err != nil { // or "code.svg"
	log.Fatal(err)
}

Styling

Everything is configured with functional options passed to New:

qr, err := qrkit.New("https://q.mohamedation.com",
	qrkit.WithSize(600),
	qrkit.WithForeground(color.NRGBA{0x1b, 0x2a, 0x49, 0xff}),
	qrkit.WithTransparentBackground(),
	qrkit.WithModuleShape(qrkit.ShapeRounded),
	qrkit.WithFinderStyle(qrkit.FinderStyle{Shape: qrkit.FinderRounded}),
	qrkit.WithLogo(logoImage, qrkit.LogoClip(qrkit.LogoCircle)),
)

Module shapes, finder ("eye") shapes and colours, foreground and background colours (including transparency), module gaps, quiet zone, output size, mask, version range and error-correction level can all be chosen independently.

Logos

WithLogo places an image in the centre. The modules beneath it are removed and the code relies on error correction to stay readable, so the library defaults to the highest recovery level, verifies for every error-correction block that the logo stays within a conservative budget, and automatically moves to a larger symbol version when that is needed. If a logo cannot be placed safely, New returns an error wrapping ErrLogoTooLarge. Always test the final code with real scanners.

Output

A QRCode can be rendered as an image.Image (QRCode.Image), PNG (QRCode.PNG, QRCode.WritePNG), SVG (QRCode.SVG, QRCode.WriteSVG), written to a file (QRCode.Save) or inspected as a boolean matrix (QRCode.Matrix) so that you can render it any way you like.

Concurrency

A QRCode is immutable; all its methods are safe for concurrent use.

Limitations

Supported: versions 1-40, all four error-correction levels, numeric, alphanumeric and byte modes (chosen automatically, one mode per code). Not supported: Kanji mode, ECI headers, structured append, FNC1, Micro QR and rMQR. Text is encoded as UTF-8 bytes, which virtually all modern scanners handle.

Example
package main

import (
	"fmt"

	"github.com/mohamedation/qrkit"
)

func main() {
	qr, err := qrkit.New("https://q.mohamedation.com")
	if err != nil {
		panic(err)
	}
	fmt.Printf("version %d, %dx%d modules, level %s\n", qr.Version(), qr.Size(), qr.Size(), qr.RecoveryLevel())
	// qr.Save("code.png") writes a PNG; "code.svg" writes an SVG.
	

Index

Examples

Constants

View Source
const (
	MinVersion = 1
	MaxVersion = 40
)

Supported symbol versions.

Variables

View Source
var (
	// ErrEmptyData is returned when the content to encode is empty.
	ErrEmptyData = errors.New("qrkit: empty content")

	// ErrDataTooLong is returned when the content does not fit in any
	// permitted symbol version at the requested recovery level.
	ErrDataTooLong = errors.New("qrkit: content too long")

	// ErrInvalidOption is returned when an option has an out-of-range or
	// otherwise invalid value.
	ErrInvalidOption = errors.New("qrkit: invalid option")

	// ErrLogoTooLarge is returned when a logo would cover too much of the
	// symbol to remain reliably scannable (or would cover structural
	// patterns such as the finder patterns).
	ErrLogoTooLarge = errors.New("qrkit: logo too large")
)

Sentinel errors returned (possibly wrapped) by this package. Use errors.Is to test for them.

Functions

This section is empty.

Types

type FinderShape

type FinderShape int

FinderShape selects how the three large corner "eyes" are drawn.

const (
	// FinderSquare draws classic square finder patterns (the default).
	FinderSquare FinderShape = iota
	// FinderRounded draws finder patterns with rounded corners.
	FinderRounded
	// FinderCircle draws circular finder patterns.
	FinderCircle
	// FinderModules draws finder patterns module by module, using the
	// selected ModuleShape.
	FinderModules
)

type FinderStyle

type FinderStyle struct {
	// Shape of the eyes.
	Shape FinderShape
	// OuterColor is the colour of the outer 7x7 ring. Nil means the
	// foreground colour.
	OuterColor color.Color
	// InnerColor is the colour of the central 3x3 block. Nil means
	// OuterColor if set, otherwise the foreground colour.
	InnerColor color.Color
}

FinderStyle customises the finder patterns.

type LogoOption

type LogoOption func(*logoCfg)

LogoOption customises WithLogo.

func LogoAllowUnsafe

func LogoAllowUnsafe() LogoOption

LogoAllowUnsafe disables the error-correction budget check. The code may then be impossible to scan; the finder/timing/format patterns are still protected. Use only if you test the result.

func LogoClip

func LogoClip(s LogoShape) LogoOption

LogoClip sets the logo's clipping/plate shape. Default LogoSquare.

func LogoPadding

func LogoPadding(modules float64) LogoOption

LogoPadding sets the empty margin around the logo, in modules. Default 1.

func LogoPlate

func LogoPlate(col color.Color) LogoOption

LogoPlate paints a solid plate of the given colour behind the logo, which helps logos with transparency stand out on busy or transparent backgrounds.

func LogoScale

func LogoScale(s float64) LogoOption

LogoScale sets the logo's longer side as a fraction of the symbol width, in (0, 0.5]. Default 0.2.

type LogoShape

type LogoShape int

LogoShape is the shape of the logo's clipping mask and backing plate.

const (
	// LogoSquare leaves the logo unclipped (rectangular).
	LogoSquare LogoShape = iota
	// LogoRounded clips the logo to a rounded rectangle.
	LogoRounded
	// LogoCircle clips the logo to a circle.
	LogoCircle
)

type ModuleShape

type ModuleShape int

ModuleShape selects how each dark module is drawn.

const (
	// ShapeSquare draws square modules (the standard look).
	ShapeSquare ModuleShape = iota
	// ShapeRounded draws squares whose free corners are rounded; adjacent
	// modules merge into smooth, blob-like shapes.
	ShapeRounded
	// ShapeCircle draws each module as a dot.
	ShapeCircle
	// ShapeDiamond draws each module as a diamond.
	ShapeDiamond
	// ShapeVerticalBars merges vertically adjacent modules into
	// rounded-end bars.
	ShapeVerticalBars
	// ShapeHorizontalBars merges horizontally adjacent modules into
	// rounded-end bars.
	ShapeHorizontalBars
)

type Option

type Option func(*config)

Option configures New and NewFromBytes.

func WithBackground

func WithBackground(col color.Color) Option

WithBackground sets the background colour (default white). The colour may be translucent or fully transparent.

func WithCornerRadius

func WithCornerRadius(r float64) Option

WithCornerRadius sets the corner radius of ShapeRounded as a fraction of the module size, in [0, 0.5] (default 0.4).

func WithFinderStyle

func WithFinderStyle(s FinderStyle) Option

WithFinderStyle customises the three finder patterns.

func WithForeground

func WithForeground(col color.Color) Option

WithForeground sets the colour of the dark modules (default black).

func WithLogo(img image.Image, opts ...LogoOption) Option

WithLogo places an image in the centre of the code. The modules beneath it are left out, and the recovery level defaults to LevelHigh. See the Logo* options for size, padding and shape. New fails with ErrLogoTooLarge if the logo cannot be placed safely.

func WithMask

func WithMask(m int) Option

WithMask forces a mask pattern (0-7). The default, -1, evaluates all eight and picks the best, as the standard recommends.

func WithModuleScale

func WithModuleScale(s float64) Option

WithModuleScale shrinks each module inside its cell; 1 (default) fills the cell completely, 0.8 leaves a small gap. Values in (0, 1].

func WithModuleShape

func WithModuleShape(s ModuleShape) Option

WithModuleShape selects the module shape (default ShapeSquare).

func WithModuleSize

func WithModuleSize(px int) Option

WithModuleSize sets the size of one module in pixels, so the image is (modules + 2*quiet zone) * px wide. Overrides WithSize (last one wins).

func WithQuietZone

func WithQuietZone(modules int) Option

WithQuietZone sets the blank border around the symbol, in modules. The standard requires 4 (the default); scanners may need it.

func WithRecoveryLevel

func WithRecoveryLevel(l RecoveryLevel) Option

WithRecoveryLevel sets the error-correction level (default LevelMedium, or LevelHigh when a logo is used and no level was chosen).

func WithSize

func WithSize(px int) Option

WithSize sets the output image size in pixels (default 512). The image is always exactly px x px; modules get the largest whole pixel size that fits and any remainder is added to the quiet zone. SVG output uses it as the width and height attributes.

func WithTransparentBackground

func WithTransparentBackground() Option

WithTransparentBackground makes the background fully transparent.

func WithVersion

func WithVersion(v int) Option

WithVersion forces a specific symbol version (1-40). Encoding fails with ErrDataTooLong if the content does not fit.

func WithVersionRange

func WithVersionRange(min, max int) Option

WithVersionRange restricts automatic version selection to [min, max].

type QRCode

type QRCode struct {
	// contains filtered or unexported fields
}

QRCode is an encoded QR code together with its rendering style. A QRCode is immutable and safe for concurrent use.

func New

func New(content string, opts ...Option) (*QRCode, error)

New encodes content as a QR code. The most compact of the numeric, alphanumeric and byte (UTF-8) modes is chosen automatically, as is the smallest version that fits.

Example (Styled)
package main

import (
	"fmt"
	"image/color"

	"github.com/mohamedation/qrkit"
)

func main() {
	qr, err := qrkit.New("https://q.mohamedation.com",
		qrkit.WithSize(600),
		qrkit.WithForeground(color.NRGBA{0x1b, 0x2a, 0x49, 0xff}),
		qrkit.WithTransparentBackground(),
		qrkit.WithModuleShape(qrkit.ShapeRounded),
		qrkit.WithFinderStyle(qrkit.FinderStyle{
			Shape:      qrkit.FinderRounded,
			OuterColor: color.NRGBA{0xe8, 0x4a, 0x5f, 0xff},
		}),
	)
	if err != nil {
		panic(err)
	}
	b := qr.Image().Bounds()
	fmt.Println(b.Dx(), b.Dy())
}
Output:
600 600

func NewFromBytes

func NewFromBytes(data []byte, opts ...Option) (*QRCode, error)

NewFromBytes is like New for arbitrary binary data.

func (*QRCode) Image

func (q *QRCode) Image() image.Image

Image renders the code as a raster image (*image.NRGBA). Edges are anti-aliased and the background is transparent if so configured.

func (*QRCode) IsDark

func (q *QRCode) IsDark(x, y int) bool

IsDark reports whether the module at column x, row y is dark. Coordinates outside the symbol (the quiet zone) report false. The logo area is not taken into account; use Matrix for the raw symbol.

func (*QRCode) Mask

func (q *QRCode) Mask() int

Mask returns the mask pattern (0-7) that was applied.

func (*QRCode) Matrix

func (q *QRCode) Matrix() [][]bool

Matrix returns a copy of the module matrix, indexed [row][column], with true for dark modules and no quiet zone.

Example
package main

import (
	"fmt"

	"github.com/mohamedation/qrkit"
)

func main() {
	qr, _ := qrkit.New("HELLO WORLD")
	m := qr.Matrix()
	// Print the top-left finder pattern's first row.
	for x := 0; x < 7; x++ {
		if m[0][x] {
			fmt.Print("#")
		} else {
			fmt.Print(".")
		}
	}
	fmt.Println()
}
Output:
#######

func (*QRCode) PNG

func (q *QRCode) PNG() ([]byte, error)

PNG returns the code encoded as a PNG file.

func (*QRCode) RecoveryLevel

func (q *QRCode) RecoveryLevel() RecoveryLevel

RecoveryLevel returns the error-correction level actually used.

func (*QRCode) SVG

func (q *QRCode) SVG() (string, error)

SVG returns the code as a standalone SVG document. SVG output is resolution independent; a logo is embedded as a PNG data URI.

func (*QRCode) Save

func (q *QRCode) Save(path string) error

Save writes the code to a file; the format is chosen by the extension (".png" or ".svg", case-insensitive). The destination is replaced only after a successful write, so a failure does not leave a partial file.

func (*QRCode) Size

func (q *QRCode) Size() int

Size returns the side length in modules, excluding the quiet zone.

func (*QRCode) Version

func (q *QRCode) Version() int

Version returns the symbol version (1-40).

func (*QRCode) WritePNG

func (q *QRCode) WritePNG(w io.Writer) error

WritePNG encodes the code as PNG to w.

func (*QRCode) WriteSVG

func (q *QRCode) WriteSVG(w io.Writer) error

WriteSVG writes the code as an SVG document to w.

type RecoveryLevel

type RecoveryLevel int

RecoveryLevel is the error-correction level of a QR code. Higher levels tolerate more damage (or a larger logo) at the cost of a denser symbol.

const (
	// LevelLow recovers roughly 7% of the codewords.
	LevelLow RecoveryLevel = iota
	// LevelMedium recovers roughly 15% of the codewords. It is the default.
	LevelMedium
	// LevelQuartile recovers roughly 25% of the codewords.
	LevelQuartile
	// LevelHigh recovers roughly 30% of the codewords. It is the default
	// when a logo is used.
	LevelHigh
)

func (RecoveryLevel) String

func (l RecoveryLevel) String() string

String returns "L", "M", "Q" or "H".

Directories

Path Synopsis
cmd
qrkit command
Command qrkit generates QR codes from the command line.
Command qrkit generates QR codes from the command line.
examples
gallery command
Command gallery renders a set of sample QR codes demonstrating the styling options of qrkit.
Command gallery renders a set of sample QR codes demonstrating the styling options of qrkit.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL