Skip to content

Commit

Permalink
Support WebP and AVIF image formats for writing canvas, also add bett…
Browse files Browse the repository at this point in the history
…er documentation, see #300
  • Loading branch information
tdewolff committed May 14, 2024
1 parent 50f56b3 commit a888e79
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 44 deletions.
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ require (
fyne.io/fyne/v2 v2.4.1
gioui.org v0.3.1
github.com/ByteArena/poly2tri-go v0.0.0-20170716161910-d102ad91854f
github.com/Kagami/go-avif v0.1.0
github.com/benoitkugler/textprocessing v0.0.3
github.com/go-fonts/latin-modern v0.3.1
github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20231031225837-d1c54e5847d0
github.com/go-text/typesetting v0.0.0-20231013144250-6cc35dbfae7d
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
github.com/kolesa-team/go-webp v1.0.4
github.com/paulmach/orb v0.10.0
github.com/paulmach/osm v0.7.1
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8
Expand Down
5 changes: 5 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/ByteArena/poly2tri-go v0.0.0-20170716161910-d102ad91854f h1:l7moT9o/v/9acCWA64Yz/HDLqjcRTvc0noQACi4MsJw=
github.com/ByteArena/poly2tri-go v0.0.0-20170716161910-d102ad91854f/go.mod h1:vIOkSdX3NDCPwgu8FIuTat2zDF0FPXXQ0RYFRy+oQic=
github.com/Kagami/go-avif v0.1.0 h1:8GHAGLxCdFfhpd4Zg8j1EqO7rtcQNenxIDerC/uu68w=
github.com/Kagami/go-avif v0.1.0/go.mod h1:OPmPqzNdQq3+sXm0HqaUJQ9W/4k+Elbc3RSfJUemDKA=
github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY=
github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk=
github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b h1:slYM766cy2nI3BwyRiyQj/Ud48djTMtMebDqepE95rw=
Expand Down Expand Up @@ -259,6 +261,8 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/kolesa-team/go-webp v1.0.4 h1:wQvU4PLG/X7RS0vAeyhiivhLRoxfLVRlDq4I3frdxIQ=
github.com/kolesa-team/go-webp v1.0.4/go.mod h1:oMvdivD6K+Q5qIIkVC2w4k2ZUnI1H+MyP7inwgWq9aA=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
Expand Down Expand Up @@ -416,6 +420,7 @@ golang.org/x/exp/shiny v0.0.0-20231006140011-7918f672742d/go.mod h1:UH99kUObWAZk
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20210504121937-7319ad40d33e/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
golang.org/x/image v0.11.0/go.mod h1:bglhjqbqVuEb9e9+eNR45Jfu7D+T4Qan+NhQk8Ck2P8=
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
Expand Down
134 changes: 90 additions & 44 deletions renderers/renderers.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package renderers

import (
"compress/flate"
"fmt"
"image/gif"
"image/jpeg"
Expand All @@ -9,7 +10,8 @@ import (
"path/filepath"
"strings"

//webp "github.com/kolesa-team/go-webp/encoder"
"github.com/Kagami/go-avif"

Check failure on line 13 in renderers/renderers.go

View workflow job for this annotation

GitHub Actions / lint

could not import github.com/Kagami/go-avif (-: # github.com/Kagami/go-avif
webp "github.com/kolesa-team/go-webp/encoder"

Check failure on line 14 in renderers/renderers.go

View workflow job for this annotation

GitHub Actions / lint

could not import github.com/kolesa-team/go-webp/encoder (-: # github.com/kolesa-team/go-webp/encoder
"github.com/tdewolff/canvas"
"github.com/tdewolff/canvas/renderers/pdf"
"github.com/tdewolff/canvas/renderers/ps"
Expand All @@ -24,6 +26,7 @@ const mmPerPt = 25.4 / 72.0
const ptPerMm = 72.0 / 25.4
const mmPerPx = 25.4 / 96.0

// Write renders the canvas and writes to a file. A renderer is chosen based on the filename extension. The options will be passed to the respective renderer. Supported extensions: .(png|jpe?g|gif|tiff?|bmp|webp|avif|svgz?|pdf|tex|pgf|ps|eps).
func Write(filename string, c *canvas.Canvas, opts ...interface{}) error {
switch ext := strings.ToLower(filepath.Ext(filename)); ext {
case ".png":
Expand All @@ -36,12 +39,14 @@ func Write(filename string, c *canvas.Canvas, opts ...interface{}) error {
return c.WriteFile(filename, TIFF(opts...))
case ".bmp":
return c.WriteFile(filename, BMP(opts...))
//case ".webp":
// return c.WriteFile(filename, WEBP(opts...))
case ".svgz":
return c.WriteFile(filename, SVGZ(opts...))
case ".webp":
return c.WriteFile(filename, WebP(opts...))
case ".avif":
return c.WriteFile(filename, AVIF(opts...))
case ".svg":
return c.WriteFile(filename, SVG(opts...))
case ".svgz":
return c.WriteFile(filename, SVGZ(opts...))
case ".pdf":
return c.WriteFile(filename, PDF(opts...))
case ".tex", ".pgf":
Expand All @@ -62,25 +67,30 @@ func errorWriter(err error) canvas.Writer {
}
}

// PNG returns a PNG writer and accepts the following options: canvas.Resolution, canvas.Colorspace, image/png.Encoder
func PNG(opts ...interface{}) canvas.Writer {
resolution := canvas.DPMM(1.0)
colorSpace := canvas.DefaultColorSpace
encoder := png.Encoder{}
for _, opt := range opts {
switch o := opt.(type) {
case canvas.Resolution:
resolution = o
case canvas.ColorSpace:
colorSpace = o
case png.Encoder:
encoder = o
default:
return errorWriter(fmt.Errorf("unknown PNG option: %T(%v)", opt, opt))
}
}
return func(w io.Writer, c *canvas.Canvas) error {
img := rasterizer.Draw(c, resolution, colorSpace)
return png.Encode(w, img)
return encoder.Encode(w, img)
}
}

// JPEG returns a JPEG writer and accepts the following options: canvas.Resolution, canvas.Colorspace, image/jpeg.*Options
func JPEG(opts ...interface{}) canvas.Writer {
resolution := canvas.DPMM(1.0)
colorSpace := canvas.DefaultColorSpace
Expand All @@ -103,6 +113,7 @@ func JPEG(opts ...interface{}) canvas.Writer {
}
}

// GIF returns a GIF writer and accepts the following options: canvas.Resolution, canvas.Colorspace, image/gif.*Options
func GIF(opts ...interface{}) canvas.Writer {
resolution := canvas.DPMM(1.0)
colorSpace := canvas.DefaultColorSpace
Expand All @@ -116,7 +127,7 @@ func GIF(opts ...interface{}) canvas.Writer {
case *gif.Options:
options = o
default:
return errorWriter(fmt.Errorf("unknown option: %T(%v)", opt, opt))
return errorWriter(fmt.Errorf("unknown GIF option: %T(%v)", opt, opt))
}
}
return func(w io.Writer, c *canvas.Canvas) error {
Expand All @@ -125,6 +136,7 @@ func GIF(opts ...interface{}) canvas.Writer {
}
}

// TIFF returns a TIFF writer and accepts the following options: canvas.Resolution, canvas.Colorspace, golang.org/x/image/tiff.*Options
func TIFF(opts ...interface{}) canvas.Writer {
resolution := canvas.DPMM(1.0)
colorSpace := canvas.DefaultColorSpace
Expand All @@ -138,7 +150,7 @@ func TIFF(opts ...interface{}) canvas.Writer {
case *tiff.Options:
options = o
default:
return errorWriter(fmt.Errorf("unknown option: %T(%v)", opt, opt))
return errorWriter(fmt.Errorf("unknown TIFF option: %T(%v)", opt, opt))
}
}
return func(w io.Writer, c *canvas.Canvas) error {
Expand All @@ -147,6 +159,7 @@ func TIFF(opts ...interface{}) canvas.Writer {
}
}

// BMP returns a BMP writer and accepts the following options: canvas.Resolution, canvas.Colorspace
func BMP(opts ...interface{}) canvas.Writer {
resolution := canvas.DPMM(1.0)
colorSpace := canvas.DefaultColorSpace
Expand All @@ -157,7 +170,7 @@ func BMP(opts ...interface{}) canvas.Writer {
case canvas.ColorSpace:
colorSpace = o
default:
return errorWriter(fmt.Errorf("unknown option: %T(%v)", opt, opt))
return errorWriter(fmt.Errorf("unknown BMP option: %T(%v)", opt, opt))
}
}
return func(w io.Writer, c *canvas.Canvas) error {
Expand All @@ -166,48 +179,69 @@ func BMP(opts ...interface{}) canvas.Writer {
}
}

//func WEBP(opts ...interface{}) canvas.Writer {
// options := &webp.Options{}
// resolution := canvas.DPMM(1.0)
// colorSpace := canvas.DefaultColorSpace
// for _, opt := range opts {
// switch o := opt.(type) {
// case *webp.Options:
// options = o
// case canvas.Resolution:
// resolution = o
// case canvas.ColorSpace:
// colorSpace = o
// default:
// return errorWriter(fmt.Errorf("unknown WEBP option: %T(%v)", opt,opt))
// }
// }
// return func(w io.Writer, c *canvas.Canvas) error {
// img := rasterizer.Draw(c, resolution, colorSpace)
// enc, err := webp.NewEncoder(img, options)
// if err != nil {
// return err
// }
// return enc.Encode(w)
// }
//}
// WebP returns a Webp writer that uses libwebp and accepts the following options: canvas.Resolution, canvas.Colorspace, github.com/kolesa-team/go-webp/encoder.*Options
func WebP(opts ...interface{}) canvas.Writer {
resolution := canvas.DPMM(1.0)
colorSpace := canvas.DefaultColorSpace
var options *webp.Options
for _, opt := range opts {
switch o := opt.(type) {
case canvas.Resolution:
resolution = o
case canvas.ColorSpace:
colorSpace = o
case *webp.Options:
options = o
default:
return errorWriter(fmt.Errorf("unknown WebP option: %T(%v)", opt, opt))
}
}
return func(w io.Writer, c *canvas.Canvas) error {
img := rasterizer.Draw(c, resolution, colorSpace)
enc, err := webp.NewEncoder(img, options)
if err != nil {
return err
}
return enc.Encode(w)
}
}

func SVGZ(opts ...interface{}) canvas.Writer {
// AVIF returns a AVIF writer that uses libaom and accepts the following options: canvas.Resolution, canvas.Colorspace, github.com/Kagami/go-avif.*Options
func AVIF(opts ...interface{}) canvas.Writer {
resolution := canvas.DPMM(1.0)
colorSpace := canvas.DefaultColorSpace
var options *avif.Options

Check failure on line 213 in renderers/renderers.go

View workflow job for this annotation

GitHub Actions / lint

undefined: avif (typecheck)
for _, opt := range opts {
switch o := opt.(type) {
case canvas.Resolution:
resolution = o
case canvas.ColorSpace:
colorSpace = o
case *avif.Options:

Check failure on line 220 in renderers/renderers.go

View workflow job for this annotation

GitHub Actions / lint

undefined: avif (typecheck)
options = o
default:
return errorWriter(fmt.Errorf("unknown AVIF option: %T(%v)", opt, opt))
}
}
return func(w io.Writer, c *canvas.Canvas) error {
img := rasterizer.Draw(c, resolution, colorSpace)
return avif.Encode(w, img, options)

Check failure on line 228 in renderers/renderers.go

View workflow job for this annotation

GitHub Actions / lint

undefined: avif (typecheck)
}
}

// SVG returns an SVG writer and accepts the following options: canvas/renderers/svg.*Options
func SVG(opts ...interface{}) canvas.Writer {
var options *svg.Options
for _, opt := range opts {
switch o := opt.(type) {
case *svg.Options:
options = o
default:
return errorWriter(fmt.Errorf("unknown SVGZ option: %T(%v)", opt, opt))
return errorWriter(fmt.Errorf("unknown SVG option: %T(%v)", opt, opt))
}
}
if options == nil {
options := svg.DefaultOptions
options.Compression = -1
opts = append(opts, &options)
} else {
options.Compression = -1
if options != nil && options.Compression != 0 {
options.Compression = 0
}
return func(w io.Writer, c *canvas.Canvas) error {
svg := svg.New(w, c.W, c.H, options)
Expand All @@ -216,23 +250,32 @@ func SVGZ(opts ...interface{}) canvas.Writer {
}
}

func SVG(opts ...interface{}) canvas.Writer {
// SVGZ returns a GZIP compressed SVG writer and accepts the following options: canvas/renderers/svgsvg.*Options
func SVGZ(opts ...interface{}) canvas.Writer {
var options *svg.Options
for _, opt := range opts {
switch o := opt.(type) {
case *svg.Options:
options = o
default:
return errorWriter(fmt.Errorf("unknown SVG option: %T(%v)", opt, opt))
return errorWriter(fmt.Errorf("unknown SVGZ option: %T(%v)", opt, opt))
}
}
if options == nil {
options := svg.DefaultOptions
options.Compression = flate.DefaultCompression
opts = append(opts, &options)
} else if options.Compression < -2 || options.Compression == 0 || 9 < options.Compression {
options.Compression = flate.DefaultCompression
}
return func(w io.Writer, c *canvas.Canvas) error {
svg := svg.New(w, c.W, c.H, options)
c.RenderTo(svg)
return svg.Close()
}
}

// PDF returns a PDF writer and accepts the following options: canvas/renderers/pdf.*Options
func PDF(opts ...interface{}) canvas.Writer {
var options *pdf.Options
for _, opt := range opts {
Expand All @@ -250,6 +293,7 @@ func PDF(opts ...interface{}) canvas.Writer {
}
}

// TeX returns a TeX writer.
func TeX(opts ...interface{}) canvas.Writer {
for _, opt := range opts {
return errorWriter(fmt.Errorf("unknown TeX option: %T(%v)", opt, opt))
Expand All @@ -261,6 +305,7 @@ func TeX(opts ...interface{}) canvas.Writer {
}
}

// PS returns a PostScript writer and accepts the following options: canvas/renderers/ps.*Options
func PS(opts ...interface{}) canvas.Writer {
var options *ps.Options
for _, opt := range opts {
Expand All @@ -283,6 +328,7 @@ func PS(opts ...interface{}) canvas.Writer {
}
}

// EPS returns a Encapsulated PostScript writer and accepts the following options: canvas/renderers/ps.*Options
func EPS(opts ...interface{}) canvas.Writer {
var options *ps.Options
for _, opt := range opts {
Expand Down

0 comments on commit a888e79

Please sign in to comment.